Refactor common generic views
Add keyword arguments. Sort arguments. Unify the ObjectListPermissionFilterMixin and ObjectPermissionCheckMixin into the RestrictedQuerysetMixin. Add MultipleObjectDownloadView. Update SingleObjectDownloadView to do queryset filtering. The method that returns the base queryset for views is now named get_source_queryset(). The views now use .get_object_list as a multi object homologous of get_object. The queryset returned by .get_object_list is restricted by access. Make MultipleObjectMixin a subclass of Django's SingleObjectMixin to reduce repeated code. All generic views are now imported from common.generics and not from common.views. Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
@@ -27,29 +27,29 @@ from .icons import (
|
||||
icon_sort_up
|
||||
)
|
||||
from .literals import (
|
||||
TEXT_CHOICE_ITEMS, TEXT_CHOICE_LIST, TEXT_LIST_AS_ITEMS_VARIABLE_NAME,
|
||||
TEXT_LIST_AS_ITEMS_PARAMETER, TEXT_SORT_FIELD_PARAMETER,
|
||||
TEXT_SORT_FIELD_VARIABLE_NAME, TEXT_SORT_ORDER_CHOICE_ASCENDING,
|
||||
TEXT_SORT_ORDER_PARAMETER, TEXT_SORT_ORDER_VARIABLE_NAME
|
||||
TEXT_SORT_FIELD_PARAMETER, TEXT_SORT_FIELD_VARIABLE_NAME,
|
||||
TEXT_SORT_ORDER_CHOICE_ASCENDING, TEXT_SORT_ORDER_PARAMETER,
|
||||
TEXT_SORT_ORDER_VARIABLE_NAME
|
||||
)
|
||||
from .mixins import (
|
||||
DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin,
|
||||
FormExtraKwargsMixin, ListModeMixin, MultipleObjectMixin,
|
||||
ObjectActionMixin, ObjectListPermissionFilterMixin, ObjectNameMixin,
|
||||
ObjectPermissionCheckMixin, RedirectionMixin, ViewPermissionCheckMixin
|
||||
ObjectActionMixin, ObjectNameMixin, RedirectionMixin,
|
||||
RestrictedQuerysetMixin, ViewPermissionCheckMixin
|
||||
)
|
||||
from .settings import setting_paginate_by
|
||||
|
||||
__all__ = (
|
||||
'AssignRemoveView', 'ConfirmView', 'FormView', 'MultiFormView',
|
||||
'MultipleObjectConfirmActionView', 'MultipleObjectFormActionView',
|
||||
'AssignRemoveView', 'ConfirmView', 'FormView',
|
||||
'MultiFormView', 'MultipleObjectConfirmActionView',
|
||||
'MultipleObjectFormActionView', 'MultipleObjectDownloadView',
|
||||
'SingleObjectCreateView', 'SingleObjectDeleteView',
|
||||
'SingleObjectDetailView', 'SingleObjectEditView', 'SingleObjectListView',
|
||||
'SimpleView'
|
||||
'SingleObjectDetailView', 'MultipleObjectDownloadView',
|
||||
'SingleObjectEditView', 'SingleObjectListView', 'SimpleView'
|
||||
)
|
||||
|
||||
|
||||
class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
|
||||
class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, RestrictedQuerysetMixin, TemplateView):
|
||||
decode_content_type = False
|
||||
left_list_help_text = _(
|
||||
'Select entries to be added. Hold Control to select multiple '
|
||||
@@ -73,7 +73,7 @@ class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermis
|
||||
def generate_choices(choices):
|
||||
results = []
|
||||
for choice in choices:
|
||||
ct = ContentType.objects.get_for_model(choice)
|
||||
ct = ContentType.objects.get_for_model(model=choice)
|
||||
label = force_text(choice)
|
||||
|
||||
results.append(('%s,%s' % (ct.model, choice.pk), '%s' % (label)))
|
||||
@@ -108,21 +108,21 @@ class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermis
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.unselected_list = ChoiceForm(
|
||||
prefix=self.LEFT_LIST_NAME, choices=self.left_list(),
|
||||
help_text=self.get_left_list_help_text()
|
||||
choices=self.left_list(), help_text=self.get_left_list_help_text(),
|
||||
prefix=self.LEFT_LIST_NAME
|
||||
)
|
||||
self.selected_list = ChoiceForm(
|
||||
prefix=self.RIGHT_LIST_NAME, choices=self.right_list(),
|
||||
choices=self.right_list(),
|
||||
disabled_choices=self.get_disabled_choices(),
|
||||
help_text=self.get_right_list_help_text()
|
||||
help_text=self.get_right_list_help_text(),
|
||||
prefix=self.RIGHT_LIST_NAME
|
||||
)
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
def process_form(self, prefix, items_function, action_function):
|
||||
if '%s-submit' % prefix in self.request.POST.keys():
|
||||
form = ChoiceForm(
|
||||
self.request.POST, prefix=prefix,
|
||||
choices=items_function()
|
||||
self.request.POST, choices=items_function(), prefix=prefix
|
||||
)
|
||||
|
||||
if form.is_valid():
|
||||
@@ -156,12 +156,12 @@ class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermis
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.process_form(
|
||||
prefix=self.LEFT_LIST_NAME, items_function=self.left_list,
|
||||
action_function=self.add
|
||||
action_function=self.add, items_function=self.left_list,
|
||||
prefix=self.LEFT_LIST_NAME
|
||||
)
|
||||
self.process_form(
|
||||
prefix=self.RIGHT_LIST_NAME, items_function=self.right_list,
|
||||
action_function=self.remove
|
||||
action_function=self.remove, items_function=self.right_list,
|
||||
prefix=self.RIGHT_LIST_NAME
|
||||
)
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
@@ -200,12 +200,12 @@ class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermis
|
||||
return data
|
||||
|
||||
|
||||
class ConfirmView(ObjectListPermissionFilterMixin, ObjectPermissionCheckMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, TemplateView):
|
||||
class ConfirmView(RestrictedQuerysetMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, TemplateView):
|
||||
template_name = 'appearance/generic_confirm.html'
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.view_action()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
||||
|
||||
|
||||
class FormView(ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, FormExtraKwargsMixin, DjangoFormView):
|
||||
@@ -216,6 +216,62 @@ class DynamicFormView(DynamicFormViewMixin, FormView):
|
||||
pass
|
||||
|
||||
|
||||
class DownloadViewBase(VirtualDownloadView):
|
||||
TextIteratorIO = TextIteratorIO
|
||||
VirtualFile = VirtualFile
|
||||
|
||||
|
||||
class MultipleObjectDownloadView(RestrictedQuerysetMixin, MultipleObjectMixin, DownloadViewBase):
|
||||
"""
|
||||
View that support receiving multiple objects via a pk_list query.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
result = super(MultipleObjectDownloadView, self).__init__(*args, **kwargs)
|
||||
|
||||
if self.__class__.mro()[0].get_queryset != MultipleObjectDownloadView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return super(MultipleObjectDownloadView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_source_queryset()
|
||||
return super(MultipleObjectDownloadView, self).get_queryset()
|
||||
|
||||
|
||||
class SingleObjectDownloadView(RestrictedQuerysetMixin, SingleObjectMixin, DownloadViewBase):
|
||||
"""
|
||||
View that provides a .get_object() method to download content from a
|
||||
single object.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
result = super(SingleObjectDownloadView, self).__init__(*args, **kwargs)
|
||||
|
||||
if self.__class__.mro()[0].get_queryset != SingleObjectDownloadView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return super(SingleObjectDownloadView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_source_queryset()
|
||||
return super(SingleObjectDownloadView, self).get_queryset()
|
||||
|
||||
|
||||
class MultiFormView(DjangoFormView):
|
||||
prefix = None
|
||||
prefixes = {}
|
||||
@@ -238,7 +294,7 @@ class MultiFormView(DjangoFormView):
|
||||
|
||||
self.all_forms_valid(forms)
|
||||
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
||||
|
||||
def forms_invalid(self, forms):
|
||||
return self.render_to_response(self.get_context_data(forms=forms))
|
||||
@@ -298,12 +354,12 @@ class MultiFormView(DjangoFormView):
|
||||
forms = self.get_forms(form_classes)
|
||||
|
||||
if all([form.is_valid() for form in forms.values()]):
|
||||
return self.forms_valid(forms)
|
||||
return self.forms_valid(forms=forms)
|
||||
else:
|
||||
return self.forms_invalid(forms)
|
||||
return self.forms_invalid(forms=forms)
|
||||
|
||||
|
||||
class MultipleObjectFormActionView(ObjectActionMixin, MultipleObjectMixin, FormExtraKwargsMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DjangoFormView):
|
||||
class MultipleObjectFormActionView(ObjectActionMixin, ViewPermissionCheckMixin, RestrictedQuerysetMixin, MultipleObjectMixin, FormExtraKwargsMixin, ExtraContextMixin, RedirectionMixin, DjangoFormView):
|
||||
"""
|
||||
This view will present a form and upon receiving a POST request will
|
||||
perform an action on an object or queryset
|
||||
@@ -316,7 +372,7 @@ class MultipleObjectFormActionView(ObjectActionMixin, MultipleObjectMixin, FormE
|
||||
if self.__class__.mro()[0].get_queryset != MultipleObjectFormActionView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_object_list method instead. ' % {
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
@@ -331,16 +387,36 @@ class MultipleObjectFormActionView(ObjectActionMixin, MultipleObjectMixin, FormE
|
||||
try:
|
||||
return super(MultipleObjectFormActionView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_object_list()
|
||||
self.queryset = self.get_source_queryset()
|
||||
return super(MultipleObjectFormActionView, self).get_queryset()
|
||||
|
||||
|
||||
class MultipleObjectConfirmActionView(ObjectActionMixin, MultipleObjectMixin, ObjectListPermissionFilterMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, TemplateView):
|
||||
class MultipleObjectConfirmActionView(ObjectActionMixin, ViewPermissionCheckMixin, RestrictedQuerysetMixin, MultipleObjectMixin, ExtraContextMixin, RedirectionMixin, TemplateView):
|
||||
template_name = 'appearance/generic_confirm.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
result = super(MultipleObjectConfirmActionView, self).__init__(*args, **kwargs)
|
||||
|
||||
if self.__class__.mro()[0].get_queryset != MultipleObjectConfirmActionView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return super(MultipleObjectConfirmActionView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_source_queryset()
|
||||
return super(MultipleObjectConfirmActionView, self).get_queryset()
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.view_action()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
||||
|
||||
|
||||
class SimpleView(ViewPermissionCheckMixin, ExtraContextMixin, TemplateView):
|
||||
@@ -377,7 +453,7 @@ class SingleObjectCreateView(ObjectNameMixin, ViewPermissionCheckMixin, ExtraCon
|
||||
}
|
||||
|
||||
messages.error(
|
||||
request=self.request, message=error_message
|
||||
message=error_message, request=self.request
|
||||
)
|
||||
return super(
|
||||
SingleObjectCreateView, self
|
||||
@@ -402,13 +478,13 @@ class SingleObjectCreateView(ObjectNameMixin, ViewPermissionCheckMixin, ExtraCon
|
||||
context = self.get_context_data()
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
message=_(
|
||||
'%(object)s created successfully.'
|
||||
) % {'object': self.get_object_name(context=context)}
|
||||
) % {'object': self.get_object_name(context=context)},
|
||||
request=self.request
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
||||
|
||||
def get_error_message_duplicate(self):
|
||||
return self.error_message_duplicate
|
||||
@@ -418,7 +494,7 @@ class SingleObjectDynamicFormCreateView(DynamicFormViewMixin, SingleObjectCreate
|
||||
pass
|
||||
|
||||
|
||||
class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DeleteView):
|
||||
class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissionCheckMixin, RestrictedQuerysetMixin, ExtraContextMixin, RedirectionMixin, DeleteView):
|
||||
template_name = 'appearance/generic_confirm.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -427,7 +503,7 @@ class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissi
|
||||
if self.__class__.mro()[0].get_queryset != SingleObjectDeleteView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_object_list method instead. ' % {
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
@@ -443,20 +519,19 @@ class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissi
|
||||
result = super(SingleObjectDeleteView, self).delete(request, *args, **kwargs)
|
||||
except Exception as exception:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('%(object)s not deleted, error: %(error)s.') % {
|
||||
message=_('%(object)s not deleted, error: %(error)s.') % {
|
||||
'object': object_name,
|
||||
'error': exception
|
||||
}
|
||||
}, request=self.request
|
||||
)
|
||||
|
||||
raise exception
|
||||
else:
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
message=_(
|
||||
'%(object)s deleted successfully.'
|
||||
) % {'object': object_name}
|
||||
) % {'object': object_name},
|
||||
request=self.request
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -470,11 +545,11 @@ class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissi
|
||||
try:
|
||||
return super(SingleObjectDeleteView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_object_list()
|
||||
self.queryset = self.get_source_queryset()
|
||||
return super(SingleObjectDeleteView, self).get_queryset()
|
||||
|
||||
|
||||
class SingleObjectDetailView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, FormExtraKwargsMixin, ExtraContextMixin, ModelFormMixin, DetailView):
|
||||
class SingleObjectDetailView(ViewPermissionCheckMixin, RestrictedQuerysetMixin, FormExtraKwargsMixin, ExtraContextMixin, ModelFormMixin, DetailView):
|
||||
template_name = 'appearance/generic_form.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -483,7 +558,7 @@ class SingleObjectDetailView(ViewPermissionCheckMixin, ObjectPermissionCheckMixi
|
||||
if self.__class__.mro()[0].get_queryset != SingleObjectDetailView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_object_list method instead. ' % {
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
@@ -499,36 +574,11 @@ class SingleObjectDetailView(ViewPermissionCheckMixin, ObjectPermissionCheckMixi
|
||||
try:
|
||||
return super(SingleObjectDetailView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_object_list()
|
||||
self.queryset = self.get_source_queryset()
|
||||
return super(SingleObjectDetailView, self).get_queryset()
|
||||
|
||||
|
||||
class SingleObjectDownloadView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, VirtualDownloadView, SingleObjectMixin):
|
||||
TextIteratorIO = TextIteratorIO
|
||||
VirtualFile = VirtualFile
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
result = super(SingleObjectDownloadView, self).__init__(*args, **kwargs)
|
||||
|
||||
if self.__class__.mro()[0].get_queryset != SingleObjectDownloadView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_object_list method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return super(SingleObjectDownloadView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_object_list()
|
||||
return super(SingleObjectDownloadView, self).get_queryset()
|
||||
|
||||
|
||||
class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, FormExtraKwargsMixin, RedirectionMixin, UpdateView):
|
||||
class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, RestrictedQuerysetMixin, ExtraContextMixin, FormExtraKwargsMixin, RedirectionMixin, UpdateView):
|
||||
template_name = 'appearance/generic_form.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
@@ -552,24 +602,22 @@ class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, ObjectPerm
|
||||
self.object.save(**save_extra_data)
|
||||
except Exception as exception:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('%(object)s not updated, error: %(error)s.') % {
|
||||
message=_('%(object)s not updated, error: %(error)s.') % {
|
||||
'object': object_name,
|
||||
'error': exception
|
||||
}
|
||||
}, request=self.request
|
||||
)
|
||||
return super(
|
||||
SingleObjectEditView, self
|
||||
).form_invalid(form=form)
|
||||
else:
|
||||
messages.success(
|
||||
self.request,
|
||||
_(
|
||||
message=_(
|
||||
'%(object)s updated successfully.'
|
||||
) % {'object': object_name}
|
||||
) % {'object': object_name}, request=self.request
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
obj = super(SingleObjectEditView, self).get_object(queryset=queryset)
|
||||
@@ -585,7 +633,7 @@ class SingleObjectDynamicFormEditView(DynamicFormViewMixin, SingleObjectEditView
|
||||
pass
|
||||
|
||||
|
||||
class SingleObjectListView(ListModeMixin, PaginationMixin, ViewPermissionCheckMixin, ObjectListPermissionFilterMixin, ExtraContextMixin, RedirectionMixin, ListView):
|
||||
class SingleObjectListView(ListModeMixin, PaginationMixin, ViewPermissionCheckMixin, RestrictedQuerysetMixin, ExtraContextMixin, RedirectionMixin, ListView):
|
||||
template_name = 'appearance/generic_list.html'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -594,7 +642,7 @@ class SingleObjectListView(ListModeMixin, PaginationMixin, ViewPermissionCheckMi
|
||||
if self.__class__.mro()[0].get_queryset != SingleObjectListView.get_queryset:
|
||||
raise ImproperlyConfigured(
|
||||
'%(cls)s is overloading the get_queryset method. Subclasses '
|
||||
'should implement the get_object_list method instead. ' % {
|
||||
'should implement the get_source_queryset method instead. ' % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
@@ -635,7 +683,7 @@ class SingleObjectListView(ListModeMixin, PaginationMixin, ViewPermissionCheckMi
|
||||
try:
|
||||
queryset = super(SingleObjectListView, self).get_queryset()
|
||||
except ImproperlyConfigured:
|
||||
self.queryset = self.get_object_list()
|
||||
self.queryset = self.get_source_queryset()
|
||||
queryset = super(SingleObjectListView, self).get_queryset()
|
||||
|
||||
self.field_name = self.get_sort_field()
|
||||
|
||||
@@ -4,11 +4,11 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, resolve_url
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
from mayan.apps.acls.models import AccessControlList
|
||||
from mayan.apps.permissions import Permission
|
||||
@@ -23,9 +23,8 @@ from .literals import (
|
||||
__all__ = (
|
||||
'DeleteExtraDataMixin', 'DynamicFormViewMixin', 'ExtraContextMixin',
|
||||
'FormExtraKwargsMixin', 'ListModeMixin', 'MultipleObjectMixin',
|
||||
'ObjectActionMixin', 'ObjectListPermissionFilterMixin', 'ObjectNameMixin',
|
||||
'ObjectPermissionCheckMixin', 'RedirectionMixin',
|
||||
'ViewPermissionCheckMixin'
|
||||
'ObjectActionMixin', 'ObjectNameMixin', 'RedirectionMixin',
|
||||
'RestrictedQuerysetMixin', 'ViewPermissionCheckMixin'
|
||||
)
|
||||
|
||||
|
||||
@@ -204,39 +203,48 @@ class MultipleInstanceActionMixin(object):
|
||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
||||
|
||||
|
||||
class MultipleObjectMixin(object):
|
||||
class MultipleObjectMixin(SingleObjectMixin):
|
||||
"""
|
||||
Mixin that allows a view to work on a single or multiple objects. It can
|
||||
receive a pk, a slug or a list of IDs via an id_list query.
|
||||
The pk, slug, and ID list parameter name can be changed using the
|
||||
attributes: pk_url_kwargs, slug_url_kwarg, and pk_list_key.
|
||||
"""
|
||||
model = None
|
||||
object_permission = None
|
||||
pk_list_key = 'id_list'
|
||||
pk_list_separator = PK_LIST_SEPARATOR
|
||||
pk_url_kwarg = 'pk'
|
||||
queryset = None
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
def get_pk_list(self):
|
||||
result = self.request.GET.get(
|
||||
self.pk_list_key, self.request.POST.get(self.pk_list_key)
|
||||
)
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Override BaseDetailView.get()
|
||||
"""
|
||||
return super(SingleObjectMixin, self).get(request, *args, **kwargs)
|
||||
|
||||
if result:
|
||||
return result.split(self.pk_list_separator)
|
||||
else:
|
||||
return None
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Override SingleObjectMixin.get_context_data()
|
||||
"""
|
||||
return super(SingleObjectMixin, self).get_context_data(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
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()
|
||||
def get_object(self):
|
||||
"""
|
||||
Remove this method from the subclass
|
||||
"""
|
||||
raise AttributeError
|
||||
|
||||
def get_object_list(self, queryset=None):
|
||||
"""
|
||||
Returns the list of objects the view is displaying.
|
||||
|
||||
By default this requires `self.queryset` and a `pk`, `slug` ro
|
||||
`pk_list' argument in the URLconf, but subclasses can override this
|
||||
to return any object.
|
||||
"""
|
||||
# Use a custom queryset if provided; this is required for subclasses
|
||||
# like DateDetailView
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Next, try looking up by primary key.
|
||||
pk = self.kwargs.get(self.pk_url_kwarg)
|
||||
slug = self.kwargs.get(self.slug_url_kwarg)
|
||||
pk_list = self.get_pk_list()
|
||||
@@ -252,21 +260,37 @@ class MultipleObjectMixin(object):
|
||||
if pk_list is not None:
|
||||
queryset = queryset.filter(pk__in=self.get_pk_list())
|
||||
|
||||
# If none of those are defined, it's an error.
|
||||
if pk is None and slug is None and pk_list is None:
|
||||
raise AttributeError(
|
||||
'Generic detail view %s must be called with '
|
||||
'either an object pk, a slug or an id list.'
|
||||
'View %s must be called with '
|
||||
'either an object pk, a slug or an pk list.'
|
||||
% self.__class__.__name__
|
||||
)
|
||||
|
||||
if self.object_permission:
|
||||
return AccessControlList.objects.restrict_queryset(
|
||||
permission=self.object_permission, queryset=queryset,
|
||||
user=self.request.user
|
||||
try:
|
||||
# Get the single item from the filtered queryset
|
||||
queryset.get()
|
||||
except queryset.model.MultipleObjectsReturned:
|
||||
# Queryset has more than one item, this is good.
|
||||
return queryset
|
||||
except queryset.model.DoesNotExist:
|
||||
raise Http404(
|
||||
_('No %(verbose_name)s found matching the query') %
|
||||
{'verbose_name': queryset.model._meta.verbose_name}
|
||||
)
|
||||
else:
|
||||
# Queryset has one item, this is good.
|
||||
return queryset
|
||||
|
||||
def get_pk_list(self):
|
||||
result = self.request.GET.get(self.pk_list_key)
|
||||
|
||||
if result:
|
||||
return result.split(self.pk_list_separator)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class ObjectActionMixin(object):
|
||||
"""
|
||||
@@ -311,36 +335,6 @@ class ObjectActionMixin(object):
|
||||
)
|
||||
|
||||
|
||||
class ObjectListPermissionFilterMixin(object):
|
||||
"""
|
||||
access_object_retrieve_method is used to have the entire view check
|
||||
against an object permission and not the individual secondary items.
|
||||
"""
|
||||
access_object_retrieve_method = None
|
||||
object_permission = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.access_object_retrieve_method and self.object_permission:
|
||||
AccessControlList.objects.check_access(
|
||||
obj=getattr(self, self.access_object_retrieve_method)(),
|
||||
permissions=(self.object_permission,), user=request.user
|
||||
)
|
||||
return super(ObjectListPermissionFilterMixin, self).dispatch(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(ObjectListPermissionFilterMixin, self).get_queryset()
|
||||
|
||||
if not self.access_object_retrieve_method and self.object_permission:
|
||||
return AccessControlList.objects.restrict_queryset(
|
||||
permission=self.object_permission, queryset=queryset,
|
||||
user=self.request.user
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class ObjectNameMixin(object):
|
||||
def get_object_name(self, context=None):
|
||||
if not context:
|
||||
@@ -357,26 +351,6 @@ class ObjectNameMixin(object):
|
||||
return object_name
|
||||
|
||||
|
||||
class ObjectPermissionCheckMixin(object):
|
||||
"""
|
||||
Filter the queryset of the view by the `object_permission` provided.
|
||||
If no `object_permission` is provide the queryset will be returned
|
||||
as is.
|
||||
"""
|
||||
object_permission = None
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super(ObjectPermissionCheckMixin, self).get_queryset()
|
||||
|
||||
if self.object_permission:
|
||||
return AccessControlList.objects.restrict_queryset(
|
||||
permission=self.object_permission, queryset=queryset,
|
||||
user=self.request.user
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class RedirectionMixin(object):
|
||||
action_cancel_redirect = None
|
||||
post_action_redirect = None
|
||||
@@ -425,14 +399,56 @@ class RedirectionMixin(object):
|
||||
return self.next_url or self.previous_url
|
||||
|
||||
|
||||
class RestrictedQuerysetMixin(object):
|
||||
"""
|
||||
Restrict the view's queryset against a permission via ACL checking.
|
||||
Used to restrict the object list of a multiple object view or the source
|
||||
queryset of the .get_object() method.
|
||||
"""
|
||||
model = None
|
||||
object_permission = None
|
||||
source_queryset = None
|
||||
|
||||
def get_source_queryset(self):
|
||||
if self.source_queryset is None:
|
||||
if self.model:
|
||||
return self.model._default_manager.all()
|
||||
else:
|
||||
raise ImproperlyConfigured(
|
||||
"%(cls)s is missing a QuerySet. Define "
|
||||
"%(cls)s.model, %(cls)s.source_queryset, or override "
|
||||
"%(cls)s.get_source_queryset()." % {
|
||||
'cls': self.__class__.__name__
|
||||
}
|
||||
)
|
||||
|
||||
return self.source_queryset.all()
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.get_source_queryset()
|
||||
|
||||
if self.object_permission:
|
||||
return AccessControlList.objects.restrict_queryset(
|
||||
permission=self.object_permission, queryset=queryset,
|
||||
user=self.request.user
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class ViewPermissionCheckMixin(object):
|
||||
"""
|
||||
Restrict access to the view based on the user's direct permissions from
|
||||
roles. This mixing is used for views whose objects don't support ACLs or
|
||||
for views that perform actions that are not related to a specify object or
|
||||
object's permission like maintenance views.
|
||||
"""
|
||||
view_permission = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if self.view_permission:
|
||||
Permission.check_permissions(
|
||||
permissions=(self.view_permission,),
|
||||
requester=self.request.user
|
||||
Permission.check_user_permission(
|
||||
permission=self.view_permission, user=self.request.user
|
||||
)
|
||||
|
||||
return super(
|
||||
|
||||
@@ -18,7 +18,7 @@ from .storages import storage_sharedupload
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
#TODO: move outside of models.py
|
||||
# TODO: move outside of models.py or as a static method of SharedUploadedFile
|
||||
def upload_to(instance, filename):
|
||||
return 'shared-file-{}'.format(uuid.uuid4().hex)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class CommonViewTestCase(GenericViewTestCase):
|
||||
|
||||
def _create_error_log_entry(self):
|
||||
ModelPermission.register(
|
||||
model=get_user_model(), permissions=(permission_error_log_view,)
|
||||
model=get_user_model(), permission=permission_error_log_view
|
||||
)
|
||||
ErrorLogEntry.objects.register(model=get_user_model())
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ from django.utils.http import urlencode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import RedirectView, TemplateView
|
||||
|
||||
from mayan.apps.acls.models import AccessControlList
|
||||
from mayan.apps.common.mixins import (
|
||||
ContentTypeViewMixin, ExternalObjectMixin
|
||||
)
|
||||
@@ -24,12 +23,8 @@ from .forms import (
|
||||
LicenseForm, LocaleProfileForm, LocaleProfileForm_view,
|
||||
PackagesLicensesForm
|
||||
)
|
||||
from .generics import ( # NOQA
|
||||
AssignRemoveView, ConfirmView, FormView, MultiFormView,
|
||||
MultipleObjectConfirmActionView, MultipleObjectFormActionView, SimpleView,
|
||||
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectDetailView,
|
||||
SingleObjectDownloadView, SingleObjectDynamicFormCreateView,
|
||||
SingleObjectDynamicFormEditView, SingleObjectEditView, SingleObjectListView
|
||||
from .generics import (
|
||||
ConfirmView, SimpleView, SingleObjectEditView, SingleObjectListView
|
||||
)
|
||||
from .icons import icon_object_error_list, icon_setup
|
||||
from .menus import menu_setup, menu_tools
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.template import Context, Template
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from .icons import icon_fail as default_icon_fail
|
||||
|
||||
Reference in New Issue
Block a user