~ubuntuone-pqm-team/django/stable

« back to all changes in this revision

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

  • Committer: Natalia
  • Date: 2014-12-05 15:21:13 UTC
  • Revision ID: natalia.bidart@ubuntu.com-20141205152113-cchtmygjia45gb87
Tags: 1.6.8
- Imported Django 1.6.8 from released tarball.

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