Login

Django Admin Replacer Code

Author:
riccardodivirgilio
Posted:
November 18, 2010
Language:
Python
Version:
Not specified
Tags:
admin object delete modeladmin replacer
Score:
0 (after 0 ratings)

Ok let's descrive what i have done I subclassed the django admin to create a form that makes you choose if activate a delete and replace login inside your admin.

Then i have added a form with a modelChoiceField to make you select another model instance when you are selecting an istance to delete. If you select another instance the current instance will be replaced.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#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 %}

More like this

  1. Javascript Chain Select Widget by ogo 7 years, 2 months ago
  2. Form with Two InlineFormSets by maeck 6 years, 7 months ago
  3. Custom Admin Redirects by leitjohn 6 years, 4 months ago
  4. Add delete buttons to admin changelist by kylefox 8 years, 2 months ago
  5. Markup Selection in Admin by jonathan 7 years, 11 months ago

Comments

Please login first before commenting.