ServiceDefinition as part of the through model

This commit is contained in:
Tobias Kunze 2025-03-25 12:03:27 +01:00
parent 267dc56f32
commit b074a3413a
5 changed files with 185 additions and 82 deletions

View file

@ -14,6 +14,7 @@ from servala.core.models import (
ServiceCategory, ServiceCategory,
ServiceDefinition, ServiceDefinition,
ServiceOffering, ServiceOffering,
ServiceOfferingControlPlane,
User, User,
) )
@ -176,7 +177,7 @@ class PlanAdmin(admin.ModelAdmin):
class ServiceDefinitionAdmin(admin.ModelAdmin): class ServiceDefinitionAdmin(admin.ModelAdmin):
form = ServiceDefinitionAdminForm form = ServiceDefinitionAdminForm
list_display = ("name", "service") list_display = ("name", "service")
list_filter = ("service", "control_planes") list_filter = ("service",)
search_fields = ("name", "description") search_fields = ("name", "description")
autocomplete_fields = ("service",) autocomplete_fields = ("service",)
@ -199,11 +200,27 @@ class ServiceDefinitionAdmin(admin.ModelAdmin):
return ["api_definition"] return ["api_definition"]
class ServiceOfferingControlPlaneInline(admin.TabularInline):
model = ServiceOfferingControlPlane
extra = 1
autocomplete_fields = ("control_plane", "service_definition")
@admin.register(ServiceOfferingControlPlane)
class ServiceOfferingControlPlaneAdmin(admin.ModelAdmin):
list_display = ("service_offering", "control_plane", "service_definition")
list_filter = ("service_offering", "control_plane", "service_definition")
search_fields = ("service_offering__service__name", "control_plane__name")
autocomplete_fields = ("service_offering", "control_plane", "service_definition")
@admin.register(ServiceOffering) @admin.register(ServiceOffering)
class ServiceOfferingAdmin(admin.ModelAdmin): class ServiceOfferingAdmin(admin.ModelAdmin):
list_display = ("id", "service", "provider") list_display = ("id", "service", "provider")
list_filter = ("service", "provider", "control_planes") list_filter = ("service", "provider")
search_fields = ("description",) search_fields = ("description",)
autocomplete_fields = ("service", "provider") autocomplete_fields = ("service", "provider")
filter_horizontal = ("control_planes",) inlines = (
inlines = (PlanInline,) ServiceOfferingControlPlaneInline,
PlanInline,
)

View file

@ -0,0 +1,115 @@
# Generated by Django 5.2b1 on 2025-03-25 11:02
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0006_service_slug"),
]
operations = [
migrations.RemoveField(
model_name="serviceoffering",
name="control_plane",
),
migrations.CreateModel(
name="ServiceDefinition",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, verbose_name="Name")),
(
"description",
models.TextField(blank=True, verbose_name="Description"),
),
(
"api_definition",
models.JSONField(
blank=True,
help_text="Contains group, version, and kind information",
null=True,
verbose_name="API Definition",
),
),
(
"service",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="service_definitions",
to="core.service",
verbose_name="Service",
),
),
],
options={
"verbose_name": "Service definition",
"verbose_name_plural": "Service definitions",
},
),
migrations.CreateModel(
name="ServiceOfferingControlPlane",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"control_plane",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="offering_connections",
to="core.controlplane",
verbose_name="Control plane",
),
),
(
"service_definition",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="offering_control_planes",
to="core.servicedefinition",
verbose_name="Service definition",
),
),
(
"service_offering",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="control_plane_connections",
to="core.serviceoffering",
verbose_name="Service offering",
),
),
],
options={
"verbose_name": "Service offering control plane connection",
"verbose_name_plural": "Service offering control planes connections",
"unique_together": {("service_offering", "control_plane")},
},
),
migrations.AddField(
model_name="serviceoffering",
name="control_planes",
field=models.ManyToManyField(
related_name="offerings",
through="core.ServiceOfferingControlPlane",
to="core.controlplane",
verbose_name="Control planes",
),
),
]

View file

@ -1,72 +0,0 @@
# Generated by Django 5.2b1 on 2025-03-24 14:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0006_service_slug"),
]
operations = [
migrations.RenameField(
model_name="serviceoffering",
old_name="control_plane",
new_name="control_planes",
),
migrations.CreateModel(
name="ServiceDefinition",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, verbose_name="Name")),
(
"description",
models.TextField(blank=True, verbose_name="Description"),
),
(
"api_definition",
models.JSONField(
blank=True,
help_text="Contains group, version, and kind information",
null=True,
verbose_name="API Definition",
),
),
(
"service",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="service_definitions",
to="core.service",
verbose_name="Service",
),
),
],
options={
"verbose_name": "Service definition",
"verbose_name_plural": "Service definitions",
},
),
migrations.AddField(
model_name="controlplane",
name="service_definition",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.PROTECT,
related_name="control_planes",
to="core.servicedefinition",
verbose_name="Service definition",
),
preserve_default=False,
),
]

View file

@ -13,6 +13,7 @@ from .service import (
ServiceCategory, ServiceCategory,
ServiceDefinition, ServiceDefinition,
ServiceOffering, ServiceOffering,
ServiceOfferingControlPlane,
) )
from .user import User from .user import User
@ -29,5 +30,6 @@ __all__ = [
"ServiceCategory", "ServiceCategory",
"ServiceDefinition", "ServiceDefinition",
"ServiceOffering", "ServiceOffering",
"ServiceOfferingControlPlane",
"User", "User",
] ]

View file

@ -103,12 +103,6 @@ class ControlPlane(models.Model):
related_name="control_planes", related_name="control_planes",
verbose_name=_("Cloud provider"), verbose_name=_("Cloud provider"),
) )
service_definition = models.ForeignKey(
to="ServiceDefinition",
related_name="control_planes",
verbose_name=_("Service definition"),
on_delete=models.PROTECT,
)
class Meta: class Meta:
verbose_name = _("Control plane") verbose_name = _("Control plane")
@ -263,6 +257,18 @@ class ServiceDefinition(models.Model):
verbose_name=_("Service"), verbose_name=_("Service"),
) )
@property
def control_planes(self):
return ControlPlane.objects.filter(
offering_connections__service_definition=self
).distinct()
@property
def service_offerings(self):
return ServiceOffering.objects.filter(
control_plane_connections__service_definition=self
).distinct()
class Meta: class Meta:
verbose_name = _("Service definition") verbose_name = _("Service definition")
verbose_name_plural = _("Service definitions") verbose_name_plural = _("Service definitions")
@ -271,6 +277,40 @@ class ServiceDefinition(models.Model):
return self.name return self.name
class ServiceOfferingControlPlane(models.Model):
"""
Each combination of ServiceOffering and ControlPlane can have a different
ServiceDefinition, which is here modeled as the "through" model.
"""
service_offering = models.ForeignKey(
to="ServiceOffering",
on_delete=models.CASCADE,
related_name="control_plane_connections",
verbose_name=_("Service offering"),
)
control_plane = models.ForeignKey(
to="ControlPlane",
on_delete=models.CASCADE,
related_name="offering_connections",
verbose_name=_("Control plane"),
)
service_definition = models.ForeignKey(
to="ServiceDefinition",
on_delete=models.PROTECT,
related_name="offering_control_planes",
verbose_name=_("Service definition"),
)
class Meta:
verbose_name = _("Service offering control plane connection")
verbose_name_plural = _("Service offering control planes connections")
unique_together = [("service_offering", "control_plane")]
def __str__(self):
return f"{self.service_offering} on {self.control_plane} with {self.service_definition}"
class ServiceOffering(models.Model): class ServiceOffering(models.Model):
""" """
A service offering, e.g. "PostgreSQL on AWS", "MinIO on GCP". A service offering, e.g. "PostgreSQL on AWS", "MinIO on GCP".
@ -290,6 +330,7 @@ class ServiceOffering(models.Model):
) )
control_planes = models.ManyToManyField( control_planes = models.ManyToManyField(
to="ControlPlane", to="ControlPlane",
through="ServiceOfferingControlPlane",
related_name="offerings", related_name="offerings",
verbose_name=_("Control planes"), verbose_name=_("Control planes"),
) )