PageRenderTime 297ms CodeModel.GetById 81ms app.highlight 61ms RepoModel.GetById 150ms app.codeStats 0ms

/django/contrib/admin/views/main.py

https://code.google.com/p/mango-py/
Python | 332 lines | 286 code | 20 blank | 26 comment | 49 complexity | 7667e6e24ec8667e18813a6ec79ab476 MD5 | raw file
  1from django.contrib.admin.filterspecs import FilterSpec, DistantFieldFilterSpec, RelatedFilterSpec, RangeFilterSpec
  2from django.contrib.admin.options import IncorrectLookupParameters
  3from django.contrib.admin.util import quote, get_fields_from_path
  4from django.core.exceptions import SuspiciousOperation
  5from django.core.paginator import InvalidPage
  6from django.db import models
  7from django.utils.encoding import force_unicode, smart_str
  8from django.utils.translation import ugettext, ugettext_lazy
  9from django.utils.http import urlencode
 10import operator
 11
 12# The system will display a "Show all" link on the change list only if the
 13# total result count is less than or equal to this setting.
 14MAX_SHOW_ALL_ALLOWED = 200
 15
 16# Changelist settings
 17ALL_VAR = 'all'
 18ORDER_VAR = 'o'
 19ORDER_TYPE_VAR = 'ot'
 20PAGE_VAR = 'p'
 21SEARCH_VAR = 'q'
 22TO_FIELD_VAR = 't'
 23IS_POPUP_VAR = 'pop'
 24ERROR_FLAG = 'e'
 25
 26# Text to display within change-list table cells if the value is blank.
 27EMPTY_CHANGELIST_VALUE = ugettext_lazy('(None)')
 28
 29def field_needs_distinct(field):
 30    if ((hasattr(field, 'rel') and
 31         isinstance(field.rel, models.ManyToManyRel)) or
 32        (isinstance(field, models.related.RelatedObject) and
 33         not field.field.unique)):
 34         return True
 35    return False
 36
 37
 38class ChangeList(object):
 39    def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin):
 40        self.model = model
 41        self.opts = model._meta
 42        self.lookup_opts = self.opts
 43        self.root_query_set = model_admin.queryset(request)
 44        self.list_display = list_display
 45        self.list_display_links = list_display_links
 46        self.list_filter = list_filter
 47        self.date_hierarchy = date_hierarchy
 48        self.search_fields = search_fields
 49        self.list_select_related = list_select_related
 50        self.list_per_page = list_per_page
 51        self.model_admin = model_admin
 52
 53        # Get search parameters from the query string.
 54        try:
 55            self.page_num = int(request.GET.get(PAGE_VAR, 0))
 56        except ValueError:
 57            self.page_num = 0
 58        self.show_all = ALL_VAR in request.GET
 59        self.is_popup = IS_POPUP_VAR in request.GET
 60        self.to_field = request.GET.get(TO_FIELD_VAR)
 61        self.params = dict(request.GET.items())
 62        if PAGE_VAR in self.params:
 63            del self.params[PAGE_VAR]
 64        if ERROR_FLAG in self.params:
 65            del self.params[ERROR_FLAG]
 66
 67        if self.is_popup:
 68            self.list_editable = ()
 69        else:
 70            self.list_editable = list_editable
 71        self.order_field, self.order_type = self.get_ordering()
 72        self.query = request.GET.get(SEARCH_VAR, '')
 73        self.query_set = self.get_query_set()
 74        self.get_results(request)
 75        self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
 76        self.filter_specs, self.has_filters = self.get_filters(request)
 77        self.pk_attname = self.lookup_opts.pk.attname
 78
 79    def get_filters(self, request):
 80        filter_specs = []
 81        if self.list_filter:
 82            for filter_name in self.list_filter:
 83                
 84                if callable(filter_name):
 85                    # filter_spec type as a callable
 86                    spec=False
 87                elif filter_name.startswith('range:'):
 88                    # range filter
 89                    # lets find the field object.
 90                    if "__" in filter_name[6:]:
 91                        filter_parts=filter_name[6:].split("__")
 92                        
 93                        # Go through the filter parts (seperated by __) and traverse the model structure to find the final model to do a lookup on.
 94                        mod = self.model
 95                        endfield = None            # does it end with a non joining field.
 96                        for part in filter_parts:
 97                            fld = mod._meta.get_field_by_name(part)[0]
 98                            if fld.rel:
 99                                mod = fld.rel.to
100                            else:
101                                endfield = part
102                                
103                        spec = RangeFilterSpec( filter_name[6:], fld, request, self.params, self.model, self.model_admin, prefix="__".join(filter_parts[:-1])+"__", field_path=filter_name )
104                    else:
105                        spec = RangeFilterSpec( filter_name[6:], self.lookup_opts.get_field(filter_name[6:]), request, self.params, self.model, self.model_admin, field_path=filter_name )
106                elif '__' in filter_name:
107                    # foreign -> foreign filter
108                    spec = DistantFieldFilterSpec( filter_name, request, self.params, self.model, self.model_admin, field_path=filter_name )  #f, request, params, model, model_admin
109                #elif bool(self.lookup_opts.get_field(filter_name).rel):
110                #    # Related Field. Special new constructor.
111                #    spec = RelatedFilterSpec(filter_name, self.lookup_opts.get_field(filter_name), request, self.params, self.model, self.model_admin, field_path=filter_name)
112                else:
113                    # standard Django 1.3 FilterSpec creation
114                    # (the other methods above are Mango)
115                    field = get_fields_from_path(self.model, filter_name)[-1]
116                    spec = FilterSpec.create(field, request, self.params,
117                                             self.model, self.model_admin,
118                                             field_path=filter_name)
119                if spec and spec.has_output():
120                    filter_specs.append(spec)
121        return filter_specs, bool(filter_specs)
122
123    def get_query_string(self, new_params=None, remove=None):
124        if new_params is None: new_params = {}
125        if remove is None: remove = []
126        p = self.params.copy()
127        for r in remove:
128            for k in p.keys():
129                if k.startswith(r):
130                    del p[k]
131        for k, v in new_params.items():
132            if v is None:
133                if k in p:
134                    del p[k]
135            else:
136                p[k] = v
137        return '?%s' % urlencode(p)
138        
139    def build_hidden_input_tags(self, new_params=None, remove=None):
140        if new_params is None: new_params = {}
141        if remove is None: remove = []
142        p = self.params.copy()
143        for r in remove:
144            for k in p.keys():
145                if k.startswith(r):
146                    del p[k]
147        for k, v in new_params.items():
148            if v is None:
149                if k in p:
150                    del p[k]
151            else:
152                p[k] = v
153
154    def get_results(self, request):
155        paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
156        # Get the number of objects, with admin filters applied.
157        result_count = paginator.count
158
159        # Get the total number of objects, with no admin filters applied.
160        # Perform a slight optimization: Check to see whether any filters were
161        # given. If not, use paginator.hits to calculate the number of objects,
162        # because we've already done paginator.hits and the value is cached.
163        if not self.query_set.query.where:
164            full_result_count = result_count
165        else:
166            full_result_count = self.root_query_set.count()
167
168        can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
169        multi_page = result_count > self.list_per_page
170
171        # Get the list of objects to display on this page.
172        if (self.show_all and can_show_all) or not multi_page:
173            result_list = self.query_set._clone()
174        else:
175            try:
176                result_list = paginator.page(self.page_num+1).object_list
177            except InvalidPage:
178                raise IncorrectLookupParameters
179
180        self.result_count = result_count
181        self.full_result_count = full_result_count
182        self.result_list = result_list
183        self.can_show_all = can_show_all
184        self.multi_page = multi_page
185        self.paginator = paginator
186
187    def get_ordering(self):
188        lookup_opts, params = self.lookup_opts, self.params
189        # For ordering, first check the "ordering" parameter in the admin
190        # options, then check the object's default ordering. If neither of
191        # those exist, order descending by ID by default. Finally, look for
192        # manually-specified ordering from the query string.
193        ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
194
195        if ordering[0].startswith('-'):
196            order_field, order_type = ordering[0][1:], 'desc'
197        else:
198            order_field, order_type = ordering[0], 'asc'
199        if ORDER_VAR in params:
200            try:
201                field_name = self.list_display[int(params[ORDER_VAR])]
202                try:
203                    f = lookup_opts.get_field(field_name)
204                except models.FieldDoesNotExist:
205                    # See whether field_name is a name of a non-field
206                    # that allows sorting.
207                    try:
208                        if callable(field_name):
209                            attr = field_name
210                        elif hasattr(self.model_admin, field_name):
211                            attr = getattr(self.model_admin, field_name)
212                        else:
213                            attr = getattr(self.model, field_name)
214                        order_field = attr.admin_order_field
215                    except AttributeError:
216                        pass
217                else:
218                    order_field = f.name
219            except (IndexError, ValueError):
220                pass # Invalid ordering specified. Just use the default.
221        if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
222            order_type = params[ORDER_TYPE_VAR]
223        return order_field, order_type
224
225    def get_query_set(self):
226        use_distinct = False
227
228        qs = self.root_query_set
229        lookup_params = self.params.copy() # a dictionary of the query string
230        for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_VAR):
231            if i in lookup_params:
232                del lookup_params[i]
233        for key, value in lookup_params.items():
234            if not isinstance(key, str):
235                # 'key' will be used as a keyword argument later, so Python
236                # requires it to be a string.
237                del lookup_params[key]
238                lookup_params[smart_str(key)] = value
239
240            if not use_distinct:
241                # Check if it's a relationship that might return more than one
242                # instance
243                field_name = key.split('__', 1)[0]
244                try:
245                    f = self.lookup_opts.get_field_by_name(field_name)[0]
246                except models.FieldDoesNotExist:
247                    raise IncorrectLookupParameters
248                use_distinct = field_needs_distinct(f)
249
250            # if key ends with __in, split parameter into separate values
251            if key.endswith('__in'):
252                value = value.split(',')
253                lookup_params[key] = value
254
255            # if key ends with __isnull, special case '' and false
256            if key.endswith('__isnull'):
257                if value.lower() in ('', 'false'):
258                    value = False
259                else:
260                    value = True
261                lookup_params[key] = value
262
263            if not self.model_admin.lookup_allowed(key, value):
264                raise SuspiciousOperation(
265                    "Filtering by %s not allowed" % key
266                )
267
268        # Apply lookup parameters from the query string.
269        try:
270            qs = qs.filter(**lookup_params)
271        # Naked except! Because we don't have any other way of validating "params".
272        # They might be invalid if the keyword arguments are incorrect, or if the
273        # values are not in the correct type, so we might get FieldError, ValueError,
274        # ValicationError, or ? from a custom field that raises yet something else
275        # when handed impossible data.
276        except:
277            raise IncorrectLookupParameters
278
279        # Use select_related() if one of the list_display options is a field
280        # with a relationship and the provided queryset doesn't already have
281        # select_related defined.
282        if not qs.query.select_related:
283            if self.list_select_related:
284                qs = qs.select_related()
285            else:
286                for field_name in self.list_display:
287                    try:
288                        f = self.lookup_opts.get_field(field_name)
289                    except models.FieldDoesNotExist:
290                        pass
291                    else:
292                        if isinstance(f.rel, models.ManyToOneRel):
293                            qs = qs.select_related()
294                            break
295
296        # Set ordering.
297        if self.order_field:
298            qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
299
300        # Apply keyword searches.
301        def construct_search(field_name):
302            if field_name.startswith('^'):
303                return "%s__istartswith" % field_name[1:]
304            elif field_name.startswith('='):
305                return "%s__iexact" % field_name[1:]
306            elif field_name.startswith('@'):
307                return "%s__search" % field_name[1:]
308            else:
309                return "%s__icontains" % field_name
310
311        if self.search_fields and self.query:
312            orm_lookups = [construct_search(str(search_field))
313                           for search_field in self.search_fields]
314            for bit in self.query.split():
315                or_queries = [models.Q(**{orm_lookup: bit})
316                              for orm_lookup in orm_lookups]
317                qs = qs.filter(reduce(operator.or_, or_queries))
318            if not use_distinct:
319                for search_spec in orm_lookups:
320                    field_name = search_spec.split('__', 1)[0]
321                    f = self.lookup_opts.get_field_by_name(field_name)[0]
322                    if field_needs_distinct(f):
323                        use_distinct = True
324                        break
325
326        if use_distinct:
327            return qs.distinct()
328        else:
329            return qs
330
331    def url_for_result(self, result):
332        return "%s/" % quote(getattr(result, self.pk_attname))