| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- from django.core.exceptions import ImproperlyConfigured
- from django.core.paginator import InvalidPage, Paginator
- from django.db.models import QuerySet
- from django.http import Http404
- from django.utils.translation import gettext as _
- from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
- class MultipleObjectMixin(ContextMixin):
- """A mixin for views manipulating multiple objects."""
- allow_empty = True
- queryset = None
- model = None
- paginate_by = None
- paginate_orphans = 0
- context_object_name = None
- paginator_class = Paginator
- page_kwarg = "page"
- ordering = None
- def get_queryset(self):
- """
- Return the list of items for this view.
- The return value must be an iterable and may be an instance of
- `QuerySet` in which case `QuerySet` specific behavior will be enabled.
- """
- if self.queryset is not None:
- queryset = self.queryset
- if isinstance(queryset, QuerySet):
- queryset = queryset.all()
- elif self.model is not None:
- queryset = self.model._default_manager.all()
- else:
- raise ImproperlyConfigured(
- "%(cls)s is missing a QuerySet. Define "
- "%(cls)s.model, %(cls)s.queryset, or override "
- "%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
- )
- ordering = self.get_ordering()
- if ordering:
- if isinstance(ordering, str):
- ordering = (ordering,)
- queryset = queryset.order_by(*ordering)
- return queryset
- def get_ordering(self):
- """Return the field or fields to use for ordering the queryset."""
- return self.ordering
- def paginate_queryset(self, queryset, page_size):
- """Paginate the queryset, if needed."""
- paginator = self.get_paginator(
- queryset,
- page_size,
- orphans=self.get_paginate_orphans(),
- allow_empty_first_page=self.get_allow_empty(),
- )
- page_kwarg = self.page_kwarg
- page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
- try:
- page_number = int(page)
- except ValueError:
- if page == "last":
- page_number = paginator.num_pages
- else:
- raise Http404(
- _("Page is not “last”, nor can it be converted to an int.")
- )
- try:
- page = paginator.page(page_number)
- return (paginator, page, page.object_list, page.has_other_pages())
- except InvalidPage as e:
- raise Http404(
- _("Invalid page (%(page_number)s): %(message)s")
- % {"page_number": page_number, "message": str(e)}
- )
- def get_paginate_by(self, queryset):
- """
- Get the number of items to paginate by, or ``None`` for no pagination.
- """
- return self.paginate_by
- def get_paginator(
- self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
- ):
- """Return an instance of the paginator for this view."""
- return self.paginator_class(
- queryset,
- per_page,
- orphans=orphans,
- allow_empty_first_page=allow_empty_first_page,
- **kwargs,
- )
- def get_paginate_orphans(self):
- """
- Return the maximum number of orphans extend the last page by when
- paginating.
- """
- return self.paginate_orphans
- def get_allow_empty(self):
- """
- Return ``True`` if the view should display empty lists and ``False``
- if a 404 should be raised instead.
- """
- return self.allow_empty
- def get_context_object_name(self, object_list):
- """Get the name of the item to be used in the context."""
- if self.context_object_name:
- return self.context_object_name
- elif hasattr(object_list, "model"):
- return "%s_list" % object_list.model._meta.model_name
- else:
- return None
- def get_context_data(self, *, object_list=None, **kwargs):
- """Get the context for this view."""
- queryset = object_list if object_list is not None else self.object_list
- page_size = self.get_paginate_by(queryset)
- context_object_name = self.get_context_object_name(queryset)
- if page_size:
- paginator, page, queryset, is_paginated = self.paginate_queryset(
- queryset, page_size
- )
- context = {
- "paginator": paginator,
- "page_obj": page,
- "is_paginated": is_paginated,
- "object_list": queryset,
- }
- else:
- context = {
- "paginator": None,
- "page_obj": None,
- "is_paginated": False,
- "object_list": queryset,
- }
- if context_object_name is not None:
- context[context_object_name] = queryset
- context.update(kwargs)
- return super().get_context_data(**context)
- class BaseListView(MultipleObjectMixin, View):
- """A base view for displaying a list of objects."""
- def get(self, request, *args, **kwargs):
- self.object_list = self.get_queryset()
- allow_empty = self.get_allow_empty()
- if not allow_empty:
- # When pagination is enabled and object_list is a queryset,
- # it's better to do a cheap query than to load the unpaginated
- # queryset in memory.
- if self.get_paginate_by(self.object_list) is not None and hasattr(
- self.object_list, "exists"
- ):
- is_empty = not self.object_list.exists()
- else:
- is_empty = not self.object_list
- if is_empty:
- raise Http404(
- _("Empty list and “%(class_name)s.allow_empty” is False.")
- % {
- "class_name": self.__class__.__name__,
- }
- )
- context = self.get_context_data()
- return self.render_to_response(context)
- class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
- """Mixin for responding with a template and list of objects."""
- template_name_suffix = "_list"
- def get_template_names(self):
- """
- Return a list of template names to be used for the request. Must return
- a list. May not be called if render_to_response is overridden.
- """
- try:
- names = super().get_template_names()
- except ImproperlyConfigured:
- # If template_name isn't specified, it's not a problem --
- # we just start with an empty list.
- names = []
- # If the list is a queryset, we'll invent a template name based on the
- # app and model name. This name gets put at the end of the template
- # name list so that user-supplied names override the automatically-
- # generated ones.
- if hasattr(self.object_list, "model"):
- opts = self.object_list.model._meta
- names.append(
- "%s/%s%s.html"
- % (opts.app_label, opts.model_name, self.template_name_suffix)
- )
- elif not names:
- raise ImproperlyConfigured(
- "%(cls)s requires either a 'template_name' attribute "
- "or a get_queryset() method that returns a QuerySet."
- % {
- "cls": self.__class__.__name__,
- }
- )
- return names
- class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
- """
- Render some list of objects, set by `self.model` or `self.queryset`.
- `self.queryset` can actually be any iterable of items, not just a queryset.
- """
|