From 431372c4e48370bae28640a6440fac5924d88d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 22 Aug 2024 19:46:30 +0200 Subject: [PATCH] start implementing data structured for new changesets --- .../migrations/0004_changeset_rewrite_2024.py | 28 +++++ src/c3nav/editor/models/changeset.py | 101 +++++++++++++++--- 2 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 src/c3nav/editor/migrations/0004_changeset_rewrite_2024.py diff --git a/src/c3nav/editor/migrations/0004_changeset_rewrite_2024.py b/src/c3nav/editor/migrations/0004_changeset_rewrite_2024.py new file mode 100644 index 00000000..32c96a62 --- /dev/null +++ b/src/c3nav/editor/migrations/0004_changeset_rewrite_2024.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.8 on 2024-08-22 17:03 + +import c3nav.editor.models.changeset +import django.core.serializers.json +import django_pydantic_field.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('editor', '0003_changedobject_json_encoder'), + ] + + operations = [ + migrations.RemoveField( + model_name='changeset', + name='last_cleaned_with', + ), + migrations.AddField( + model_name='changeset', + name='changes', + field=django_pydantic_field.fields.PydanticSchemaField(config=None, default=c3nav.editor.models.changeset.ChangeSetChanges, encoder=django.core.serializers.json.DjangoJSONEncoder, schema=c3nav.editor.models.changeset.ChangeSetChanges), + ), + migrations.DeleteModel( + name='ChangedObject', + ), + ] diff --git a/src/c3nav/editor/models/changeset.py b/src/c3nav/editor/models/changeset.py index 8cc19525..b7f530be 100644 --- a/src/c3nav/editor/models/changeset.py +++ b/src/c3nav/editor/models/changeset.py @@ -1,28 +1,106 @@ -import operator -import typing +import datetime from collections import OrderedDict from contextlib import contextmanager -from functools import reduce -from itertools import chain +from enum import StrEnum +from typing import Literal, TypeAlias, Union, Annotated from django.apps import apps from django.conf import settings from django.core.cache import cache from django.core.exceptions import FieldDoesNotExist from django.db import models, transaction -from django.db.models import Q from django.urls import reverse from django.utils.http import int_to_base36 from django.utils.timezone import make_naive from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy +from django_pydantic_field import SchemaField +from pydantic.config import ConfigDict +from pydantic.fields import Field +from pydantic.types import Discriminator +from c3nav.api.schema import BaseSchema from c3nav.editor.tasks import send_changeset_proposed_notification -from c3nav.editor.wrappers import is_created_pk from c3nav.mapdata.models import LocationSlug, MapUpdate from c3nav.mapdata.models.locations import LocationRedirect from c3nav.mapdata.utils.cache.changes import changed_geometries +FieldValuesDict: TypeAlias = dict[int, str] +ExistingOrCreatedID: TypeAlias = int # negative = temporary ID of created object + + +class ObjectReferenceType(StrEnum): + EXISTING = "existing" + CREATED = "created" + + +class ObjectReference(BaseSchema): + model_config = ConfigDict(frozen=True) + model: str + id: ExistingOrCreatedID + + +class BaseChange(BaseSchema): + obj: ObjectReference + datetime: datetime.datetime + + +class CreateObjectChange(BaseChange): + type: Literal["create"] + fields: FieldValuesDict + + +class UpdateObjectChange(BaseChange): + type: Literal["update"] + fields: FieldValuesDict + + +class DeleteObjectChange(BaseChange): + type: Literal["delete"] + + +class AddManyToManyChange(BaseSchema): + type: Literal["m2m_add"] + field: str + values: list[int] + + +class RemoveManyToManyChange(BaseSchema): + type: Literal["m2m_remove"] + field: str + values: list[int] + + +class ClearManyToManyChange(BaseSchema): + type: Literal["m2m_clear"] + field: str + + +ChangeSetChange = Annotated[ + Union[ + CreateObjectChange, + UpdateObjectChange, + DeleteObjectChange, + AddManyToManyChange, + RemoveManyToManyChange, + ClearManyToManyChange, + ], + Discriminator("type"), +] + + +class ChangeSetChanges(BaseSchema): + prev_reprs: dict[ObjectReference, str] = {} + prev_values: dict[ObjectReference, FieldValuesDict] = {} + prev_m2m: dict[ObjectReference, dict[str, list[int]]] = {} + changes: list[ChangeSetChange] = [] + + # maps negative IDs of created objects to the ID during the current transaction + mapped_ids: Annotated[dict[ObjectReference, int], Field(exclude=True)] = {} + + # maps IDs as used during the current transaction to the negative IDs + reverse_mapped_ids: Annotated[dict[ObjectReference, int], Field(exclude=True)] = {} + class ChangeSet(models.Model): STATES = ( @@ -49,8 +127,7 @@ class ChangeSet(models.Model): related_name='assigned_changesets', verbose_name=_('assigned to')) map_update = models.OneToOneField(MapUpdate, null=True, related_name='changeset', verbose_name=_('map update'), on_delete=models.PROTECT) - last_cleaned_with = models.ForeignKey(MapUpdate, null=True, related_name='checked_changesets', - on_delete=models.PROTECT) + changes: ChangeSetChanges = SchemaField(schema=ChangeSetChanges, default=ChangeSetChanges) class Meta: verbose_name = _('Change Set') @@ -73,14 +150,6 @@ class ChangeSet(models.Model): self.direct_editing = False - self._wrapped_model_cache = {} - - def __getstate__(self): - return { - **self.__dict__, - '_wrapped_model_cache': {} - } - """ Get Changesets for Request/Session/User """