def _merge_item(self, original, draft_copy): """ Delete original, clean up and publish copy.""" # Handle FK and M2M references. refs = filter( lambda obj: obj != draft_copy, get_referencing_objects(original) ) for ref in refs: field_names = lookup_referencing_object_relationships(original, ref) for field_name in field_names: fld_class = ref._meta.get_field(field_name).__class__ if issubclass(fld_class, models.fields.related.ManyToManyField): getattr(ref, field_name).remove(original) getattr(ref, field_name).add(draft_copy) else: setattr(ref, field_name, draft_copy) ref.save() # Handle generic references. for ref in get_generic_referencing_objects(original): generic_fk_field = [f for f in ref._meta.virtual_fields \ if isinstance(f, generic.GenericForeignKey) ][0].name setattr(ref, generic_fk_field, draft_copy) ref.save() # Overwrite the old object. if self.slug: setattr(original, self.slug_field, original.slug + "-merge") original.save() if self.slug: import re slug = re.sub( "-draft-copy$", "", getattr(draft_copy, self.slug_field) ) setattr(draft_copy, self.slug_field, slug) draft_copy.copy_of = None draft_copy.save() original.delete() draft_copy.publish() return draft_copy
def merge_view(self, request, object_id, extra_context=None): """ The 'merge' admin view for this model. Allows a user to merge a copy back over the original. """ opts = self.model._meta app_label = opts.app_label if not self.draft_copy_allowed: return HttpResponseBadRequest("Draft copy not allowed for %s." % force_unicode(opts.verbose_name_plural) ) obj = self.get_object(request, unquote(object_id)) # For our purposes, permission to merge is equivalent to # has_change_permisison and has_delete_permission. if not self.has_change_permission(request, obj) \ or not self.has_delete_permission(request, obj) : raise PermissionDenied if obj is None: raise Http404(_( '%(name)s object with primary key %(key)r does not exist.') % { 'name': force_unicode(opts.verbose_name), 'key': escape(object_id) } ) if not obj.is_draft_copy(): return HttpResponseBadRequest(_( 'The %s object could not be merged because it is not a ' 'draft copy. There is nothing to merge it into.' ) % force_unicode(opts.verbose_name)) # Populate deleted_objects, a data structure of all related objects # that will also be deleted when this copy is deleted. generic_refs = get_generic_referencing_objects(obj.copy_of) direct_refs = get_referencing_objects(obj.copy_of) direct_refs = filter(lambda o: obj != o, direct_refs) object_refs = [(unicode(o), o._meta.verbose_name) for o in \ chain(direct_refs, generic_refs) ] perms_needed = False if request.POST: # The user has already confirmed the merge. if perms_needed: raise PermissionDenied original = obj.copy_of original_pk = original.pk self._merge_item(original, obj) # Look up admin log entries for the old object and reassign them # to the new object. ctype = ContentType.objects.get_for_model(original) LogEntry.objects.filter( content_type=ctype, object_id=original_pk ).update(object_id=obj.pk) message = 'Merged %s "%s".' % ( force_unicode(obj._meta.verbose_name), force_unicode(obj) ) self.log_change(request, obj, message) self.message_user( request, _('The %(name)s "%(obj)s" was merged successfully.') % { 'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj) } ) return HttpResponseRedirect("../../") context = { "title": _("Are you sure?"), "object_name": force_unicode(opts.verbose_name), "object": obj, "escaped_original": force_unicode(obj.copy_of), "referencing_objects": object_refs, "opts": opts, "root_path": self.admin_site.root_path, "app_label": app_label, } context.update(extra_context or {}) context_instance = template.RequestContext( request, current_app=self.admin_site.name ) return render_to_response(self.merge_form_template, context, context_instance=context_instance )
def copy_view(self, request, object_id, extra_context=None): """ Create a draft copy of the item after user has confirmed. """ opts = self.model._meta app_label = opts.app_label if not self.draft_copy_allowed: return HttpResponseBadRequest("Draft copy not allowed for %s." % force_unicode(opts.verbose_name_plural) ) obj = self.get_object(request, unquote(object_id)) object_refs = None # For our purposes, permission to copy is equivalent to # has_add_permisison. if not self.has_add_permission(request): raise PermissionDenied if obj is None: raise Http404(_( '%(name)s object with primary key %(key)r does not exist.') % { 'name': force_unicode(opts.verbose_name), 'key': escape(object_id) } ) if request.POST: # The user has already confirmed the copy. if obj.is_draft_copy(): self.message_user( request, _('You cannot copy a draft copy.') ) return HttpResponseRedirect(request.path) if obj.get_draft_copy(): self.message_user( request, _('A draft copy already exists.') ) return HttpResponseRedirect(request.path) copy = self._copy_item(obj) original_message = 'Created a draft copy for %s "%s".' % ( force_unicode(obj._meta.verbose_name), force_unicode(obj) ) copy_message = 'Copied from %s "%s".' % ( force_unicode(obj._meta.verbose_name), force_unicode(obj) ) self.log_change(request, obj, original_message) self.log_change(request, copy, copy_message) self.message_user( request, _('The %(name)s "%(obj)s" was copied successfully.') % { 'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj) } ) url = reverse( "admin:%s_%s_change" % ( app_label, self.model._meta.module_name ), args=(copy.id,) ) return HttpResponseRedirect(url) if self.model.objects.filter(copy_of=obj).exists(): draft_already_exists = True title = _("Draft Copy Exists") edit_copy_url = reverse( "admin:%s_%s_change" % ( app_label, self.model._meta.module_name ), args=(self.model.objects.filter(copy_of=obj)[0].id,) ) else: draft_already_exists = False title = _("Are you sure?") edit_copy_url = None generic_refs = get_generic_referencing_objects(obj) direct_refs = get_referencing_objects(obj) object_refs = [(unicode(o), o._meta.verbose_name) for o in \ chain(direct_refs, generic_refs) ] context = { "title": title, "object_name": force_unicode(opts.verbose_name), "object": obj, "referencing_objects": object_refs, "opts": opts, "root_path": self.admin_site.root_path, "app_label": app_label, 'draft_already_exists': draft_already_exists, 'edit_copy_url': edit_copy_url } context.update(extra_context or {}) context_instance = template.RequestContext( request, current_app=self.admin_site.name ) return render_to_response(self.copy_form_template, context, context_instance=context_instance )