phase3: add normalized domain schema, admin, services, and multistage docker build

This commit is contained in:
Alfredo Di Stasio
2026-03-10 10:39:45 +01:00
parent f47ffe6c15
commit fc7289a343
30 changed files with 1548 additions and 3 deletions

17
apps/providers/admin.py Normal file
View File

@ -0,0 +1,17 @@
from django.contrib import admin
from .models import ExternalMapping
@admin.register(ExternalMapping)
class ExternalMappingAdmin(admin.ModelAdmin):
list_display = (
"provider_namespace",
"external_id",
"content_type",
"object_id",
"is_active",
"last_seen_at",
)
list_filter = ("provider_namespace", "is_active", "content_type")
search_fields = ("external_id", "external_secondary_id", "provider_namespace")

View File

@ -0,0 +1,35 @@
# Generated by Django 5.2.12 on 2026-03-10 09:33
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='ExternalMapping',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('provider_namespace', models.CharField(max_length=80)),
('external_id', models.CharField(max_length=160)),
('external_secondary_id', models.CharField(blank=True, max_length=160)),
('object_id', models.PositiveBigIntegerField()),
('raw_payload', models.JSONField(blank=True, default=dict)),
('last_seen_at', models.DateTimeField(auto_now=True)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
],
options={
'indexes': [models.Index(fields=['provider_namespace', 'external_id'], name='providers_e_provide_8e3a4f_idx'), models.Index(fields=['content_type', 'object_id'], name='providers_e_content_f29ca2_idx'), models.Index(fields=['is_active'], name='providers_e_is_acti_8ef53c_idx')],
'constraints': [models.UniqueConstraint(fields=('provider_namespace', 'external_id'), name='uq_mapping_provider_external_id'), models.UniqueConstraint(fields=('provider_namespace', 'content_type', 'object_id'), name='uq_mapping_provider_entity')],
},
),
]

41
apps/providers/models.py Normal file
View File

@ -0,0 +1,41 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class ExternalMapping(models.Model):
"""Maps internal entities to provider-specific IDs across namespaces."""
provider_namespace = models.CharField(max_length=80)
external_id = models.CharField(max_length=160)
external_secondary_id = models.CharField(max_length=160, blank=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveBigIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
# Raw payload is kept only for troubleshooting and parsing diagnostics.
raw_payload = models.JSONField(default=dict, blank=True)
last_seen_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["provider_namespace", "external_id"],
name="uq_mapping_provider_external_id",
),
models.UniqueConstraint(
fields=["provider_namespace", "content_type", "object_id"],
name="uq_mapping_provider_entity",
),
]
indexes = [
models.Index(fields=["provider_namespace", "external_id"]),
models.Index(fields=["content_type", "object_id"]),
models.Index(fields=["is_active"]),
]
def __str__(self) -> str:
return f"{self.provider_namespace}:{self.external_id}"

View File

View File

@ -0,0 +1,19 @@
from django.contrib.contenttypes.models import ContentType
from apps.providers.models import ExternalMapping
def upsert_external_mapping(*, provider_namespace: str, external_id: str, instance, raw_payload: dict | None = None) -> ExternalMapping:
"""Create or update a provider mapping for any internal model instance."""
content_type = ContentType.objects.get_for_model(instance.__class__)
mapping, _ = ExternalMapping.objects.update_or_create(
provider_namespace=provider_namespace,
content_type=content_type,
object_id=instance.pk,
defaults={
"external_id": external_id,
"raw_payload": raw_payload or {},
"is_active": True,
},
)
return mapping