rip out most of the old wrapper and changeset code

This commit is contained in:
Laura Klünder 2024-08-22 14:08:42 +02:00
parent c6b77eb6b5
commit aab4230a3a
8 changed files with 46 additions and 1723 deletions

View file

@ -19,7 +19,7 @@ from django.utils.translation import ngettext_lazy
from c3nav.editor.models.changedobject import ApplyToInstanceError, ChangedObject, NoopChangedObject
from c3nav.editor.tasks import send_changeset_proposed_notification
from c3nav.editor.wrappers import ModelInstanceWrapper, ModelWrapper, is_created_pk
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
@ -139,27 +139,6 @@ class ChangeSet(models.Model):
"""
Wrap Objects
"""
def wrap_model(self, model):
if isinstance(model, str):
model = apps.get_model('mapdata', model)
assert isinstance(model, type) and issubclass(model, models.Model)
if self.direct_editing:
model.EditorForm = ModelWrapper(self, model).EditorForm
return model
return self._get_wrapped_model(model)
def _get_wrapped_model(self, model):
wrapped = self._wrapped_model_cache.get(model, None)
if wrapped is None:
wrapped = ModelWrapper(self, model)
self._wrapped_model_cache[model] = wrapped
return wrapped
def wrap_instance(self, instance):
assert isinstance(instance, models.Model)
if self.direct_editing:
return instance
return self.wrap_model(instance.__class__).wrapped_model_class(self, instance)
def relevant_changed_objects(self) -> typing.Iterable[ChangedObject]:
return self.changed_objects_set.exclude(existing_object_pk__isnull=True, deleted=True)
@ -298,133 +277,8 @@ class ChangeSet(models.Model):
Analyse Changes
"""
def get_objects(self, many=True, changed_objects=None, prefetch_related=()):
if changed_objects is None:
if self.changed_objects is None:
raise TypeError
changed_objects = self.iter_changed_objects()
# collect pks of relevant objects
object_pks = {}
for change in changed_objects:
change.add_relevant_object_pks(object_pks, many=many)
# create dummy objects for deleted ones
objects = {}
for model, pks in object_pks.items():
objects[model] = {pk: model(pk=pk) for pk in pks}
slug_submodels = tuple(model for model in object_pks.keys()
if model is not LocationSlug and issubclass(model, LocationSlug))
if slug_submodels:
object_pks[LocationSlug] = reduce(operator.or_, (object_pks[model] for model in slug_submodels))
for model in slug_submodels:
object_pks.pop(model)
# retrieve relevant objects
for model, pks in object_pks.items():
if not pks:
continue
created_pks = set(pk for pk in pks if is_created_pk(pk))
existing_pks = pks - created_pks
model_objects = {}
if existing_pks:
qs = model.objects
if model is LocationSlug:
qs = qs.select_related_target()
qs = qs.filter(pk__in=existing_pks)
for prefetch in prefetch_related:
try:
model._meta.get_field(prefetch)
except FieldDoesNotExist:
pass
else:
qs = qs.prefetch_related(prefetch)
for obj in qs:
if model == LocationSlug:
obj = obj.get_child()
model_objects[obj.pk] = obj
if created_pks:
for pk in created_pks:
model_objects[pk] = self.get_created_object(model, pk, allow_deleted=True)._obj
objects[model] = model_objects
# add LocationSlug objects as their correct model
for pk, obj in objects.get(LocationSlug, {}).items():
objects.setdefault(obj.__class__, {})[pk] = obj
for pk, obj in objects.get(LocationRedirect, {}).items():
try:
target = obj.target.get_child(obj.target)
except FieldDoesNotExist:
# todo: fix this
continue
# todo: why is it sometimes wrapped and sometimes not?
objects.setdefault(LocationSlug, {})[target.pk] = getattr(target, '_obj', target)
objects.setdefault(target.__class__, {})[target.pk] = getattr(target, '_obj', target)
return objects
def get_changed_values(self, model: models.Model, name: str) -> tuple:
"""
Get all changes values for a specific field on existing models
:param model: model class
:param name: field name
:return: returns a dictionary with primary keys as keys and new values as values
"""
r = tuple((pk, values[name]) for pk, values in self.updated_existing.get(model, {}).items() if name in values)
return r
def get_changed_object(self, obj, allow_noop=False) -> typing.Union[ChangedObject, typing.Type[NoopChangedObject]]:
if isinstance(obj, ModelInstanceWrapper):
obj = obj._obj
model = obj.__class__
pk = obj.pk
if pk is None:
return ChangedObject(changeset=self, model_class=model)
self.fill_changes_cache()
objects = tuple(obj for obj in ((submodel, self.changed_objects.get(submodel, {}).get(pk, None))
for submodel in get_submodels(model)) if obj[1] is not None)
if len(objects) > 1:
raise model.MultipleObjectsReturned
if objects:
return objects[0][1]
if is_created_pk(pk):
raise model.DoesNotExist
if allow_noop:
return NoopChangedObject
return ChangedObject(changeset=self, model_class=model, existing_object_pk=pk)
def get_created_object(self, model, pk, get_foreign_objects=False, allow_deleted=False):
"""
Gets a created model instance.
:param model: model class
:param pk: primary key
:param get_foreign_objects: whether to fetch foreign objects and not just set their id to field.attname
:param allow_deleted: return created objects that have already been deleted (needs get_history=True)
:return: a wrapped model instance
"""
self.fill_changes_cache()
if issubclass(model, ModelWrapper):
model = model._obj
obj = self.get_changed_object(model(pk=pk))
if obj.deleted and not allow_deleted:
raise model.DoesNotExist
return obj.get_obj(get_foreign_objects=get_foreign_objects)
def get_created_pks(self, model) -> set:
"""
Returns a set with the primary keys of created objects from this model
"""
self.fill_changes_cache()
if issubclass(model, ModelWrapper):
model = model._obj
return set(self.created_objects.get(model, {}).keys())
# todo: reimplement, maybe
pass
"""
Permissions
@ -677,85 +531,7 @@ class ChangeSet(models.Model):
def apply(self, user):
with MapUpdate.lock():
changed_geometries.reset()
self._clean_changes()
changed_objects = self.relevant_changed_objects()
created_objects = []
existing_objects = []
for changed_object in changed_objects:
(created_objects if changed_object.is_created else existing_objects).append(changed_object)
objects = self.get_objects(changed_objects=changed_objects)
# remove slugs on all changed existing objects
slugs_updated = set(changed_object.obj_pk for changed_object in existing_objects
if (issubclass(changed_object.model_class, LocationSlug) and
'slug' in changed_object.updated_fields))
LocationSlug.objects.filter(pk__in=slugs_updated).update(slug=None)
redirects_deleted = set(changed_object.obj_pk for changed_object in existing_objects
if (issubclass(changed_object.model_class, LocationRedirect) and
changed_object.deleted))
LocationRedirect.objects.filter(pk__in=redirects_deleted).delete()
# create created objects
created_pks = {}
objects_to_create = set(created_objects)
while objects_to_create:
created_in_last_run = set()
for created_object in objects_to_create:
model = created_object.model_class
pk = created_object.obj_pk
# lets try to create this object
obj = model()
try:
created_object.apply_to_instance(obj, created_pks=created_pks)
except ApplyToInstanceError:
continue
obj.save()
created_in_last_run.add(created_object)
created_pks.setdefault(model, {})[pk] = obj.pk
objects.setdefault(model, {})[pk] = obj
if issubclass(model, LocationSlug):
# todo: make this generic
created_pks.setdefault(LocationSlug, {})[pk] = obj.pk
objects.setdefault(LocationSlug, {})[pk] = obj
objects_to_create -= created_in_last_run
# update existing objects
for existing_object in existing_objects:
if existing_object.deleted:
continue
model = existing_object.model_class
pk = existing_object.obj_pk
obj = objects[model][pk]
existing_object.apply_to_instance(obj, created_pks=created_pks)
obj.save()
# delete existing objects
for existing_object in existing_objects:
if not existing_object.deleted and not issubclass(existing_object.model_class, LocationRedirect):
continue
model = existing_object.model_class
pk = existing_object.obj_pk
obj = objects[model][pk]
obj.delete()
# update m2m
for changed_object in changed_objects:
obj = objects[changed_object.model_class][changed_object.obj_pk]
for mode, updates in (('remove', changed_object.m2m_removed), ('add', changed_object.m2m_added)):
for name, pks in updates.items():
field = changed_object.model_class._meta.get_field(name)
pks = tuple(objects[field.related_model][pk].pk for pk in pks)
getattr(getattr(obj, name), mode)(*pks)
# todo: reimplement
update = self.updates.create(user=user, state='applied')
map_update = MapUpdate.objects.create(user=user, type='changeset')
self.state = 'applied'