web/lib/django/contrib/admin/views/main.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 from django.contrib.admin.filterspecs import FilterSpec
       
     2 from django.contrib.admin.options import IncorrectLookupParameters
       
     3 from django.contrib.admin.util import quote
       
     4 from django.core.paginator import Paginator, InvalidPage
       
     5 from django.db import models
       
     6 from django.db.models.query import QuerySet
       
     7 from django.utils.encoding import force_unicode, smart_str
       
     8 from django.utils.translation import ugettext
       
     9 from django.utils.http import urlencode
       
    10 import 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.
       
    14 MAX_SHOW_ALL_ALLOWED = 200
       
    15 
       
    16 # Changelist settings
       
    17 ALL_VAR = 'all'
       
    18 ORDER_VAR = 'o'
       
    19 ORDER_TYPE_VAR = 'ot'
       
    20 PAGE_VAR = 'p'
       
    21 SEARCH_VAR = 'q'
       
    22 TO_FIELD_VAR = 't'
       
    23 IS_POPUP_VAR = 'pop'
       
    24 ERROR_FLAG = 'e'
       
    25 
       
    26 # Text to display within change-list table cells if the value is blank.
       
    27 EMPTY_CHANGELIST_VALUE = '(None)'
       
    28 
       
    29 class ChangeList(object):
       
    30     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):
       
    31         self.model = model
       
    32         self.opts = model._meta
       
    33         self.lookup_opts = self.opts
       
    34         self.root_query_set = model_admin.queryset(request)
       
    35         self.list_display = list_display
       
    36         self.list_display_links = list_display_links
       
    37         self.list_filter = list_filter
       
    38         self.date_hierarchy = date_hierarchy
       
    39         self.search_fields = search_fields
       
    40         self.list_select_related = list_select_related
       
    41         self.list_per_page = list_per_page
       
    42         self.list_editable = list_editable
       
    43         self.model_admin = model_admin
       
    44 
       
    45         # Get search parameters from the query string.
       
    46         try:
       
    47             self.page_num = int(request.GET.get(PAGE_VAR, 0))
       
    48         except ValueError:
       
    49             self.page_num = 0
       
    50         self.show_all = ALL_VAR in request.GET
       
    51         self.is_popup = IS_POPUP_VAR in request.GET
       
    52         self.to_field = request.GET.get(TO_FIELD_VAR)
       
    53         self.params = dict(request.GET.items())
       
    54         if PAGE_VAR in self.params:
       
    55             del self.params[PAGE_VAR]
       
    56         if TO_FIELD_VAR in self.params:
       
    57             del self.params[TO_FIELD_VAR]
       
    58         if ERROR_FLAG in self.params:
       
    59             del self.params[ERROR_FLAG]
       
    60 
       
    61         self.order_field, self.order_type = self.get_ordering()
       
    62         self.query = request.GET.get(SEARCH_VAR, '')
       
    63         self.query_set = self.get_query_set()
       
    64         self.get_results(request)
       
    65         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))
       
    66         self.filter_specs, self.has_filters = self.get_filters(request)
       
    67         self.pk_attname = self.lookup_opts.pk.attname
       
    68 
       
    69     def get_filters(self, request):
       
    70         filter_specs = []
       
    71         if self.list_filter:
       
    72             filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter]
       
    73             for f in filter_fields:
       
    74                 spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin)
       
    75                 if spec and spec.has_output():
       
    76                     filter_specs.append(spec)
       
    77         return filter_specs, bool(filter_specs)
       
    78 
       
    79     def get_query_string(self, new_params=None, remove=None):
       
    80         if new_params is None: new_params = {}
       
    81         if remove is None: remove = []
       
    82         p = self.params.copy()
       
    83         for r in remove:
       
    84             for k in p.keys():
       
    85                 if k.startswith(r):
       
    86                     del p[k]
       
    87         for k, v in new_params.items():
       
    88             if v is None:
       
    89                 if k in p:
       
    90                     del p[k]
       
    91             else:
       
    92                 p[k] = v
       
    93         return '?%s' % urlencode(p)
       
    94 
       
    95     def get_results(self, request):
       
    96         paginator = Paginator(self.query_set, self.list_per_page)
       
    97         # Get the number of objects, with admin filters applied.
       
    98         result_count = paginator.count
       
    99 
       
   100         # Get the total number of objects, with no admin filters applied.
       
   101         # Perform a slight optimization: Check to see whether any filters were
       
   102         # given. If not, use paginator.hits to calculate the number of objects,
       
   103         # because we've already done paginator.hits and the value is cached.
       
   104         if not self.query_set.query.where:
       
   105             full_result_count = result_count
       
   106         else:
       
   107             full_result_count = self.root_query_set.count()
       
   108 
       
   109         can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED
       
   110         multi_page = result_count > self.list_per_page
       
   111 
       
   112         # Get the list of objects to display on this page.
       
   113         if (self.show_all and can_show_all) or not multi_page:
       
   114             result_list = self.query_set._clone()
       
   115         else:
       
   116             try:
       
   117                 result_list = paginator.page(self.page_num+1).object_list
       
   118             except InvalidPage:
       
   119                 result_list = ()
       
   120 
       
   121         self.result_count = result_count
       
   122         self.full_result_count = full_result_count
       
   123         self.result_list = result_list
       
   124         self.can_show_all = can_show_all
       
   125         self.multi_page = multi_page
       
   126         self.paginator = paginator
       
   127 
       
   128     def get_ordering(self):
       
   129         lookup_opts, params = self.lookup_opts, self.params
       
   130         # For ordering, first check the "ordering" parameter in the admin
       
   131         # options, then check the object's default ordering. If neither of
       
   132         # those exist, order descending by ID by default. Finally, look for
       
   133         # manually-specified ordering from the query string.
       
   134         ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name]
       
   135 
       
   136         if ordering[0].startswith('-'):
       
   137             order_field, order_type = ordering[0][1:], 'desc'
       
   138         else:
       
   139             order_field, order_type = ordering[0], 'asc'
       
   140         if ORDER_VAR in params:
       
   141             try:
       
   142                 field_name = self.list_display[int(params[ORDER_VAR])]
       
   143                 try:
       
   144                     f = lookup_opts.get_field(field_name)
       
   145                 except models.FieldDoesNotExist:
       
   146                     # See whether field_name is a name of a non-field
       
   147                     # that allows sorting.
       
   148                     try:
       
   149                         if callable(field_name):
       
   150                             attr = field_name
       
   151                         elif hasattr(self.model_admin, field_name):
       
   152                             attr = getattr(self.model_admin, field_name)
       
   153                         else:
       
   154                             attr = getattr(self.model, field_name)
       
   155                         order_field = attr.admin_order_field
       
   156                     except AttributeError:
       
   157                         pass
       
   158                 else:
       
   159                     order_field = f.name
       
   160             except (IndexError, ValueError):
       
   161                 pass # Invalid ordering specified. Just use the default.
       
   162         if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
       
   163             order_type = params[ORDER_TYPE_VAR]
       
   164         return order_field, order_type
       
   165 
       
   166     def get_query_set(self):
       
   167         qs = self.root_query_set
       
   168         lookup_params = self.params.copy() # a dictionary of the query string
       
   169         for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
       
   170             if i in lookup_params:
       
   171                 del lookup_params[i]
       
   172         for key, value in lookup_params.items():
       
   173             if not isinstance(key, str):
       
   174                 # 'key' will be used as a keyword argument later, so Python
       
   175                 # requires it to be a string.
       
   176                 del lookup_params[key]
       
   177                 lookup_params[smart_str(key)] = value
       
   178 
       
   179             # if key ends with __in, split parameter into separate values
       
   180             if key.endswith('__in'):
       
   181                 lookup_params[key] = value.split(',')
       
   182 
       
   183             # if key ends with __isnull, special case '' and false
       
   184             if key.endswith('__isnull'):
       
   185                 if value.lower() in ('', 'false'):
       
   186                     lookup_params[key] = False
       
   187                 else:
       
   188                     lookup_params[key] = True
       
   189 
       
   190         # Apply lookup parameters from the query string.
       
   191         try:
       
   192             qs = qs.filter(**lookup_params)
       
   193         # Naked except! Because we don't have any other way of validating "params".
       
   194         # They might be invalid if the keyword arguments are incorrect, or if the
       
   195         # values are not in the correct type, so we might get FieldError, ValueError,
       
   196         # ValicationError, or ? from a custom field that raises yet something else 
       
   197         # when handed impossible data.
       
   198         except:
       
   199             raise IncorrectLookupParameters
       
   200 
       
   201         # Use select_related() if one of the list_display options is a field
       
   202         # with a relationship and the provided queryset doesn't already have
       
   203         # select_related defined.
       
   204         if not qs.query.select_related:
       
   205             if self.list_select_related:
       
   206                 qs = qs.select_related()
       
   207             else:
       
   208                 for field_name in self.list_display:
       
   209                     try:
       
   210                         f = self.lookup_opts.get_field(field_name)
       
   211                     except models.FieldDoesNotExist:
       
   212                         pass
       
   213                     else:
       
   214                         if isinstance(f.rel, models.ManyToOneRel):
       
   215                             qs = qs.select_related()
       
   216                             break
       
   217 
       
   218         # Set ordering.
       
   219         if self.order_field:
       
   220             qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
       
   221 
       
   222         # Apply keyword searches.
       
   223         def construct_search(field_name):
       
   224             if field_name.startswith('^'):
       
   225                 return "%s__istartswith" % field_name[1:]
       
   226             elif field_name.startswith('='):
       
   227                 return "%s__iexact" % field_name[1:]
       
   228             elif field_name.startswith('@'):
       
   229                 return "%s__search" % field_name[1:]
       
   230             else:
       
   231                 return "%s__icontains" % field_name
       
   232 
       
   233         if self.search_fields and self.query:
       
   234             for bit in self.query.split():
       
   235                 or_queries = [models.Q(**{construct_search(str(field_name)): bit}) for field_name in self.search_fields]
       
   236                 qs = qs.filter(reduce(operator.or_, or_queries))
       
   237             for field_name in self.search_fields:
       
   238                 if '__' in field_name:
       
   239                     qs = qs.distinct()
       
   240                     break
       
   241 
       
   242         return qs
       
   243 
       
   244     def url_for_result(self, result):
       
   245         return "%s/" % quote(getattr(result, self.pk_attname))