| from django.core.paginator import Paginator, InvalidPage |
| from django.core.exceptions import ImproperlyConfigured |
| from django.http import Http404 |
| from django.utils.encoding import smart_str |
| from django.views.generic.base import TemplateResponseMixin, View |
| |
| class MultipleObjectMixin(object): |
| allow_empty = True |
| queryset = None |
| model = None |
| paginate_by = None |
| context_object_name = None |
| paginator_class = Paginator |
| |
| def get_queryset(self): |
| """ |
| Get the list of items for this view. This must be an interable, and may |
| be a queryset (in which qs-specific behavior will be enabled). |
| """ |
| if self.queryset is not None: |
| queryset = self.queryset |
| if hasattr(queryset, '_clone'): |
| queryset = queryset._clone() |
| elif self.model is not None: |
| queryset = self.model._default_manager.all() |
| else: |
| raise ImproperlyConfigured(u"'%s' must define 'queryset' or 'model'" |
| % self.__class__.__name__) |
| return queryset |
| |
| def paginate_queryset(self, queryset, page_size): |
| """ |
| Paginate the queryset, if needed. |
| """ |
| paginator = self.get_paginator(queryset, page_size, allow_empty_first_page=self.get_allow_empty()) |
| if paginator.num_pages > 1: |
| page = self.kwargs.get('page') or self.request.GET.get('page') 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, True) |
| except InvalidPage: |
| raise Http404(u'Invalid page (%s)' % page_number) |
| else: |
| return (None, None, queryset, False) |
| |
| 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): |
| """ |
| 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) |
| |
| def get_allow_empty(self): |
| """ |
| Returns ``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 smart_str(object_list.model._meta.verbose_name_plural) |
| else: |
| return None |
| |
| def get_context_data(self, **kwargs): |
| """ |
| Get the context for this view. |
| """ |
| queryset = kwargs.pop('object_list') |
| page_size = self.get_paginate_by(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 |
| } |
| context.update(kwargs) |
| context_object_name = self.get_context_object_name(queryset) |
| if context_object_name is not None: |
| context[context_object_name] = queryset |
| return context |
| |
| |
| class BaseListView(MultipleObjectMixin, View): |
| def get(self, request, *args, **kwargs): |
| self.object_list = self.get_queryset() |
| allow_empty = self.get_allow_empty() |
| if not allow_empty and len(self.object_list) == 0: |
| raise Http404(u"Empty list and '%s.allow_empty' is False." |
| % self.__class__.__name__) |
| context = self.get_context_data(object_list=self.object_list) |
| return self.render_to_response(context) |
| |
| class MultipleObjectTemplateResponseMixin(TemplateResponseMixin): |
| 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 get_template is overridden. |
| """ |
| names = super(MultipleObjectTemplateResponseMixin, self).get_template_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.object_name.lower(), self.template_name_suffix)) |
| |
| 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. |
| """ |