list.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. from __future__ import unicode_literals
  2. from django.core.paginator import Paginator, InvalidPage
  3. from django.core.exceptions import ImproperlyConfigured
  4. from django.db.models.query import QuerySet
  5. from django.http import Http404
  6. from django.utils.translation import ugettext as _
  7. from django.views.generic.base import TemplateResponseMixin, ContextMixin, View
  8. class MultipleObjectMixin(ContextMixin):
  9. """
  10. A mixin for views manipulating multiple objects.
  11. """
  12. allow_empty = True
  13. queryset = None
  14. model = None
  15. paginate_by = None
  16. paginate_orphans = 0
  17. context_object_name = None
  18. paginator_class = Paginator
  19. page_kwarg = 'page'
  20. def get_queryset(self):
  21. """
  22. Return the list of items for this view.
  23. The return value must be an iterable and may be an instance of
  24. `QuerySet` in which case `QuerySet` specific behavior will be enabled.
  25. """
  26. if self.queryset is not None:
  27. queryset = self.queryset
  28. if isinstance(queryset, QuerySet):
  29. queryset = queryset.all()
  30. elif self.model is not None:
  31. queryset = self.model._default_manager.all()
  32. else:
  33. raise ImproperlyConfigured(
  34. "%(cls)s is missing a QuerySet. Define "
  35. "%(cls)s.model, %(cls)s.queryset, or override "
  36. "%(cls)s.get_queryset()." % {
  37. 'cls': self.__class__.__name__
  38. }
  39. )
  40. return queryset
  41. def paginate_queryset(self, queryset, page_size):
  42. """
  43. Paginate the queryset, if needed.
  44. """
  45. paginator = self.get_paginator(
  46. queryset, page_size, orphans=self.get_paginate_orphans(),
  47. allow_empty_first_page=self.get_allow_empty())
  48. page_kwarg = self.page_kwarg
  49. page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
  50. try:
  51. page_number = int(page)
  52. except ValueError:
  53. if page == 'last':
  54. page_number = paginator.num_pages
  55. else:
  56. raise Http404(_("Page is not 'last', nor can it be converted to an int."))
  57. try:
  58. page = paginator.page(page_number)
  59. return (paginator, page, page.object_list, page.has_other_pages())
  60. except InvalidPage as e:
  61. raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
  62. 'page_number': page_number,
  63. 'message': str(e)
  64. })
  65. def get_paginate_by(self, queryset):
  66. """
  67. Get the number of items to paginate by, or ``None`` for no pagination.
  68. """
  69. return self.paginate_by
  70. def get_paginator(self, queryset, per_page, orphans=0,
  71. allow_empty_first_page=True, **kwargs):
  72. """
  73. Return an instance of the paginator for this view.
  74. """
  75. return self.paginator_class(
  76. queryset, per_page, orphans=orphans,
  77. allow_empty_first_page=allow_empty_first_page, **kwargs)
  78. def get_paginate_orphans(self):
  79. """
  80. Returns the maximum number of orphans extend the last page by when
  81. paginating.
  82. """
  83. return self.paginate_orphans
  84. def get_allow_empty(self):
  85. """
  86. Returns ``True`` if the view should display empty lists, and ``False``
  87. if a 404 should be raised instead.
  88. """
  89. return self.allow_empty
  90. def get_context_object_name(self, object_list):
  91. """
  92. Get the name of the item to be used in the context.
  93. """
  94. if self.context_object_name:
  95. return self.context_object_name
  96. elif hasattr(object_list, 'model'):
  97. return '%s_list' % object_list.model._meta.model_name
  98. else:
  99. return None
  100. def get_context_data(self, **kwargs):
  101. """
  102. Get the context for this view.
  103. """
  104. queryset = kwargs.pop('object_list', self.object_list)
  105. page_size = self.get_paginate_by(queryset)
  106. context_object_name = self.get_context_object_name(queryset)
  107. if page_size:
  108. paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
  109. context = {
  110. 'paginator': paginator,
  111. 'page_obj': page,
  112. 'is_paginated': is_paginated,
  113. 'object_list': queryset
  114. }
  115. else:
  116. context = {
  117. 'paginator': None,
  118. 'page_obj': None,
  119. 'is_paginated': False,
  120. 'object_list': queryset
  121. }
  122. if context_object_name is not None:
  123. context[context_object_name] = queryset
  124. context.update(kwargs)
  125. return super(MultipleObjectMixin, self).get_context_data(**context)
  126. class BaseListView(MultipleObjectMixin, View):
  127. """
  128. A base view for displaying a list of objects.
  129. """
  130. def get(self, request, *args, **kwargs):
  131. self.object_list = self.get_queryset()
  132. allow_empty = self.get_allow_empty()
  133. if not allow_empty:
  134. # When pagination is enabled and object_list is a queryset,
  135. # it's better to do a cheap query than to load the unpaginated
  136. # queryset in memory.
  137. if (self.get_paginate_by(self.object_list) is not None
  138. and hasattr(self.object_list, 'exists')):
  139. is_empty = not self.object_list.exists()
  140. else:
  141. is_empty = len(self.object_list) == 0
  142. if is_empty:
  143. raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.")
  144. % {'class_name': self.__class__.__name__})
  145. context = self.get_context_data()
  146. return self.render_to_response(context)
  147. class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
  148. """
  149. Mixin for responding with a template and list of objects.
  150. """
  151. template_name_suffix = '_list'
  152. def get_template_names(self):
  153. """
  154. Return a list of template names to be used for the request. Must return
  155. a list. May not be called if render_to_response is overridden.
  156. """
  157. try:
  158. names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()
  159. except ImproperlyConfigured:
  160. # If template_name isn't specified, it's not a problem --
  161. # we just start with an empty list.
  162. names = []
  163. # If the list is a queryset, we'll invent a template name based on the
  164. # app and model name. This name gets put at the end of the template
  165. # name list so that user-supplied names override the automatically-
  166. # generated ones.
  167. if hasattr(self.object_list, 'model'):
  168. opts = self.object_list.model._meta
  169. names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
  170. return names
  171. class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
  172. """
  173. Render some list of objects, set by `self.model` or `self.queryset`.
  174. `self.queryset` can actually be any iterable of items, not just a queryset.
  175. """