~ubuntu-branches/debian/sid/python-django/sid

« back to all changes in this revision

Viewing changes to django/contrib/admin/views/main.py

  • Committer: Package Import Robot
  • Author(s): Luke Faraone
  • Date: 2013-11-07 15:33:49 UTC
  • mfrom: (1.3.12)
  • Revision ID: package-import@ubuntu.com-20131107153349-e31sc149l2szs3jb
Tags: 1.6-1
* New upstream version. Closes: #557474, #724637.
* python-django now also suggests the installation of ipython,
  bpython, python-django-doc, and libgdal1.
  Closes: #636511, #686333, #704203
* Set package maintainer to Debian Python Modules Team.
* Bump standards version to 3.9.5, no changes needed.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import operator
2
 
from functools import reduce
 
1
import sys
 
2
import warnings
3
3
 
4
4
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
5
5
from django.core.paginator import InvalidPage
6
6
from django.core.urlresolvers import reverse
7
7
from django.db import models
8
8
from django.db.models.fields import FieldDoesNotExist
 
9
from django.utils import six
9
10
from django.utils.datastructures import SortedDict
 
11
from django.utils.deprecation import RenameMethodsBase
10
12
from django.utils.encoding import force_str, force_text
11
13
from django.utils.translation import ugettext, ugettext_lazy
12
14
from django.utils.http import urlencode
13
15
 
14
16
from django.contrib.admin import FieldListFilter
15
 
from django.contrib.admin.options import IncorrectLookupParameters
 
17
from django.contrib.admin.exceptions import DisallowedModelAdminLookup
 
18
from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR
16
19
from django.contrib.admin.util import (quote, get_fields_from_path,
17
20
    lookup_needs_distinct, prepare_lookup_value)
18
21
 
23
26
PAGE_VAR = 'p'
24
27
SEARCH_VAR = 'q'
25
28
TO_FIELD_VAR = 't'
26
 
IS_POPUP_VAR = 'pop'
27
29
ERROR_FLAG = 'e'
28
30
 
29
31
IGNORED_PARAMS = (
33
35
EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
34
36
 
35
37
 
36
 
class ChangeList(object):
 
38
def _is_changelist_popup(request):
 
39
    """
 
40
    Returns True if the popup GET parameter is set.
 
41
 
 
42
    This function is introduced to facilitate deprecating the legacy
 
43
    value for IS_POPUP_VAR and should be removed at the end of the
 
44
    deprecation cycle.
 
45
    """
 
46
 
 
47
    if IS_POPUP_VAR in request.GET:
 
48
        return True
 
49
 
 
50
    IS_LEGACY_POPUP_VAR = 'pop'
 
51
    if IS_LEGACY_POPUP_VAR in request.GET:
 
52
        warnings.warn(
 
53
        "The `%s` GET parameter has been renamed to `%s`." %
 
54
        (IS_LEGACY_POPUP_VAR, IS_POPUP_VAR),
 
55
        PendingDeprecationWarning, 2)
 
56
        return True
 
57
 
 
58
    return False
 
59
 
 
60
 
 
61
class RenameChangeListMethods(RenameMethodsBase):
 
62
    renamed_methods = (
 
63
        ('get_query_set', 'get_queryset', PendingDeprecationWarning),
 
64
    )
 
65
 
 
66
 
 
67
class ChangeList(six.with_metaclass(RenameChangeListMethods)):
37
68
    def __init__(self, request, model, list_display, list_display_links,
38
69
            list_filter, date_hierarchy, search_fields, list_select_related,
39
70
            list_per_page, list_max_show_all, list_editable, model_admin):
40
71
        self.model = model
41
72
        self.opts = model._meta
42
73
        self.lookup_opts = self.opts
43
 
        self.root_query_set = model_admin.queryset(request)
 
74
        self.root_queryset = model_admin.get_queryset(request)
44
75
        self.list_display = list_display
45
76
        self.list_display_links = list_display_links
46
77
        self.list_filter = list_filter
50
81
        self.list_per_page = list_per_page
51
82
        self.list_max_show_all = list_max_show_all
52
83
        self.model_admin = model_admin
 
84
        self.preserved_filters = model_admin.get_preserved_filters(request)
53
85
 
54
86
        # Get search parameters from the query string.
55
87
        try:
57
89
        except ValueError:
58
90
            self.page_num = 0
59
91
        self.show_all = ALL_VAR in request.GET
60
 
        self.is_popup = IS_POPUP_VAR in request.GET
 
92
        self.is_popup = _is_changelist_popup(request)
61
93
        self.to_field = request.GET.get(TO_FIELD_VAR)
62
94
        self.params = dict(request.GET.items())
63
95
        if PAGE_VAR in self.params:
70
102
        else:
71
103
            self.list_editable = list_editable
72
104
        self.query = request.GET.get(SEARCH_VAR, '')
73
 
        self.query_set = self.get_query_set(request)
 
105
        self.queryset = self.get_queryset(request)
74
106
        self.get_results(request)
75
107
        if self.is_popup:
76
108
            title = ugettext('Select %s')
79
111
        self.title = title % force_text(self.opts.verbose_name)
80
112
        self.pk_attname = self.lookup_opts.pk.attname
81
113
 
82
 
    def get_filters(self, request):
83
 
        lookup_params = self.params.copy() # a dictionary of the query string
84
 
        use_distinct = False
85
 
 
 
114
    @property
 
115
    def root_query_set(self):
 
116
        warnings.warn("`ChangeList.root_query_set` is deprecated, "
 
117
                      "use `root_queryset` instead.",
 
118
                      PendingDeprecationWarning, 2)
 
119
        return self.root_queryset
 
120
 
 
121
    @property
 
122
    def query_set(self):
 
123
        warnings.warn("`ChangeList.query_set` is deprecated, "
 
124
                      "use `queryset` instead.",
 
125
                      PendingDeprecationWarning, 2)
 
126
        return self.queryset
 
127
 
 
128
    def get_filters_params(self, params=None):
 
129
        """
 
130
        Returns all params except IGNORED_PARAMS
 
131
        """
 
132
        if not params:
 
133
            params = self.params
 
134
        lookup_params = params.copy() # a dictionary of the query string
86
135
        # Remove all the parameters that are globally and systematically
87
136
        # ignored.
88
137
        for ignored in IGNORED_PARAMS:
89
138
            if ignored in lookup_params:
90
139
                del lookup_params[ignored]
 
140
        return lookup_params
 
141
 
 
142
    def get_filters(self, request):
 
143
        lookup_params = self.get_filters_params()
 
144
        use_distinct = False
91
145
 
92
146
        # Normalize the types of keys
93
147
        for key, value in lookup_params.items():
98
152
                lookup_params[force_str(key)] = value
99
153
 
100
154
            if not self.model_admin.lookup_allowed(key, value):
101
 
                raise SuspiciousOperation("Filtering by %s not allowed" % key)
 
155
                raise DisallowedModelAdminLookup("Filtering by %s not allowed" % key)
102
156
 
103
157
        filter_specs = []
104
158
        if self.list_filter:
142
196
                                lookup_needs_distinct(self.lookup_opts, key))
143
197
            return filter_specs, bool(filter_specs), lookup_params, use_distinct
144
198
        except FieldDoesNotExist as e:
145
 
            raise IncorrectLookupParameters(e)
 
199
            six.reraise(IncorrectLookupParameters, IncorrectLookupParameters(e), sys.exc_info()[2])
146
200
 
147
201
    def get_query_string(self, new_params=None, remove=None):
148
202
        if new_params is None: new_params = {}
161
215
        return '?%s' % urlencode(sorted(p.items()))
162
216
 
163
217
    def get_results(self, request):
164
 
        paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
 
218
        paginator = self.model_admin.get_paginator(request, self.queryset, self.list_per_page)
165
219
        # Get the number of objects, with admin filters applied.
166
220
        result_count = paginator.count
167
221
 
168
222
        # Get the total number of objects, with no admin filters applied.
169
 
        # Perform a slight optimization: Check to see whether any filters were
170
 
        # given. If not, use paginator.hits to calculate the number of objects,
171
 
        # because we've already done paginator.hits and the value is cached.
172
 
        if not self.query_set.query.where:
 
223
        # Perform a slight optimization:
 
224
        # full_result_count is equal to paginator.count if no filters
 
225
        # were applied
 
226
        if self.get_filters_params():
 
227
            full_result_count = self.root_queryset.count()
 
228
        else:
173
229
            full_result_count = result_count
174
 
        else:
175
 
            full_result_count = self.root_query_set.count()
176
 
 
177
230
        can_show_all = result_count <= self.list_max_show_all
178
231
        multi_page = result_count > self.list_per_page
179
232
 
180
233
        # Get the list of objects to display on this page.
181
234
        if (self.show_all and can_show_all) or not multi_page:
182
 
            result_list = self.query_set._clone()
 
235
            result_list = self.queryset._clone()
183
236
        else:
184
237
            try:
185
238
                result_list = paginator.page(self.page_num+1).object_list
297
350
                ordering_fields[idx] = 'desc' if pfx == '-' else 'asc'
298
351
        return ordering_fields
299
352
 
300
 
    def get_query_set(self, request):
 
353
    def get_queryset(self, request):
301
354
        # First, we collect all the declared list filters.
302
355
        (self.filter_specs, self.has_filters, remaining_lookup_params,
303
 
         use_distinct) = self.get_filters(request)
 
356
         filters_use_distinct) = self.get_filters(request)
304
357
 
305
358
        # Then, we let every list filter modify the queryset to its liking.
306
 
        qs = self.root_query_set
 
359
        qs = self.root_queryset
307
360
        for filter_spec in self.filter_specs:
308
361
            new_qs = filter_spec.queryset(request, qs)
309
362
            if new_qs is not None:
326
379
            # ValueError, ValidationError, or ?.
327
380
            raise IncorrectLookupParameters(e)
328
381
 
329
 
        # Use select_related() if one of the list_display options is a field
330
 
        # with a relationship and the provided queryset doesn't already have
331
 
        # select_related defined.
332
382
        if not qs.query.select_related:
333
 
            if self.list_select_related:
334
 
                qs = qs.select_related()
335
 
            else:
336
 
                for field_name in self.list_display:
337
 
                    try:
338
 
                        field = self.lookup_opts.get_field(field_name)
339
 
                    except models.FieldDoesNotExist:
340
 
                        pass
341
 
                    else:
342
 
                        if isinstance(field.rel, models.ManyToOneRel):
343
 
                            qs = qs.select_related()
344
 
                            break
 
383
            qs = self.apply_select_related(qs)
345
384
 
346
385
        # Set ordering.
347
386
        ordering = self.get_ordering(request, qs)
348
387
        qs = qs.order_by(*ordering)
349
388
 
350
 
        # Apply keyword searches.
351
 
        def construct_search(field_name):
352
 
            if field_name.startswith('^'):
353
 
                return "%s__istartswith" % field_name[1:]
354
 
            elif field_name.startswith('='):
355
 
                return "%s__iexact" % field_name[1:]
356
 
            elif field_name.startswith('@'):
357
 
                return "%s__search" % field_name[1:]
358
 
            else:
359
 
                return "%s__icontains" % field_name
360
 
 
361
 
        if self.search_fields and self.query:
362
 
            orm_lookups = [construct_search(str(search_field))
363
 
                           for search_field in self.search_fields]
364
 
            for bit in self.query.split():
365
 
                or_queries = [models.Q(**{orm_lookup: bit})
366
 
                              for orm_lookup in orm_lookups]
367
 
                qs = qs.filter(reduce(operator.or_, or_queries))
368
 
            if not use_distinct:
369
 
                for search_spec in orm_lookups:
370
 
                    if lookup_needs_distinct(self.lookup_opts, search_spec):
371
 
                        use_distinct = True
372
 
                        break
373
 
 
374
 
        if use_distinct:
 
389
        # Apply search results
 
390
        qs, search_use_distinct = self.model_admin.get_search_results(
 
391
            request, qs, self.query)
 
392
 
 
393
        # Remove duplicates from results, if necessary
 
394
        if filters_use_distinct | search_use_distinct:
375
395
            return qs.distinct()
376
396
        else:
377
397
            return qs
378
398
 
 
399
    def apply_select_related(self, qs):
 
400
        if self.list_select_related is True:
 
401
            return qs.select_related()
 
402
 
 
403
        if self.list_select_related is False:
 
404
            if self.has_related_field_in_list_display():
 
405
                return qs.select_related()
 
406
 
 
407
        if self.list_select_related:
 
408
            return qs.select_related(*self.list_select_related)
 
409
        return qs
 
410
 
 
411
    def has_related_field_in_list_display(self):
 
412
        for field_name in self.list_display:
 
413
            try:
 
414
                field = self.lookup_opts.get_field(field_name)
 
415
            except models.FieldDoesNotExist:
 
416
                pass
 
417
            else:
 
418
                if isinstance(field.rel, models.ManyToOneRel):
 
419
                    return True
 
420
        return False
 
421
 
379
422
    def url_for_result(self, result):
380
423
        pk = getattr(result, self.pk_attname)
381
424
        return reverse('admin:%s_%s_change' % (self.opts.app_label,
382
 
                                               self.opts.module_name),
 
425
                                               self.opts.model_name),
383
426
                       args=(quote(pk),),
384
427
                       current_app=self.model_admin.admin_site.name)