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

https://code.google.com/p/mango-py/ · Python · 332 lines · 254 code · 34 blank · 44 comment · 103 complexity · 7667e6e24ec8667e18813a6ec79ab476 MD5 · raw file

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