complete rework of offerings

This commit is contained in:
Tobias Brunner 2025-02-28 14:13:51 +01:00
parent 84e25c82d1
commit 20f27bd6b5
No known key found for this signature in database
16 changed files with 313 additions and 294 deletions

View file

@ -11,6 +11,44 @@ def validate_image_size(value):
raise ValidationError("Maximum file size is 1MB")
class ReusableText(models.Model):
name = models.CharField(max_length=100)
textsnippet = models.ForeignKey(
"self",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="children",
)
text = ProseEditorField()
class Meta:
ordering = ["name"]
def __str__(self):
return self.name
def get_full_text(self):
"""Returns the text with all nested textsnippet content recursively included in reverse order"""
text_parts = []
# Recursively collect all text parts
def collect_text(snippet):
if snippet is None:
return
collect_text(snippet.textsnippet) # Collect deepest snippets first
text_parts.append(snippet.text)
# Start collection with the deepest snippets
collect_text(self.textsnippet)
# Add the main text at the end
text_parts.append(self.text)
# Join all text parts
return "".join(text_parts)
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
@ -69,31 +107,6 @@ class CloudProvider(models.Model):
return reverse("services:provider_detail", kwargs={"slug": self.slug})
class Currency(models.Model):
code = models.CharField(max_length=3, unique=True) # ISO 4217 currency code
name = models.CharField(max_length=50)
symbol = models.CharField(max_length=5)
class Meta:
verbose_name_plural = "Currencies"
ordering = ["code"]
def __str__(self):
return f"{self.code} ({self.name})"
class Term(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
order = models.IntegerField(default=0)
class Meta:
ordering = ["order", "name"]
def __str__(self):
return self.name
class Service(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=250, unique=True)
@ -182,19 +195,18 @@ class ServiceOffering(models.Model):
CloudProvider, on_delete=models.CASCADE, related_name="offerings"
)
slug = models.SlugField(max_length=250, unique=True)
description = ProseEditorField(help_text="Provider-specific details and features")
description = ProseEditorField(blank=True, null=True)
offer_description = models.ForeignKey(
ReusableText,
on_delete=models.PROTECT,
related_name="offer_descriptions",
blank=True,
null=True,
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
STATUS_CHOICES = [
("available", "Available"),
("planned", "Planned"),
("on_request", "On Request"),
]
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default="available"
)
class Meta:
unique_together = ["service", "cloud_provider"]
ordering = ["service__name", "cloud_provider__name"]
@ -219,40 +231,54 @@ class ServiceOffering(models.Model):
class Plan(models.Model):
name = models.CharField(max_length=100)
description = ProseEditorField()
plan_description = models.ForeignKey(
ReusableText,
on_delete=models.PROTECT,
related_name="plan_descriptions",
blank=True,
null=True,
)
offering = models.ForeignKey(
ServiceOffering, on_delete=models.CASCADE, related_name="plans"
)
is_default = models.BooleanField(default=False)
features = ProseEditorField(blank=True)
order = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["order", "name"]
ordering = ["name"]
unique_together = [["offering", "name"]]
def __str__(self):
return f"{self.offering} - {self.name}"
def save(self, *args, **kwargs):
if self.is_default:
# Ensure only one default plan per offering
Plan.objects.filter(offering=self.offering).update(is_default=False)
super().save(*args, **kwargs)
class PlanPrice(models.Model):
plan = models.ForeignKey(Plan, on_delete=models.CASCADE, related_name="prices")
currency = models.ForeignKey(Currency, on_delete=models.PROTECT)
term = models.ForeignKey(Term, on_delete=models.PROTECT)
price = models.DecimalField(max_digits=10, decimal_places=2)
class ExternalLinkOffering(models.Model):
offering = models.ForeignKey(
ServiceOffering, on_delete=models.CASCADE, related_name="external_links"
)
url = models.URLField()
description = models.CharField(max_length=200)
order = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
unique_together = [["plan", "currency", "term"]]
ordering = ["order", "description"]
verbose_name = "External Link"
verbose_name_plural = "External Links"
def __str__(self):
return f"{self.plan.name} - {self.currency.code} {self.price}"
return f"{self.description} ({self.url})"
def clean(self):
from django.core.validators import URLValidator
validate = URLValidator()
try:
validate(self.url)
except ValidationError:
raise ValidationError({"url": "Enter a valid URL."})
class ExternalLink(models.Model):