2010-10-18 21:34:47 +08:00
|
|
|
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
|
2010-12-05 12:32:36 +08:00
|
|
|
paginator_class = Paginator
|
2010-10-18 21:34:47 +08:00
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
2010-12-09 10:34:14 +08:00
|
|
|
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
|
2010-10-18 21:34:47 +08:00
|
|
|
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
|
|
|
|
|
2010-12-05 12:32:36 +08:00
|
|
|
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)
|
|
|
|
|
2010-10-18 21:34:47 +08:00
|
|
|
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.
|
|
|
|
"""
|
2010-10-28 10:58:28 +08:00
|
|
|
queryset = kwargs.pop('object_list')
|
2010-10-18 21:34:47 +08:00
|
|
|
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.
|
|
|
|
"""
|