list.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. from __future__ import absolute_import
  2. from collections import OrderedDict
  3. from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
  4. from django.core.paginator import InvalidPage, Paginator
  5. from django.urls.base import NoReverseMatch
  6. from django.db import models
  7. from django.http import HttpResponseRedirect
  8. from django.template.response import SimpleTemplateResponse, TemplateResponse
  9. from django.utils import six
  10. from django.utils.encoding import force_text, smart_text
  11. from django.utils.html import escape, conditional_escape
  12. from django.utils.safestring import mark_safe
  13. from django.utils.text import capfirst
  14. from django.utils.translation import ugettext as _
  15. from xadmin.util import lookup_field, display_for_field, label_for_field, boolean_icon
  16. from .base import ModelAdminView, filter_hook, inclusion_tag, csrf_protect_m
  17. # List settings
  18. ALL_VAR = 'all'
  19. ORDER_VAR = 'o'
  20. PAGE_VAR = 'p'
  21. TO_FIELD_VAR = 't'
  22. COL_LIST_VAR = '_cols'
  23. ERROR_FLAG = 'e'
  24. DOT = '.'
  25. # Text to display within change-list table cells if the value is blank.
  26. EMPTY_CHANGELIST_VALUE = _('Null')
  27. class FakeMethodField(object):
  28. """
  29. This class used when a column is an model function, wrap function as a fake field to display in select columns.
  30. """
  31. def __init__(self, name, verbose_name):
  32. # Initial comm field attrs
  33. self.name = name
  34. self.verbose_name = verbose_name
  35. self.primary_key = False
  36. class ResultRow(dict):
  37. pass
  38. class ResultItem(object):
  39. def __init__(self, field_name, row):
  40. self.classes = []
  41. self.text = ' '
  42. self.wraps = []
  43. self.tag = 'td'
  44. self.tag_attrs = []
  45. self.allow_tags = False
  46. self.btns = []
  47. self.menus = []
  48. self.is_display_link = False
  49. self.row = row
  50. self.field_name = field_name
  51. self.field = None
  52. self.attr = None
  53. self.value = None
  54. @property
  55. def label(self):
  56. text = mark_safe(
  57. self.text) if self.allow_tags else conditional_escape(self.text)
  58. if force_text(text) == '':
  59. text = mark_safe(' ')
  60. for wrap in self.wraps:
  61. text = mark_safe(wrap % text)
  62. return text
  63. @property
  64. def tagattrs(self):
  65. return mark_safe(
  66. '%s%s' % ((self.tag_attrs and ' '.join(self.tag_attrs) or ''),
  67. (self.classes and (' class="%s"' % ' '.join(self.classes)) or '')))
  68. class ResultHeader(ResultItem):
  69. def __init__(self, field_name, row):
  70. super(ResultHeader, self).__init__(field_name, row)
  71. self.tag = 'th'
  72. self.tag_attrs = ['scope="col"']
  73. self.sortable = False
  74. self.allow_tags = True
  75. self.sorted = False
  76. self.ascending = None
  77. self.sort_priority = None
  78. self.url_primary = None
  79. self.url_remove = None
  80. self.url_toggle = None
  81. class ListAdminView(ModelAdminView):
  82. """
  83. Display models objects view. this class has ordering and simple filter features.
  84. """
  85. list_display = ('__str__',)
  86. list_display_links = ()
  87. list_display_links_details = False
  88. list_select_related = None
  89. list_per_page = 50
  90. list_max_show_all = 200
  91. list_exclude = ()
  92. search_fields = ()
  93. paginator_class = Paginator
  94. ordering = None
  95. # Change list templates
  96. object_list_template = None
  97. def init_request(self, *args, **kwargs):
  98. if not self.has_view_permission():
  99. raise PermissionDenied
  100. request = self.request
  101. request.session['LIST_QUERY'] = (self.model_info, self.request.META['QUERY_STRING'])
  102. self.pk_attname = self.opts.pk.attname
  103. self.lookup_opts = self.opts
  104. self.list_display = self.get_list_display()
  105. self.list_display_links = self.get_list_display_links()
  106. # Get page number parameters from the query string.
  107. try:
  108. self.page_num = int(request.GET.get(PAGE_VAR, 0))
  109. except ValueError:
  110. self.page_num = 0
  111. # Get params from request
  112. self.show_all = ALL_VAR in request.GET
  113. self.to_field = request.GET.get(TO_FIELD_VAR)
  114. self.params = dict(request.GET.items())
  115. if PAGE_VAR in self.params:
  116. del self.params[PAGE_VAR]
  117. if ERROR_FLAG in self.params:
  118. del self.params[ERROR_FLAG]
  119. @filter_hook
  120. def get_list_display(self):
  121. """
  122. Return a sequence containing the fields to be displayed on the list.
  123. """
  124. self.base_list_display = (COL_LIST_VAR in self.request.GET and self.request.GET[COL_LIST_VAR] != "" and
  125. self.request.GET[COL_LIST_VAR].split('.')) or self.list_display
  126. return list(self.base_list_display)
  127. @filter_hook
  128. def get_list_display_links(self):
  129. """
  130. Return a sequence containing the fields to be displayed as links
  131. on the changelist. The list_display parameter is the list of fields
  132. returned by get_list_display().
  133. """
  134. if self.list_display_links or not self.list_display:
  135. return self.list_display_links
  136. else:
  137. # Use only the first item in list_display as link
  138. return list(self.list_display)[:1]
  139. def make_result_list(self):
  140. # Get search parameters from the query string.
  141. self.list_queryset = self.get_list_queryset()
  142. self.ordering_field_columns = self.get_ordering_field_columns()
  143. self.paginator = self.get_paginator()
  144. # Get the number of objects, with admin filters applied.
  145. self.result_count = self.paginator.count
  146. self.can_show_all = self.result_count <= self.list_max_show_all
  147. self.multi_page = self.result_count > self.list_per_page
  148. # Get the list of objects to display on this page.
  149. if (self.show_all and self.can_show_all) or not self.multi_page:
  150. self.result_list = self.list_queryset._clone()
  151. else:
  152. try:
  153. self.result_list = self.paginator.page(
  154. self.page_num + 1).object_list
  155. except InvalidPage:
  156. if ERROR_FLAG in self.request.GET.keys():
  157. return SimpleTemplateResponse('xadmin/views/invalid_setup.html', {
  158. 'title': _('Database error'),
  159. })
  160. return HttpResponseRedirect(self.request.path + '?' + ERROR_FLAG + '=1')
  161. self.has_more = self.result_count > (
  162. self.list_per_page * self.page_num + len(self.result_list))
  163. @filter_hook
  164. def get_result_list(self):
  165. return self.make_result_list()
  166. @filter_hook
  167. def post_result_list(self):
  168. return self.make_result_list()
  169. @filter_hook
  170. def get_list_queryset(self):
  171. """
  172. Get model queryset. The query has been filted and ordered.
  173. """
  174. # First, get queryset from base class.
  175. queryset = self.queryset()
  176. # Use select_related() if one of the list_display options is a field
  177. # with a relationship and the provided queryset doesn't already have
  178. # select_related defined.
  179. if not queryset.query.select_related:
  180. if self.list_select_related:
  181. queryset = queryset.select_related()
  182. elif self.list_select_related is None:
  183. related_fields = []
  184. for field_name in self.list_display:
  185. try:
  186. field = self.opts.get_field(field_name)
  187. except models.FieldDoesNotExist:
  188. pass
  189. else:
  190. if isinstance(field.remote_field, models.ManyToOneRel):
  191. related_fields.append(field_name)
  192. if related_fields:
  193. queryset = queryset.select_related(*related_fields)
  194. else:
  195. pass
  196. # Then, set queryset ordering.
  197. queryset = queryset.order_by(*self.get_ordering())
  198. # Return the queryset.
  199. return queryset
  200. # List ordering
  201. def _get_default_ordering(self):
  202. ordering = []
  203. if self.ordering:
  204. ordering = self.ordering
  205. elif self.opts.ordering:
  206. ordering = self.opts.ordering
  207. return ordering
  208. @filter_hook
  209. def get_ordering_field(self, field_name):
  210. """
  211. Returns the proper model field name corresponding to the given
  212. field_name to use for ordering. field_name may either be the name of a
  213. proper model field or the name of a method (on the admin or model) or a
  214. callable with the 'admin_order_field' attribute. Returns None if no
  215. proper model field name can be matched.
  216. """
  217. try:
  218. field = self.opts.get_field(field_name)
  219. return field.name
  220. except models.FieldDoesNotExist:
  221. # See whether field_name is a name of a non-field
  222. # that allows sorting.
  223. if callable(field_name):
  224. attr = field_name
  225. elif hasattr(self, field_name):
  226. attr = getattr(self, field_name)
  227. else:
  228. attr = getattr(self.model, field_name)
  229. return getattr(attr, 'admin_order_field', None)
  230. @filter_hook
  231. def get_ordering(self):
  232. """
  233. Returns the list of ordering fields for the change list.
  234. First we check the get_ordering() method in model admin, then we check
  235. the object's default ordering. Then, any manually-specified ordering
  236. from the query string overrides anything. Finally, a deterministic
  237. order is guaranteed by ensuring the primary key is used as the last
  238. ordering field.
  239. """
  240. ordering = list(super(ListAdminView, self).get_ordering()
  241. or self._get_default_ordering())
  242. if ORDER_VAR in self.params and self.params[ORDER_VAR]:
  243. # Clear ordering and used params
  244. ordering = [
  245. pfx + self.get_ordering_field(field_name)
  246. for n, pfx, field_name in map(
  247. lambda p: p.rpartition('-'),
  248. self.params[ORDER_VAR].split('.')
  249. )
  250. if self.get_ordering_field(field_name)
  251. ]
  252. # Ensure that the primary key is systematically present in the list of
  253. # ordering fields so we can guarantee a deterministic order across all
  254. # database backends.
  255. pk_name = self.opts.pk.name
  256. if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])):
  257. # The two sets do not intersect, meaning the pk isn't present. So
  258. # we add it.
  259. ordering.append('-pk')
  260. return ordering
  261. @filter_hook
  262. def get_ordering_field_columns(self):
  263. """
  264. Returns a OrderedDict of ordering field column numbers and asc/desc
  265. """
  266. # We must cope with more than one column having the same underlying sort
  267. # field, so we base things on column numbers.
  268. ordering = self._get_default_ordering()
  269. ordering_fields = OrderedDict()
  270. if ORDER_VAR not in self.params or not self.params[ORDER_VAR]:
  271. # for ordering specified on ModelAdmin or model Meta, we don't know
  272. # the right column numbers absolutely, because there might be more
  273. # than one column associated with that ordering, so we guess.
  274. for field in ordering:
  275. if field.startswith('-'):
  276. field = field[1:]
  277. order_type = 'desc'
  278. else:
  279. order_type = 'asc'
  280. for attr in self.list_display:
  281. if self.get_ordering_field(attr) == field:
  282. ordering_fields[field] = order_type
  283. break
  284. else:
  285. for p in self.params[ORDER_VAR].split('.'):
  286. none, pfx, field_name = p.rpartition('-')
  287. ordering_fields[field_name] = 'desc' if pfx == '-' else 'asc'
  288. return ordering_fields
  289. def get_check_field_url(self, f):
  290. """
  291. Return the select column menu items link.
  292. We must use base_list_display, because list_display maybe changed by plugins.
  293. """
  294. fields = [fd for fd in self.base_list_display if fd != f.name]
  295. if len(self.base_list_display) == len(fields):
  296. if f.primary_key:
  297. fields.insert(0, f.name)
  298. else:
  299. fields.append(f.name)
  300. return self.get_query_string({COL_LIST_VAR: '.'.join(fields)})
  301. def get_model_method_fields(self):
  302. """
  303. Return the fields info defined in model. use FakeMethodField class wrap method as a db field.
  304. """
  305. methods = []
  306. for name in dir(self):
  307. try:
  308. if getattr(getattr(self, name), 'is_column', False):
  309. methods.append((name, getattr(self, name)))
  310. except:
  311. pass
  312. return [FakeMethodField(name, getattr(method, 'short_description', capfirst(name.replace('_', ' '))))
  313. for name, method in methods]
  314. @filter_hook
  315. def get_context(self):
  316. """
  317. Prepare the context for templates.
  318. """
  319. self.title = _('%s List') % force_text(self.opts.verbose_name)
  320. model_fields = [(f, f.name in self.list_display, self.get_check_field_url(f))
  321. for f in (list(self.opts.fields) + self.get_model_method_fields()) if f.name not in self.list_exclude]
  322. new_context = {
  323. 'model_name': force_text(self.opts.verbose_name_plural),
  324. 'title': self.title,
  325. 'cl': self,
  326. 'model_fields': model_fields,
  327. 'clean_select_field_url': self.get_query_string(remove=[COL_LIST_VAR]),
  328. 'has_add_permission': self.has_add_permission(),
  329. 'app_label': self.app_label,
  330. 'brand_name': self.opts.verbose_name_plural,
  331. 'brand_icon': self.get_model_icon(self.model),
  332. 'add_url': self.model_admin_url('add'),
  333. 'result_headers': self.result_headers(),
  334. 'results': self.results()
  335. }
  336. context = super(ListAdminView, self).get_context()
  337. context.update(new_context)
  338. return context
  339. @filter_hook
  340. def get_response(self, context, *args, **kwargs):
  341. pass
  342. @csrf_protect_m
  343. @filter_hook
  344. def get(self, request, *args, **kwargs):
  345. """
  346. The 'change list' admin view for this model.
  347. """
  348. response = self.get_result_list()
  349. if response:
  350. return response
  351. context = self.get_context()
  352. context.update(kwargs or {})
  353. response = self.get_response(context, *args, **kwargs)
  354. return response or TemplateResponse(request, self.object_list_template or
  355. self.get_template_list('views/model_list.html'), context)
  356. @filter_hook
  357. def post_response(self, *args, **kwargs):
  358. pass
  359. @csrf_protect_m
  360. @filter_hook
  361. def post(self, request, *args, **kwargs):
  362. return self.post_result_list() or self.post_response(*args, **kwargs) or self.get(request, *args, **kwargs)
  363. @filter_hook
  364. def get_paginator(self):
  365. return self.paginator_class(self.list_queryset, self.list_per_page, 0, True)
  366. @filter_hook
  367. def get_page_number(self, i):
  368. if i == DOT:
  369. return mark_safe(u'<span class="dot-page">...</span> ')
  370. elif i == self.page_num:
  371. return mark_safe(u'<span class="this-page">%d</span> ' % (i + 1))
  372. else:
  373. return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(self.get_query_string({PAGE_VAR: i})), (i == self.paginator.num_pages - 1 and ' class="end"' or ''), i + 1))
  374. # Result List methods
  375. @filter_hook
  376. def result_header(self, field_name, row):
  377. ordering_field_columns = self.ordering_field_columns
  378. item = ResultHeader(field_name, row)
  379. text, attr = label_for_field(field_name, self.model,
  380. model_admin=self,
  381. return_attr=True
  382. )
  383. item.text = text
  384. item.attr = attr
  385. if attr and not getattr(attr, "admin_order_field", None):
  386. return item
  387. # OK, it is sortable if we got this far
  388. th_classes = ['sortable']
  389. order_type = ''
  390. new_order_type = 'desc'
  391. sort_priority = 0
  392. sorted = False
  393. # Is it currently being sorted on?
  394. if field_name in ordering_field_columns:
  395. sorted = True
  396. order_type = ordering_field_columns.get(field_name).lower()
  397. arr = ordering_field_columns.keys()
  398. if six.PY3:
  399. arr = list(arr)
  400. sort_priority = arr.index(field_name) + 1
  401. th_classes.append('sorted %sending' % order_type)
  402. new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type]
  403. # build new ordering param
  404. o_list_asc = [] # URL for making this field the primary sort
  405. o_list_desc = [] # URL for making this field the primary sort
  406. o_list_remove = [] # URL for removing this field from sort
  407. o_list_toggle = [] # URL for toggling order type for this field
  408. make_qs_param = lambda t, n: ('-' if t == 'desc' else '') + str(n)
  409. for j, ot in ordering_field_columns.items():
  410. if j == field_name: # Same column
  411. param = make_qs_param(new_order_type, j)
  412. # We want clicking on this header to bring the ordering to the
  413. # front
  414. o_list_asc.insert(0, j)
  415. o_list_desc.insert(0, '-' + j)
  416. o_list_toggle.append(param)
  417. # o_list_remove - omit
  418. else:
  419. param = make_qs_param(ot, j)
  420. o_list_asc.append(param)
  421. o_list_desc.append(param)
  422. o_list_toggle.append(param)
  423. o_list_remove.append(param)
  424. if field_name not in ordering_field_columns:
  425. o_list_asc.insert(0, field_name)
  426. o_list_desc.insert(0, '-' + field_name)
  427. item.sorted = sorted
  428. item.sortable = True
  429. item.ascending = (order_type == "asc")
  430. item.sort_priority = sort_priority
  431. menus = [
  432. ('asc', o_list_asc, 'caret-up', _(u'Sort ASC')),
  433. ('desc', o_list_desc, 'caret-down', _(u'Sort DESC')),
  434. ]
  435. if sorted:
  436. row['num_sorted_fields'] = row['num_sorted_fields'] + 1
  437. menus.append((None, o_list_remove, 'times', _(u'Cancel Sort')))
  438. item.btns.append('<a class="toggle" href="%s"><i class="fa fa-%s"></i></a>' % (
  439. self.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}), 'sort-up' if order_type == "asc" else 'sort-down'))
  440. item.menus.extend(['<li%s><a href="%s" class="active"><i class="fa fa-%s"></i> %s</a></li>' %
  441. (
  442. (' class="active"' if sorted and order_type == i[
  443. 0] else ''),
  444. self.get_query_string({ORDER_VAR: '.'.join(i[1])}), i[2], i[3]) for i in menus])
  445. item.classes.extend(th_classes)
  446. return item
  447. @filter_hook
  448. def result_headers(self):
  449. """
  450. Generates the list column headers.
  451. """
  452. row = ResultRow()
  453. row['num_sorted_fields'] = 0
  454. row.cells = [self.result_header(
  455. field_name, row) for field_name in self.list_display]
  456. return row
  457. @filter_hook
  458. def result_item(self, obj, field_name, row):
  459. """
  460. Generates the actual list of data.
  461. """
  462. item = ResultItem(field_name, row)
  463. try:
  464. f, attr, value = lookup_field(field_name, obj, self)
  465. except (AttributeError, ObjectDoesNotExist, NoReverseMatch):
  466. item.text = mark_safe("<span class='text-muted'>%s</span>" % EMPTY_CHANGELIST_VALUE)
  467. else:
  468. if f is None:
  469. item.allow_tags = getattr(attr, 'allow_tags', False)
  470. boolean = getattr(attr, 'boolean', False)
  471. if boolean:
  472. item.allow_tags = True
  473. item.text = boolean_icon(value)
  474. else:
  475. item.text = smart_text(value)
  476. else:
  477. if isinstance(f.remote_field, models.ManyToOneRel):
  478. field_val = getattr(obj, f.name)
  479. if field_val is None:
  480. item.text = mark_safe("<span class='text-muted'>%s</span>" % EMPTY_CHANGELIST_VALUE)
  481. else:
  482. item.text = field_val
  483. else:
  484. item.text = display_for_field(value, f)
  485. if isinstance(f, models.DateField)\
  486. or isinstance(f, models.TimeField)\
  487. or isinstance(f, models.ForeignKey):
  488. item.classes.append('nowrap')
  489. item.field = f
  490. item.attr = attr
  491. item.value = value
  492. # If list_display_links not defined, add the link tag to the first field
  493. if (item.row['is_display_first'] and not self.list_display_links) \
  494. or field_name in self.list_display_links:
  495. item.row['is_display_first'] = False
  496. item.is_display_link = True
  497. if self.list_display_links_details:
  498. item_res_uri = self.model_admin_url("detail", getattr(obj, self.pk_attname))
  499. if item_res_uri:
  500. if self.has_change_permission(obj):
  501. edit_url = self.model_admin_url("change", getattr(obj, self.pk_attname))
  502. else:
  503. edit_url = ""
  504. item.wraps.append('<a data-res-uri="%s" data-edit-uri="%s" class="details-handler" rel="tooltip" title="%s">%%s</a>'
  505. % (item_res_uri, edit_url, _(u'Details of %s') % str(obj)))
  506. else:
  507. url = self.url_for_result(obj)
  508. item.wraps.append(u'<a href="%s">%%s</a>' % url)
  509. return item
  510. @filter_hook
  511. def result_row(self, obj):
  512. row = ResultRow()
  513. row['is_display_first'] = True
  514. row['object'] = obj
  515. row.cells = [self.result_item(
  516. obj, field_name, row) for field_name in self.list_display]
  517. return row
  518. @filter_hook
  519. def results(self):
  520. results = []
  521. for obj in self.result_list:
  522. results.append(self.result_row(obj))
  523. return results
  524. @filter_hook
  525. def url_for_result(self, result):
  526. return self.get_object_url(result)
  527. # Media
  528. @filter_hook
  529. def get_media(self):
  530. media = super(ListAdminView, self).get_media() + self.vendor('xadmin.page.list.js', 'xadmin.page.form.js')
  531. if self.list_display_links_details:
  532. media += self.vendor('xadmin.plugin.details.js', 'xadmin.form.css')
  533. return media
  534. # Blocks
  535. @inclusion_tag('xadmin/includes/pagination.html')
  536. def block_pagination(self, context, nodes, page_type='normal'):
  537. """
  538. Generates the series of links to the pages in a paginated list.
  539. """
  540. paginator, page_num = self.paginator, self.page_num
  541. pagination_required = (
  542. not self.show_all or not self.can_show_all) and self.multi_page
  543. if not pagination_required:
  544. page_range = []
  545. else:
  546. ON_EACH_SIDE = {'normal': 5, 'small': 3}.get(page_type, 3)
  547. ON_ENDS = 2
  548. # If there are 10 or fewer pages, display links to every page.
  549. # Otherwise, do some fancy
  550. if paginator.num_pages <= 10:
  551. page_range = range(paginator.num_pages)
  552. else:
  553. # Insert "smart" pagination links, so that there are always ON_ENDS
  554. # links at either end of the list of pages, and there are always
  555. # ON_EACH_SIDE links at either end of the "current page" link.
  556. page_range = []
  557. if page_num > (ON_EACH_SIDE + ON_ENDS):
  558. page_range.extend(range(0, ON_EACH_SIDE - 1))
  559. page_range.append(DOT)
  560. page_range.extend(
  561. range(page_num - ON_EACH_SIDE, page_num + 1))
  562. else:
  563. page_range.extend(range(0, page_num + 1))
  564. if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1):
  565. page_range.extend(
  566. range(page_num + 1, page_num + ON_EACH_SIDE + 1))
  567. page_range.append(DOT)
  568. page_range.extend(range(
  569. paginator.num_pages - ON_ENDS, paginator.num_pages))
  570. else:
  571. page_range.extend(range(page_num + 1, paginator.num_pages))
  572. need_show_all_link = self.can_show_all and not self.show_all and self.multi_page
  573. return {
  574. 'cl': self,
  575. 'pagination_required': pagination_required,
  576. 'show_all_url': need_show_all_link and self.get_query_string({ALL_VAR: ''}),
  577. 'page_range': map(self.get_page_number, page_range),
  578. 'ALL_VAR': ALL_VAR,
  579. '1': 1,
  580. }