diff --git a/HISTORY.rst b/HISTORY.rst index a043e0d512..40be54b214 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,8 @@ long labels. * Update Django to version 1.11.24 * Update jQuery to version 3.4.1 +* Add support for deleting the OCR content of a document + or selection of documents. 3.2.7 (2019-08-28) ================== diff --git a/docs/releases/3.2.8.rst b/docs/releases/3.2.8.rst index 47f747c71a..8c916b1dd2 100644 --- a/docs/releases/3.2.8.rst +++ b/docs/releases/3.2.8.rst @@ -21,7 +21,8 @@ Other changes - Add cabinet add and remove workflow actions. - Update Django to version 1.11.24. - Update jQuery to version 3.4.1 - +- Add support for deleting the OCR content of a document + or selection of documents. Removals -------- diff --git a/mayan/apps/ocr/apps.py b/mayan/apps/ocr/apps.py index 93a7e34d70..e79c90c898 100644 --- a/mayan/apps/ocr/apps.py +++ b/mayan/apps/ocr/apps.py @@ -23,10 +23,11 @@ from .handlers import ( ) from .links import ( link_document_page_ocr_content, link_document_ocr_content, - link_document_ocr_download, link_document_ocr_errors_list, - link_document_submit, link_document_submit_multiple, - link_document_type_ocr_settings, link_document_type_submit, - link_entry_list + link_document_ocr_content_delete, + link_document_ocr_content_delete_multiple, link_document_ocr_download, + link_document_ocr_errors_list, link_document_submit, + link_document_submit_multiple, link_document_type_ocr_settings, + link_document_type_submit, link_entry_list ) from .methods import ( method_document_ocr_submit, method_document_version_ocr_submit @@ -131,10 +132,14 @@ class OCRApp(MayanAppConfig): links=(link_document_type_ocr_settings,), sources=(DocumentType,) ) menu_multi_item.bind_links( - links=(link_document_submit_multiple,), sources=(Document,) + links=( + link_document_ocr_content_delete_multiple, + link_document_submit_multiple, + ), sources=(Document,) ) menu_secondary.bind_links( links=( + link_document_ocr_content_delete, link_document_ocr_errors_list, link_document_ocr_download, link_document_submit ), diff --git a/mayan/apps/ocr/icons.py b/mayan/apps/ocr/icons.py index 174b718dbc..f6e1ce3141 100644 --- a/mayan/apps/ocr/icons.py +++ b/mayan/apps/ocr/icons.py @@ -3,6 +3,11 @@ from __future__ import absolute_import, unicode_literals from mayan.apps.appearance.classes import Icon icon_document_ocr_content = Icon(driver_name='fontawesome', symbol='font') +icon_document_ocr_content_delete = Icon( + driver_name='fontawesome-dual', + primary_symbol='font', + secondary_symbol='minus' +) icon_document_page_ocr_content = Icon(driver_name='fontawesome', symbol='font') icon_document_multiple_submit = Icon(driver_name='fontawesome', symbol='font') icon_document_ocr_download = Icon( diff --git a/mayan/apps/ocr/links.py b/mayan/apps/ocr/links.py index 6186f82bc7..5e580013c1 100644 --- a/mayan/apps/ocr/links.py +++ b/mayan/apps/ocr/links.py @@ -21,6 +21,17 @@ link_document_ocr_content = Link( permissions=(permission_ocr_content_view,), text=_('OCR'), view='ocr:document_ocr_content', ) +link_document_ocr_content_delete = Link( + args='resolved_object.id', + icon_class_path='mayan.apps.ocr.icons.icon_document_ocr_content_delete', + permissions=(permission_ocr_content_view,), text=_('Delete OCR content'), + view='ocr:document_ocr_content_delete', +) +link_document_ocr_content_delete_multiple = Link( + icon_class_path='mayan.apps.ocr.icons.icon_document_ocr_content_delete', + text=_('Delete OCR content'), + view='ocr:document_ocr_content_delete_multiple', +) link_document_submit = Link( args='resolved_object.id', icon_class_path='mayan.apps.ocr.icons.icon_document_submit', diff --git a/mayan/apps/ocr/managers.py b/mayan/apps/ocr/managers.py index d03b0dc071..9dac566824 100644 --- a/mayan/apps/ocr/managers.py +++ b/mayan/apps/ocr/managers.py @@ -6,7 +6,7 @@ import traceback from django.apps import apps from django.conf import settings -from django.db import models +from django.db import models, transaction from mayan.apps.documents.storages import storage_documentimagecache from mayan.apps.documents.literals import DOCUMENT_IMAGE_TASK_TIMEOUT @@ -20,6 +20,11 @@ logger = logging.getLogger(__name__) class DocumentPageOCRContentManager(models.Manager): + def delete_ocr_content_for(self, document): + with transaction.atomic(): + for document_page in document.pages.all(): + self.filter(document_page=document_page).delete() + def process_document_page(self, document_page): logger.info( 'Processing page: %d of document version: %s', diff --git a/mayan/apps/ocr/tests/test_views.py b/mayan/apps/ocr/tests/test_views.py index ccc327ea6b..5326797b01 100644 --- a/mayan/apps/ocr/tests/test_views.py +++ b/mayan/apps/ocr/tests/test_views.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from mayan.apps.documents.tests import GenericDocumentViewTestCase +from ..models import DocumentPageOCRContent from ..permissions import ( permission_ocr_content_view, permission_ocr_document, permission_document_type_ocr_setup @@ -11,11 +12,7 @@ from ..utils import get_document_ocr_content from .literals import TEST_DOCUMENT_CONTENT -class OCRViewsTestCase(GenericDocumentViewTestCase): - # PyOCR's leak descriptor in get_available_languages and image_to_string - # Disable descriptor leak test until fixed in upstream - _skip_file_descriptor_test = True - +class OCRViewTestMixin(object): def _request_document_content_view(self): return self.get( viewname='ocr:document_ocr_content', kwargs={ @@ -23,6 +20,54 @@ class OCRViewsTestCase(GenericDocumentViewTestCase): } ) + def _request_document_content_delete_view(self): + return self.post( + viewname='ocr:document_ocr_content_delete', kwargs={ + 'pk': self.test_document.pk + } + ) + + def _request_document_page_content_view(self): + return self.get( + viewname='ocr:document_page_ocr_content', kwargs={ + 'pk': self.test_document.pages.first().pk + } + ) + + def _request_document_submit_view(self): + return self.post( + viewname='ocr:document_submit', kwargs={ + 'pk': self.test_document.pk + } + ) + + def _request_multiple_document_submit_view(self): + return self.post( + viewname='ocr:document_submit_multiple', + data={ + 'id_list': self.test_document.pk, + } + ) + + def _request_document_ocr_download_view(self): + return self.get( + viewname='ocr:document_ocr_download', kwargs={ + 'pk': self.test_document.pk + } + ) + + def _request_document_type_ocr_settings_view(self): + return self.get( + viewname='ocr:document_type_ocr_settings', + kwargs={'pk': self.test_document.document_type.pk} + ) + + +class OCRViewsTestCase(OCRViewTestMixin, GenericDocumentViewTestCase): + # PyOCR's leak descriptor in get_available_languages and image_to_string + # Disable descriptor leak test until fixed in upstream + _skip_file_descriptor_test = True + def test_document_content_view_no_permissions(self): self.test_document.submit_for_ocr() @@ -40,11 +85,31 @@ class OCRViewsTestCase(GenericDocumentViewTestCase): response=response, text=TEST_DOCUMENT_CONTENT, status_code=200 ) - def _request_document_page_content_view(self): - return self.get( - viewname='ocr:document_page_ocr_content', kwargs={ - 'pk': self.test_document.pages.first().pk - } + def test_document_content_delete_view_no_permissions(self): + self.test_document.submit_for_ocr() + + response = self._request_document_content_delete_view() + self.assertEqual(response.status_code, 404) + + self.assertTrue( + DocumentPageOCRContent.objects.filter( + document_page=self.test_document.pages.first() + ).exists() + ) + + def test_document_content_delete_view_with_access(self): + self.test_document.submit_for_ocr() + self.grant_access( + obj=self.test_document, permission=permission_ocr_document + ) + + response = self._request_document_content_delete_view() + self.assertEqual(response.status_code, 302) + + self.assertFalse( + DocumentPageOCRContent.objects.filter( + document_page=self.test_document.pages.first() + ).exists() ) def test_document_page_content_view_no_permissions(self): @@ -64,13 +129,6 @@ class OCRViewsTestCase(GenericDocumentViewTestCase): response=response, text=TEST_DOCUMENT_CONTENT, status_code=200 ) - def _request_document_submit_view(self): - return self.post( - viewname='ocr:document_submit', kwargs={ - 'pk': self.test_document.pk - } - ) - def test_document_submit_view_no_permission(self): response = self._request_document_submit_view() self.assertEqual(response.status_code, 404) @@ -92,14 +150,6 @@ class OCRViewsTestCase(GenericDocumentViewTestCase): ) ) - def _request_multiple_document_submit_view(self): - return self.post( - viewname='ocr:document_submit_multiple', - data={ - 'id_list': self.test_document.pk, - } - ) - def test_multiple_document_submit_view_no_permission(self): response = self._request_multiple_document_submit_view() self.assertEqual(response.status_code, 404) @@ -121,13 +171,6 @@ class OCRViewsTestCase(GenericDocumentViewTestCase): ) ) - def _request_document_ocr_download_view(self): - return self.get( - viewname='ocr:document_ocr_download', kwargs={ - 'pk': self.test_document.pk - } - ) - def test_document_ocr_download_view_no_permission(self): self.test_document.submit_for_ocr() @@ -151,12 +194,6 @@ class OCRViewsTestCase(GenericDocumentViewTestCase): ), ) - def _request_document_type_ocr_settings_view(self): - return self.get( - viewname='ocr:document_type_ocr_settings', - kwargs={'pk': self.test_document.document_type.pk} - ) - def test_document_type_ocr_settings_view_no_permission(self): response = self._request_document_type_ocr_settings_view() self.assertEqual(response.status_code, 404) diff --git a/mayan/apps/ocr/urls.py b/mayan/apps/ocr/urls.py index a56543baba..5ef6a45244 100644 --- a/mayan/apps/ocr/urls.py +++ b/mayan/apps/ocr/urls.py @@ -7,7 +7,8 @@ from .api_views import ( APIDocumentVersionOCRView ) from .views import ( - DocumentOCRContentView, DocumentOCRDownloadView, + DocumentOCRContentDeleteView, DocumentOCRContentView, + DocumentOCRDownloadView, DocumentOCRErrorsListView, DocumentPageOCRContentView, DocumentSubmitView, DocumentTypeSettingsEditView, DocumentTypeSubmitView, EntryListView ) @@ -22,6 +23,16 @@ urlpatterns = [ regex=r'^documents/(?P\d+)/content/$', view=DocumentOCRContentView.as_view(), name='document_ocr_content' ), + url( + regex=r'^documents/(?P\d+)/content/delete/$', + view=DocumentOCRContentDeleteView.as_view(), + name='document_ocr_content_delete' + ), + url( + regex=r'^documents/multiple/content/delete/$', + view=DocumentOCRContentDeleteView.as_view(), + name='document_ocr_content_delete_multiple' + ), url( regex=r'^documents/(?P\d+)/submit/$', view=DocumentSubmitView.as_view(), name='document_submit' diff --git a/mayan/apps/ocr/views.py b/mayan/apps/ocr/views.py index 5eeb03e163..cf89f22c70 100644 --- a/mayan/apps/ocr/views.py +++ b/mayan/apps/ocr/views.py @@ -15,7 +15,7 @@ from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm from mayan.apps.documents.models import Document, DocumentPage, DocumentType from .forms import DocumentPageOCRContentForm, DocumentOCRContentForm -from .models import DocumentVersionOCRError +from .models import DocumentPageOCRContent, DocumentVersionOCRError from .permissions import ( permission_ocr_content_view, permission_ocr_document, permission_document_type_ocr_setup @@ -23,6 +23,34 @@ from .permissions import ( from .utils import get_document_ocr_content +class DocumentOCRContentDeleteView(MultipleObjectConfirmActionView): + model = Document + object_permission = permission_ocr_document + success_message = 'Deleted OCR content of %(count)d document.' + success_message_plural = 'Deleted OCR content of %(count)d documents.' + + def get_extra_context(self): + queryset = self.object_list + + result = { + 'title': ungettext( + singular='Delete the OCR content of the selected document?', + plural='Delete the OCR content of the selected documents?', + number=queryset.count() + ) + } + + if queryset.count() == 1: + result['object'] = queryset.first() + + return result + + def object_action(self, form, instance): + DocumentPageOCRContent.objects.delete_ocr_content_for( + document=instance + ) + + class DocumentOCRContentView(SingleObjectDetailView): form_class = DocumentOCRContentForm model = Document