def _copy_page(self, item): """ Create a draft copy of a published item to edit.""" if not item.is_published: return None new_item = deepcopy(item) new_item.id = None new_item.status = get_unpublished_status_name() new_item.copy_of = item new_item.title += _(" (draft copy)") new_item.slug += "-draft-copy" # Position the new item as the next neighbor of # the original new_item.insert_at(item, position='right') new_item.save() for obj in introspect.get_referencing_objects(new_item): if not obj.__class__ is new_item.__class__: copy_method = self._get_copy_method_name(obj) obj_copy = getattr(self, copy_method, self._copy_object)(obj) for data in introspect.get_referencing_models( new_item.__class__ ): if data['model'] is obj_copy.__class__: for m2m_field_name in data['m2m_field_names']: getattr(obj_copy, m2m_field_name).add(new_item) for field_name in data['field_names']: setattr(obj_copy, field_name, new_item) obj_copy.save() return new_item
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 draft copy back over the original. """ opts = self.model._meta app_label = opts.app_label 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. all_objects = introspect.get_referencing_objects(obj.copy_of) all_objects.insert(0, obj.copy_of) using = router.db_for_write(self.model) (deleted_objects, perms_needed, protected) = get_deleted_objects( all_objects, opts, request.user, self.admin_site, using ) # Flatten nested list: deleted_objects = map( lambda i: hasattr(i, '__iter__') and i or [i], deleted_objects ) deleted_objects = chain(*deleted_objects) deleted_objects = list(deleted_objects) # ``get_deleted_objects`` is zealous and will add the draft copy to # the list of things to be deleted. This needs to be removed. obj_url = reverse("admin:pagemanager_page_change", args=(obj.pk,)) deleted_objects = filter( lambda link: obj_url not in link, deleted_objects ) # Filter out child pages: these will be preserved too. for child in obj.copy_of.children.all(): child_url = reverse("admin:pagemanager_page_change", args=(child.pk,)) deleted_objects = filter( lambda link: child_url not in link, deleted_objects ) # Populate replacing_objects, a data structure of all related objects # that will be replacing the originals. replacing_objects = introspect.get_referencing_objects(obj) replacing_objects.insert(0, obj) (replacing_objects, perms_needed, protected) = get_deleted_objects( replacing_objects, opts, request.user, self.admin_site, using ) # Flatten nested list: replacing_objects = map( lambda i: hasattr(i, '__iter__') and i or [i], replacing_objects ) replacing_objects = chain(*replacing_objects) replacing_objects = list(replacing_objects) if request.POST: # The user has already confirmed the merge. if perms_needed: raise PermissionDenied obj_display = force_unicode(obj) + " merged." self.log_change(request, obj, obj_display) original = obj.copy_of original_pk = original.pk original_layout_pk = original.page_layout.pk self._merge_item(original, obj) # Look up admin log entries for the old object and reassign them # to the new object. page_ctype = ContentType.objects.get_for_model(original) layout_ctype = ContentType.objects.get_for_model(original.page_layout) LogEntry.objects.filter( content_type=page_ctype, object_id=original_pk ).update(object_id=obj.pk) LogEntry.objects.filter( content_type=layout_ctype, object_id=original_layout_pk ).update(object_id=obj.page_layout.pk) self.message_user( request, _('The %(name)s "%(obj)s" was merged successfully.') % { 'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display) } ) redirect_url = reverse("admin:pagemanager_page_change", args=(original.pk,) ) return HttpResponseRedirect(redirect_url) context = { "title": _("Are you sure?"), "object_name": force_unicode(opts.verbose_name), "object": obj, "escaped_original": force_unicode(obj.copy_of), "deleted_objects": deleted_objects, "replacing_objects": replacing_objects, "perms_lacking": perms_needed, "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 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 )
def merge_view(self, request, object_id, extra_context=None): """ The 'merge' admin view for this model. Allows a user to merge a draft copy back over the original. """ opts = self.model._meta app_label = opts.app_label 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. all_objects = introspect.get_referencing_objects(obj.copy_of) all_objects.insert(0, obj.copy_of) using = router.db_for_write(self.model) (deleted_objects, perms_needed, protected) = get_deleted_objects(all_objects, opts, request.user, self.admin_site, using) # Flatten nested list: deleted_objects = map(lambda i: hasattr(i, '__iter__') and i or [i], deleted_objects) deleted_objects = chain(*deleted_objects) deleted_objects = list(deleted_objects) # ``get_deleted_objects`` is zealous and will add the draft copy to # the list of things to be deleted. This needs to be removed. obj_url = reverse("admin:pagemanager_page_change", args=(obj.pk, )) deleted_objects = filter(lambda link: obj_url not in link, deleted_objects) # Filter out child pages: these will be preserved too. for child in obj.copy_of.children.all(): child_url = reverse("admin:pagemanager_page_change", args=(child.pk, )) deleted_objects = filter(lambda link: child_url not in link, deleted_objects) # Populate replacing_objects, a data structure of all related objects # that will be replacing the originals. replacing_objects = introspect.get_referencing_objects(obj) replacing_objects.insert(0, obj) (replacing_objects, perms_needed, protected) = get_deleted_objects(replacing_objects, opts, request.user, self.admin_site, using) # Flatten nested list: replacing_objects = map(lambda i: hasattr(i, '__iter__') and i or [i], replacing_objects) replacing_objects = chain(*replacing_objects) replacing_objects = list(replacing_objects) if request.POST: # The user has already confirmed the merge. if perms_needed: raise PermissionDenied obj_display = force_unicode(obj) + " merged." self.log_change(request, obj, obj_display) original = obj.copy_of original_pk = original.pk original_layout_pk = original.page_layout.pk self._merge_item(original, obj) # Look up admin log entries for the old object and reassign them # to the new object. page_ctype = ContentType.objects.get_for_model(original) layout_ctype = ContentType.objects.get_for_model( original.page_layout) LogEntry.objects.filter( content_type=page_ctype, object_id=original_pk).update(object_id=obj.pk) LogEntry.objects.filter(content_type=layout_ctype, object_id=original_layout_pk).update( object_id=obj.page_layout.pk) self.message_user( request, _('The %(name)s "%(obj)s" was merged successfully.') % { 'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display) }) redirect_url = reverse("admin:pagemanager_page_change", args=(original.pk, )) return HttpResponseRedirect(redirect_url) context = { "title": _("Are you sure?"), "object_name": force_unicode(opts.verbose_name), "object": obj, "escaped_original": force_unicode(obj.copy_of), "deleted_objects": deleted_objects, "replacing_objects": replacing_objects, "perms_lacking": perms_needed, "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)