From bbce7607075f2ab1bc333618bb2b8000e3104607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Mon, 26 Aug 2024 16:45:16 +0200 Subject: [PATCH] add i18n logic and only store fields that have actually changed --- src/c3nav/editor/operations.py | 12 ++++++++++-- src/c3nav/editor/overlay.py | 25 ++++++++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/c3nav/editor/operations.py b/src/c3nav/editor/operations.py index cbc71153..486bf5af 100644 --- a/src/c3nav/editor/operations.py +++ b/src/c3nav/editor/operations.py @@ -13,6 +13,7 @@ from pydantic.fields import Field from pydantic.types import Discriminator from c3nav.api.schema import BaseSchema +from c3nav.mapdata.fields import I18nField FieldValuesDict: TypeAlias = dict[str, Any] @@ -61,7 +62,7 @@ class UpdateObjectOperation(BaseOperation): instance = list(serializers.deserialize("json", json.dumps([{ "model": f"mapdata.{self.obj.model}", "pk": self.obj.id, - "fields": self.fields, + "fields": values, }])))[0] instance.save(save_m2m=False) return instance.object @@ -156,7 +157,14 @@ class CollectedChanges(BaseSchema): changed_object.created = True changed_object.fields.update(operation.fields) elif isinstance(operation, UpdateObjectOperation): - changed_object.fields.update(operation.fields) + model = apps.get_model('mapdata', operation.obj.model) + for field_name, value in operation.fields.items(): + field = model._meta.get_field(field_name) + if isinstance(field, I18nField) and field_name in changed_object.fields: + changed_object.fields[field_name] = {lang: val + for lang, val in field[field_name].update(value).items()} + else: + changed_object.fields[field_name] = value elif isinstance(operation, DeleteObjectOperation): changed_object.deleted = False else: diff --git a/src/c3nav/editor/overlay.py b/src/c3nav/editor/overlay.py index f4831126..805d03bc 100644 --- a/src/c3nav/editor/overlay.py +++ b/src/c3nav/editor/overlay.py @@ -10,6 +10,7 @@ from django.db.models.fields.related import ManyToManyField from c3nav.editor.operations import DatabaseOperation, ObjectReference, FieldValuesDict, CreateObjectOperation, \ UpdateObjectOperation, DeleteObjectOperation, ClearManyToManyOperation, UpdateManyToManyOperation, CollectedChanges +from c3nav.mapdata.fields import I18nField try: from asgiref.local import Local as LocalContext @@ -57,7 +58,7 @@ class DatabaseOverlayManager: def get_model_field_values(instance: Model) -> FieldValuesDict: return json.loads(serializers.serialize("json", [instance]))[0]["fields"] - def get_ref_and_pre_change_values(self, instance: Model) -> ObjectReference: + def get_ref_and_pre_change_values(self, instance: Model) -> tuple[ObjectReference, FieldValuesDict]: ref = ObjectReference.from_instance(instance) pre_change_values = self.pre_change_values.pop(ref, None) @@ -65,7 +66,7 @@ class DatabaseOverlayManager: self.changes.prev_values.setdefault(ref.model, {})[ref.id] = pre_change_values self.changes.prev_reprs.setdefault(ref.model, {})[ref.id] = str(instance) - return ref + return ref, pre_change_values def handle_pre_change_instance(self, instance: Model, **kwargs): if instance.pk is None: @@ -79,7 +80,7 @@ class DatabaseOverlayManager: def handle_post_save(self, instance: Model, created: bool, update_fields: set | None, **kwargs): field_values = self.get_model_field_values(instance) - ref = self.get_ref_and_pre_change_values(instance) + ref, pre_change_values = self.get_ref_and_pre_change_values(instance) if created: self.new_operations.append(CreateObjectOperation(obj=ref, fields=field_values)) @@ -88,10 +89,24 @@ class DatabaseOverlayManager: if update_fields: field_values = {name: value for name, value in field_values.items() if name in update_fields} + field_values = {name: value for name, value in field_values.items() if value != pre_change_values[name]} + + # special diffing within the i18n fields + for field_name in tuple(field_values): + if isinstance(instance._meta.get_field(field_name), I18nField): + before_val = pre_change_values[field_name] + after_val = field_values[field_name] + + diff_val = {} + for lang in (set(before_val) | set(after_val)): + if before_val.get(lang, None) != after_val.get(lang, None): + diff_val[lang] = after_val.get(lang, None) + field_values[field_name] = diff_val + self.new_operations.append(UpdateObjectOperation(obj=ref, fields=field_values)) def handle_post_delete(self, instance: Model, **kwargs): - ref = self.get_ref_and_pre_change_values(instance) + ref, pre_change_values = self.get_ref_and_pre_change_values(instance) self.new_operations.append(DeleteObjectOperation(obj=ref)) def handle_m2m_changed(self, sender: Type[Model], instance: Model, action: str, model: Type[Model], @@ -108,7 +123,7 @@ class DatabaseOverlayManager: else: raise ValueError - ref = self.get_ref_and_pre_change_values(instance) + ref, pre_change_values = self.get_ref_and_pre_change_values(instance) if action == "post_clear": self.new_operations.append(ClearManyToManyOperation(obj=ref, field=field.name))