diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py index a4364053ef..d080f2e5b1 100644 --- a/mayan/apps/documents/apps.py +++ b/mayan/apps/documents/apps.py @@ -269,18 +269,20 @@ class DocumentsApp(MayanAppConfig): ) # DocumentPage + SourceColumn( + attribute='get_label', is_identifier=True, + is_object_absolute_url=True, source=DocumentPage + ) SourceColumn( func=lambda context: document_page_thumbnail_widget.render( instance=context['object'] ), label=_('Thumbnail'), source=DocumentPage ) - SourceColumn( func=lambda context: document_page_thumbnail_widget.render( instance=context['object'] ), label=_('Thumbnail'), source=DocumentPageResult ) - SourceColumn( attribute='document_version.document.document_type', label=_('Type'), source=DocumentPageResult diff --git a/mayan/apps/documents/models/document_page_models.py b/mayan/apps/documents/models/document_page_models.py index cd87a19ee0..0d9e21051e 100644 --- a/mayan/apps/documents/models/document_page_models.py +++ b/mayan/apps/documents/models/document_page_models.py @@ -54,13 +54,7 @@ class DocumentPage(models.Model): verbose_name_plural = _('Document pages') def __str__(self): - return _( - 'Page %(page_num)d out of %(total_pages)d of %(document)s' - ) % { - 'document': force_text(self.document), - 'page_num': self.page_number, - 'total_pages': self.document_version.pages.count() - } + return self.get_label() @property def cache_filename(self): @@ -111,7 +105,9 @@ class DocumentPage(models.Model): return cache_filename def get_absolute_url(self): - return reverse('documents:document_page_view', args=(self.pk,)) + return reverse( + viewname='documents:document_page_view', kwargs={'pk': self.pk} + ) def get_api_image_url(self, *args, **kwargs): """ @@ -240,6 +236,16 @@ class DocumentPage(models.Model): def is_in_trash(self): return self.document.is_in_trash + def get_label(self): + return _( + 'Page %(page_num)d out of %(total_pages)d of %(document)s' + ) % { + 'document': force_text(self.document), + 'page_num': self.page_number, + 'total_pages': self.document_version.pages.count() + } + get_label.short_description = _('Label') + def natural_key(self): return (self.page_number, self.document_version.natural_key()) natural_key.dependencies = ['documents.DocumentVersion'] diff --git a/mayan/apps/documents/tests/test_document_page_views.py b/mayan/apps/documents/tests/test_document_page_views.py index 79f3a18240..57752396de 100644 --- a/mayan/apps/documents/tests/test_document_page_views.py +++ b/mayan/apps/documents/tests/test_document_page_views.py @@ -1,12 +1,14 @@ from __future__ import unicode_literals +from django.utils.encoding import force_text + from ..permissions import permission_document_view from .base import GenericDocumentViewTestCase class DocumentPageViewTestCase(GenericDocumentViewTestCase): - def _document_page_list_view(self): + def _request_test_document_page_list_view(self): return self.get( viewname='documents:document_pages', kwargs={ 'pk': self.test_document.pk @@ -14,15 +16,42 @@ class DocumentPageViewTestCase(GenericDocumentViewTestCase): ) def test_document_page_list_view_no_permission(self): - response = self._document_page_list_view() - self.assertEqual(response.status_code, 403) + response = self._request_test_document_page_list_view() + self.assertEqual(response.status_code, 404) def test_document_page_list_view_with_access(self): self.grant_access( obj=self.test_document, permission=permission_document_view ) - response = self._document_page_list_view() + response = self._request_test_document_page_list_view() self.assertContains( response=response, text=self.test_document.label, status_code=200 ) + + def _request_test_document_page_view(self, document_page): + return self.get( + viewname='documents:document_page_view', kwargs={ + 'pk': document_page.pk, + } + ) + + def test_document_page_view_no_permissions(self): + response = self._request_test_document_page_view( + document_page=self.test_document.pages.first() + ) + self.assertEqual(response.status_code, 404) + + def test_document_page_view_with_access(self): + self.grant_access( + obj=self.test_document, permission=permission_document_view + ) + + response = self._request_test_document_page_view( + document_page=self.test_document.pages.first() + ) + self.assertContains( + response=response, text=force_text( + self.test_document.pages.first() + ), status_code=200 + ) diff --git a/mayan/apps/documents/tests/test_document_views.py b/mayan/apps/documents/tests/test_document_views.py index fd6d65566c..a8f4d160bd 100644 --- a/mayan/apps/documents/tests/test_document_views.py +++ b/mayan/apps/documents/tests/test_document_views.py @@ -510,32 +510,6 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase): self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(Document.objects.count(), 0) - def _request_document_page_view(self, document_page): - return self.get( - viewname='documents:document_page_view', kwargs={ - 'pk': document_page.pk, - } - ) - - def test_document_page_view_no_permissions(self): - response = self._request_document_page_view( - document_page=self.test_document.pages.first() - ) - self.assertEqual(response.status_code, 403) - - def test_document_page_view_with_access(self): - self.grant_access( - obj=self.test_document, permission=permission_document_view - ) - - response = self._request_document_page_view( - document_page=self.test_document.pages.first() - ) - self.assertContains( - response=response, text=force_text(self.test_document.pages.first()), - status_code=200 - ) - def _request_document_print_view(self): return self.get( viewname='documents:document_print', kwargs={ diff --git a/mayan/apps/documents/views/document_page_views.py b/mayan/apps/documents/views/document_page_views.py index 77b3715a9d..7586f5793b 100644 --- a/mayan/apps/documents/views/document_page_views.py +++ b/mayan/apps/documents/views/document_page_views.py @@ -2,16 +2,16 @@ from __future__ import absolute_import, unicode_literals import logging +from furl import furl + from django.contrib import messages -from django.shortcuts import get_object_or_404 from django.urls import reverse -from django.utils.http import urlencode -from django.utils.six.moves.urllib.parse import parse_qs, urlparse +from django.utils.encoding import force_text 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.settings import setting_home_view from mayan.apps.common.utils import resolve from mayan.apps.converter.literals import DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL @@ -35,101 +35,84 @@ __all__ = ( logger = logging.getLogger(__name__) -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) - - def get_document(self): - return get_object_or_404(klass=Document, pk=self.kwargs['pk']) +class DocumentPageListView(ExternalObjectMixin, SingleObjectListView): + external_object_class = Document + external_object_permission = permission_document_view + external_object_pk_url_kwarg = 'pk' def get_extra_context(self): return { + 'hide_object': True, 'list_as_items': True, - 'object': self.get_document(), - 'title': _('Pages for document: %s') % self.get_document(), + 'object': self.external_object, + 'title': _('Pages for document: %s') % self.external_object, } def get_source_queryset(self): - return self.get_document().pages.all() + return self.external_object.pages.all() -class DocumentPageNavigationBase(RedirectView): - def dispatch(self, request, *args, **kwargs): - document_page = self.get_object() - - AccessControlList.objects.check_access( - obj=document_page.document, - permissions=(permission_document_view,), user=request.user - ) - - 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 = 'pk' def get_object(self): - return get_object_or_404(klass=DocumentPage, pk=self.kwargs['pk']) + return self.get_external_object() def get_redirect_url(self, *args, **kwargs): - parse_result = urlparse( - self.request.META.get( - 'HTTP_REFERER', reverse(setting_home_view.value) - ) - ) + """ + Attempt to jump to the same kind of view but resolved to a new + object of the same kind. + """ + previous_url = self.request.META.get('HTTP_REFERER', None) - query_dict = parse_qs(parse_result.query) + if not previous_url: + try: + previous_url = self.get_object().get_absolute_url() + except AttributeError: + previous_url = reverse(viewname=setting_home_view.value) - resolver_match = resolve(parse_result.path) + parsed_url = furl(url=previous_url) - # Default is to stay on the same view - url = parse_result.path + # Obtain the view name to be able to resolve it back with new keyword + # arguments. + resolver_match = resolve(path=force_text(parsed_url.path)) - new_object = self.navigation_function() + new_kwargs = self.get_new_kwargs() - # Inject new_object pk in the referer's view pk or object_id kwargs - if 'pk' in resolver_match.kwargs: - resolver_match.kwargs['pk'] = new_object.pk + if set(new_kwargs) == set(resolver_match.kwargs): + # It is the same type of object, reuse the URL to stay in the + # same kind of view but pointing to a new object url = reverse( - resolver_match.view_name, kwargs=resolver_match.kwargs - ) - elif 'object_id' in resolver_match.kwargs: - resolver_match.kwargs['object_id'] = new_object.pk - url = reverse( - resolver_match.view_name, kwargs=resolver_match.kwargs + viewname=resolver_match.view_name, kwargs=new_kwargs ) else: - messages.warning( - self.request, _( - 'Unknown view keyword argument schema, unable to ' - 'redirect.' - ) - ) + url = parsed_url.path - return '{}?{}'.format(url, urlencode(query_dict, doseq=True)) + # Update just the path to retain the querystring in case there is + # transformation data. + parsed_url.path = url + + return parsed_url.tostr() class DocumentPageNavigationFirst(DocumentPageNavigationBase): - def navigation_function(self): + def get_new_kwargs(self): document_page = self.get_object() - return document_page.siblings.first() + return {'pk': document_page.siblings.first().pk} class DocumentPageNavigationLast(DocumentPageNavigationBase): - def navigation_function(self): + def get_new_kwargs(self): document_page = self.get_object() - return document_page.siblings.last() + return {'pk': document_page.siblings.last().pk} class DocumentPageNavigationNext(DocumentPageNavigationBase): - def navigation_function(self): + def get_new_kwargs(self): document_page = self.get_object() try: @@ -138,14 +121,16 @@ class DocumentPageNavigationNext(DocumentPageNavigationBase): ) except DocumentPage.DoesNotExist: messages.warning( - self.request, _('There are no more pages in this document') + message=_( + 'There are no more pages in this document' + ), request=self.request ) finally: - return document_page + return {'pk': document_page.pk} class DocumentPageNavigationPrevious(DocumentPageNavigationBase): - def navigation_function(self): + def get_new_kwargs(self): document_page = self.get_object() try: @@ -154,33 +139,26 @@ class DocumentPageNavigationPrevious(DocumentPageNavigationBase): ) except DocumentPage.DoesNotExist: messages.warning( - self.request, _( + message=_( 'You are already at the first page of this document' - ) + ), request=self.request ) finally: - return document_page + return {'pk': document_page.pk} -class DocumentPageView(SimpleView): +class DocumentPageView(ExternalObjectMixin, SimpleView): + external_object_class = DocumentPage + external_object_permission = permission_document_view + external_object_pk_url_kwarg = 'pk' template_name = 'appearance/generic_form.html' - def dispatch(self, request, *args, **kwargs): - AccessControlList.objects.check_access( - obj=self.get_object().document, - permissions=(permission_document_view,), user=request.user - ) - - 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() @@ -196,54 +174,51 @@ 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['pk']) + return self.get_external_object() class DocumentPageViewResetView(RedirectView): pattern_name = 'documents:document_page_view' -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 = 'pk' def get_object(self): - return get_object_or_404(klass=DocumentPage, pk=self.kwargs['pk']) + return self.get_external_object() def get_redirect_url(self, *args, **kwargs): - url = reverse( - 'documents:document_page_view', args=(self.kwargs['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={'pk': self.kwargs['pk']} + ) - return '{}?{}'.format(url, urlencode(query_dict)) + ) + + self.transformation_function(query_dict=query_dict) + # Refresh query_dict to args reference + url.args = query_dict + + return url.tostr() 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 @@ -253,7 +228,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 @@ -264,12 +239,12 @@ class DocumentPageZoomOutView(DocumentPageInteractiveTransformation): 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