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