Exemple #1
0
    def get_changed_object(self, obj) -> ChangedObject:
        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

        return ChangedObject(changeset=self,
                             model_class=model,
                             existing_object_pk=pk)
Exemple #2
0
    def get_missing_dependencies(self, force_query=False, max_one=False):
        result = set()
        if not self.deleted:
            return result
        for field in self.model_class._meta.get_fields():
            if not field.many_to_one:
                continue
            if field.name not in self.updated_fields:
                continue
            related_model = field.related_model
            if related_model._meta.app_label != 'mapdata':
                continue

            pk = self.updated_fields[field.name]

            if force_query:
                # query here to avoid a race condition
                related_content_type = ContentType.objects.get_for_model(
                    related_model)
                qs = self.changeset.changed_objects_set.filter(
                    content_type=related_content_type)
                if is_created_pk(pk):
                    if not qs.filter(pk=int(pk[1:]), deleted=False).exists():
                        result.add(field.name)
                else:
                    if qs.filter(existing_object_pk=pk, deleted=True).exists():
                        result.add(field.name)
            else:
                if is_created_pk(pk):
                    if pk not in self.changeset.created_objects.get(
                            related_model, ()):
                        result.add(field.name)
                else:
                    if pk in self.changeset.deleted_existing.get(
                            related_model, ()):
                        result.add(field.name)

            if result and max_one:
                return result

        return result
Exemple #3
0
    def save_instance(self, instance):
        old_updated_fields = self.updated_fields
        self.updated_fields = {}

        if instance.pk is None and self.model_class == LocationRedirect and not is_created_pk(
                instance.target_id):
            obj = LocationRedirect.objects.filter(
                pk__in=self.changeset.deleted_existing.get(
                    LocationRedirect, ()),
                slug=instance.slug,
                target_id=instance.target_id).first()
            if obj is not None:
                self.changeset.get_changed_object(obj).restore()
                return

        for field in self.model_class._meta.get_fields():
            if not isinstance(field, Field) or field.primary_key:
                continue

            elif not field.is_relation:
                value = getattr(instance, field.attname)
                if isinstance(field, I18nField):
                    for lang, subvalue in value.items():
                        self.updated_fields['%s__i18n__%s' %
                                            (field.name, lang)] = subvalue
                elif isinstance(field, (CharField, TextField)):
                    self.updated_fields[
                        field.
                        name] = None if field.null and not value else field.get_prep_value(
                            value)
                else:
                    self.updated_fields[field.name] = field.get_prep_value(
                        value)
            elif field.many_to_one or field.one_to_one:
                try:
                    value = getattr(instance, field.get_cache_name())
                except AttributeError:
                    value = getattr(instance, field.attname)
                else:
                    value = None if value is None else value.pk
                self.updated_fields[field.name] = value

        self.clean_updated_fields()
        for name, value in self.updated_fields.items():
            if old_updated_fields.get(name, None) != value:
                self.changeset._object_changed = True
                break
        self.save()
        if instance.pk is None and self.pk is not None:
            instance.pk = self.obj_pk
Exemple #4
0
    def get_missing_dependencies(self, force_query=False, max_one=False):
        result = set()
        if not self.deleted:
            return result
        for field in self.model_class._meta.get_fields():
            if not field.many_to_one:
                continue
            if field.name not in self.updated_fields:
                continue
            related_model = field.related_model
            if related_model._meta.app_label != 'mapdata':
                continue

            pk = self.updated_fields[field.name]

            if force_query:
                # query here to avoid a race condition
                related_content_type = ContentType.objects.get_for_model(related_model)
                qs = self.changeset.changed_objects_set.filter(content_type=related_content_type)
                if is_created_pk(pk):
                    if not qs.filter(pk=int(pk[1:]), deleted=False).exists():
                        result.add(field.name)
                else:
                    if qs.filter(existing_object_pk=pk, deleted=True).exists():
                        result.add(field.name)
            else:
                if is_created_pk(pk):
                    if pk not in self.changeset.created_objects.get(related_model, ()):
                        result.add(field.name)
                else:
                    if pk in self.changeset.deleted_existing.get(related_model, ()):
                        result.add(field.name)

            if result and max_one:
                return result

        return result
Exemple #5
0
    def apply_to_instance(self,
                          instance: ModelInstanceWrapper,
                          created_pks=None):
        for name, value in self.updated_fields.items():
            if '__i18n__' in name:
                name, i18n, lang = name.split('__')
                field = instance._meta.get_field(name)
                if not value:
                    getattr(instance, field.attname).pop(lang, None)
                else:
                    getattr(instance, field.attname)[lang] = value
                continue

            field = instance._meta.get_field(name)
            if not field.is_relation:
                setattr(instance, field.name, field.to_python(value))
            elif field.many_to_one or field.one_to_one:
                if is_created_pk(value):
                    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:
                        try:
                            delattr(instance, field.get_cache_name())
                        except AttributeError:
                            pass
                        try:
                            value = created_pks[field.related_model][value]
                        except KeyError:
                            raise ApplyToInstanceError
                else:
                    try:
                        delattr(instance, field.get_cache_name())
                    except AttributeError:
                        pass
                setattr(instance, field.attname, value)
            else:
                raise NotImplementedError
Exemple #6
0
    def save_instance(self, instance):
        old_updated_fields = self.updated_fields
        self.updated_fields = {}

        if instance.pk is None and self.model_class == LocationRedirect and not is_created_pk(instance.target_id):
            obj = LocationRedirect.objects.filter(pk__in=self.changeset.deleted_existing.get(LocationRedirect, ()),
                                                  slug=instance.slug, target_id=instance.target_id).first()
            if obj is not None:
                self.changeset.get_changed_object(obj).restore()
                return

        for field in self.model_class._meta.get_fields():
            if not isinstance(field, Field) or field.primary_key:
                continue

            elif not field.is_relation:
                value = getattr(instance, field.attname)
                if isinstance(field, I18nField):
                    for lang, subvalue in value.items():
                        self.updated_fields['%s__i18n__%s' % (field.name, lang)] = subvalue
                elif isinstance(field, (CharField, TextField)):
                    self.updated_fields[field.name] = None if field.null and not value else field.get_prep_value(value)
                else:
                    self.updated_fields[field.name] = field.get_prep_value(value)
            elif field.many_to_one or field.one_to_one:
                try:
                    value = getattr(instance, field.get_cache_name())
                except AttributeError:
                    value = getattr(instance, field.attname)
                else:
                    value = None if value is None else value.pk
                self.updated_fields[field.name] = value

        self.clean_updated_fields()
        for name, value in self.updated_fields.items():
            if old_updated_fields.get(name, None) != value:
                self.changeset._object_changed = True
                break
        self.save()
        if instance.pk is None and self.pk is not None:
            instance.pk = self.obj_pk
Exemple #7
0
    def get_changed_object(self, obj) -> ChangedObject:
        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

        return ChangedObject(changeset=self, model_class=model, existing_object_pk=pk)
Exemple #8
0
    def apply_to_instance(self, instance: ModelInstanceWrapper, created_pks=None):
        for name, value in self.updated_fields.items():
            if '__i18n__' in name:
                name, i18n, lang = name.split('__')
                field = instance._meta.get_field(name)
                if not value:
                    getattr(instance, field.attname).pop(lang, None)
                else:
                    getattr(instance, field.attname)[lang] = value
                continue

            field = instance._meta.get_field(name)
            if not field.is_relation:
                setattr(instance, field.name, field.to_python(value))
            elif field.many_to_one or field.one_to_one:
                if is_created_pk(value):
                    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:
                        try:
                            delattr(instance, field.get_cache_name())
                        except AttributeError:
                            pass
                        try:
                            value = created_pks[field.related_model][value]
                        except KeyError:
                            raise ApplyToInstanceError
                else:
                    try:
                        delattr(instance, field.get_cache_name())
                    except AttributeError:
                        pass
                setattr(instance, field.attname, value)
            else:
                raise NotImplementedError
Exemple #9
0
    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
Exemple #10
0
    def _clean_changes(self):
        if self.direct_editing:
            return
        with self.lock_to_edit() as changeset:
            last_map_update_pk = MapUpdate.last_update()[0]
            if changeset.last_cleaned_with_id == last_map_update_pk:
                return

            changed_objects = changeset.changed_objects_set.all()

            # delete changed objects that refer in some way to deleted objects and clean up m2m changes
            object_pks = {}
            for changed_object in changed_objects:
                changed_object.add_relevant_object_pks(object_pks)

            to_save = set()

            deleted_object_pks = {}
            for model, pks in object_pks.items():
                pks = set(pk for pk in pks if not is_created_pk(pk))
                deleted_object_pks[model] = pks - set(model.objects.filter(pk__in=pks).values_list('pk', flat=True))

            repeat = True
            while repeat:
                repeat = False
                for changed_object in changed_objects:
                    if changed_object.handle_deleted_object_pks(deleted_object_pks):
                        to_save.add(changed_object)
                    if changed_object.pk is None:
                        repeat = True

                # remove deleted objects
                changed_objects = [obj for obj in changed_objects if obj.pk is not None]

            # clean updated fields
            objects = changeset.get_objects(many=False, changed_objects=changed_objects, prefetch_related=('groups', ))
            for changed_object in changed_objects:
                if changed_object.clean_updated_fields(objects):
                    to_save.add(changed_object)

            # clean m2m
            for changed_object in changed_objects:
                if changed_object.clean_m2m(objects):
                    to_save.add(changed_object)

            # remove duplicate slugs
            slugs = set()
            for changed_object in changed_objects:
                if issubclass(changed_object.model_class, LocationSlug):
                    slug = changed_object.updated_fields.get('slug', None)
                    if slug is not None:
                        slugs.add(slug)

            qs = LocationSlug.objects.filter(slug__in=slugs)
            if slugs:
                qs = qs.filter(reduce(operator.or_, (Q(slug__startswith=slug+'__') for slug in slugs)))
            existing_slugs = dict(qs.values_list('slug', 'redirect__target_id'))

            slug_length = LocationSlug._meta.get_field('slug').max_length
            for changed_object in changed_objects:
                if issubclass(changed_object.model_class, LocationSlug):
                    slug = changed_object.updated_fields.get('slug', None)
                    if slug is None:
                        continue
                    if slug in existing_slugs:
                        redirect_to = existing_slugs[slug]
                        if issubclass(changed_object.model_class, LocationRedirect) and redirect_to is not None:
                            to_save.discard(changed_object)
                            changed_object.delete()
                            continue
                        new_slug = slug
                        i = 0
                        while new_slug in existing_slugs:
                            suffix = '__'+str(i)
                            new_slug = slug[:slug_length-len(suffix)]+suffix
                            i += 1
                        slug = new_slug
                        changed_object.updated_fields['slug'] = new_slug
                        to_save.add(changed_object)
                    existing_slugs[slug] = (None if not issubclass(changed_object.model_class, LocationRedirect)
                                            else changed_object.updated_fields['target'])

            for changed_object in to_save:
                changed_object.save(standalone=True)

            changeset.last_cleaned_with_id = last_map_update_pk
            changeset.save()
Exemple #11
0
def changeset_detail(request, pk):
    changeset = request.changeset
    active = True
    if str(pk) != str(request.changeset.pk):
        active = False
        qs = ChangeSet.qs_for_request(request).select_related('last_update', 'last_state_update',
                                                              'last_change', 'author')
        changeset = get_object_or_404(qs, pk=pk)

    if not changeset.can_see(request):
        raise Http404

    can_edit = changeset.can_edit(request)
    can_delete = changeset.can_delete(request)

    if request.method == 'POST':
        restore = request.POST.get('restore')
        if restore and restore.isdigit():
            with changeset.lock_to_edit(request) as changeset:
                if changeset.can_edit(request):
                    try:
                        changed_object = changeset.changed_objects_set.get(pk=restore)
                    except Exception:
                        pass
                    else:
                        try:
                            changed_object.restore()
                            messages.success(request, _('Object has been successfully restored.'))
                        except PermissionError:
                            messages.error(request, _('You cannot restore this object, because it depends on '
                                                      'a deleted object or it would violate a unique contraint.'))

                else:
                    messages.error(request, _('You can not edit changes on this change set.'))

            return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

        elif request.POST.get('activate') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if changeset.can_activate(request):
                    changeset.activate(request)
                    messages.success(request, _('You activated this change set.'))
                else:
                    messages.error(request, _('You can not activate this change set.'))

            return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

        elif request.POST.get('propose') == '1':
            if not request.user.is_authenticated:
                messages.info(request, _('You need to log in to propose changes.'))
                return redirect(reverse('editor.login')+'?r='+request.path)

            with changeset.lock_to_edit(request) as changeset:
                if not changeset.title or not changeset.description:
                    messages.warning(request, _('You need to add a title an a description to propose this change set.'))
                    return redirect(reverse('editor.changesets.edit', kwargs={'pk': changeset.pk}))

                if changeset.can_propose(request):
                    changeset.propose(request.user)
                    messages.success(request, _('You proposed your changes.'))
                else:
                    messages.error(request, _('You cannot propose this change set.'))

            return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

        elif request.POST.get('unpropose') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if changeset.can_unpropose(request):
                    changeset.unpropose(request.user)
                    messages.success(request, _('You unproposed your changes.'))
                else:
                    messages.error(request, _('You cannot unpropose this change set.'))

            return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

        elif request.POST.get('review') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if changeset.can_start_review(request):
                    changeset.start_review(request.user)
                    messages.success(request, _('You are now reviewing these changes.'))
                else:
                    messages.error(request, _('You cannot review these changes.'))

            return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

        elif request.POST.get('reject') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if not changeset.can_end_review(request):
                    messages.error(request, _('You cannot reject these changes.'))
                    return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

                if request.POST.get('reject_confirm') == '1':
                    form = RejectForm(data=request.POST)
                    if form.is_valid():
                        changeset.reject(request.user, form.cleaned_data['comment'], form.cleaned_data['final'])
                        messages.success(request, _('You rejected these changes.'))
                        return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))
                else:
                    form = RejectForm()

                return render(request, 'editor/changeset_reject.html', {
                    'changeset': changeset,
                    'form': form,
                })

        elif request.POST.get('unreject') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if not changeset.can_unreject(request):
                    messages.error(request, _('You cannot unreject these changes.'))
                    return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

                changeset.unreject(request.user)
                messages.success(request, _('You unrejected these changes.'))

            return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

        elif request.POST.get('apply') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if not changeset.can_end_review(request):
                    messages.error(request, _('You cannot accept and apply these changes.'))
                    return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

                if request.POST.get('apply_confirm') == '1':
                    changeset.apply(request.user)
                    messages.success(request, _('You accepted and applied these changes.'))
                    return redirect(reverse('editor.changesets.detail', kwargs={'pk': changeset.pk}))

                return render(request, 'editor/changeset_apply.html', {})

        elif request.POST.get('delete') == '1':
            with changeset.lock_to_edit(request) as changeset:
                if not changeset.can_delete(request):
                    messages.error(request, _('You cannot delete this change set.'))

                if request.POST.get('delete_confirm') == '1':
                    changeset.delete()
                    messages.success(request, _('You deleted this change set.'))
                    if request.user.is_authenticated:
                        return redirect(reverse('editor.users.detail', kwargs={'pk': request.user.pk}))
                    else:
                        return redirect(reverse('editor.index'))

                return render(request, 'editor/delete.html', {
                    'model_title': ChangeSet._meta.verbose_name,
                    'obj_title': changeset.title,
                })

    changeset.fill_changes_cache()

    ctx = {
        'changeset': changeset,
        'can_edit': can_edit,
        'can_delete': can_delete,
        'can_propose': changeset.can_propose(request),
        'can_unpropose': changeset.can_unpropose(request),
        'can_start_review': changeset.can_start_review(request),
        'can_end_review': changeset.can_end_review(request),
        'can_unreject': changeset.can_unreject(request),
        'active': active,
    }

    cache_key = '%s:%s:%s:view_data' % (changeset.cache_key_by_changes,
                                        changeset.last_update_id,
                                        int(can_edit))
    changed_objects_data = cache.get(cache_key)
    if changed_objects_data:
        ctx['changed_objects'] = changed_objects_data
        return render(request, 'editor/changeset.html', ctx)

    objects = changeset.get_objects()

    changed_objects_data = []

    added_redirects = {}
    removed_redirects = {}
    for changed_object in changeset.changed_objects.get(LocationRedirect, {}).values():
        if changed_object.is_created == changed_object.deleted:
            continue
        obj = objects[LocationRedirect][changed_object.obj_pk]
        redirect_list = (removed_redirects if changed_object.deleted else added_redirects)
        redirect_list.setdefault(obj.target_id, []).append(obj.slug)

    redirect_changed_objects = []

    for pk in set(added_redirects.keys()) | set(removed_redirects.keys()):
        obj = objects[LocationSlug][pk]
        model = obj.__class__
        try:
            changeset.changed_objects[model][pk]
        except KeyError:
            redirect_changed_objects.append((model, {pk: changeset.get_changed_object(obj)}))

    for model, changed_objects in chain(changeset.changed_objects.items(), redirect_changed_objects):
        if model == LocationRedirect:
            continue

        for pk, changed_object in changed_objects.items():
            obj = objects[model][pk]

            obj_desc = format_lazy(_('{model} #{id}'), model=obj.__class__._meta.verbose_name, id=pk)
            if is_created_pk(pk):
                obj_still_exists = pk in changeset.created_objects.get(obj.__class__, ())
            else:
                obj_still_exists = pk not in changeset.deleted_existing.get(obj.__class__, ())

            edit_url = None
            if obj_still_exists and can_edit and not isinstance(obj, LocationRedirect):
                reverse_kwargs = {'pk': obj.pk}
                if hasattr(obj, 'space_id'):
                    reverse_kwargs['space'] = obj.space_id
                elif hasattr(obj, 'level_id'):
                    reverse_kwargs['level'] = obj.level_id
                try:
                    edit_url = reverse('editor.' + obj.__class__._meta.default_related_name + '.edit',
                                       kwargs=reverse_kwargs)
                except NoReverseMatch:
                    pass

            changes = []
            missing_dependencies = changed_object.get_missing_dependencies()
            unique_collisions = changed_object.get_unique_collisions()
            changed_object_data = {
                'model': obj.__class__,
                'model_title': obj.__class__._meta.verbose_name,
                'pk': changed_object.pk,
                'desc': obj_desc,
                'title': obj.title if getattr(obj, 'titles', None) else None,
                'changes': changes,
                'edit_url': edit_url,
                'deleted': changed_object.deleted,
                'missing_dependencies': missing_dependencies,
                'unique_collisions': unique_collisions,
                'order': (changed_object.deleted and changed_object.is_created, not changed_object.is_created),
            }
            changed_objects_data.append(changed_object_data)

            form_fields = changeset.wrap_model(type(obj)).EditorForm._meta.fields

            if changed_object.is_created:
                changes.append({
                    'icon': 'plus',
                    'class': 'success',
                    'empty': True,
                    'title': _('created'),
                })

            update_changes = []

            for name, value in changed_object.updated_fields.items():
                change_data = {
                    'icon': 'option-vertical',
                    'class': 'muted',
                }
                if name == 'geometry':
                    change_data.update({
                        'icon': 'map-marker',
                        'class': 'info',
                        'empty': True,
                        'title': _('created geometry') if changed_object.is_created else _('edited geometry'),
                        'order': (8,),
                    })
                elif name == 'data':
                    change_data.update({
                        'icon': 'signal',
                        'class': 'info',
                        'empty': True,
                        'title': _('created scan data') if changed_object.is_created else _('edited scan data'),
                        'order': (9,),
                    })
                else:
                    if '__i18n__' in name:
                        orig_name, i18n, lang = name.split('__')
                        lang_info = get_language_info(lang)
                        field = model._meta.get_field(orig_name)
                        field_title = format_lazy(_('{field_name} ({lang})'),
                                                  field_name=field.verbose_name,
                                                  lang=lang_info['name_translated'])
                        field_value = str(value)
                        if field_value:
                            getattr(obj, field.attname)[lang] = field_value
                        else:
                            getattr(obj, field.attname).pop(lang, None)
                        change_data.update({
                            'order': (4, tuple(code for code, title in settings.LANGUAGES).index(lang)),
                        })
                    else:
                        field = model._meta.get_field(name)
                        field_title = field.verbose_name
                        field_value = field.to_python(value)
                        if field.related_model is not None:
                            if issubclass(field.related_model, User):
                                field_value = objects[field.related_model][field_value].username
                            else:
                                field_value = objects[field.related_model][field_value].title
                            change_data.update({
                                'missing_dependency': field.name in missing_dependencies,
                            })
                        if name in unique_collisions:
                            change_data.update({
                                'unique_collision': field.name in unique_collisions,
                            })
                        order = 5
                        if name == 'slug':
                            order = 1
                        if name not in form_fields:
                            order = 0
                        change_data.update({
                            'order': (order, form_fields.index(name) if order else 1),
                        })
                    if field_value == '' or field_value is None:
                        change_data.update({
                            'empty': True,
                            'title': format_lazy(_('remove {field_title}'), field_title=field_title),
                        })
                    else:
                        change_data.update({
                            'title': field_title,
                            'value': field_value,
                        })
                update_changes.append(change_data)

            changes.extend(sorted(update_changes, key=itemgetter('order')))

            for m2m_mode in ('m2m_added', 'm2m_removed'):
                m2m_list = getattr(changed_object, m2m_mode).items()
                for name, values in sorted(m2m_list, key=lambda nv: form_fields.index(nv[0])):
                    field = model._meta.get_field(name)
                    for value in values:
                        changes.append({
                            'icon': 'chevron-right' if m2m_mode == 'm2m_added' else 'chevron-left',
                            'class': 'info',
                            'title': field.verbose_name,
                            'value': objects[field.related_model][value].title,
                        })

            if isinstance(obj, LocationSlug):
                for slug in added_redirects.get(obj.pk, ()):
                    changes.append({
                        'icon': 'chevron-right',
                        'class': 'info',
                        'title': _('Redirect slugs'),
                        'value': slug,
                    })
                for slug in removed_redirects.get(obj.pk, ()):
                    changes.append({
                        'icon': 'chevron-left',
                        'class': 'info',
                        'title': _('Redirect slugs'),
                        'value': slug,
                    })

            if changed_object.deleted:
                changes.append({
                    'icon': 'minus',
                    'class': 'danger',
                    'empty': True,
                    'title': _('deleted'),
                    'order': (9,),
                })

    changed_objects_data = sorted(changed_objects_data, key=itemgetter('order'))

    cache.set(cache_key, changed_objects_data, 300)
    ctx['changed_objects'] = changed_objects_data

    return render(request, 'editor/changeset.html', ctx)
Exemple #12
0
    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
Exemple #13
0
    def _clean_changes(self):
        if self.direct_editing:
            return
        with self.lock_to_edit() as changeset:
            last_map_update_pk = MapUpdate.last_update()[0]
            if changeset.last_cleaned_with_id == last_map_update_pk:
                return

            changed_objects = changeset.changed_objects_set.all()

            # delete changed objects that refer in some way to deleted objects and clean up m2m changes
            object_pks = {}
            for changed_object in changed_objects:
                changed_object.add_relevant_object_pks(object_pks)

            to_save = set()

            deleted_object_pks = {}
            for model, pks in object_pks.items():
                pks = set(pk for pk in pks if not is_created_pk(pk))
                deleted_object_pks[model] = pks - set(
                    model.objects.filter(pk__in=pks).values_list('pk',
                                                                 flat=True))

            repeat = True
            while repeat:
                repeat = False
                for changed_object in changed_objects:
                    if changed_object.handle_deleted_object_pks(
                            deleted_object_pks):
                        to_save.add(changed_object)
                    if changed_object.pk is None:
                        repeat = True

                # remove deleted objects
                changed_objects = [
                    obj for obj in changed_objects if obj.pk is not None
                ]

            # clean updated fields
            objects = changeset.get_objects(many=False,
                                            changed_objects=changed_objects,
                                            prefetch_related=('groups', ))
            for changed_object in changed_objects:
                if changed_object.clean_updated_fields(objects):
                    to_save.add(changed_object)

            # clean m2m
            for changed_object in changed_objects:
                if changed_object.clean_m2m(objects):
                    to_save.add(changed_object)

            # remove duplicate slugs
            slugs = set()
            for changed_object in changed_objects:
                if issubclass(changed_object.model_class, LocationSlug):
                    slug = changed_object.updated_fields.get('slug', None)
                    if slug is not None:
                        slugs.add(slug)

            qs = LocationSlug.objects.filter(slug__in=slugs)
            if slugs:
                qs = qs.filter(
                    reduce(operator.or_, (Q(slug__startswith=slug + '__')
                                          for slug in slugs)))
            existing_slugs = dict(qs.values_list('slug',
                                                 'redirect__target_id'))

            slug_length = LocationSlug._meta.get_field('slug').max_length
            for changed_object in changed_objects:
                if issubclass(changed_object.model_class, LocationSlug):
                    slug = changed_object.updated_fields.get('slug', None)
                    if slug is None:
                        continue
                    if slug in existing_slugs:
                        redirect_to = existing_slugs[slug]
                        if issubclass(
                                changed_object.model_class,
                                LocationRedirect) and redirect_to is not None:
                            to_save.discard(changed_object)
                            changed_object.delete()
                            continue
                        new_slug = slug
                        i = 0
                        while new_slug in existing_slugs:
                            suffix = '__' + str(i)
                            new_slug = slug[:slug_length -
                                            len(suffix)] + suffix
                            i += 1
                        slug = new_slug
                        changed_object.updated_fields['slug'] = new_slug
                        to_save.add(changed_object)
                    existing_slugs[slug] = (
                        None if not issubclass(changed_object.model_class,
                                               LocationRedirect) else
                        changed_object.updated_fields['target'])

            for changed_object in to_save:
                changed_object.save(standalone=True)

            changeset.last_cleaned_with_id = last_map_update_pk
            changeset.save()