Backport document page view improvements
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user