# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import reversion
from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import InvalidPage, Paginator
from django.db.models.query import QuerySet
from django.http import Http404
from django.utils import six
from django.utils.translation import ugettext as _
from django.views.generic import DetailView, UpdateView
from reversion.models import Version
__all__ = (
'MultipleVersionObjectMixin',
'DetailVersionListView',
'UpdateVersionListView'
)
[docs]class MultipleVersionObjectMixin(object):
"""
A mixin for views manipulating multiple django-reversion Versions of object.
"""
version_allow_empty = True
version_queryset = None
version_model = Version
version_paginate_by = None
version_paginate_orphans = 0
version_context_object_name = None
version_paginator_class = Paginator
version_page_kwarg = 'versionpage'
version_ordering = '-revision__date_created'
version_object_list = None
[docs] def get_version_queryset(self):
"""
Return the list of version 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.version_queryset is not None:
queryset = self.version_queryset
if isinstance(queryset, QuerySet):
queryset = queryset.all()
elif self.version_model is not None:
queryset = reversion.get_for_object(self.get_object())
else:
raise ImproperlyConfigured(
"%(cls)s is missing a Version QuerySet. Define "
"%(cls)s.version_model, %(cls)s.version_queryset, or override "
"%(cls)s.get_version_queryset()." % {
'cls': self.__class__.__name__
}
)
ordering = self.get_version_ordering()
if ordering:
if isinstance(ordering, six.string_types):
ordering = (ordering,)
queryset = queryset.order_by(*ordering)
return queryset
[docs] def get_version_ordering(self):
"""
Return the field or fields to use for ordering the version queryset.
"""
return self.version_ordering
[docs] def paginate_version_queryset(self, queryset, page_size):
"""
Paginate the version queryset, if needed.
"""
paginator = self.get_version_paginator(
queryset, page_size, orphans=self.get_version_paginate_orphans(),
allow_empty_first_page=self.get_version_allow_empty())
page_kwarg = self.version_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)
})
[docs] def get_version_paginate_by(self, queryset):
"""
Get the number of version items to paginate by, or ``None`` for no pagination.
"""
return self.version_paginate_by
[docs] def get_version_paginator(self, queryset, per_page, orphans=0,
allow_empty_first_page=True, **kwargs):
"""
Return an instance of the version paginator for this view.
"""
return self.version_paginator_class(
queryset, per_page, orphans=orphans,
allow_empty_first_page=allow_empty_first_page, **kwargs)
[docs] def get_version_paginate_orphans(self):
"""
Returns the maximum number of orphans extend the last page by when
paginating.
"""
return self.version_paginate_orphans
[docs] def get_version_allow_empty(self):
"""
Returns ``True`` if the view should display empty version lists, and ``False``
if a 404 should be raised instead.
"""
return self.version_allow_empty
[docs] def get_version_context_object_name(self):
"""
Get the name of the version item to be used in the context.
"""
if self.version_context_object_name:
return self.version_context_object_name
elif hasattr(self, 'model'):
return '%s_versions_list' % self.model._meta.model_name
else:
return None
[docs] def get_context_data(self, **kwargs):
"""
Get the context for this view.
"""
version_queryset = kwargs.pop('version_object_list', self.version_object_list)
version_page_size = self.get_version_paginate_by(version_queryset)
version_context_object_name = self.get_version_context_object_name()
if version_page_size:
version_paginator, version_page, version_queryset, version_is_paginated = self.paginate_version_queryset(
version_queryset, version_page_size)
context = {
'version_paginator': version_paginator,
'version_page_obj': version_page,
'version_is_paginated': version_is_paginated,
'object_versions_list': version_queryset
}
else:
context = {
'version_paginator': None,
'version_page_obj': None,
'version_is_paginated': False,
'object_versions_list': version_queryset
}
if version_context_object_name is not None:
context[version_context_object_name] = version_queryset
context.update(kwargs)
return super(MultipleVersionObjectMixin, self).get_context_data(**context)
[docs] def get(self, request, *args, **kwargs):
self.version_object_list = self.get_version_queryset()
version_allow_empty = self.get_version_allow_empty()
if not version_allow_empty:
# When pagination is enabled and object_versions_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if (self.get_version_paginate_by(self.version_object_list) is not None and hasattr(self.version_object_list, 'exists')): # noqa
is_empty = not self.version_object_list.exists()
else:
is_empty = len(self.version_object_list) == 0
if is_empty:
raise Http404(
_("Empty Version list and '%(class_name)s.version_allow_empty' is False.")
% {'class_name': self.__class__.__name__})
return super(MultipleVersionObjectMixin, self).get(self, request, *args, **kwargs)
[docs]class DetailVersionListView(MultipleVersionObjectMixin, DetailView):
"""
Render some list of django-reversion Versions of object, set by `self.model` or
`self.queryset`. `self.queryset` can actually be any iterable of items, not just a queryset.
"""
template_name_suffix = '_version_list'
[docs]class UpdateVersionListView(MultipleVersionObjectMixin, UpdateView):
"""
Render some list of versions of object, set by `self.model` or `self.queryset`.
`self.queryset` can actually be any iterable of items, not just a queryset.
"""
template_name_suffix = '_form_version_list'