Add support for disabling document pages

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-07-30 03:11:20 -04:00
parent cc81a6905a
commit 4ecf075fd4
17 changed files with 368 additions and 38 deletions

View File

@@ -68,6 +68,7 @@
as a tooltip. as a tooltip.
- Update numeric dashboard widget to display - Update numeric dashboard widget to display
thousand commas. thousand commas.
- Add support for disabling document pages.
3.2.6 (2019-07-10) 3.2.6 (2019-07-10)
================== ==================

View File

@@ -83,6 +83,7 @@ Changes
as a tooltip. as a tooltip.
- Update numeric dashboard widget to display - Update numeric dashboard widget to display
thousand commas. thousand commas.
- Add support for disabling document pages.
Removals Removals
-------- --------

View File

@@ -86,7 +86,7 @@ class DocumentParsingApp(MayanAppConfig):
) )
ModelField( ModelField(
model=Document, name='versions__pages__content__content' model=Document, name='versions__version_pages__content__content'
) )
ModelPermission.register( ModelPermission.register(
@@ -118,7 +118,7 @@ class DocumentParsingApp(MayanAppConfig):
) )
document_search.add_model_field( document_search.add_model_field(
field='versions__pages__content__content', label=_('Content') field='versions__version_pages__content__content', label=_('Content')
) )
document_page_search.add_model_field( document_page_search.add_model_field(

View File

@@ -174,7 +174,7 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
) )
def get_queryset(self): def get_queryset(self):
return self.get_document_version().pages.all() return self.get_document_version().pages_all.all()
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
return None return None

View File

@@ -60,6 +60,8 @@ from .links import (
link_document_multiple_download, link_document_multiple_favorites_add, link_document_multiple_download, link_document_multiple_favorites_add,
link_document_multiple_favorites_remove, link_document_multiple_restore, link_document_multiple_favorites_remove, link_document_multiple_restore,
link_document_multiple_trash, link_document_multiple_update_page_count, link_document_multiple_trash, link_document_multiple_update_page_count,
link_document_page_disable, link_document_page_multiple_disable,
link_document_page_enable, link_document_page_multiple_enable,
link_document_page_navigation_first, link_document_page_navigation_last, link_document_page_navigation_first, link_document_page_navigation_last,
link_document_page_navigation_next, link_document_page_navigation_previous, link_document_page_navigation_next, link_document_page_navigation_previous,
link_document_page_return, link_document_page_rotate_left, link_document_page_return, link_document_page_rotate_left,
@@ -214,12 +216,21 @@ class DocumentsApp(MayanAppConfig):
ModelPermission.register_inheritance( ModelPermission.register_inheritance(
model=Document, related='document_type', model=Document, related='document_type',
) )
ModelPermission.register_manager(
model=Document, manager_name='passthrough'
)
ModelPermission.register_inheritance( ModelPermission.register_inheritance(
model=DocumentPage, related='document_version__document', model=DocumentPage, related='document_version__document',
) )
ModelPermission.register_manager(
model=DocumentPage, manager_name='passthrough'
)
ModelPermission.register_inheritance( ModelPermission.register_inheritance(
model=DocumentPageResult, related='document_version__document', model=DocumentPageResult, related='document_version__document',
) )
ModelPermission.register_manager(
model=DocumentPageResult, manager_name='passthrough'
)
ModelPermission.register_inheritance( ModelPermission.register_inheritance(
model=DocumentTypeFilename, related='document_type', model=DocumentTypeFilename, related='document_type',
) )
@@ -269,6 +280,13 @@ class DocumentsApp(MayanAppConfig):
instance=context['object'] instance=context['object']
), label=_('Thumbnail'), source=DocumentPage ), label=_('Thumbnail'), source=DocumentPage
) )
SourceColumn(
attribute='enabled', include_label=True, source=DocumentPage,
widget=TwoStateWidget
)
SourceColumn(
attribute='page_number', include_label=True, source=DocumentPage
)
SourceColumn( SourceColumn(
attribute='get_label', is_identifier=True, attribute='get_label', is_identifier=True,
@@ -503,6 +521,16 @@ class DocumentsApp(MayanAppConfig):
link_document_page_navigation_last link_document_page_navigation_last
), sources=(DocumentPage,) ), sources=(DocumentPage,)
) )
menu_multi_item.bind_links(
links=(
link_document_page_multiple_disable,
link_document_page_multiple_enable
), sources=(DocumentPage,)
)
menu_object.bind_links(
links=(link_document_page_disable, link_document_page_enable),
sources=(DocumentPage,)
)
menu_list_facet.bind_links( menu_list_facet.bind_links(
links=(link_transformation_list,), sources=(DocumentPage,) links=(link_transformation_list,), sources=(DocumentPage,)
) )

View File

@@ -25,8 +25,6 @@ icon_dashboard_new_documents_this_month = Icon(
icon_dashboard_total_document = Icon( icon_dashboard_total_document = Icon(
driver_name='fontawesome', symbol='book' driver_name='fontawesome', symbol='book'
) )
icon_document_quick_download = Icon( icon_document_quick_download = Icon(
driver_name='fontawesome', symbol='download' driver_name='fontawesome', symbol='download'
) )
@@ -104,6 +102,14 @@ icon_favorite_document_remove = Icon(
secondary_symbol='minus' secondary_symbol='minus'
) )
# Document pages
icon_document_page_disable = Icon(
driver_name='fontawesomecss', css_classes='far fa-eye-slash'
)
icon_document_page_enable = Icon(
driver_name='fontawesomecss', css_classes='far fa-eye'
)
icon_document_page_navigation_first = Icon( icon_document_page_navigation_first = Icon(
driver_name='fontawesome', symbol='step-backward' driver_name='fontawesome', symbol='step-backward'
) )

View File

@@ -19,14 +19,14 @@ from .icons import (
icon_duplicated_document_list, icon_duplicated_document_scan icon_duplicated_document_list, icon_duplicated_document_scan
) )
from .permissions import ( from .permissions import (
permission_document_delete, permission_document_download, permission_document_delete, permission_document_edit,
permission_document_properties_edit, permission_document_print, permission_document_download, permission_document_properties_edit,
permission_document_restore, permission_document_tools, permission_document_print, permission_document_restore,
permission_document_version_revert, permission_document_view, permission_document_tools, permission_document_version_revert,
permission_document_trash, permission_document_type_create, permission_document_view, permission_document_trash,
permission_document_type_delete, permission_document_type_edit, permission_document_type_create, permission_document_type_delete,
permission_document_type_view, permission_empty_trash, permission_document_type_edit, permission_document_type_view,
permission_document_version_view permission_empty_trash, permission_document_version_view
) )
from .settings import setting_zoom_max_level, setting_zoom_min_level from .settings import setting_zoom_max_level, setting_zoom_min_level
@@ -270,6 +270,29 @@ link_trash_can_empty = Link(
) )
# Document pages # Document pages
link_document_page_disable = Link(
icon_class_path='mayan.apps.documents.icons.icon_document_page_disable',
kwargs={'pk': 'resolved_object.id'},
permissions=(permission_document_edit,), text=_('Disable page'),
view='documents:document_page_disable'
)
link_document_page_multiple_disable = Link(
icon_class_path='mayan.apps.documents.icons.icon_document_page_disable',
text=_('Disable pages'),
view='documents:document_page_multiple_disable'
)
link_document_page_enable = Link(
icon_class_path='mayan.apps.documents.icons.icon_document_page_enable',
kwargs={'pk': 'resolved_object.id'},
permissions=(permission_document_edit,), text=_('Enable page'),
view='documents:document_page_enable'
)
link_document_page_multiple_enable = Link(
icon_class_path='mayan.apps.documents.icons.icon_document_page_enable',
text=_('Enable pages'),
view='documents:document_page_multiple_enable'
)
link_document_page_navigation_first = Link( link_document_page_navigation_first = Link(
args='resolved_object.pk', conditional_disable=is_first_page, args='resolved_object.pk', conditional_disable=is_first_page,
icon_class=icon_document_page_navigation_first, icon_class=icon_document_page_navigation_first,

View File

@@ -22,7 +22,7 @@ class DocumentManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return TrashCanQuerySet( return TrashCanQuerySet(
self.model, using=self._db model=self.model, using=self._db
).filter(in_trash=False).filter(is_stub=False) ).filter(in_trash=False).filter(is_stub=False)
@@ -38,6 +38,11 @@ class DocumentPageManager(models.Manager):
return self.get(document_version__pk=document_version.pk, page_number=page_number) return self.get(document_version__pk=document_version.pk, page_number=page_number)
def get_queryset(self):
return models.QuerySet(
model=self.model, using=self._db
).filter(enabled=True)
class DocumentTypeManager(models.Manager): class DocumentTypeManager(models.Manager):
def check_delete_periods(self): def check_delete_periods(self):

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.22 on 2019-07-29 07:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('documents', '0050_auto_20190725_0451'),
]
operations = [
migrations.AddField(
model_name='documentpage',
name='enabled',
field=models.BooleanField(default=True, verbose_name='Enabled'),
),
]

View File

@@ -236,6 +236,18 @@ class Document(models.Model):
def page_count(self): def page_count(self):
return self.latest_version.page_count return self.latest_version.page_count
@property
def pages_all(self):
try:
return self.latest_version.pages_all
except AttributeError:
# Document has no version yet
DocumentPage = apps.get_model(
app_label='documents', model_name='DocumentPage'
)
return DocumentPage.objects.none()
@property @property
def pages(self): def pages(self):
try: try:

View File

@@ -38,15 +38,17 @@ class DocumentPage(models.Model):
Model that describes a document version page Model that describes a document version page
""" """
document_version = models.ForeignKey( document_version = models.ForeignKey(
on_delete=models.CASCADE, related_name='pages', to=DocumentVersion, on_delete=models.CASCADE, related_name='version_pages', to=DocumentVersion,
verbose_name=_('Document version') verbose_name=_('Document version')
) )
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
page_number = models.PositiveIntegerField( page_number = models.PositiveIntegerField(
db_index=True, default=1, editable=False, db_index=True, default=1, editable=False,
verbose_name=_('Page number') verbose_name=_('Page number')
) )
objects = DocumentPageManager() objects = DocumentPageManager()
passthrough = models.Manager()
class Meta: class Meta:
ordering = ('page_number',) ordering = ('page_number',)
@@ -244,7 +246,7 @@ class DocumentPage(models.Model):
) % { ) % {
'document': force_text(self.document), 'document': force_text(self.document),
'page_num': self.page_number, 'page_num': self.page_number,
'total_pages': self.document_version.pages.count() 'total_pages': self.document_version.pages_all.count()
} }
get_label.short_description = _('Label') get_label.short_description = _('Label')

View File

@@ -246,6 +246,17 @@ class DocumentVersion(models.Model):
return result return result
@property
def pages_all(self):
DocumentPage = apps.get_model(
app_label='documents', model_name='DocumentPage'
)
return DocumentPage.passthrough.filter(document_version=self)
@property
def pages(self):
return self.version_pages.all()
@property @property
def page_count(self): def page_count(self):
""" """

View File

@@ -70,8 +70,7 @@ def task_generate_document_page_image(document_page_id, *args, **kwargs):
app_label='documents', model_name='DocumentPage' app_label='documents', model_name='DocumentPage'
) )
document_page = DocumentPage.objects.get(pk=document_page_id) document_page = DocumentPage.passthrough.get(pk=document_page_id)
return document_page.generate_image(*args, **kwargs) return document_page.generate_image(*args, **kwargs)

View File

@@ -2,11 +2,152 @@ from __future__ import unicode_literals
from django.utils.encoding import force_text from django.utils.encoding import force_text
from ..permissions import permission_document_view from ..permissions import (
permission_document_edit, permission_document_view
)
from .base import GenericDocumentViewTestCase from .base import GenericDocumentViewTestCase
class DocumentPageDisableViewTestCase(GenericDocumentViewTestCase):
def setUp(self):
super(DocumentPageDisableViewTestCase, self).setUp()
self.test_document_page = self.test_document.pages_all.first()
def _request_test_document_page_disable_view(self):
return self.post(
viewname='documents:document_page_disable', kwargs={
'pk': self.test_document_page.pk
}
)
def test_document_page_disable_view_no_permission(self):
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_disable_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(
test_document_page_count, self.test_document.pages.count()
)
def test_document_page_disable_view_with_access(self):
self.grant_access(
obj=self.test_document, permission=permission_document_edit
)
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_disable_view()
self.assertEqual(response.status_code, 302)
self.assertNotEqual(
test_document_page_count, self.test_document.pages.count()
)
def _request_test_document_page_multiple_disable_view(self):
return self.post(
viewname='documents:document_page_multiple_disable', data={
'id_list': self.test_document_page.pk
}
)
def test_document_page_multiple_disable_view_no_permission(self):
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_multiple_disable_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(
test_document_page_count, self.test_document.pages.count()
)
def test_document_page_multiple_disable_view_with_access(self):
self.grant_access(
obj=self.test_document, permission=permission_document_edit
)
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_multiple_disable_view()
self.assertEqual(response.status_code, 302)
self.assertNotEqual(
test_document_page_count, self.test_document.pages.count()
)
def _disable_test_document_page(self):
self.test_document_page.enabled = False
self.test_document_page.save()
def _request_test_document_page_enable_view(self):
return self.post(
viewname='documents:document_page_enable', kwargs={
'pk': self.test_document_page.pk
}
)
def test_document_page_enable_view_no_permission(self):
self._disable_test_document_page()
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_enable_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(
test_document_page_count, self.test_document.pages.count()
)
def test_document_page_enable_view_with_access(self):
self._disable_test_document_page()
self.grant_access(
obj=self.test_document, permission=permission_document_edit
)
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_enable_view()
self.assertEqual(response.status_code, 302)
self.assertNotEqual(
test_document_page_count, self.test_document.pages.count()
)
def _request_test_document_page_multiple_enable_view(self):
return self.post(
viewname='documents:document_page_multiple_enable', data={
'id_list': self.test_document_page.pk
}
)
def test_document_page_multiple_enable_view_no_permission(self):
self._disable_test_document_page()
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_multiple_enable_view()
self.assertEqual(response.status_code, 404)
self.assertEqual(
test_document_page_count, self.test_document.pages.count()
)
def test_document_page_multiple_enable_view_with_access(self):
self._disable_test_document_page()
self.grant_access(
obj=self.test_document, permission=permission_document_edit
)
test_document_page_count = self.test_document.pages.count()
response = self._request_test_document_page_multiple_enable_view()
self.assertEqual(response.status_code, 302)
self.assertNotEqual(
test_document_page_count, self.test_document.pages.count()
)
class DocumentPageViewTestCase(GenericDocumentViewTestCase): class DocumentPageViewTestCase(GenericDocumentViewTestCase):
def _request_test_document_page_list_view(self): def _request_test_document_page_list_view(self):
return self.get( return self.get(

View File

@@ -32,6 +32,9 @@ from .views import (
RecentAccessDocumentListView, RecentAddedDocumentListView, RecentAccessDocumentListView, RecentAddedDocumentListView,
ScanDuplicatedDocuments ScanDuplicatedDocuments
) )
from .views.document_page_views import (
DocumentPageDisable, DocumentPageEnable
)
from .views.document_type_views import DocumentTypeDeletionPoliciesEditView from .views.document_type_views import DocumentTypeDeletionPoliciesEditView
from .views.favorite_document_views import ( from .views.favorite_document_views import (
FavoriteAddView, FavoriteDocumentListView, FavoriteRemoveView FavoriteAddView, FavoriteDocumentListView, FavoriteRemoveView
@@ -266,7 +269,6 @@ urlpatterns = [
regex=r'^(?P<pk>\d+)/pages/all/$', view=DocumentPageListView.as_view(), regex=r'^(?P<pk>\d+)/pages/all/$', view=DocumentPageListView.as_view(),
name='document_pages' name='document_pages'
), ),
url( url(
regex=r'^multiple/clear_transformations/$', regex=r'^multiple/clear_transformations/$',
view=DocumentTransformationsClearView.as_view(), view=DocumentTransformationsClearView.as_view(),
@@ -276,6 +278,22 @@ urlpatterns = [
regex=r'^page/(?P<pk>\d+)/$', view=DocumentPageView.as_view(), regex=r'^page/(?P<pk>\d+)/$', view=DocumentPageView.as_view(),
name='document_page_view' name='document_page_view'
), ),
url(
regex=r'^pages/(?P<pk>\d+)/disable/$',
name='document_page_disable', view=DocumentPageDisable.as_view()
),
url(
regex=r'^pages/multiple/disable/$', name='document_page_multiple_disable',
view=DocumentPageDisable.as_view()
),
url(
regex=r'^pages/(?P<pk>\d+)/enable/$',
name='document_page_enable', view=DocumentPageEnable.as_view()
),
url(
regex=r'^pages/multiple/enable/$', name='document_page_multiple_enable',
view=DocumentPageEnable.as_view()
),
url( url(
regex=r'^page/(?P<pk>\d+)/navigation/next/$', regex=r'^page/(?P<pk>\d+)/navigation/next/$',
view=DocumentPageNavigationNext.as_view(), view=DocumentPageNavigationNext.as_view(),

View File

@@ -7,10 +7,12 @@ from furl import furl
from django.contrib import messages from django.contrib import messages
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _, ungettext
from django.views.generic import RedirectView from django.views.generic import RedirectView
from mayan.apps.common.generics import SimpleView, SingleObjectListView from mayan.apps.common.generics import (
MultipleObjectConfirmActionView, SimpleView, SingleObjectListView
)
from mayan.apps.common.mixins import ExternalObjectMixin from mayan.apps.common.mixins import ExternalObjectMixin
from mayan.apps.common.settings import setting_home_view from mayan.apps.common.settings import setting_home_view
from mayan.apps.common.utils import resolve from mayan.apps.common.utils import resolve
@@ -20,19 +22,20 @@ from ..forms import DocumentPageForm
from ..icons import icon_document_pages from ..icons import icon_document_pages
from ..links import link_document_update_page_count from ..links import link_document_update_page_count
from ..models import Document, DocumentPage from ..models import Document, DocumentPage
from ..permissions import permission_document_view from ..permissions import permission_document_edit, permission_document_view
from ..settings import ( from ..settings import (
setting_rotation_step, setting_zoom_percent_step, setting_zoom_max_level, setting_rotation_step, setting_zoom_percent_step, setting_zoom_max_level,
setting_zoom_min_level setting_zoom_min_level
) )
__all__ = ( __all__ = (
'DocumentPageListView', 'DocumentPageNavigationFirst', 'DocumentPageDisable', 'DocumentPageEnable', 'DocumentPageListView',
'DocumentPageNavigationLast', 'DocumentPageNavigationNext', 'DocumentPageNavigationFirst', 'DocumentPageNavigationLast',
'DocumentPageNavigationPrevious', 'DocumentPageView', 'DocumentPageNavigationNext', 'DocumentPageNavigationPrevious',
'DocumentPageViewResetView', 'DocumentPageInteractiveTransformation', 'DocumentPageView', 'DocumentPageViewResetView',
'DocumentPageZoomInView', 'DocumentPageZoomOutView', 'DocumentPageInteractiveTransformation', 'DocumentPageZoomInView',
'DocumentPageRotateLeftView', 'DocumentPageRotateRightView' 'DocumentPageZoomOutView', 'DocumentPageRotateLeftView',
'DocumentPageRotateRightView'
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,7 +65,7 @@ class DocumentPageListView(ExternalObjectMixin, SingleObjectListView):
} }
def get_source_queryset(self): def get_source_queryset(self):
return self.external_object.pages.all() return self.external_object.pages_all
class DocumentPageNavigationBase(ExternalObjectMixin, RedirectView): class DocumentPageNavigationBase(ExternalObjectMixin, RedirectView):
@@ -129,9 +132,9 @@ class DocumentPageNavigationNext(DocumentPageNavigationBase):
document_page = self.get_object() document_page = self.get_object()
try: try:
document_page = document_page.siblings.get( document_page = document_page.siblings.filter(
page_number=document_page.page_number + 1 page_number__gt=document_page.page_number
) ).first()
except DocumentPage.DoesNotExist: except DocumentPage.DoesNotExist:
messages.warning( messages.warning(
message=_( message=_(
@@ -147,9 +150,9 @@ class DocumentPageNavigationPrevious(DocumentPageNavigationBase):
document_page = self.get_object() document_page = self.get_object()
try: try:
document_page = document_page.siblings.get( document_page = document_page.siblings.filter(
page_number=document_page.page_number - 1 page_number__lt=document_page.page_number
) ).last()
except DocumentPage.DoesNotExist: except DocumentPage.DoesNotExist:
messages.warning( messages.warning(
message=_( message=_(
@@ -261,3 +264,63 @@ class DocumentPageRotateRightView(DocumentPageInteractiveTransformation):
query_dict['rotation'] = ( query_dict['rotation'] = (
int(query_dict['rotation']) + setting_rotation_step.value int(query_dict['rotation']) + setting_rotation_step.value
) % 360 ) % 360
class DocumentPageDisable(MultipleObjectConfirmActionView):
object_permission = permission_document_edit
pk_url_kwarg = 'pk'
success_message_singular = '%(count)d document page disabled.'
success_message_plural = '%(count)d document pages disabled.'
def get_extra_context(self):
queryset = self.object_list
result = {
'title': ungettext(
singular='Disable the selected document page?',
plural='Disable the selected document pages?',
number=queryset.count()
)
}
if queryset.count() == 1:
result['object'] = queryset.first()
return result
def get_source_queryset(self):
return DocumentPage.passthrough.all()
def object_action(self, form, instance):
instance.enabled = False
instance.save()
class DocumentPageEnable(MultipleObjectConfirmActionView):
object_permission = permission_document_edit
pk_url_kwarg = 'pk'
success_message_singular = '%(count)d document page enabled.'
success_message_plural = '%(count)d document pages enabled.'
def get_extra_context(self):
queryset = self.object_list
result = {
'title': ungettext(
singular='Enable the selected document page?',
plural='Enable the selected document pages?',
number=queryset.count()
)
}
if queryset.count() == 1:
result['object'] = queryset.first()
return result
def get_source_queryset(self):
return DocumentPage.passthrough.all()
def object_action(self, form, instance):
instance.enabled = True
instance.save()

View File

@@ -83,7 +83,7 @@ class OCRApp(MayanAppConfig):
) )
ModelField( ModelField(
model=Document, name='versions__pages__ocr_content__content' model=Document, name='versions__version_pages__ocr_content__content'
) )
ModelPermission.register( ModelPermission.register(
@@ -114,7 +114,7 @@ class OCRApp(MayanAppConfig):
) )
document_search.add_model_field( document_search.add_model_field(
field='versions__pages__ocr_content__content', label=_('OCR') field='versions__version_pages__ocr_content__content', label=_('OCR')
) )
document_page_search.add_model_field( document_page_search.add_model_field(