Files
mayan-edms/mayan/apps/common/generics.py
Roberto Rosario 6832450221 Generic forms: Don't raise, display exceptions
Update the generic single object create and edit views to display
critical exceptions instead of just raising them and stoping
execution. The should allow removing duplicated exception to message
code in subclasses of these views.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
2018-12-09 14:59:59 -04:00

510 lines
18 KiB
Python

from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView
from django.views.generic import FormView as DjangoFormView
from django.views.generic import TemplateView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import (
CreateView, DeleteView, FormMixin, ModelFormMixin, UpdateView
)
from django.views.generic.list import ListView
from django_downloadview import (
TextIteratorIO, VirtualDownloadView, VirtualFile
)
from pure_pagination.mixins import PaginationMixin
from .forms import ChoiceForm
from .icons import icon_assign_remove_add, icon_assign_remove_remove
from .mixins import (
DeleteExtraDataMixin, DynamicFormViewMixin, ExtraContextMixin,
FormExtraKwargsMixin, MultipleObjectMixin, ObjectActionMixin,
ObjectListPermissionFilterMixin, ObjectNameMixin,
ObjectPermissionCheckMixin, RedirectionMixin, ViewPermissionCheckMixin
)
from .settings import setting_paginate_by
__all__ = (
'AssignRemoveView', 'ConfirmView', 'FormView', 'MultiFormView',
'MultipleObjectConfirmActionView', 'MultipleObjectFormActionView',
'SingleObjectCreateView', 'SingleObjectDeleteView',
'SingleObjectDetailView', 'SingleObjectEditView', 'SingleObjectListView',
'SimpleView'
)
class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
decode_content_type = False
right_list_help_text = None
left_list_help_text = None
grouped = False
left_list_title = None
right_list_title = None
template_name = 'appearance/generic_form.html'
LEFT_LIST_NAME = 'left_list'
RIGHT_LIST_NAME = 'right_list'
@staticmethod
def generate_choices(choices):
results = []
for choice in choices:
ct = ContentType.objects.get_for_model(choice)
label = force_text(choice)
results.append(('%s,%s' % (ct.model, choice.pk), '%s' % (label)))
# Sort results by the label not the key value
return sorted(results, key=lambda x: x[1])
def left_list(self):
# Subclass must override
raise NotImplementedError
def right_list(self):
# Subclass must override
raise NotImplementedError
def add(self, item):
# Subclass must override
raise NotImplementedError
def remove(self, item):
# Subclass must override
raise NotImplementedError
def get_disabled_choices(self):
return ()
def get_left_list_help_text(self):
return self.left_list_help_text
def get_right_list_help_text(self):
return self.right_list_help_text
def get(self, request, *args, **kwargs):
self.unselected_list = ChoiceForm(
prefix=self.LEFT_LIST_NAME, choices=self.left_list()
)
self.selected_list = ChoiceForm(
prefix=self.RIGHT_LIST_NAME, choices=self.right_list(),
disabled_choices=self.get_disabled_choices(),
help_text=self.get_right_list_help_text()
)
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()
)
if form.is_valid():
for selection in form.cleaned_data['selection']:
if self.grouped:
flat_list = []
for group in items_function():
flat_list.extend(group[1])
else:
flat_list = items_function()
label = dict(flat_list)[selection]
if self.decode_content_type:
model, pk = selection.split(',')
selection_obj = ContentType.objects.get(
model=model
).get_object_for_this_type(pk=pk)
else:
selection_obj = selection
try:
action_function(selection_obj)
except Exception:
if settings.DEBUG:
raise
else:
messages.error(
self.request,
_('Unable to transfer selection: %s.') % label
)
def post(self, request, *args, **kwargs):
self.process_form(
prefix=self.LEFT_LIST_NAME, items_function=self.left_list,
action_function=self.add
)
self.process_form(
prefix=self.RIGHT_LIST_NAME, items_function=self.right_list,
action_function=self.remove
)
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
data = super(AssignRemoveView, self).get_context_data(**kwargs)
data.update({
'subtemplates_list': [
{
'name': 'appearance/generic_form_subtemplate.html',
'column_class': 'col-xs-12 col-sm-6 col-md-6 col-lg-6',
'context': {
'form': self.unselected_list,
'title': self.left_list_title or ' ',
'submit_label': _('Add'),
'submit_icon_class': icon_assign_remove_add,
'hide_labels': True,
}
},
{
'name': 'appearance/generic_form_subtemplate.html',
'column_class': 'col-xs-12 col-sm-6 col-md-6 col-lg-6',
'context': {
'form': self.selected_list,
'title': self.right_list_title or ' ',
'submit_label': _('Remove'),
'submit_icon_class': icon_assign_remove_remove,
'hide_labels': True,
}
},
],
})
return data
class ConfirmView(ObjectListPermissionFilterMixin, ObjectPermissionCheckMixin, 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())
class FormView(ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, FormExtraKwargsMixin, DjangoFormView):
template_name = 'appearance/generic_form.html'
class DynamicFormView(DynamicFormViewMixin, FormView):
pass
class MultiFormView(DjangoFormView):
prefix = None
prefixes = {}
def _create_form(self, form_name, klass):
form_kwargs = self.get_form_kwargs(form_name)
form_create_method = 'create_%s_form' % form_name
if hasattr(self, form_create_method):
form = getattr(self, form_create_method)(**form_kwargs)
else:
form = klass(**form_kwargs)
return form
def forms_valid(self, forms):
for form_name, form in forms.items():
form_valid_method = '%s_form_valid' % form_name
if hasattr(self, form_valid_method):
return getattr(self, form_valid_method)(form)
self.all_forms_valid(forms)
return HttpResponseRedirect(self.get_success_url())
def forms_invalid(self, forms):
return self.render_to_response(self.get_context_data(forms=forms))
def get(self, request, *args, **kwargs):
form_classes = self.get_form_classes()
forms = self.get_forms(form_classes)
return self.render_to_response(self.get_context_data(forms=forms))
def get_context_data(self, **kwargs):
"""
Insert the form into the context dict.
"""
if 'forms' not in kwargs:
kwargs['forms'] = self.get_forms(
form_classes=self.get_form_classes()
)
return super(FormMixin, self).get_context_data(**kwargs)
def get_form_classes(self):
return self.form_classes
def get_form_kwargs(self, form_name):
kwargs = {}
kwargs.update({'initial': self.get_initial(form_name)})
kwargs.update({'prefix': self.get_prefix(form_name)})
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_forms(self, form_classes):
return dict(
[
(
key, self._create_form(key, klass)
) for key, klass in form_classes.items()
]
)
def get_initial(self, form_name):
initial_method = 'get_%s_initial' % form_name
if hasattr(self, initial_method):
return getattr(self, initial_method)()
else:
return self.initial.copy()
def get_prefix(self, form_name):
return self.prefixes.get(form_name, self.prefix)
def post(self, request, *args, **kwargs):
form_classes = self.get_form_classes()
forms = self.get_forms(form_classes)
if all([form.is_valid() for form in forms.values()]):
return self.forms_valid(forms)
else:
return self.forms_invalid(forms)
class MultipleObjectFormActionView(ObjectActionMixin, MultipleObjectMixin, FormExtraKwargsMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DjangoFormView):
"""
This view will present a form and upon receiving a POST request will
perform an action on an object or queryset
"""
template_name = 'appearance/generic_form.html'
def __init__(self, *args, **kwargs):
result = super(MultipleObjectFormActionView, self).__init__(*args, **kwargs)
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. ' % {
'cls': self.__class__.__name__
}
)
return result
def form_valid(self, form):
self.view_action(form=form)
return super(MultipleObjectFormActionView, self).form_valid(form=form)
def get_queryset(self):
try:
return super(MultipleObjectFormActionView, self).get_queryset()
except ImproperlyConfigured:
self.queryset = self.get_object_list()
return super(MultipleObjectFormActionView, self).get_queryset()
class MultipleObjectConfirmActionView(ObjectActionMixin, MultipleObjectMixin, ObjectListPermissionFilterMixin, 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())
class SimpleView(ViewPermissionCheckMixin, ExtraContextMixin, TemplateView):
pass
class SingleObjectCreateView(ObjectNameMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, FormExtraKwargsMixin, CreateView):
template_name = 'appearance/generic_form.html'
def form_valid(self, form):
# This overrides the original Django form_valid method
self.object = form.save(commit=False)
if hasattr(self, 'get_instance_extra_data'):
for key, value in self.get_instance_extra_data().items():
setattr(self.object, key, value)
if hasattr(self, 'get_save_extra_data'):
save_extra_data = self.get_save_extra_data()
else:
save_extra_data = {}
try:
self.object.save(**save_extra_data)
except Exception as exception:
context = self.get_context_data()
messages.error(
self.request,
_('%(object)s not created, error: %(error)s') % {
'object': self.get_object_name(context=context),
'error': exception
}
)
return super(
SingleObjectCreateView, self
).form_invalid(form=form)
else:
context = self.get_context_data()
messages.success(
self.request,
_(
'%(object)s created successfully.'
) % {'object': self.get_object_name(context=context)}
)
return HttpResponseRedirect(self.get_success_url())
class SingleObjectDynamicFormCreateView(DynamicFormViewMixin, SingleObjectCreateView):
pass
class SingleObjectDeleteView(ObjectNameMixin, DeleteExtraDataMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, DeleteView):
template_name = 'appearance/generic_confirm.html'
def get_context_data(self, **kwargs):
context = super(SingleObjectDeleteView, self).get_context_data(**kwargs)
context.update({'delete_view': True})
return context
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data()
object_name = self.get_object_name(context=context)
try:
result = super(SingleObjectDeleteView, self).delete(request, *args, **kwargs)
except Exception as exception:
messages.error(
self.request,
_('%(object)s not deleted, error: %(error)s.') % {
'object': object_name,
'error': exception
}
)
raise exception
else:
messages.success(
self.request,
_(
'%(object)s deleted successfully.'
) % {'object': object_name}
)
return result
class SingleObjectDetailView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, FormExtraKwargsMixin, ExtraContextMixin, ModelFormMixin, DetailView):
template_name = 'appearance/generic_form.html'
def get_context_data(self, **kwargs):
context = super(SingleObjectDetailView, self).get_context_data(**kwargs)
context.update({'read_only': True, 'form': self.get_form()})
return context
class SingleObjectDownloadView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, VirtualDownloadView, SingleObjectMixin):
TextIteratorIO = TextIteratorIO
VirtualFile = VirtualFile
class SingleObjectEditView(ObjectNameMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, FormExtraKwargsMixin, RedirectionMixin, UpdateView):
template_name = 'appearance/generic_form.html'
def form_valid(self, form):
# This overrides the original Django form_valid method
self.object = form.save(commit=False)
if hasattr(self, 'get_instance_extra_data'):
for key, value in self.get_instance_extra_data().items():
setattr(self.object, key, value)
if hasattr(self, 'get_save_extra_data'):
save_extra_data = self.get_save_extra_data()
else:
save_extra_data = {}
context = self.get_context_data()
object_name = self.get_object_name(context=context)
try:
self.object.save(**save_extra_data)
except Exception as exception:
messages.error(
self.request,
_('%(object)s not updated, error: %(error)s.') % {
'object': object_name,
'error': exception
}
)
return super(
SingleObjectEditView, self
).form_invalid(form=form)
else:
messages.success(
self.request,
_(
'%(object)s updated successfully.'
) % {'object': object_name}
)
return HttpResponseRedirect(self.get_success_url())
def get_object(self, queryset=None):
obj = super(SingleObjectEditView, self).get_object(queryset=queryset)
if hasattr(self, 'get_instance_extra_data'):
for key, value in self.get_instance_extra_data().items():
setattr(obj, key, value)
return obj
class SingleObjectDynamicFormEditView(DynamicFormViewMixin, SingleObjectEditView):
pass
class SingleObjectListView(PaginationMixin, ViewPermissionCheckMixin, ObjectListPermissionFilterMixin, ExtraContextMixin, RedirectionMixin, ListView):
template_name = 'appearance/generic_list.html'
def __init__(self, *args, **kwargs):
result = super(SingleObjectListView, self).__init__(*args, **kwargs)
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. ' % {
'cls': self.__class__.__name__
}
)
return result
def get_paginate_by(self, queryset):
return setting_paginate_by.value
def get_queryset(self):
try:
return super(SingleObjectListView, self).get_queryset()
except ImproperlyConfigured:
self.queryset = self.get_object_list()
return super(SingleObjectListView, self).get_queryset()