# External ID support for serializer

* adds support for sending "external_identifier" in POST and PUT requests
* if an external identifier already exists on the database, the client will be informed that the entry should not be POSTed again but rather an update via PUT should be performed
This commit is contained in:
2026-05-10 10:06:56 +02:00
parent 2df178f4e1
commit d26e363f8b
5 changed files with 92 additions and 6 deletions

View File

@@ -113,7 +113,9 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj = self._initialize_objects(json_model, user)
# Fill in data to objects
properties = json_model["properties"]
properties = json_model.get("properties", None)
if not properties:
raise AssertionError("No 'properties' found in payload!")
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
obj.is_cef = properties["is_cef"]
@@ -129,6 +131,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
self._set_external_identifier(obj, properties.get("external_identifier", None))
obj.log.add(obj.created)
celery_update_parcels.delay(obj.geometry.id)
@@ -153,6 +156,10 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
# Fill in data to objects
properties = json_model["properties"]
external_identifier = properties.get("external_identifier", None)
self._check_external_identifier_on_entry_creation(external_identifier)
obj.title = properties["title"]
obj.is_cef = properties["is_cef"]
obj.is_coherence_keeping = properties["is_coherence_keeping"]
@@ -170,6 +177,7 @@ class CompensationAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensa
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
self._set_external_identifier(obj, properties.get("external_identifier", None))
obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id)

View File

@@ -121,7 +121,9 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
obj = self._initialize_objects(json_model, user)
# Fill in data to objects
properties = json_model["properties"]
properties = json_model.get("properties", None)
if not properties:
raise AssertionError("No 'properties' found in payload!")
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
obj.is_pik = properties.get("is_pik", False)
@@ -147,6 +149,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
self._set_external_identifier(obj, properties.get("external_identifier", None))
obj.log.add(obj.created)
obj.users.add(user)
@@ -172,6 +175,10 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
# Fill in data to objects
properties = json_model["properties"]
external_identifier = properties.get("external_identifier", None)
self._check_external_identifier_on_entry_creation(external_identifier)
obj.title = properties["title"]
obj.is_pik = properties.get("is_pik", False)
obj.deductable_surface = float(properties["deductable_surface"])
@@ -192,6 +199,7 @@ class EcoAccountAPISerializerV1(AbstractModelAPISerializerV1,
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
self._set_external_identifier(obj, external_identifier)
obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id)

View File

@@ -104,6 +104,10 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
# Fill in data to objects
properties = json_model["properties"]
external_identifier = properties.get("external_identifier", None)
self._check_external_identifier_on_entry_creation(external_identifier)
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
obj.is_pik = properties.get("is_pik", False)
@@ -119,6 +123,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
self._set_external_identifier(obj, external_identifier)
obj.log.add(obj.created)
obj.users.add(user)
@@ -161,6 +166,7 @@ class EmaAPISerializerV1(AbstractModelAPISerializerV1, AbstractCompensationAPISe
obj = self._set_compensation_states(obj, properties["after_states"], obj.after_states)
obj = self._set_deadlines(obj, properties["deadlines"])
self._set_external_identifier(obj, properties.get("external_identifier", None))
obj.log.add(update_action)
celery_update_parcels.delay(obj.geometry.id)

View File

@@ -150,10 +150,14 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
# Fill in data to objects
properties = json_model["properties"]
external_identifier = properties.get("external_identifier", None)
self._check_external_identifier_on_entry_creation(external_identifier)
obj.identifier = obj.generate_new_identifier()
obj.title = properties["title"]
self._set_responsibility(obj, properties["responsible"])
self._set_legal(obj, properties["legal"])
obj.title = properties.get("title", None)
self._set_responsibility(obj, properties.get("responsible", None))
self._set_legal(obj, properties.get("legal", None))
obj.responsible.handler.save()
obj.responsible.save()
@@ -161,6 +165,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
obj.legal.save()
obj.save()
self._set_external_identifier(obj, external_identifier)
obj.users.add(user)
obj.log.add(obj.created)
@@ -186,7 +191,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
# Fill in data to objects
properties = json_model["properties"]
obj.title = properties["title"]
obj.title = properties.get("title")
self._set_responsibility(obj, properties.get("responsible", None))
self._set_legal(obj, properties.get("legal", None))
self._set_payments(obj, properties.get("payments", None))
@@ -199,6 +204,7 @@ class InterventionAPISerializerV1(AbstractModelAPISerializerV1,
obj.legal.save()
obj.save()
self._set_external_identifier(obj, properties.get("external_identifier", None))
obj.mark_as_edited(user, edit_comment="API update")
obj.send_data_to_egon()

View File

@@ -12,6 +12,7 @@ from django.contrib.gis.geos import MultiPolygon
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import QuerySet
from api.models import ExternalIdentifier
from api.utils.serializer.serializer import AbstractModelAPISerializer
from codelist.models import KonovaCode
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_PROCESS_TYPE_ID, \
@@ -137,6 +138,63 @@ class AbstractModelAPISerializerV1(AbstractModelAPISerializer):
success = entry.deleted is not None
return success
def _set_external_identifier(self, obj, external_identifier):
""" If an external identifier was provided in the payload, we set it
in the database
Args:
obj (BaseObject): The already processed konova object (EIV, KOM, ...)
external_identifier (any): The external identifier taken from the payload
Returns:
"""
if external_identifier is None:
return None
ext_id_obj = ExternalIdentifier.objects.get_or_create(
internal_id=obj.id,
external_id=external_identifier
)[0]
if not ext_id_obj.created:
ext_id_obj.created = obj.created
ext_id_obj.save()
return ext_id_obj
def _get_external_identifier(self, external_identifier):
""" Checks whether a linkage based on an external identifier already exists and returns it if so.
Args:
external_identifier (any): The external identifier according to payload
Returns:
ExternalIdentifier | None
"""
if external_identifier:
try:
obj = ExternalIdentifier.objects.get(external_id=external_identifier)
return obj
except ObjectDoesNotExist:
pass
return None
def _check_external_identifier_on_entry_creation(self, external_identifier):
""" Special check for POST processing:
Checks whether an external identifier already exists on the database. This hints that
the entry already has been created in the past. Instead of POST, the PUT method shall be used
to avoid creating duplicates.
Args:
external_identifier (any): The external identifier according to payload
Returns:
"""
persisted_external_identifier = self._get_external_identifier(external_identifier)
if persisted_external_identifier:
raise AssertionError(f"{external_identifier} has already been initially created! Use PUT for updates!")
class DeductableAPISerializerV1Mixin:
class Meta: