phase3: add normalized domain schema, admin, services, and multistage docker build
This commit is contained in:
17
apps/providers/admin.py
Normal file
17
apps/providers/admin.py
Normal 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")
|
||||
35
apps/providers/migrations/0001_initial.py
Normal file
35
apps/providers/migrations/0001_initial.py
Normal 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
41
apps/providers/models.py
Normal 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}"
|
||||
0
apps/providers/services/__init__.py
Normal file
0
apps/providers/services/__init__.py
Normal file
19
apps/providers/services/mappings.py
Normal file
19
apps/providers/services/mappings.py
Normal 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
|
||||
Reference in New Issue
Block a user