Update documents app

Rename the DeletedDocument proxy model to a TrashedDocument.

Rename the deleted_document views to trashed_document.

Rename the document and deleted_document URL parameters to
trashed_document.

Update URL parameters to the '_id' form.

Add keyword arguments.

Update use of .filter_by_access().

Enclose trashed document restore method in a transaction.

Sort arguments.

Update app for compliance with MERCs 5 and 6.

Add document page view tests.

Add favorite document view tests.

Movernize tests.

Replace use of urlencode with furl.

Update views to use ExternalObjectMixin.

Refactor the document and version download views.

Rename the DocumentDocumentTypeEditView to DocumentChangeTypeView.

Move the trashed document views to their own module.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-01-28 05:25:48 -04:00
parent 7532429b0b
commit 74dfa53787
40 changed files with 1914 additions and 1615 deletions
+1
View File
@@ -3,3 +3,4 @@ from .document_type_views import * # NOQA
from .document_version_views import * # NOQA
from .document_views import * # NOQA
from .misc_views import * # NOQA
from .trashed_document_views import * # NOQA
@@ -6,15 +6,14 @@ from furl import furl
from django.conf import settings
from django.contrib import messages
from django.shortcuts import get_object_or_404, resolve_url
from django.shortcuts import resolve_url
from django.urls import reverse
from django.utils.encoding import force_text
from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _
from django.views.generic import RedirectView
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import SimpleView, SingleObjectListView
from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.common.utils import resolve
from mayan.apps.converter.literals import DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
@@ -37,53 +36,40 @@ __all__ = (
logger = logging.getLogger(__name__)
class DocumentPageInteractiveTransformation(RedirectView):
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
AccessControlList.objects.check_access(
obj=obj, permissions=permission_document_view, user=request.user,
)
return super(DocumentPageInteractiveTransformation, self).dispatch(
request, *args, **kwargs
)
class DocumentPageInteractiveTransformation(ExternalObjectMixin, RedirectView):
external_object_class = DocumentPage
external_object_permission = permission_document_view
external_object_pk_url_kwarg = 'document_page_id'
def get_object(self):
return get_object_or_404(
klass=DocumentPage, pk=self.kwargs['document_page_pk']
)
return self.get_external_object()
def get_redirect_url(self, *args, **kwargs):
url = reverse(
viewname='documents:document_page_view',
kwargs={'document_page_pk': self.kwargs['document_page_pk']}
)
query_dict = {
'rotation': int(
self.request.GET.get('rotation', DEFAULT_ROTATION)
), 'zoom': int(self.request.GET.get('zoom', DEFAULT_ZOOM_LEVEL))
'rotation': self.request.GET.get('rotation', DEFAULT_ROTATION),
'zoom': self.request.GET.get('zoom', DEFAULT_ZOOM_LEVEL)
}
self.transformation_function(query_dict)
url = furl(
args=query_dict, path=reverse(
viewname='documents:document_page_view',
kwargs={'document_page_id': self.kwargs['document_page_id']}
)
return '{}?{}'.format(url, urlencode(query_dict))
class DocumentPageListView(SingleObjectListView):
def dispatch(self, request, *args, **kwargs):
AccessControlList.objects.check_access(
obj=self.get_document(), permissions=permission_document_view,
user=self.request.user,
)
return super(
DocumentPageListView, self
).dispatch(request, *args, **kwargs)
self.transformation_function(query_dict=query_dict)
return url.tostr()
class DocumentPageListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
external_object_permission = permission_document_view
external_object_pk_url_kwarg = 'document_id'
def get_document(self):
return get_object_or_404(klass=Document, pk=self.kwargs['document_pk'])
return self.get_external_object()
def get_extra_context(self):
return {
@@ -95,27 +81,17 @@ class DocumentPageListView(SingleObjectListView):
'title': _('Pages for document: %s') % self.get_document(),
}
def get_object_list(self):
def get_source_queryset(self):
return self.get_document().pages.all()
class DocumentPageNavigationBase(RedirectView):
def dispatch(self, request, *args, **kwargs):
document_page = self.get_object()
AccessControlList.objects.check_access(
permissions=permission_document_view, user=request.user,
obj=document_page.document
)
return super(DocumentPageNavigationBase, self).dispatch(
request, *args, **kwargs
)
class DocumentPageNavigationBase(ExternalObjectMixin, RedirectView):
external_object_class = DocumentPage
external_object_permission = permission_document_view
external_object_pk_url_kwarg = 'document_page_id'
def get_object(self):
return get_object_or_404(
klass=DocumentPage, pk=self.kwargs['document_page_pk']
)
return self.get_external_object()
def get_redirect_url(self, *args, **kwargs):
"""
@@ -158,14 +134,14 @@ class DocumentPageNavigationFirst(DocumentPageNavigationBase):
def get_new_kwargs(self):
document_page = self.get_object()
return {'document_page_pk': document_page.siblings.first().pk}
return {'document_page_id': document_page.siblings.first().pk}
class DocumentPageNavigationLast(DocumentPageNavigationBase):
def get_new_kwargs(self):
document_page = self.get_object()
return {'document_page_pk': document_page.siblings.last().pk}
return {'document_page_id': document_page.siblings.last().pk}
class DocumentPageNavigationNext(DocumentPageNavigationBase):
@@ -178,12 +154,12 @@ class DocumentPageNavigationNext(DocumentPageNavigationBase):
)
except DocumentPage.DoesNotExist:
messages.warning(
request=self.request, message=_(
message=_(
'There are no more pages in this document'
)
), request=self.request
)
finally:
return {'document_page_pk': document_page.pk}
return {'document_page_id': document_page.pk}
class DocumentPageNavigationPrevious(DocumentPageNavigationBase):
@@ -196,47 +172,40 @@ class DocumentPageNavigationPrevious(DocumentPageNavigationBase):
)
except DocumentPage.DoesNotExist:
messages.warning(
request=self.request, message=_(
message=_(
'You are already at the first page of this document'
)
), request=self.request
)
finally:
return {'document_page_pk': document_page.pk}
return {'document_page_id': document_page.pk}
class DocumentPageRotateLeftView(DocumentPageInteractiveTransformation):
def transformation_function(self, query_dict):
query_dict['rotation'] = (
query_dict['rotation'] - setting_rotation_step.value
int(query_dict['rotation']) - setting_rotation_step.value
) % 360
class DocumentPageRotateRightView(DocumentPageInteractiveTransformation):
def transformation_function(self, query_dict):
query_dict['rotation'] = (
query_dict['rotation'] + setting_rotation_step.value
int(query_dict['rotation']) + setting_rotation_step.value
) % 360
class DocumentPageView(SimpleView):
class DocumentPageView(ExternalObjectMixin, SimpleView):
external_object_class = DocumentPage
external_object_permission = permission_document_view
external_object_pk_url_kwarg = 'document_page_id'
template_name = 'appearance/generic_form.html'
def dispatch(self, request, *args, **kwargs):
AccessControlList.objects.check_access(
permissions=permission_document_view, user=request.user,
obj=self.get_object().document
)
return super(
DocumentPageView, self
).dispatch(request, *args, **kwargs)
def get_extra_context(self):
zoom = int(self.request.GET.get('zoom', DEFAULT_ZOOM_LEVEL))
rotation = int(self.request.GET.get('rotation', DEFAULT_ROTATION))
document_page_form = DocumentPageForm(
instance=self.get_object(), zoom=zoom, rotation=rotation
instance=self.get_object(), rotation=rotation, zoom=zoom
)
base_title = _('Image of: %s') % self.get_object()
@@ -252,15 +221,13 @@ class DocumentPageView(SimpleView):
'navigation_object_list': ('page',),
'page': self.get_object(),
'rotation': rotation,
'title': ' '.join((base_title, zoom_text,)),
'title': ' '.join((base_title, zoom_text)),
'read_only': True,
'zoom': zoom,
}
def get_object(self):
return get_object_or_404(
klass=DocumentPage, pk=self.kwargs['document_page_pk']
)
return self.get_external_object()
class DocumentPageViewResetView(RedirectView):
@@ -269,7 +236,7 @@ class DocumentPageViewResetView(RedirectView):
class DocumentPageZoomInView(DocumentPageInteractiveTransformation):
def transformation_function(self, query_dict):
zoom = query_dict['zoom'] + setting_zoom_percent_step.value
zoom = int(query_dict['zoom']) + setting_zoom_percent_step.value
if zoom > setting_zoom_max_level.value:
zoom = setting_zoom_max_level.value
@@ -279,7 +246,7 @@ class DocumentPageZoomInView(DocumentPageInteractiveTransformation):
class DocumentPageZoomOutView(DocumentPageInteractiveTransformation):
def transformation_function(self, query_dict):
zoom = query_dict['zoom'] - setting_zoom_percent_step.value
zoom = int(query_dict['zoom']) - setting_zoom_percent_step.value
if zoom < setting_zoom_min_level.value:
zoom = setting_zoom_min_level.value
@@ -2,16 +2,15 @@ from __future__ import absolute_import, unicode_literals
import logging
from django.shortcuts import get_object_or_404
from django.template import RequestContext
from django.urls import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import (
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView,
SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin
from ..forms import DocumentTypeFilenameForm_create
from ..icons import icon_document_type_filename, icon_document_type_setup
@@ -60,7 +59,7 @@ class DocumentTypeDeleteView(SingleObjectDeleteView):
model = DocumentType
object_permission = permission_document_type_delete
post_action_redirect = reverse_lazy(viewname='documents:document_type_list')
pk_url_kwarg = 'document_type_pk'
pk_url_kwarg = 'document_type_id'
def get_extra_context(self):
return {
@@ -70,13 +69,17 @@ class DocumentTypeDeleteView(SingleObjectDeleteView):
}
class DocumentTypeDocumentListView(DocumentListView):
def get_document_type(self):
return get_object_or_404(klass=DocumentType, pk=self.kwargs['document_type_pk'])
class DocumentTypeDocumentListView(ExternalObjectMixin, DocumentListView):
external_object_class = DocumentType
external_object_permission = permission_document_type_view
external_object_pk_url_kwarg = 'document_type_id'
def get_document_queryset(self):
return self.get_document_type().documents.all()
def get_document_type(self):
return self.get_external_object()
def get_extra_context(self):
context = super(DocumentTypeDocumentListView, self).get_extra_context()
context.update(
@@ -95,7 +98,7 @@ class DocumentTypeEditView(SingleObjectEditView):
)
model = DocumentType
object_permission = permission_document_type_edit
pk_url_kwarg = 'document_type_pk'
pk_url_kwarg = 'document_type_id'
post_action_redirect = reverse_lazy(
viewname='documents:document_type_list'
)
@@ -136,21 +139,14 @@ class DocumentTypeListView(SingleObjectListView):
}
class DocumentTypeFilenameCreateView(SingleObjectCreateView):
class DocumentTypeFilenameCreateView(ExternalObjectMixin, SingleObjectCreateView):
external_object_class = DocumentType
external_object_permission = permission_document_type_edit
external_object_pk_url_kwarg = 'document_type_id'
form_class = DocumentTypeFilenameForm_create
def dispatch(self, request, *args, **kwargs):
AccessControlList.objects.check_access(
permissions=permission_document_type_edit, user=request.user,
obj=self.get_document_type()
)
return super(DocumentTypeFilenameCreateView, self).dispatch(
request, *args, **kwargs
)
def get_document_type(self):
return get_object_or_404(klass=DocumentType, pk=self.kwargs['document_type_pk'])
return self.get_external_object()
def get_extra_context(self):
return {
@@ -168,7 +164,7 @@ class DocumentTypeFilenameCreateView(SingleObjectCreateView):
class DocumentTypeFilenameDeleteView(SingleObjectDeleteView):
model = DocumentTypeFilename
object_permission = permission_document_type_edit
pk_url_kwarg = 'filename_pk'
pk_url_kwarg = 'filename_id'
def get_extra_context(self):
return {
@@ -187,7 +183,7 @@ class DocumentTypeFilenameDeleteView(SingleObjectDeleteView):
def get_post_action_redirect(self):
return reverse(
viewname='documents:document_type_filename_list',
kwargs={'document_type_pk': self.get_object().document_type.pk}
kwargs={'document_type_id': self.get_object().document_type.pk}
)
@@ -195,7 +191,7 @@ class DocumentTypeFilenameEditView(SingleObjectEditView):
fields = ('enabled', 'filename',)
model = DocumentTypeFilename
object_permission = permission_document_type_edit
pk_url_kwarg = 'filename_pk'
pk_url_kwarg = 'filename_id'
def get_extra_context(self):
document_type_filename = self.get_object()
@@ -216,16 +212,17 @@ class DocumentTypeFilenameEditView(SingleObjectEditView):
def get_post_action_redirect(self):
return reverse(
viewname='documents:document_type_filename_list',
kwargs={'document_type_pk': self.get_object().document_type.pk}
kwargs={'document_type_id': self.get_object().document_type.pk}
)
class DocumentTypeFilenameListView(SingleObjectListView):
access_object_retrieve_method = 'get_document_type'
object_permission = permission_document_type_view
class DocumentTypeFilenameListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = DocumentType
external_object_permission = permission_document_type_view
external_object_pk_url_kwarg = 'document_type_id'
def get_document_type(self):
return get_object_or_404(klass=DocumentType, pk=self.kwargs['document_type_pk'])
return self.get_external_object()
def get_extra_context(self):
return {
@@ -255,5 +252,5 @@ class DocumentTypeFilenameListView(SingleObjectListView):
) % self.get_document_type(),
}
def get_object_list(self):
def get_source_queryset(self):
return self.get_document_type().filenames.all()
@@ -3,15 +3,14 @@ from __future__ import absolute_import, unicode_literals
import logging
from django.contrib import messages
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import (
ConfirmView, SingleObjectDetailView, SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin
from ..events import event_document_view
from ..events import event_document_download, event_document_view
from ..forms import DocumentVersionDownloadForm, DocumentVersionPreviewForm
from ..models import Document, DocumentVersion
from ..permissions import (
@@ -21,79 +20,22 @@ from ..permissions import (
from .document_views import DocumentDownloadFormView, DocumentDownloadView
__all__ = (
'DocumentVersionDownloadFormView', 'DocumentVersionDownloadView',
'DocumentVersionListView', 'DocumentVersionRevertView',
'DocumentVersionView'
)
logger = logging.getLogger(__name__)
class DocumentVersionListView(SingleObjectListView):
def dispatch(self, request, *args, **kwargs):
AccessControlList.objects.check_access(
obj=self.get_document(),
permissions=permission_document_version_view, user=request.user
)
self.get_document().add_as_recent_document_for_user(user=request.user)
return super(
DocumentVersionListView, self
).dispatch(request, *args, **kwargs)
def get_document(self):
return get_object_or_404(klass=Document, pk=self.kwargs['document_pk'])
def get_extra_context(self):
return {
'hide_object': True,
'list_as_items': True,
'object': self.get_document(),
'table_cell_container_classes': 'td-container-thumbnail',
'title': _('Versions of document: %s') % self.get_document(),
}
def get_object_list(self):
return self.get_document().versions.order_by('-timestamp')
class DocumentVersionRevertView(ConfirmView):
object_permission = permission_document_version_revert
def get_extra_context(self):
return {
'message': _(
'All later version after this one will be deleted too.'
),
'object': self.get_object().document,
'title': _('Revert to this version?'),
}
def get_object(self):
return get_object_or_404(
klass=DocumentVersion, pk=self.kwargs['document_version_pk']
)
def view_action(self):
try:
self.get_object().revert(_user=self.request.user)
messages.success(
request=self.request, message=_(
'Document version reverted successfully'
)
)
except Exception as exception:
messages.error(
request=self.request,
message=_('Error reverting document version; %s') % exception
)
class DocumentVersionDownloadFormView(DocumentDownloadFormView):
form_class = DocumentVersionDownloadForm
model = DocumentVersion
multiple_download_view = None
pk_url_kwarg = 'document_version_pk'
pk_url_kwarg = 'document_version_id'
querystring_form_fields = (
'compressed', 'zip_filename', 'preserve_extension'
)
single_download_view = 'documents:document_version_download'
viewname = 'documents:document_version_download'
def get_extra_context(self):
result = super(
@@ -106,30 +48,26 @@ class DocumentVersionDownloadFormView(DocumentDownloadFormView):
return result
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
if not id_list:
id_list = self.kwargs['document_version_pk']
return self.model.objects.filter(
pk__in=id_list.split(',')
)
class DocumentVersionDownloadView(DocumentDownloadView):
model = DocumentVersion
object_permission = permission_document_download
pk_url_kwarg = 'document_version_pk'
pk_url_kwarg = 'document_version_id'
@staticmethod
def commit_event(item, request):
# TODO: Improve by adding a document version download event
event_document_download.commit(
actor=request.user,
target=item.document
)
@staticmethod
def get_item_file(item):
return item.file
def get_encoding(self):
return self.get_object().encoding
return self.get_object_list().first().encoding
def get_item_label(self, item):
preserve_extension = self.request.GET.get(
@@ -143,13 +81,69 @@ class DocumentVersionDownloadView(DocumentDownloadView):
return item.get_rendered_string(preserve_extension=preserve_extension)
def get_mimetype(self):
return self.get_object().mimetype
return self.get_object_list().first().mimetype
class DocumentVersionListView(ExternalObjectMixin, SingleObjectListView):
external_object_class = Document
external_object_permission = permission_document_version_view
external_object_pk_url_kwarg = 'document_id'
def get_document(self):
document = self.get_external_object()
document.add_as_recent_document_for_user(user=self.request.user)
return document
def get_extra_context(self):
return {
'hide_object': True,
'list_as_items': True,
'object': self.get_document(),
'table_cell_container_classes': 'td-container-thumbnail',
'title': _('Versions of document: %s') % self.get_document(),
}
def get_source_queryset(self):
return self.get_document().versions.order_by('-timestamp')
class DocumentVersionRevertView(ExternalObjectMixin, ConfirmView):
external_object_class = DocumentVersion
external_object_permission = permission_document_version_revert
external_object_pk_url_kwarg = 'document_version_id'
def get_extra_context(self):
return {
'message': _(
'All later version after this one will be deleted too.'
),
'object': self.get_object().document,
'title': _('Revert to this version?'),
}
def get_object(self):
return self.get_external_object()
def view_action(self):
try:
self.get_object().revert(_user=self.request.user)
messages.success(
message=_(
'Document version reverted successfully'
), request=self.request
)
except Exception as exception:
messages.error(
message=_('Error reverting document version; %s') % exception,
request=self.request
)
class DocumentVersionView(SingleObjectDetailView):
form_class = DocumentVersionPreviewForm
model = DocumentVersion
object_permission = permission_document_version_view
pk_url_kwarg = 'document_version_id'
def dispatch(self, request, *args, **kwargs):
result = super(
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -24,7 +24,8 @@ class ClearImageCacheView(ConfirmView):
def view_action(self):
task_clear_image_cache.apply_async()
messages.success(
self.request, _('Document cache clearing queued successfully.')
message=_('Document cache clearing queued successfully.'),
request=self.request
)
@@ -37,5 +38,6 @@ class ScanDuplicatedDocuments(ConfirmView):
def view_action(self):
task_scan_duplicates_all.apply_async()
messages.success(
self.request, _('Duplicated document scan queued successfully.')
message=_('Duplicated document scan queued successfully.'),
request=self.request
)
@@ -0,0 +1,162 @@
from __future__ import absolute_import, unicode_literals
import logging
from django.contrib import messages
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.generics import (
ConfirmView, MultipleObjectConfirmActionView
)
from ..icons import icon_trashed_document_list
from ..models import Document, TrashedDocument
from ..permissions import (
permission_document_trash, permission_document_view, permission_empty_trash,
permission_trashed_document_delete, permission_trashed_document_restore
)
from ..tasks import task_delete_document
from .document_views import DocumentListView
__all__ = (
'DocumentTrashView', 'EmptyTrashCanView', 'TrashedDocumentDeleteView',
'TrashedDocumentListView', 'TrashedDocumentRestoreView',
)
logger = logging.getLogger(__name__)
class DocumentTrashView(MultipleObjectConfirmActionView):
model = Document
object_permission = permission_document_trash
pk_url_kwarg = 'document_id'
success_message = _(
'%(count)d document moved to the trash.'
)
success_message_plural = _(
'%(count)d documents moved to the trash.'
)
def get_extra_context(self):
queryset = self.get_object_list()
result = {
'title': ungettext(
single='Move the selected document to the trash?',
plural='Move the selected documents to the trash?',
number=queryset.count()
)
}
return result
def object_action(self, form, instance):
instance.delete()
class EmptyTrashCanView(ConfirmView):
extra_context = {
'title': _('Empty trash?')
}
view_permission = permission_empty_trash
action_cancel_redirect = post_action_redirect = reverse_lazy(
viewname='documents:trashed_document_list'
)
def view_action(self):
for trashed_document in TrashedDocument.objects.all():
task_delete_document.apply_async(
kwargs={'trashed_document_id': trashed_document.pk}
)
messages.success(
request=self.request, message=_('Trash emptied successfully')
)
class TrashedDocumentDeleteView(MultipleObjectConfirmActionView):
model = TrashedDocument
object_permission = permission_trashed_document_delete
pk_url_kwarg = 'trashed_document_id'
success_message = _(
'%(count)d trashed document deleted.'
)
success_message_plural = _(
'%(count)d trashed documents deleted.'
)
def get_extra_context(self):
queryset = self.get_object_list()
result = {
'title': ungettext(
single='Delete the selected trashed document?',
plural='Delete the selected trashed documents?',
number=queryset.count()
)
}
return result
def object_action(self, form, instance):
instance.delete()
class TrashedDocumentListView(DocumentListView):
object_permission = None
def get_document_queryset(self):
return AccessControlList.objects.restrict_queryset(
permission=permission_document_view,
queryset=TrashedDocument.trash.all(), user=self.request.user
)
def get_extra_context(self):
context = super(TrashedDocumentListView, self).get_extra_context()
context.update(
{
'hide_link': True,
'no_results_icon': icon_trashed_document_list,
'no_results_text': _(
'To avoid loss of data, documents are not deleted '
'instantly. First, they are placed in the trash can. '
'From here they can be then finally deleted or restored.'
),
'no_results_title': _(
'There are no documents in the trash can'
),
'title': _('Documents in trash'),
}
)
return context
class TrashedDocumentRestoreView(MultipleObjectConfirmActionView):
model = TrashedDocument
object_permission = permission_trashed_document_restore
pk_url_kwarg = 'trashed_document_id'
success_message = _(
'%(count)d trashed document restored.'
)
success_message_plural = _(
'%(count)d trashed documents restored.'
)
def get_extra_context(self):
queryset = self.get_object_list()
result = {
'title': ungettext(
single='Restore the selected trashed document?',
plural='Restore the selected trashed documents?',
number=queryset.count()
)
}
return result
def object_action(self, form, instance):
instance.restore()