Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 8 commits
  • 12 files changed
  • 1 comment
  • 2 contributors
May 27, 2012
Julien Phalip Converted the admin change view to CBV. eb8ea16
Julien Phalip Converted the admin delete and add views to CBV. a47bf38
Julien Phalip Converted the admin change list view to CBV. 55c91c7
Julien Phalip Converted the admin history view to CBV. 519c630
Julien Phalip Restored the admin views' decorators exactly to what they were before…
… the CBV conversion.
808e9ad
Julien Phalip Moved the admin CBVs into logically-named modules. 27f8d4b
Ivan Refactored how the formsets are constructed
Merged all the code in a Mixin that looks for self.object and
instantiates the formsets with it or with the base model. This replaces
all the code that previously managed the formsets and their prefixes.
3c1c283
Jun 02, 2012
Julien Phalip Merge pull request #1 from rasca/admin-cbv
Simplified the construction of admin inline formsets.
68ebca9
4  django/contrib/admin/filters.py
@@ -186,7 +186,7 @@ def expected_parameters(self):
186 186
         return [self.lookup_kwarg, self.lookup_kwarg_isnull]
187 187
 
188 188
     def choices(self, cl):
189  
-        from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
  189
+        from django.contrib.admin.views.list import EMPTY_CHANGELIST_VALUE
190 190
         yield {
191 191
             'selected': self.lookup_val is None and not self.lookup_val_isnull,
192 192
             'query_string': cl.get_query_string({},
@@ -368,7 +368,7 @@ def expected_parameters(self):
368 368
         return [self.lookup_kwarg, self.lookup_kwarg_isnull]
369 369
 
370 370
     def choices(self, cl):
371  
-        from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
  371
+        from django.contrib.admin.views.list import EMPTY_CHANGELIST_VALUE
372 372
         yield {
373 373
             'selected': (self.lookup_val is None
374 374
                 and self.lookup_val_isnull is None),
2  django/contrib/admin/helpers.py
@@ -169,7 +169,7 @@ def label_tag(self):
169 169
 
170 170
     def contents(self):
171 171
         from django.contrib.admin.templatetags.admin_list import _boolean_icon
172  
-        from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
  172
+        from django.contrib.admin.views.list import EMPTY_CHANGELIST_VALUE
173 173
         field, obj, model_admin = self.field['field'], self.form.instance, self.model_admin
174 174
         try:
175 175
             f, attr, value = lookup_field(field, obj, model_admin)
449  django/contrib/admin/options.py
... ...
@@ -1,32 +1,31 @@
1 1
 from functools import update_wrapper, partial
2 2
 from django import forms
3 3
 from django.conf import settings
4  
-from django.forms.formsets import all_valid
5 4
 from django.forms.models import (modelform_factory, modelformset_factory,
6 5
     inlineformset_factory, BaseInlineFormSet)
7 6
 from django.contrib.contenttypes.models import ContentType
8 7
 from django.contrib.admin import widgets, helpers
9  
-from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
  8
+from django.contrib.admin.util import flatten_fieldsets, model_format_dict
10 9
 from django.contrib.admin.templatetags.admin_static import static
  10
+from django.contrib.admin.views import (AdminChangeView, AdminAddView,
  11
+    AdminDeleteView, AdminChangeListView, AdminHistoryView)
11 12
 from django.contrib import messages
12 13
 from django.views.decorators.csrf import csrf_protect
13  
-from django.core.exceptions import PermissionDenied, ValidationError
  14
+from django.core.exceptions import ValidationError
14 15
 from django.core.paginator import Paginator
15 16
 from django.core.urlresolvers import reverse
16  
-from django.db import models, transaction, router
  17
+from django.db import models, transaction
17 18
 from django.db.models.related import RelatedObject
18 19
 from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
19 20
 from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
20  
-from django.http import Http404, HttpResponse, HttpResponseRedirect
21  
-from django.shortcuts import get_object_or_404
22  
-from django.template.response import SimpleTemplateResponse, TemplateResponse
  21
+from django.http import HttpResponse, HttpResponseRedirect
  22
+from django.template.response import TemplateResponse
23 23
 from django.utils.decorators import method_decorator
24 24
 from django.utils.datastructures import SortedDict
25 25
 from django.utils.html import escape, escapejs
26 26
 from django.utils.safestring import mark_safe
27 27
 from django.utils.text import capfirst, get_text_list
28 28
 from django.utils.translation import ugettext as _
29  
-from django.utils.translation import ungettext
30 29
 from django.utils.encoding import force_unicode
31 30
 
32 31
 HORIZONTAL, VERTICAL = 1, 2
@@ -462,16 +461,16 @@ def get_changelist(self, request, **kwargs):
462 461
         """
463 462
         Returns the ChangeList class for use on the changelist page.
464 463
         """
465  
-        from django.contrib.admin.views.main import ChangeList
  464
+        from django.contrib.admin.views.list import ChangeList
466 465
         return ChangeList
467 466
 
468  
-    def get_object(self, request, object_id):
  467
+    def get_object(self, request, object_id, queryset=None):
469 468
         """
470 469
         Returns an instance matching the primary key provided. ``None``  is
471 470
         returned if no match is found (or the object_id failed validation
472 471
         against the primary key field).
473 472
         """
474  
-        queryset = self.queryset(request)
  473
+        queryset = queryset or self.queryset(request)
475 474
         model = queryset.model
476 475
         try:
477 476
             object_id = model._meta.pk.to_python(object_id)
@@ -571,7 +570,7 @@ def get_actions(self, request):
571 570
         """
572 571
         # If self.actions is explicitally set to None that means that we don't
573 572
         # want *any* actions enabled on this page.
574  
-        from django.contrib.admin.views.main import IS_POPUP_VAR
  573
+        from django.contrib.admin.views.list import IS_POPUP_VAR
575 574
         if self.actions is None or IS_POPUP_VAR in request.GET:
576 575
             return SortedDict()
577 576
 
@@ -922,424 +921,46 @@ def response_action(self, request, queryset):
922 921
     @csrf_protect_m
923 922
     @transaction.commit_on_success
924 923
     def add_view(self, request, form_url='', extra_context=None):
925  
-        "The 'add' admin view for this model."
926  
-        model = self.model
927  
-        opts = model._meta
928  
-
929  
-        if not self.has_add_permission(request):
930  
-            raise PermissionDenied
931  
-
932  
-        ModelForm = self.get_form(request)
933  
-        formsets = []
934  
-        inline_instances = self.get_inline_instances(request)
935  
-        if request.method == 'POST':
936  
-            form = ModelForm(request.POST, request.FILES)
937  
-            if form.is_valid():
938  
-                new_object = self.save_form(request, form, change=False)
939  
-                form_validated = True
940  
-            else:
941  
-                form_validated = False
942  
-                new_object = self.model()
943  
-            prefixes = {}
944  
-            for FormSet, inline in zip(self.get_formsets(request), inline_instances):
945  
-                prefix = FormSet.get_default_prefix()
946  
-                prefixes[prefix] = prefixes.get(prefix, 0) + 1
947  
-                if prefixes[prefix] != 1 or not prefix:
948  
-                    prefix = "%s-%s" % (prefix, prefixes[prefix])
949  
-                formset = FormSet(data=request.POST, files=request.FILES,
950  
-                                  instance=new_object,
951  
-                                  save_as_new="_saveasnew" in request.POST,
952  
-                                  prefix=prefix, queryset=inline.queryset(request))
953  
-                formsets.append(formset)
954  
-            if all_valid(formsets) and form_validated:
955  
-                self.save_model(request, new_object, form, False)
956  
-                self.save_related(request, form, formsets, False)
957  
-                self.log_addition(request, new_object)
958  
-                return self.response_add(request, new_object)
959  
-        else:
960  
-            # Prepare the dict of initial data from the request.
961  
-            # We have to special-case M2Ms as a list of comma-separated PKs.
962  
-            initial = dict(request.GET.items())
963  
-            for k in initial:
964  
-                try:
965  
-                    f = opts.get_field(k)
966  
-                except models.FieldDoesNotExist:
967  
-                    continue
968  
-                if isinstance(f, models.ManyToManyField):
969  
-                    initial[k] = initial[k].split(",")
970  
-            form = ModelForm(initial=initial)
971  
-            prefixes = {}
972  
-            for FormSet, inline in zip(self.get_formsets(request), inline_instances):
973  
-                prefix = FormSet.get_default_prefix()
974  
-                prefixes[prefix] = prefixes.get(prefix, 0) + 1
975  
-                if prefixes[prefix] != 1 or not prefix:
976  
-                    prefix = "%s-%s" % (prefix, prefixes[prefix])
977  
-                formset = FormSet(instance=self.model(), prefix=prefix,
978  
-                                  queryset=inline.queryset(request))
979  
-                formsets.append(formset)
980  
-
981  
-        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
982  
-            self.get_prepopulated_fields(request),
983  
-            self.get_readonly_fields(request),
984  
-            model_admin=self)
985  
-        media = self.media + adminForm.media
986  
-
987  
-        inline_admin_formsets = []
988  
-        for inline, formset in zip(inline_instances, formsets):
989  
-            fieldsets = list(inline.get_fieldsets(request))
990  
-            readonly = list(inline.get_readonly_fields(request))
991  
-            prepopulated = dict(inline.get_prepopulated_fields(request))
992  
-            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
993  
-                fieldsets, prepopulated, readonly, model_admin=self)
994  
-            inline_admin_formsets.append(inline_admin_formset)
995  
-            media = media + inline_admin_formset.media
996  
-
997  
-        context = {
998  
-            'title': _('Add %s') % force_unicode(opts.verbose_name),
999  
-            'adminform': adminForm,
1000  
-            'is_popup': "_popup" in request.REQUEST,
1001  
-            'show_delete': False,
1002  
-            'media': media,
1003  
-            'inline_admin_formsets': inline_admin_formsets,
1004  
-            'errors': helpers.AdminErrorList(form, formsets),
1005  
-            'app_label': opts.app_label,
1006  
-        }
1007  
-        context.update(extra_context or {})
1008  
-        return self.render_change_form(request, context, form_url=form_url, add=True)
  924
+        """
  925
+        The 'add' admin view for this model.
  926
+        """
  927
+        return AdminAddView(
  928
+            admin_opts=self, form_url=form_url,
  929
+            extra_context=extra_context).dispatch(request)
1009 930
 
1010 931
     @csrf_protect_m
1011 932
     @transaction.commit_on_success
1012 933
     def change_view(self, request, object_id, form_url='', extra_context=None):
1013  
-        "The 'change' admin view for this model."
1014  
-        model = self.model
1015  
-        opts = model._meta
1016  
-
1017  
-        obj = self.get_object(request, unquote(object_id))
1018  
-
1019  
-        if not self.has_change_permission(request, obj):
1020  
-            raise PermissionDenied
1021  
-
1022  
-        if obj is None:
1023  
-            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
1024  
-
1025  
-        if request.method == 'POST' and "_saveasnew" in request.POST:
1026  
-            return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
1027  
-                                    (opts.app_label, opts.module_name),
1028  
-                                    current_app=self.admin_site.name))
1029  
-
1030  
-        ModelForm = self.get_form(request, obj)
1031  
-        formsets = []
1032  
-        inline_instances = self.get_inline_instances(request)
1033  
-        if request.method == 'POST':
1034  
-            form = ModelForm(request.POST, request.FILES, instance=obj)
1035  
-            if form.is_valid():
1036  
-                form_validated = True
1037  
-                new_object = self.save_form(request, form, change=True)
1038  
-            else:
1039  
-                form_validated = False
1040  
-                new_object = obj
1041  
-            prefixes = {}
1042  
-            for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances):
1043  
-                prefix = FormSet.get_default_prefix()
1044  
-                prefixes[prefix] = prefixes.get(prefix, 0) + 1
1045  
-                if prefixes[prefix] != 1 or not prefix:
1046  
-                    prefix = "%s-%s" % (prefix, prefixes[prefix])
1047  
-                formset = FormSet(request.POST, request.FILES,
1048  
-                                  instance=new_object, prefix=prefix,
1049  
-                                  queryset=inline.queryset(request))
1050  
-
1051  
-                formsets.append(formset)
1052  
-
1053  
-            if all_valid(formsets) and form_validated:
1054  
-                self.save_model(request, new_object, form, True)
1055  
-                self.save_related(request, form, formsets, True)
1056  
-                change_message = self.construct_change_message(request, form, formsets)
1057  
-                self.log_change(request, new_object, change_message)
1058  
-                return self.response_change(request, new_object)
1059  
-
1060  
-        else:
1061  
-            form = ModelForm(instance=obj)
1062  
-            prefixes = {}
1063  
-            for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances):
1064  
-                prefix = FormSet.get_default_prefix()
1065  
-                prefixes[prefix] = prefixes.get(prefix, 0) + 1
1066  
-                if prefixes[prefix] != 1 or not prefix:
1067  
-                    prefix = "%s-%s" % (prefix, prefixes[prefix])
1068  
-                formset = FormSet(instance=obj, prefix=prefix,
1069  
-                                  queryset=inline.queryset(request))
1070  
-                formsets.append(formset)
1071  
-
1072  
-        adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
1073  
-            self.get_prepopulated_fields(request, obj),
1074  
-            self.get_readonly_fields(request, obj),
1075  
-            model_admin=self)
1076  
-        media = self.media + adminForm.media
1077  
-
1078  
-        inline_admin_formsets = []
1079  
-        for inline, formset in zip(inline_instances, formsets):
1080  
-            fieldsets = list(inline.get_fieldsets(request, obj))
1081  
-            readonly = list(inline.get_readonly_fields(request, obj))
1082  
-            prepopulated = dict(inline.get_prepopulated_fields(request, obj))
1083  
-            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
1084  
-                fieldsets, prepopulated, readonly, model_admin=self)
1085  
-            inline_admin_formsets.append(inline_admin_formset)
1086  
-            media = media + inline_admin_formset.media
1087  
-
1088  
-        context = {
1089  
-            'title': _('Change %s') % force_unicode(opts.verbose_name),
1090  
-            'adminform': adminForm,
1091  
-            'object_id': object_id,
1092  
-            'original': obj,
1093  
-            'is_popup': "_popup" in request.REQUEST,
1094  
-            'media': media,
1095  
-            'inline_admin_formsets': inline_admin_formsets,
1096  
-            'errors': helpers.AdminErrorList(form, formsets),
1097  
-            'app_label': opts.app_label,
1098  
-        }
1099  
-        context.update(extra_context or {})
1100  
-        return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
  934
+        """
  935
+        The 'change' admin view for this model.
  936
+        """
  937
+        return AdminChangeView(
  938
+            admin_opts=self, form_url=form_url, extra_context=extra_context,
  939
+            object_id=object_id).dispatch(request)
1101 940
 
1102 941
     @csrf_protect_m
1103 942
     def changelist_view(self, request, extra_context=None):
1104 943
         """
1105 944
         The 'change list' admin view for this model.
1106 945
         """
1107  
-        from django.contrib.admin.views.main import ERROR_FLAG
1108  
-        opts = self.model._meta
1109  
-        app_label = opts.app_label
1110  
-        if not self.has_change_permission(request, None):
1111  
-            raise PermissionDenied
1112  
-
1113  
-        list_display = self.get_list_display(request)
1114  
-        list_display_links = self.get_list_display_links(request, list_display)
1115  
-
1116  
-        # Check actions to see if any are available on this changelist
1117  
-        actions = self.get_actions(request)
1118  
-        if actions:
1119  
-            # Add the action checkboxes if there are any actions available.
1120  
-            list_display = ['action_checkbox'] +  list(list_display)
1121  
-
1122  
-        ChangeList = self.get_changelist(request)
1123  
-        try:
1124  
-            cl = ChangeList(request, self.model, list_display,
1125  
-                list_display_links, self.list_filter, self.date_hierarchy,
1126  
-                self.search_fields, self.list_select_related,
1127  
-                self.list_per_page, self.list_max_show_all, self.list_editable,
1128  
-                self)
1129  
-        except IncorrectLookupParameters:
1130  
-            # Wacky lookup parameters were given, so redirect to the main
1131  
-            # changelist page, without parameters, and pass an 'invalid=1'
1132  
-            # parameter via the query string. If wacky parameters were given
1133  
-            # and the 'invalid=1' parameter was already in the query string,
1134  
-            # something is screwed up with the database, so display an error
1135  
-            # page.
1136  
-            if ERROR_FLAG in request.GET.keys():
1137  
-                return SimpleTemplateResponse('admin/invalid_setup.html', {
1138  
-                    'title': _('Database error'),
1139  
-                })
1140  
-            return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
1141  
-
1142  
-        # If the request was POSTed, this might be a bulk action or a bulk
1143  
-        # edit. Try to look up an action or confirmation first, but if this
1144  
-        # isn't an action the POST will fall through to the bulk edit check,
1145  
-        # below.
1146  
-        action_failed = False
1147  
-        selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
1148  
-
1149  
-        # Actions with no confirmation
1150  
-        if (actions and request.method == 'POST' and
1151  
-                'index' in request.POST and '_save' not in request.POST):
1152  
-            if selected:
1153  
-                response = self.response_action(request, queryset=cl.get_query_set(request))
1154  
-                if response:
1155  
-                    return response
1156  
-                else:
1157  
-                    action_failed = True
1158  
-            else:
1159  
-                msg = _("Items must be selected in order to perform "
1160  
-                        "actions on them. No items have been changed.")
1161  
-                self.message_user(request, msg)
1162  
-                action_failed = True
1163  
-
1164  
-        # Actions with confirmation
1165  
-        if (actions and request.method == 'POST' and
1166  
-                helpers.ACTION_CHECKBOX_NAME in request.POST and
1167  
-                'index' not in request.POST and '_save' not in request.POST):
1168  
-            if selected:
1169  
-                response = self.response_action(request, queryset=cl.get_query_set(request))
1170  
-                if response:
1171  
-                    return response
1172  
-                else:
1173  
-                    action_failed = True
1174  
-
1175  
-        # If we're allowing changelist editing, we need to construct a formset
1176  
-        # for the changelist given all the fields to be edited. Then we'll
1177  
-        # use the formset to validate/process POSTed data.
1178  
-        formset = cl.formset = None
1179  
-
1180  
-        # Handle POSTed bulk-edit data.
1181  
-        if (request.method == "POST" and cl.list_editable and
1182  
-                '_save' in request.POST and not action_failed):
1183  
-            FormSet = self.get_changelist_formset(request)
1184  
-            formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
1185  
-            if formset.is_valid():
1186  
-                changecount = 0
1187  
-                for form in formset.forms:
1188  
-                    if form.has_changed():
1189  
-                        obj = self.save_form(request, form, change=True)
1190  
-                        self.save_model(request, obj, form, change=True)
1191  
-                        self.save_related(request, form, formsets=[], change=True)
1192  
-                        change_msg = self.construct_change_message(request, form, None)
1193  
-                        self.log_change(request, obj, change_msg)
1194  
-                        changecount += 1
1195  
-
1196  
-                if changecount:
1197  
-                    if changecount == 1:
1198  
-                        name = force_unicode(opts.verbose_name)
1199  
-                    else:
1200  
-                        name = force_unicode(opts.verbose_name_plural)
1201  
-                    msg = ungettext("%(count)s %(name)s was changed successfully.",
1202  
-                                    "%(count)s %(name)s were changed successfully.",
1203  
-                                    changecount) % {'count': changecount,
1204  
-                                                    'name': name,
1205  
-                                                    'obj': force_unicode(obj)}
1206  
-                    self.message_user(request, msg)
1207  
-
1208  
-                return HttpResponseRedirect(request.get_full_path())
1209  
-
1210  
-        # Handle GET -- construct a formset for display.
1211  
-        elif cl.list_editable:
1212  
-            FormSet = self.get_changelist_formset(request)
1213  
-            formset = cl.formset = FormSet(queryset=cl.result_list)
1214  
-
1215  
-        # Build the list of media to be used by the formset.
1216  
-        if formset:
1217  
-            media = self.media + formset.media
1218  
-        else:
1219  
-            media = self.media
1220  
-
1221  
-        # Build the action form and populate it with available actions.
1222  
-        if actions:
1223  
-            action_form = self.action_form(auto_id=None)
1224  
-            action_form.fields['action'].choices = self.get_action_choices(request)
1225  
-        else:
1226  
-            action_form = None
1227  
-
1228  
-        selection_note_all = ungettext('%(total_count)s selected',
1229  
-            'All %(total_count)s selected', cl.result_count)
1230  
-
1231  
-        context = {
1232  
-            'module_name': force_unicode(opts.verbose_name_plural),
1233  
-            'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
1234  
-            'selection_note_all': selection_note_all % {'total_count': cl.result_count},
1235  
-            'title': cl.title,
1236  
-            'is_popup': cl.is_popup,
1237  
-            'cl': cl,
1238  
-            'media': media,
1239  
-            'has_add_permission': self.has_add_permission(request),
1240  
-            'app_label': app_label,
1241  
-            'action_form': action_form,
1242  
-            'actions_on_top': self.actions_on_top,
1243  
-            'actions_on_bottom': self.actions_on_bottom,
1244  
-            'actions_selection_counter': self.actions_selection_counter,
1245  
-        }
1246  
-        context.update(extra_context or {})
1247  
-
1248  
-        return TemplateResponse(request, self.change_list_template or [
1249  
-            'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
1250  
-            'admin/%s/change_list.html' % app_label,
1251  
-            'admin/change_list.html'
1252  
-        ], context, current_app=self.admin_site.name)
  946
+        return AdminChangeListView(
  947
+            admin_opts=self, extra_context=extra_context).dispatch(request)
1253 948
 
1254 949
     @csrf_protect_m
1255 950
     @transaction.commit_on_success
1256 951
     def delete_view(self, request, object_id, extra_context=None):
1257  
-        "The 'delete' admin view for this model."
1258  
-        opts = self.model._meta
1259  
-        app_label = opts.app_label
1260  
-
1261  
-        obj = self.get_object(request, unquote(object_id))
1262  
-
1263  
-        if not self.has_delete_permission(request, obj):
1264  
-            raise PermissionDenied
1265  
-
1266  
-        if obj is None:
1267  
-            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
1268  
-
1269  
-        using = router.db_for_write(self.model)
1270  
-
1271  
-        # Populate deleted_objects, a data structure of all related objects that
1272  
-        # will also be deleted.
1273  
-        (deleted_objects, perms_needed, protected) = get_deleted_objects(
1274  
-            [obj], opts, request.user, self.admin_site, using)
1275  
-
1276  
-        if request.POST: # The user has already confirmed the deletion.
1277  
-            if perms_needed:
1278  
-                raise PermissionDenied
1279  
-            obj_display = force_unicode(obj)
1280  
-            self.log_deletion(request, obj, obj_display)
1281  
-            self.delete_model(request, obj)
1282  
-
1283  
-            self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
1284  
-
1285  
-            if not self.has_change_permission(request, None):
1286  
-                return HttpResponseRedirect(reverse('admin:index',
1287  
-                                                    current_app=self.admin_site.name))
1288  
-            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
1289  
-                                        (opts.app_label, opts.module_name),
1290  
-                                        current_app=self.admin_site.name))
1291  
-
1292  
-        object_name = force_unicode(opts.verbose_name)
1293  
-
1294  
-        if perms_needed or protected:
1295  
-            title = _("Cannot delete %(name)s") % {"name": object_name}
1296  
-        else:
1297  
-            title = _("Are you sure?")
1298  
-
1299  
-        context = {
1300  
-            "title": title,
1301  
-            "object_name": object_name,
1302  
-            "object": obj,
1303  
-            "deleted_objects": deleted_objects,
1304  
-            "perms_lacking": perms_needed,
1305  
-            "protected": protected,
1306  
-            "opts": opts,
1307  
-            "app_label": app_label,
1308  
-        }
1309  
-        context.update(extra_context or {})
1310  
-
1311  
-        return TemplateResponse(request, self.delete_confirmation_template or [
1312  
-            "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
1313  
-            "admin/%s/delete_confirmation.html" % app_label,
1314  
-            "admin/delete_confirmation.html"
1315  
-        ], context, current_app=self.admin_site.name)
  952
+        """The 'delete' admin view for this model."""
  953
+        return AdminDeleteView(
  954
+            admin_opts=self, extra_context=extra_context,
  955
+            object_id=object_id).dispatch(request)
1316 956
 
1317 957
     def history_view(self, request, object_id, extra_context=None):
1318  
-        "The 'history' admin view for this model."
1319  
-        from django.contrib.admin.models import LogEntry
1320  
-        model = self.model
1321  
-        opts = model._meta
1322  
-        app_label = opts.app_label
1323  
-        action_list = LogEntry.objects.filter(
1324  
-            object_id = object_id,
1325  
-            content_type__id__exact = ContentType.objects.get_for_model(model).id
1326  
-        ).select_related().order_by('action_time')
1327  
-        # If no history was found, see whether this object even exists.
1328  
-        obj = get_object_or_404(model, pk=unquote(object_id))
1329  
-        context = {
1330  
-            'title': _('Change history: %s') % force_unicode(obj),
1331  
-            'action_list': action_list,
1332  
-            'module_name': capfirst(force_unicode(opts.verbose_name_plural)),
1333  
-            'object': obj,
1334  
-            'app_label': app_label,
1335  
-            'opts': opts,
1336  
-        }
1337  
-        context.update(extra_context or {})
1338  
-        return TemplateResponse(request, self.object_history_template or [
1339  
-            "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
1340  
-            "admin/%s/object_history.html" % app_label,
1341  
-            "admin/object_history.html"
1342  
-        ], context, current_app=self.admin_site.name)
  958
+        """
  959
+        The 'history' admin view for this model.
  960
+        """
  961
+        return AdminHistoryView(
  962
+            admin_opts=self, extra_context=extra_context,
  963
+            object_id=object_id).dispatch(request)
1343 964
 
1344 965
 class InlineModelAdmin(BaseModelAdmin):
1345 966
     """
4  django/contrib/admin/templatetags/admin_list.py
@@ -2,7 +2,7 @@
2 2
 
3 3
 from django.contrib.admin.util import (lookup_field, display_for_field,
4 4
     display_for_value, label_for_field)
5  
-from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
  5
+from django.contrib.admin.views.list import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
6 6
     ORDER_VAR, PAGE_VAR, SEARCH_VAR)
7 7
 from django.contrib.admin.templatetags.admin_static import static
8 8
 from django.core.exceptions import ObjectDoesNotExist
@@ -12,7 +12,7 @@
12 12
 from django.utils.safestring import mark_safe
13 13
 from django.utils.text import capfirst
14 14
 from django.utils.translation import ugettext as _
15  
-from django.utils.encoding import smart_unicode, force_unicode
  15
+from django.utils.encoding import force_unicode
16 16
 from django.template import Library
17 17
 from django.template.loader import get_template
18 18
 from django.template.context import Context
4  django/contrib/admin/util.py
@@ -314,7 +314,7 @@ def help_text_for_field(name, model):
314 314
 
315 315
 def display_for_field(value, field):
316 316
     from django.contrib.admin.templatetags.admin_list import _boolean_icon
317  
-    from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
  317
+    from django.contrib.admin.views.list import EMPTY_CHANGELIST_VALUE
318 318
 
319 319
     if field.flatchoices:
320 320
         return dict(field.flatchoices).get(value, EMPTY_CHANGELIST_VALUE)
@@ -338,7 +338,7 @@ def display_for_field(value, field):
338 338
 
339 339
 def display_for_value(value, boolean=False):
340 340
     from django.contrib.admin.templatetags.admin_list import _boolean_icon
341  
-    from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
  341
+    from django.contrib.admin.views.list import EMPTY_CHANGELIST_VALUE
342 342
 
343 343
     if boolean:
344 344
         return _boolean_icon(value)
4  django/contrib/admin/views/__init__.py
... ...
@@ -0,0 +1,4 @@
  1
+from django.contrib.admin.views.edit import (
  2
+    AdminDeleteView, AdminAddView, AdminChangeView)
  3
+from django.contrib.admin.views.list import AdminChangeListView
  4
+from django.contrib.admin.views.history import AdminHistoryView
10  django/contrib/admin/views/base.py
... ...
@@ -0,0 +1,10 @@
  1
+
  2
+class AdminViewMixin(object):
  3
+
  4
+    def __init__(self, **kwargs):
  5
+        super(AdminViewMixin, self).__init__(**kwargs)
  6
+        self.model = self.admin_opts.model
  7
+        self.model_opts = self.model._meta
  8
+
  9
+    def get_queryset(self):
  10
+        return self.admin_opts.queryset(self.request)
328  django/contrib/admin/views/edit.py
... ...
@@ -0,0 +1,328 @@
  1
+from django.core.exceptions import PermissionDenied
  2
+from django.core.urlresolvers import reverse
  3
+from django.http import Http404, HttpResponseRedirect
  4
+from django.utils.encoding import force_unicode
  5
+from django.utils.translation import ugettext as _
  6
+from django.views.generic.edit import UpdateView, CreateView, DeleteView
  7
+from django.contrib.admin.util import unquote, get_deleted_objects
  8
+from django.contrib.admin.views.base import AdminViewMixin
  9
+from django.forms.formsets import all_valid
  10
+from django.contrib.admin import helpers
  11
+from django.utils.html import escape
  12
+from django.db import models, router
  13
+
  14
+
  15
+class FormSetsMixin(object):
  16
+    def construct_formsets(self, **kwargs):
  17
+        """
  18
+        Constructs the formsets taking care of any clashing prefixes.
  19
+
  20
+        It accepts kwargs for the FormSet instantiating and adds the POST and
  21
+        FILES if available.
  22
+        """
  23
+
  24
+        prefixes = {}
  25
+        # Check if we have an instance or if we are creating a new one
  26
+        object = getattr(self, 'object', None)
  27
+
  28
+        for FormSet, inline in zip(self.admin_opts.get_formsets(
  29
+            self.request, object), self.inline_instances):
  30
+
  31
+            prefix = FormSet.get_default_prefix()
  32
+            prefixes[prefix] = prefixes.get(prefix, 0) + 1
  33
+            if prefixes[prefix] != 1 or not prefix:
  34
+                prefix = "%s-%s" % (prefix, prefixes[prefix])
  35
+
  36
+            formset_kwargs = {
  37
+                'prefix': prefix,
  38
+                'queryset': inline.queryset(self.request),
  39
+                'instance': object or self.model()
  40
+            }
  41
+
  42
+            if self.request.method in ('POST', 'PUT'):
  43
+                formset_kwargs.update({
  44
+                    'data': self.request.POST,
  45
+                    'files': self.request.FILES,
  46
+                })
  47
+
  48
+            formset_kwargs.update(kwargs)
  49
+
  50
+            self.formsets.append(FormSet(**formset_kwargs))
  51
+
  52
+
  53
+class AdminDeleteView(AdminViewMixin, DeleteView):
  54
+
  55
+    def dispatch(self, request, *args, **kwargs):
  56
+        return super(AdminDeleteView, self).dispatch(request, *args, **kwargs)
  57
+
  58
+    def get_object(self, queryset=None):
  59
+        object = self.admin_opts.get_object(
  60
+            self.request, unquote(self.object_id), queryset=queryset)
  61
+        if not self.admin_opts.has_delete_permission(self.request, object):
  62
+            raise PermissionDenied
  63
+        if object is None:
  64
+            raise Http404(
  65
+                _('%(name)s object with primary key %(key)r does not exist.') % {
  66
+                    'name': force_unicode(self.model_opts.verbose_name),
  67
+                    'key': escape(self.object_id)})
  68
+        using = router.db_for_write(self.model)
  69
+        # Populate deleted_objects, a data structure of all related objects that
  70
+        # will also be deleted.
  71
+        (self.deleted_objects, self.perms_needed, self.protected) = get_deleted_objects(
  72
+            [object], self.model_opts, self.request.user, self.admin_opts.admin_site, using)
  73
+        return object
  74
+
  75
+    def post(self, *args, **kwargs):
  76
+        self.object = self.get_object()
  77
+        # The user has already confirmed the deletion.
  78
+        if self.perms_needed:
  79
+            raise PermissionDenied
  80
+        obj_display = force_unicode(self.object)
  81
+        self.admin_opts.log_deletion(self.request, self.object, obj_display)
  82
+        self.admin_opts.delete_model(self.request, self.object)
  83
+
  84
+        self.admin_opts.message_user(
  85
+            self.request, _('The %(name)s "%(obj)s" was deleted successfully.') % {
  86
+                'name': force_unicode(self.model_opts.verbose_name),
  87
+                'obj': force_unicode(obj_display)})
  88
+
  89
+        if not self.admin_opts.has_change_permission(self.request, None):
  90
+            return HttpResponseRedirect(
  91
+                reverse('admin:index', current_app=self.admin_opts.admin_site.name))
  92
+        return HttpResponseRedirect(
  93
+            reverse('admin:%s_%s_changelist' % (
  94
+                self.model_opts.app_label, self.model_opts.module_name),
  95
+                current_app=self.admin_opts.admin_site.name))
  96
+
  97
+    def get_context_data(self, **kwargs):
  98
+        context = super(AdminDeleteView, self).get_context_data(**kwargs)
  99
+
  100
+        object_name = force_unicode(self.model_opts.verbose_name)
  101
+
  102
+        if self.perms_needed or self.protected:
  103
+            title = _("Cannot delete %(name)s") % {"name": object_name}
  104
+        else:
  105
+            title = _("Are you sure?")
  106
+
  107
+        context.update({
  108
+            "title": title,
  109
+            "object_name": object_name,
  110
+            "object": self.object,
  111
+            "deleted_objects": self.deleted_objects,
  112
+            "perms_lacking": self.perms_needed,
  113
+            "protected": self.protected,
  114
+            "opts": self.model_opts,
  115
+            "app_label": self.model_opts.app_label,
  116
+        })
  117
+
  118
+        context.update(self.extra_context or {})
  119
+        return context
  120
+
  121
+    def get_template_names(self):
  122
+        form_template = self.admin_opts.delete_confirmation_template
  123
+        if form_template:
  124
+            return [form_template]
  125
+        else:
  126
+            return [
  127
+                "admin/%s/%s/delete_confirmation.html" % (self.model_opts.app_label, self.model_opts.object_name.lower()),
  128
+                "admin/%s/delete_confirmation.html" % self.model_opts.app_label,
  129
+                "admin/delete_confirmation.html"
  130
+            ]
  131
+
  132
+
  133
+class AdminAddView(AdminViewMixin, FormSetsMixin, CreateView):
  134
+
  135
+    def dispatch(self, request, *args, **kwargs):
  136
+        if not self.admin_opts.has_add_permission(request):
  137
+            raise PermissionDenied
  138
+
  139
+        self.formsets = []
  140
+        self.inline_instances = self.admin_opts.get_inline_instances(request)
  141
+        return super(AdminAddView, self).dispatch(request, *args, **kwargs)
  142
+
  143
+    def get_form_class(self):
  144
+        return self.admin_opts.get_form(self.request, self.object)
  145
+
  146
+    def get_template_names(self):
  147
+        form_template = self.admin_opts.change_form_template
  148
+        if form_template:
  149
+            return [form_template]
  150
+        else:
  151
+            return [
  152
+                "admin/%s/%s/change_form.html" % (self.model_opts.app_label, self.model_opts.object_name.lower()),
  153
+                "admin/%s/change_form.html" % self.model_opts.app_label,
  154
+                "admin/change_form.html"
  155
+            ]
  156
+
  157
+    def form_valid(self, form):
  158
+        self.object = self.admin_opts.save_form(self.request, form, change=False)
  159
+        self.construct_formsets(save_as_new="_saveasnew" in self.request.POST)
  160
+        if all_valid(self.formsets):
  161
+            self.admin_opts.save_model(self.request, self.object, form, False)
  162
+            self.admin_opts.save_related(self.request, form, self.formsets, False)
  163
+            self.admin_opts.log_addition(self.request, self.object)
  164
+            return self.admin_opts.response_add(self.request, self.object)
  165
+        return self.render_to_response(self.get_context_data(form=form))
  166
+
  167
+    def form_invalid(self, form):
  168
+        self.object = self.model()
  169
+        self.construct_formsets(save_as_new="_saveasnew" in self.request.POST)
  170
+        return self.render_to_response(self.get_context_data(form=form))
  171
+
  172
+    def get_form_kwargs(self):
  173
+        kwargs = super(AdminAddView, self).get_form_kwargs()
  174
+
  175
+        # Prepare the dict of initial data from the request.
  176
+        # We have to special-case M2Ms as a list of comma-separated PKs.
  177
+        initial = dict(self.request.GET.items())
  178
+        for k in initial:
  179
+            try:
  180
+                f = self.model_opts.get_field(k)
  181
+            except models.FieldDoesNotExist:
  182
+                continue
  183
+            if isinstance(f, models.ManyToManyField):
  184
+                initial[k] = initial[k].split(",")
  185
+        kwargs.update({'initial': initial})
  186
+        return kwargs
  187
+
  188
+    def render_to_response(self, context, **response_kwargs):
  189
+        return self.admin_opts.render_change_form(
  190
+            self.request, context, add=True, obj=self.object,
  191
+            form_url=self.form_url)
  192
+
  193
+    def get(self, request, *args, **kwargs):
  194
+        self.construct_formsets()
  195
+        return super(CreateView, self).get(request, *args, **kwargs)
  196
+
  197
+    def get_context_data(self, **kwargs):
  198
+        context = super(AdminAddView, self).get_context_data(**kwargs)
  199
+
  200
+        adminForm = helpers.AdminForm(
  201
+            kwargs['form'], list(self.admin_opts.get_fieldsets(self.request)),
  202
+            self.admin_opts.get_prepopulated_fields(self.request),
  203
+            self.admin_opts.get_readonly_fields(self.request),
  204
+            model_admin=self.admin_opts)
  205
+        media = self.admin_opts.media + adminForm.media
  206
+
  207
+        inline_admin_formsets = []
  208
+        for inline, formset in zip(self.inline_instances, self.formsets):
  209
+            fieldsets = list(inline.get_fieldsets(self.request))
  210
+            readonly = list(inline.get_readonly_fields(self.request))
  211
+            prepopulated = dict(inline.get_prepopulated_fields(self.request))
  212
+            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
  213
+                fieldsets, prepopulated, readonly, model_admin=self.admin_opts)
  214
+            inline_admin_formsets.append(inline_admin_formset)
  215
+            media = media + inline_admin_formset.media
  216
+
  217
+        context.update({
  218
+            'title': _('Add %s') % force_unicode(self.model_opts.verbose_name),
  219
+            'adminform': adminForm,
  220
+            'is_popup': "_popup" in self.request.REQUEST,
  221
+            'show_delete': False,
  222
+            'media': media,
  223
+            'inline_admin_formsets': inline_admin_formsets,
  224
+            'errors': helpers.AdminErrorList(adminForm.form, self.formsets),
  225
+            'app_label': self.model_opts.app_label,
  226
+        })
  227
+        context.update(self.extra_context or {})
  228
+        return context
  229
+
  230
+
  231
+class AdminChangeView(AdminViewMixin, FormSetsMixin, UpdateView):
  232
+
  233
+    def dispatch(self, request, *args, **kwargs):
  234
+        if request.method == 'POST' and "_saveasnew" in request.POST:
  235
+            return self.admin_opts.add_view(
  236
+                request, form_url=reverse('admin:%s_%s_add' %
  237
+                                    (self.model_opts.app_label, self.model_opts.module_name),
  238
+                                    current_app=self.admin_opts.admin_site.name))
  239
+
  240
+        self.formsets = []
  241
+        self.inline_instances = self.admin_opts.get_inline_instances(request)
  242
+        return super(AdminChangeView, self).dispatch(request, *args, **kwargs)
  243
+
  244
+    def get_form_class(self):
  245
+        return self.admin_opts.get_form(self.request, self.object)
  246
+
  247
+    def get_template_names(self):
  248
+        form_template = self.admin_opts.change_form_template
  249
+        if form_template:
  250
+            return [form_template]
  251
+        else:
  252
+            return [
  253
+                "admin/%s/%s/change_form.html" % (self.model_opts.app_label, self.model_opts.object_name.lower()),
  254
+                "admin/%s/change_form.html" % self.model_opts.app_label,
  255
+                "admin/change_form.html"
  256
+            ]
  257
+
  258
+    def get(self, request, *args, **kwargs):
  259
+        self.object = self.get_object()
  260
+        self.construct_formsets()
  261
+        return super(UpdateView, self).get(request, *args, **kwargs)
  262
+
  263
+    def get_object(self, queryset=None):
  264
+        obj = self.admin_opts.get_object(self.request, unquote(self.object_id), queryset=queryset)
  265
+
  266
+        if not self.admin_opts.has_change_permission(self.request, obj):
  267
+            raise PermissionDenied
  268
+
  269
+        if obj is None:
  270
+            raise Http404(
  271
+                _('%(name)s object with primary key %(key)r does not exist.') % {
  272
+                    'name': force_unicode(self.model_opts.verbose_name),
  273
+                    'key': escape(self.object_id)})
  274
+        return obj
  275
+
  276
+    def render_to_response(self, context, **response_kwargs):
  277
+        return self.admin_opts.render_change_form(
  278
+            self.request, context, change=True, obj=self.object,
  279
+            form_url=self.form_url)
  280
+
  281
+    def form_valid(self, form):
  282
+        self.object = self.admin_opts.save_form(self.request, form, change=True)
  283
+        self.construct_formsets()
  284
+        if all_valid(self.formsets):
  285
+            self.admin_opts.save_model(self.request, self.object, form, True)
  286
+            self.admin_opts.save_related(self.request, form, self.formsets, True)
  287
+            change_message = self.admin_opts.construct_change_message(self.request, form, self.formsets)
  288
+            self.admin_opts.log_change(self.request, self.object, change_message)
  289
+            return self.admin_opts.response_change(self.request, self.object)
  290
+        return self.render_to_response(self.get_context_data(form=form))
  291
+
  292
+    def form_invalid(self, form):
  293
+        self.construct_formsets()
  294
+        return self.render_to_response(self.get_context_data(form=form))
  295
+
  296
+    def get_context_data(self, **kwargs):
  297
+        context = super(AdminChangeView, self).get_context_data(**kwargs)
  298
+
  299
+        adminform = helpers.AdminForm(
  300
+            kwargs['form'], self.admin_opts.get_fieldsets(self.request, self.object),
  301
+            self.admin_opts.get_prepopulated_fields(self.request, self.object),
  302
+            self.admin_opts.get_readonly_fields(self.request, self.object),
  303
+            model_admin=self.admin_opts)
  304
+        media = self.admin_opts.media + adminform.media
  305
+
  306
+        inline_admin_formsets = []
  307
+        for inline, formset in zip(self.inline_instances, self.formsets):
  308
+            fieldsets = list(inline.get_fieldsets(self.request, self.object))
  309
+            readonly = list(inline.get_readonly_fields(self.request, self.object))
  310
+            prepopulated = dict(inline.get_prepopulated_fields(self.request, self.object))
  311
+            inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
  312
+                fieldsets, prepopulated, readonly, model_admin=self.admin_opts)
  313
+            inline_admin_formsets.append(inline_admin_formset)
  314
+            media = media + inline_admin_formset.media
  315
+
  316
+        context.update({
  317
+            'title': _('Change %s') % force_unicode(self.model_opts.verbose_name),
  318
+            'object_id': self.object_id,
  319
+            'is_popup': "_popup" in self.request.REQUEST,
  320
+            'adminform': adminform,
  321
+            'original': self.object,
  322
+            'media': media,
  323
+            'inline_admin_formsets': inline_admin_formsets,
  324
+            'errors': helpers.AdminErrorList(adminform.form, self.formsets),
  325
+            'app_label': self.model_opts.app_label,
  326
+        })
  327
+        context.update(self.extra_context or {})
  328
+        return context
46  django/contrib/admin/views/history.py
... ...
@@ -0,0 +1,46 @@
  1
+from django.shortcuts import get_object_or_404
  2
+from django.utils.encoding import force_unicode
  3
+from django.views.generic.base import TemplateView
  4
+from django.utils.translation import ugettext as _
  5
+from django.contrib.admin.util import unquote
  6
+from django.utils.text import capfirst
  7
+from django.contrib.contenttypes.models import ContentType
  8
+from django.contrib.admin.views.base import AdminViewMixin
  9
+
  10
+
  11
+class AdminHistoryView(AdminViewMixin, TemplateView):
  12
+
  13
+    def get_context_data(self, **kwargs):
  14
+        from django.contrib.admin.models import LogEntry
  15
+
  16
+        context = super(AdminHistoryView, self).get_context_data(**kwargs)
  17
+
  18
+        action_list = LogEntry.objects.filter(
  19
+            object_id = self.object_id,
  20
+            content_type__id__exact = ContentType.objects.get_for_model(self.model).id
  21
+        ).select_related().order_by('action_time')
  22
+        # If no history was found, see whether this object even exists.
  23
+        obj = get_object_or_404(self.model, pk=unquote(self.object_id))
  24
+
  25
+        context.update({
  26
+            'title': _('Change history: %s') % force_unicode(obj),
  27
+            'action_list': action_list,
  28
+            'module_name': capfirst(force_unicode(self.model_opts.verbose_name_plural)),
  29
+            'object': obj,
  30
+            'app_label': self.model_opts.app_label,
  31
+            'opts': self.model_opts,
  32
+        })
  33
+
  34
+        context.update(self.extra_context or {})
  35
+        return context
  36
+
  37
+    def get_template_names(self):
  38
+        form_template = self.admin_opts.object_history_template
  39
+        if form_template:
  40
+            return [form_template]
  41
+        else:
  42
+            return [
  43
+                "admin/%s/%s/object_history.html" % (self.model_opts.app_label, self.model_opts.object_name.lower()),
  44
+                "admin/%s/object_history.html" % self.model_opts.app_label,
  45
+                "admin/object_history.html"
  46
+            ]
561  django/contrib/admin/views/list.py
... ...
@@ -0,0 +1,561 @@
  1
+import operator
  2
+from functools import reduce
  3
+
  4
+from django.core.exceptions import PermissionDenied
  5
+from django.http import HttpResponseRedirect
  6
+from django.utils.encoding import force_unicode
  7
+from django.views.generic.base import TemplateView
  8
+from django.utils.translation import ugettext as _, ungettext
  9
+from django.template.response import SimpleTemplateResponse
  10
+from django.contrib.admin import helpers
  11
+from django.contrib.admin.views.base import AdminViewMixin
  12
+from django.contrib.admin.util import (quote, get_fields_from_path,
  13
+    lookup_needs_distinct, prepare_lookup_value)
  14
+from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
  15
+from django.core.paginator import InvalidPage
  16
+from django.db import models
  17
+from django.db.models.fields import FieldDoesNotExist
  18
+from django.utils.datastructures import SortedDict
  19
+from django.utils.encoding import smart_str
  20
+from django.utils.translation import ugettext, ugettext_lazy
  21
+from django.utils.http import urlencode
  22
+
  23
+
  24
+# Changelist settings
  25
+ALL_VAR = 'all'
  26
+ORDER_VAR = 'o'
  27
+ORDER_TYPE_VAR = 'ot'
  28
+PAGE_VAR = 'p'
  29
+SEARCH_VAR = 'q'
  30
+TO_FIELD_VAR = 't'
  31
+IS_POPUP_VAR = 'pop'
  32
+ERROR_FLAG = 'e'
  33
+
  34
+IGNORED_PARAMS = (
  35
+    ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR)
  36
+
  37
+# Text to display within change-list table cells if the value is blank.
  38
+EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
  39
+
  40
+
  41
+class ChangeList(object):
  42
+    def __init__(self, request, model, list_display, list_display_links,
  43
+            list_filter, date_hierarchy, search_fields, list_select_related,
  44
+            list_per_page, list_max_show_all, list_editable, model_admin):
  45
+        self.model = model
  46
+        self.opts = model._meta
  47
+        self.lookup_opts = self.opts
  48
+        self.root_query_set = model_admin.queryset(request)
  49
+        self.list_display = list_display
  50
+        self.list_display_links = list_display_links
  51
+        self.list_filter = list_filter
  52
+        self.date_hierarchy = date_hierarchy
  53
+        self.search_fields = search_fields
  54
+        self.list_select_related = list_select_related
  55
+        self.list_per_page = list_per_page
  56
+        self.list_max_show_all = list_max_show_all
  57
+        self.model_admin = model_admin
  58
+
  59
+        # Get search parameters from the query string.
  60
+        try:
  61
+            self.page_num = int(request.GET.get(PAGE_VAR, 0))
  62
+        except ValueError:
  63
+            self.page_num = 0
  64
+        self.show_all = ALL_VAR in request.GET
  65
+        self.is_popup = IS_POPUP_VAR in request.GET
  66
+        self.to_field = request.GET.get(TO_FIELD_VAR)
  67
+        self.params = dict(request.GET.items())
  68
+        if PAGE_VAR in self.params:
  69
+            del self.params[PAGE_VAR]
  70
+        if ERROR_FLAG in self.params:
  71
+            del self.params[ERROR_FLAG]
  72
+
  73
+        if self.is_popup:
  74
+            self.list_editable = ()
  75
+        else:
  76
+            self.list_editable = list_editable
  77
+        self.query = request.GET.get(SEARCH_VAR, '')
  78
+        self.query_set = self.get_query_set(request)
  79
+        self.get_results(request)
  80
+        if self.is_popup:
  81
+            title = ugettext('Select %s')
  82
+        else:
  83
+            title = ugettext('Select %s to change')
  84
+        self.title = title % force_unicode(self.opts.verbose_name)
  85
+        self.pk_attname = self.lookup_opts.pk.attname
  86
+
  87
+    def get_filters(self, request):
  88
+        from django.contrib.admin import FieldListFilter
  89
+        from django.contrib.admin.options import IncorrectLookupParameters
  90
+
  91
+        lookup_params = self.params.copy() # a dictionary of the query string
  92
+        use_distinct = False
  93
+
  94
+        # Remove all the parameters that are globally and systematically
  95
+        # ignored.
  96
+        for ignored in IGNORED_PARAMS:
  97
+            if ignored in lookup_params:
  98
+                del lookup_params[ignored]
  99
+
  100
+        # Normalize the types of keys
  101
+        for key, value in lookup_params.items():
  102
+            if not isinstance(key, str):
  103
+                # 'key' will be used as a keyword argument later, so Python
  104
+                # requires it to be a string.
  105
+                del lookup_params[key]
  106
+                lookup_params[smart_str(key)] = value
  107
+
  108
+            if not self.model_admin.lookup_allowed(key, value):
  109
+                raise SuspiciousOperation("Filtering by %s not allowed" % key)
  110
+
  111
+        filter_specs = []
  112
+        if self.list_filter:
  113
+            for list_filter in self.list_filter:
  114
+                if callable(list_filter):
  115
+                    # This is simply a custom list filter class.
  116
+                    spec = list_filter(request, lookup_params,
  117
+                        self.model, self.model_admin)
  118
+                else:
  119
+                    field_path = None
  120
+                    if isinstance(list_filter, (tuple, list)):
  121
+                        # This is a custom FieldListFilter class for a given field.
  122
+                        field, field_list_filter_class = list_filter
  123
+                    else:
  124
+                        # This is simply a field name, so use the default
  125
+                        # FieldListFilter class that has been registered for
  126
+                        # the type of the given field.
  127
+                        field, field_list_filter_class = list_filter, FieldListFilter.create
  128
+                    if not isinstance(field, models.Field):
  129
+                        field_path = field
  130
+                        field = get_fields_from_path(self.model, field_path)[-1]
  131
+                    spec = field_list_filter_class(field, request, lookup_params,
  132
+                        self.model, self.model_admin, field_path=field_path)
  133
+                    # Check if we need to use distinct()
  134
+                    use_distinct = (use_distinct or
  135
+                                    lookup_needs_distinct(self.lookup_opts,
  136
+                                                          field_path))
  137
+                if spec and spec.has_output():
  138
+                    filter_specs.append(spec)
  139
+
  140
+        # At this point, all the parameters used by the various ListFilters
  141
+        # have been removed from lookup_params, which now only contains other
  142
+        # parameters passed via the query string. We now loop through the
  143
+        # remaining parameters both to ensure that all the parameters are valid
  144
+        # fields and to determine if at least one of them needs distinct(). If
  145
+        # the lookup parameters aren't real fields, then bail out.
  146
+        try:
  147
+            for key, value in lookup_params.items():
  148
+                lookup_params[key] = prepare_lookup_value(key, value)
  149
+                use_distinct = (use_distinct or
  150
+                                lookup_needs_distinct(self.lookup_opts, key))
  151
+            return filter_specs, bool(filter_specs), lookup_params, use_distinct
  152
+        except FieldDoesNotExist as e:
  153
+            raise IncorrectLookupParameters(e)
  154
+
  155
+    def get_query_string(self, new_params=None, remove=None):
  156
+        if new_params is None: new_params = {}
  157
+        if remove is None: remove = []
  158
+        p = self.params.copy()
  159
+        for r in remove:
  160
+            for k in p.keys():
  161
+                if k.startswith(r):
  162
+                    del p[k]
  163
+        for k, v in new_params.items():
  164
+            if v is None:
  165
+                if k in p:
  166
+                    del p[k]
  167
+            else:
  168
+                p[k] = v
  169
+        return '?%s' % urlencode(p)
  170
+
  171
+    def get_results(self, request):
  172
+        from django.contrib.admin.options import IncorrectLookupParameters
  173
+
  174
+        paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
  175
+        # Get the number of objects, with admin filters applied.
  176
+        result_count = paginator.count
  177
+
  178
+        # Get the total number of objects, with no admin filters applied.
  179
+        # Perform a slight optimization: Check to see whether any filters were
  180
+        # given. If not, use paginator.hits to calculate the number of objects,
  181
+        # because we've already done paginator.hits and the value is cached.
  182
+        if not self.query_set.query.where:
  183
+            full_result_count = result_count
  184
+        else:
  185
+            full_result_count = self.root_query_set.count()
  186
+
  187
+        can_show_all = result_count <= self.list_max_show_all
  188
+        multi_page = result_count > self.list_per_page
  189
+
  190
+        # Get the list of objects to display on this page.
  191
+        if (self.show_all and can_show_all) or not multi_page:
  192
+            result_list = self.query_set._clone()
  193
+        else:
  194
+            try:
  195
+                result_list = paginator.page(self.page_num+1).object_list
  196
+            except InvalidPage:
  197
+                raise IncorrectLookupParameters
  198
+
  199
+        self.result_count = result_count
  200
+        self.full_result_count = full_result_count
  201
+        self.result_list = result_list
  202
+        self.can_show_all = can_show_all
  203
+        self.multi_page = multi_page
  204
+        self.paginator = paginator
  205
+
  206
+    def _get_default_ordering(self):
  207
+        ordering = []
  208
+        if self.model_admin.ordering:
  209
+            ordering = self.model_admin.ordering
  210
+        elif self.lookup_opts.ordering:
  211
+            ordering = self.lookup_opts.ordering
  212
+        return ordering
  213
+
  214
+    def get_ordering_field(self, field_name):
  215
+        """
  216
+        Returns the proper model field name corresponding to the given
  217
+        field_name to use for ordering. field_name may either be the name of a
  218
+        proper model field or the name of a method (on the admin or model) or a
  219
+        callable with the 'admin_order_field' attribute. Returns None if no
  220
+        proper model field name can be matched.
  221
+        """
  222
+        try:
  223
+            field = self.lookup_opts.get_field(field_name)
  224
+            return field.name
  225
+        except models.FieldDoesNotExist:
  226
+            # See whether field_name is a name of a non-field
  227
+            # that allows sorting.
  228
+            if callable(field_name):
  229
+                attr = field_name
  230
+            elif hasattr(self.model_admin, field_name):
  231
+                attr = getattr(self.model_admin, field_name)
  232
+            else:
  233
+                attr = getattr(self.model, field_name)
  234
+            return getattr(attr, 'admin_order_field', None)
  235
+
  236
+    def get_ordering(self, request, queryset):
  237
+        """
  238
+        Returns the list of ordering fields for the change list.
  239
+        First we check the get_ordering() method in model admin, then we check
  240
+        the object's default ordering. Then, any manually-specified ordering
  241
+        from the query string overrides anything. Finally, a deterministic
  242
+        order is guaranteed by ensuring the primary key is used as the last
  243
+        ordering field.
  244
+        """
  245
+        params = self.params
  246
+        ordering = list(self.model_admin.get_ordering(request)
  247
+                        or self._get_default_ordering())
  248
+        if ORDER_VAR in params:
  249
+            # Clear ordering and used params
  250
+            ordering = []
  251
+            order_params = params[ORDER_VAR].split('.')
  252
+            for p in order_params:
  253
+                try:
  254
+                    none, pfx, idx = p.rpartition('-')
  255
+                    field_name = self.list_display[int(idx)]
  256
+                    order_field = self.get_ordering_field(field_name)
  257
+                    if not order_field:
  258
+                        continue # No 'admin_order_field', skip it
  259
+                    ordering.append(pfx + order_field)
  260
+                except (IndexError, ValueError):
  261
+                    continue # Invalid ordering specified, skip it.
  262
+
  263
+        # Add the given query's ordering fields, if any.
  264
+        ordering.extend(queryset.query.order_by)
  265
+
  266
+        # Ensure that the primary key is systematically present in the list of
  267
+        # ordering fields so we can guarantee a deterministic order across all
  268
+        # database backends.
  269
+        pk_name = self.lookup_opts.pk.name
  270
+        if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])):
  271
+            # The two sets do not intersect, meaning the pk isn't present. So
  272
+            # we add it.
  273
+            ordering.append('-pk')
  274
+
  275
+        return ordering
  276
+
  277
+    def get_ordering_field_columns(self):
  278
+        """
  279
+        Returns a SortedDict of ordering field column numbers and asc/desc
  280
+        """
  281
+
  282
+        # We must cope with more than one column having the same underlying sort
  283
+        # field, so we base things on column numbers.
  284
+        ordering = self._get_default_ordering()
  285
+        ordering_fields = SortedDict()
  286
+        if ORDER_VAR not in self.params:
  287
+            # for ordering specified on ModelAdmin or model Meta, we don't know
  288
+            # the right column numbers absolutely, because there might be more
  289
+            # than one column associated with that ordering, so we guess.
  290
+            for field in ordering:
  291
+                if field.startswith('-'):
  292
+                    field = field[1:]
  293
+                    order_type = 'desc'
  294
+                else:
  295
+                    order_type = 'asc'
  296
+                for index, attr in enumerate(self.list_display):
  297
+                    if self.get_ordering_field(attr) == field:
  298
+                        ordering_fields[index] = order_type
  299
+                        break
  300
+        else:
  301
+            for p in self.params[ORDER_VAR].split('.'):
  302
+                none, pfx, idx = p.rpartition('-')
  303
+                try:
  304
+                    idx = int(idx)
  305
+                except ValueError:
  306
+                    continue # skip it
  307
+                ordering_fields[idx] = 'desc' if pfx == '-' else 'asc'
  308
+        return ordering_fields
  309
+
  310
+    def get_query_set(self, request):
  311
+        from django.contrib.admin.options import IncorrectLookupParameters
  312
+
  313
+        # First, we collect all the declared list filters.
  314
+        (self.filter_specs, self.has_filters, remaining_lookup_params,
  315
+         use_distinct) = self.get_filters(request)
  316
+
  317
+        # Then, we let every list filter modify the queryset to its liking.
  318
+        qs = self.root_query_set
  319
+        for filter_spec in self.filter_specs:
  320
+            new_qs = filter_spec.queryset(request, qs)
  321
+            if new_qs is not None: