#ALL ADMIN IMPORTS
from django.contrib.admin.options import *
from django.utils.translation import ugettext_lazy as _, ugettext
from django import forms
from django.contrib.admin.util import NestedObjects
from django.db import models
from django.db.models.related import RelatedObject
from django.utils.functional import curry

class AdminReplacer(ModelAdmin):
    
    #START PATCH
    #START PATCH
    #START PATCH
    
    substitution_delete = True
    substitution_custom_handlers = tuple()
    
    def get_deletion_form(self, request, objs):
        
        class DeletionForm(forms.Form):
            
            substituted_object = forms.ModelChoiceField(
                empty_label = ugettext("Do not replace, delete the following objects"),
                label = ugettext("Object to replace"),
                queryset = self.queryset(request).exclude(pk__in = [obj.pk for obj in objs]),
                required = False,
                )
            
        return DeletionForm
        
    def log_substitution(self, request, object, object_repr, substituted_object, substituted_object_repr):
        """
        Log that an object has been successfully deleted. Note that since the
        object is deleted, it might no longer be safe to call *any* methods
        on the object, hence this method getting object_repr.

        The default implementation creates an admin LogEntry object.
        """
        from django.contrib.admin.models import LogEntry, DELETION
        LogEntry.objects.log_action(
            user_id         = request.user.id,
            content_type_id = ContentType.objects.get_for_model(self.model).pk,
            object_id       = object.pk,
            object_repr     = object_repr,
            action_flag     = DELETION,
            change_message  = u'Replaced with object with primary key %s "%s".' % (substituted_object.pk, substituted_object_repr)
        )
        
    def substitution_m2m_handler(self, request, deleted_object, substituted_object, accessor_name, related = None):
        """
        Default behavoiur for handling incoming M2M fields
        If a RelatedObject is defined, M2M relation in Inbound, otherwise it is outbound.
        """
        
        manager = getattr(deleted_object, accessor_name)
        
        if related:
            """
            Default behaviour:
            Incoming M2M relation will be preserved
            """
            getattr(substituted_object, accessor_name).add(*manager.all())  
            return
            
        """
        Default behaviour:
        Exiting M2M objects will be deleted
        """            
        return
    
    def substitution_handler(self, request, deleted_object, substituted_object, accessor_name, related):
        """
        This is the default handler for a substitution, subclasses can define a new method.
        Related is an instance of django.db.models.related.RelatedObject
        
        Custom functions will receive (request, deleted_object, substituted_object, accessor_name)
        
        Default behaviour:
        Incoming ForeignKeys will be preserved,
        Subclass this method to add a custom behaviour, return None to delete incoming objects.
        """
        
        manager = getattr(deleted_object, accessor_name)
        manager.all().update(**{related.field.name: substituted_object})   
            
    def get_substitution_handlers(self):
        """
        The many-to-many version of get_fields_with_model().
        """
        try:
            return self._substitution_handlers_cache
        except AttributeError:
            self._fill_substitution_handlers_cache()
        return self._substitution_handlers_cache

    def _fill_substitution_handlers_cache(self):
        cache = SortedDict()
        
        accessor_names  = [(related.get_accessor_name(), curry(self.substitution_handler, related = related)) 
            for related in self.model._meta.get_all_related_objects()]
            
        accessor_names += [(related.get_accessor_name(), curry(self.substitution_m2m_handler, related = related)) 
            for related in self.model._meta.get_all_related_many_to_many_objects()]
            
        accessor_names += [(field.name, self.substitution_m2m_handler) 
            for field in self.model._meta.many_to_many]
            
        keys = [v for v, f in accessor_names]
        
        for (accessor_name, f) in accessor_names: 
            
            for attr_name, handler_name in self.substitution_custom_handlers:
                
                if not attr_name in keys:
                    raise KeyError, 'Accessor %s was not found. Choices are: %s' % (attr_name, ", ".join(keys))
                
                if accessor_name == attr_name:
                    
                    f = handler_name
                    
                    if isinstance(handler_name, basestring):
                        f = getattr(self, handler_name)
                    
                    break       
            
            if f:
                cache[accessor_name] = f
        
        self._substitution_handlers_cache = cache            
            
    def substitute_deleted_objects(self, request, deleted_objects, substituted_object):
        
        """
        Move every related object to another instance
        You may need to define a list of model to ignore for two reason:
        1. moving object with unique columns may cause integrity error
        2. you could have business reasons 
        """
        
        handlers = self.get_substitution_handlers().items()
        
        for obj in deleted_objects:
            for accessor_name, handler in handlers:
                handler(request, obj, substituted_object, accessor_name)
                
    #END PATCH
    #END PATCH
    #END PATCH
    
    @csrf_protect_m
    def delete_view(self, request, object_id, extra_context=None):
        
        if not self.substitution_delete:
            return super(AdminReplacer, self).delete_view(request, object_id, extra_context=extra_context)
        
        "The 'delete' admin view for this model."
        opts = self.model._meta
        app_label = opts.app_label

        obj = self.get_object(request, unquote(object_id))

        if 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)})
            
        #START PATCH
        #START PATCH
        #START PATCH
        
        substituted_object = deletion_form = None
        
        if self.substitution_delete:
            DeletionForm = self.get_deletion_form(request, (obj,))
            deletion_form = DeletionForm(request.POST or None, initial = request.GET)
            if deletion_form.is_valid():
                substituted_object = deletion_form.cleaned_data["substituted_object"] 
        
        #END PATCH
        #END PATCH
        #END PATCH
        
        # Populate deleted_objects, a data structure of all related objects that
        # will also be deleted.
        (deleted_objects, perms_needed) = get_deleted_objects((obj,), opts, request.user, self.admin_site)
        
        if request.POST and deletion_form and deletion_form.is_valid(): # The user has already confirmed the deletion.
            
            if perms_needed:
                raise PermissionDenied
            obj_display = force_unicode(obj)
            
            #START PATCH
            #START PATCH
            #START PATCH
            
            
            if self.substitution_delete and substituted_object:
                
                self.substitute_deleted_objects(request, [obj], substituted_object)
                self.log_substitution(request, obj, obj_display, substituted_object, force_unicode(substituted_object))
                    
            else:
                self.log_deletion(request, obj, obj_display)
            #END PATCH
            #END PATCH
            #END PATCH
            
            obj.delete()

            self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})

            if not self.has_change_permission(request, None):
                return HttpResponseRedirect("../../../../")
            return HttpResponseRedirect("../../")

        context = {
            "title": _("Are you sure?"),
            "object_name": force_unicode(opts.verbose_name),
            "object": obj,
            "deletion_form":deletion_form,
            "deleted_objects": deleted_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.delete_confirmation_template or [
            "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
            "admin/%s/delete_confirmation.html" % app_label,
            "admin/delete_confirmation.html"
        ], context, context_instance=context_instance)    




---------------------
#HTML -> admin/delete_confirmation.html

{% extends "admin/base_site.html" %}
{% load i18n %}

{% block breadcrumbs %}
<div class="breadcrumbs">
     <a href="../../../../">{% trans "Home" %}</a> &rsaquo;
     <a href="../../../">{{ app_label|capfirst }}</a> &rsaquo; 
     <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> &rsaquo;
     <a href="../">{{ object|truncatewords:"18" }}</a> &rsaquo;
     {% trans 'Delete' %}
</div>
{% endblock %}

{% block content %}
{% if perms_lacking %}
    <p>{% blocktrans with object as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
    <ul>
    {% for obj in perms_lacking %}
        <li>{{ obj }}</li>
    {% endfor %}
    </ul>
{% else %}
    <form action="" method="post">{% csrf_token %}
    {{ deletion_form.as_p }}
    <p>{% blocktrans with object as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
    <ul>{{ deleted_objects|unordered_list }}</ul>
    <div>
    <input type="hidden" name="post" value="yes" />
    <input type="submit" value="{% trans "Yes, I'm sure" %}" />
    </div>
    </form>
{% endif %}
{% endblock %}