applying changesets
This commit is contained in:
parent
ff35a31cc3
commit
0f36d18a0b
2 changed files with 100 additions and 15 deletions
|
@ -17,6 +17,10 @@ class ChangedObjectManager(models.Manager):
|
|||
return super().get_queryset().select_related('content_type')
|
||||
|
||||
|
||||
class ApplyToInstanceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ChangedObject(models.Model):
|
||||
changeset = models.ForeignKey('editor.ChangeSet', on_delete=models.CASCADE, verbose_name=_('Change Set'))
|
||||
created = models.DateTimeField(auto_now_add=True, verbose_name=_('created'))
|
||||
|
@ -128,7 +132,7 @@ class ChangedObject(models.Model):
|
|||
self.changeset.m2m_added.get(model, {}).pop(pk, None)
|
||||
self.changeset.m2m_removed.get(model, {}).pop(pk, None)
|
||||
|
||||
def apply_to_instance(self, instance: ModelInstanceWrapper):
|
||||
def apply_to_instance(self, instance: ModelInstanceWrapper, created_pks=None):
|
||||
for name, value in self.updated_fields.items():
|
||||
if name.startswith('title_'):
|
||||
if not value:
|
||||
|
@ -142,12 +146,19 @@ class ChangedObject(models.Model):
|
|||
setattr(instance, field.name, field.to_python(value))
|
||||
elif field.many_to_one or field.one_to_one:
|
||||
if is_created_pk(value):
|
||||
try:
|
||||
obj = self.changeset.get_created_object(field.related_model, value, allow_deleted=True)
|
||||
except field.related_model.DoesNotExist:
|
||||
pass
|
||||
if created_pks is None:
|
||||
try:
|
||||
obj = self.changeset.get_created_object(field.related_model, value, allow_deleted=True)
|
||||
except field.related_model.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
setattr(instance, field.get_cache_name(), obj)
|
||||
else:
|
||||
setattr(instance, field.get_cache_name(), obj)
|
||||
delattr(instance, field.get_cache_name())
|
||||
try:
|
||||
value = created_pks[field.related_model][value]
|
||||
except KeyError:
|
||||
raise ApplyToInstanceError
|
||||
else:
|
||||
try:
|
||||
delattr(instance, field.get_cache_name())
|
||||
|
|
|
@ -17,7 +17,7 @@ from django.utils.timezone import make_naive
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext_lazy
|
||||
|
||||
from c3nav.editor.models.changedobject import ChangedObject
|
||||
from c3nav.editor.models.changedobject import ApplyToInstanceError, ChangedObject
|
||||
from c3nav.editor.utils import is_created_pk
|
||||
from c3nav.editor.wrappers import ModelInstanceWrapper, ModelWrapper
|
||||
from c3nav.mapdata.models import LocationSlug, MapUpdate
|
||||
|
@ -67,6 +67,7 @@ class ChangeSet(models.Model):
|
|||
|
||||
self._object_changed = False
|
||||
self._request = None
|
||||
self._original_state = self.state
|
||||
|
||||
"""
|
||||
Get Changesets for Request/Session/User
|
||||
|
@ -129,7 +130,7 @@ class ChangeSet(models.Model):
|
|||
assert isinstance(instance, models.Model)
|
||||
return self.wrap_model(instance.__class__).create_wrapped_model_class()(self, instance)
|
||||
|
||||
def relevant_changed_objects(self):
|
||||
def relevant_changed_objects(self) -> typing.Iterable[ChangedObject]:
|
||||
return self.changed_objects_set.exclude(existing_object_pk__isnull=True, deleted=True)
|
||||
|
||||
def fill_changes_cache(self):
|
||||
|
@ -492,12 +493,85 @@ class ChangeSet(models.Model):
|
|||
|
||||
def apply(self, user):
|
||||
update = self.updates.create(user=user, state='applied')
|
||||
map_update = MapUpdate.objects.create(user=user, type='changeset')
|
||||
self.state = 'applied'
|
||||
self.last_state_update = update
|
||||
self.last_update = update
|
||||
self.map_update = map_update
|
||||
self.save()
|
||||
with MapUpdate.lock():
|
||||
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
|
||||
|
||||
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 = created_object.model_class
|
||||
pk = created_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)
|
||||
|
||||
map_update = MapUpdate.objects.create(user=user, type='changeset')
|
||||
self.state = 'applied'
|
||||
self.last_state_update = update
|
||||
self.last_update = update
|
||||
self.map_update = map_update
|
||||
self.save()
|
||||
|
||||
def activate(self, request):
|
||||
request.session['changeset'] = self.pk
|
||||
|
@ -572,7 +646,7 @@ class ChangeSet(models.Model):
|
|||
))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.state == 'applied':
|
||||
if self._original_state == 'applied':
|
||||
raise TypeError('Applied change sets can not be edited.')
|
||||
super().save(*args, **kwargs)
|
||||
if self._request is not None:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue