Unify document type selection forms

Merge all document type selection forms into a single smarter form
that can perform permission filtering and allow single or multiple
selections.

This commit also add the document type selection for submit
view to the file metadata app.

This commit also updates the document type selection views
for the OCR, document parsing, and upload wizard to use
the new document type selection form and removes their
respective document type selection forms.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2018-12-14 18:06:23 -04:00
parent 315e70309b
commit 4d46ca3343
16 changed files with 279 additions and 145 deletions

View File

@@ -126,6 +126,7 @@ class DocumentParsingApp(MayanAppConfig):
ModelPermission.register(
model=DocumentType, permissions=(
permission_document_type_parsing_setup,
permission_parse_document
)
)
ModelPermission.register_inheritance(

View File

@@ -7,12 +7,9 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.widgets import TextAreaDiv
from mayan.apps.documents.models import DocumentType
from .models import DocumentPageContent
from .permissions import permission_parse_document
class DocumentContentForm(forms.Form):
@@ -83,18 +80,3 @@ class DocumentPageContentForm(forms.Form):
content = conditional_escape(force_text(page_content))
self.fields['contents'].initial = mark_safe(content)
class DocumentTypeSelectForm(forms.Form):
document_type = forms.ModelChoiceField(
queryset=DocumentType.objects.none(), label=('Document type')
)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(DocumentTypeSelectForm, self).__init__(*args, **kwargs)
queryset = AccessControlList.objects.filter_by_access(
permission=permission_parse_document,
queryset=DocumentType.objects.all(), user=user,
)
self.fields['document_type'].queryset = queryset

View File

@@ -7,7 +7,8 @@ from mayan.apps.documents.tests import (
)
from ..permissions import (
permission_content_view, permission_document_type_parsing_setup
permission_content_view, permission_document_type_parsing_setup,
permission_parse_document
)
from ..utils import get_document_content
@@ -92,12 +93,23 @@ class DocumentContentViewsTestCase(GenericDocumentViewTestCase):
),
)
def test_document_type_parsing_settings_view_no_permission(self):
response = self.get(
class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
# Ensure we use a PDF file
test_document_filename = TEST_HYBRID_DOCUMENT
def setUp(self):
super(DocumentTypeViewsTestCase, self).setUp()
self.login_user()
def _request_document_type_parsing_settings_view(self):
return self.get(
viewname='document_parsing:document_type_parsing_settings',
args=(self.document.document_type.pk,)
)
def test_document_type_parsing_settings_view_no_permission(self):
response = self._request_document_type_parsing_settings_view()
self.assertEqual(response.status_code, 403)
def test_document_type_parsing_settings_view_with_access(self):
@@ -105,9 +117,30 @@ class DocumentContentViewsTestCase(GenericDocumentViewTestCase):
permission=permission_document_type_parsing_setup,
obj=self.document.document_type
)
response = self.get(
viewname='document_parsing:document_type_parsing_settings',
args=(self.document.document_type.pk,)
)
response = self._request_document_type_parsing_settings_view()
self.assertEqual(response.status_code, 200)
def _request_document_type_submit_view(self):
return self.post(
viewname='document_parsing:document_type_submit', data={
'document_type': self.document_type.pk,
}
)
def test_document_type_submit_view_no_permission(self):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200)
self.assertTrue(
TEST_DOCUMENT_CONTENT not in self.document.content
)
def test_document_type_submit_view_with_access(self):
self.grant_access(
obj=self.document_type, permission=permission_parse_document,
)
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302)
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.content
)

View File

@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
@@ -11,11 +11,10 @@ from mayan.apps.common.generics import (
FormView, MultipleObjectConfirmActionView, SingleObjectDetailView,
SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView
)
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
from mayan.apps.documents.models import Document, DocumentPage, DocumentType
from .forms import (
DocumentContentForm, DocumentPageContentForm, DocumentTypeSelectForm
)
from .forms import DocumentContentForm, DocumentPageContentForm
from .models import DocumentVersionParseError
from .permissions import (
permission_content_view, permission_document_type_parsing_setup,
@@ -159,37 +158,36 @@ class DocumentTypeSettingsEditView(SingleObjectEditView):
class DocumentTypeSubmitView(FormView):
form_class = DocumentTypeSelectForm
extra_context = {
'title': _('Submit all documents of a type for parsing')
}
form_class = DocumentTypeFilteredSelectForm
post_action_redirect = reverse_lazy('common:tools_list')
def get_form_extra_kwargs(self):
return {
'allow_multiple': True,
'permission': permission_parse_document,
'user': self.request.user
}
def form_valid(self, form):
count = 0
for document in form.cleaned_data['document_type'].documents.all():
document.submit_for_parsing()
count += 1
for document_type in form.cleaned_data['document_type']:
for document in document_type.documents.all():
document.submit_for_parsing()
count += 1
messages.success(
self.request, _(
'%(count)d documents of type "%(document_type)s" added to the '
'parsing queue.'
'%(count)d documents added to the parsing queue.'
) % {
'count': count,
'document_type': form.cleaned_data['document_type']
}
)
return HttpResponseRedirect(self.get_success_url())
def get_form_extra_kwargs(self):
return {
'user': self.request.user
}
def get_post_action_redirect(self):
return reverse('common:tools_list')
class ParseErrorListView(SingleObjectListView):
extra_context = {

View File

@@ -1,17 +1,15 @@
from __future__ import absolute_import, unicode_literals
import logging
from django import forms
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from ..models import DocumentType, DocumentTypeFilename
from ..permissions import permission_document_create
__all__ = ('DocumentTypeFilenameForm_create', 'DocumentTypeSelectForm')
logger = logging.getLogger(__name__)
__all__ = (
'DocumentTypeFilenameForm_create', 'DocumentTypeFilteredSelectForm'
)
class DocumentTypeFilenameForm_create(forms.ModelForm):
@@ -23,22 +21,36 @@ class DocumentTypeFilenameForm_create(forms.ModelForm):
model = DocumentTypeFilename
class DocumentTypeSelectForm(forms.Form):
class DocumentTypeFilteredSelectForm(forms.Form):
"""
Form to select the document type of a document to be created, used
as form #1 in the document creation wizard
Form to select the document type of a document to be created. This form
is meant to be reused and reconfigured by other apps. Example: Used
as form #1 in the document creation wizard.
"""
def __init__(self, *args, **kwargs):
help_text = kwargs.pop('help_text', None)
if kwargs.pop('allow_multiple', False):
extra_kwargs = {}
field_class = forms.ModelMultipleChoiceField
widget_class = forms.widgets.SelectMultiple
else:
extra_kwargs = {'empty_label': None}
field_class = forms.ModelChoiceField
widget_class = forms.widgets.Select
permission = kwargs.pop('permission', None)
user = kwargs.pop('user', None)
logger.debug('user: %s', user)
super(DocumentTypeSelectForm, self).__init__(*args, **kwargs)
queryset = AccessControlList.objects.filter_by_access(
permission_document_create, user,
queryset=DocumentType.objects.all()
)
super(DocumentTypeFilteredSelectForm, self).__init__(*args, **kwargs)
self.fields['document_type'] = forms.ModelChoiceField(
empty_label=None, label=_('Document type'), queryset=queryset,
required=True, widget=forms.widgets.Select(attrs={'size': 10})
queryset = DocumentType.objects.all()
if permission:
queryset = AccessControlList.objects.filter_by_access(
permission=permission, queryset=queryset, user=user
)
self.fields['document_type'] = field_class(
help_text=help_text, label=_('Document type'),
queryset=queryset, required=True,
widget=widget_class(attrs={'size': 10}), **extra_kwargs
)

View File

@@ -30,7 +30,7 @@ from ..events import event_document_download, event_document_view
from ..forms import (
DocumentDownloadForm, DocumentForm, DocumentPageNumberForm,
DocumentPreviewForm, DocumentPrintForm, DocumentPropertiesForm,
DocumentTypeSelectForm
DocumentTypeFilteredSelectForm
)
from ..icons import (
icon_document_list, icon_document_list_deleted,
@@ -173,7 +173,7 @@ class DeletedDocumentListView(DocumentListView):
class DocumentDocumentTypeEditView(MultipleObjectFormActionView):
form_class = DocumentTypeSelectForm
form_class = DocumentTypeFilteredSelectForm
model = Document
object_permission = permission_document_properties_edit
success_message = _(

View File

@@ -8,7 +8,7 @@ from kombu import Exchange, Queue
from mayan.apps.acls import ModelPermission
from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_tools
)
from mayan.apps.document_indexing.handlers import handler_index_document
from mayan.apps.documents.search import document_page_search, document_search
@@ -29,7 +29,7 @@ from .handlers import (
from .links import (
link_document_driver_list, link_document_file_metadata_list,
link_document_submit, link_document_submit_multiple,
link_document_type_file_metadata_settings
link_document_type_file_metadata_settings, link_document_type_submit
)
from .permissions import (
permission_document_type_file_metadata_setup,
@@ -102,6 +102,7 @@ class FileMetadataApp(MayanAppConfig):
ModelPermission.register(
model=DocumentType, permissions=(
permission_document_type_file_metadata_setup,
permission_file_metadata_submit
)
)
ModelPermission.register_inheritance(
@@ -170,7 +171,9 @@ class FileMetadataApp(MayanAppConfig):
menu_multi_item.bind_links(
links=(link_document_submit_multiple,), sources=(Document,)
)
menu_tools.bind_links(
links=(link_document_type_submit,),
)
post_save.connect(
dispatch_uid='file_metadata_handler_initialize_new_document_type_settings',
receiver=handler_initialize_new_document_type_settings,

View File

@@ -35,3 +35,9 @@ link_document_type_file_metadata_settings = Link(
text=_('Setup file metadata'),
view='file_metadata:document_type_settings',
)
link_document_type_submit = Link(
icon_class=icon_file_metadata,
permissions=(permission_file_metadata_submit,),
text=_('File metadata processing per type'),
view='file_metadata:document_type_submit'
)

View File

@@ -130,8 +130,32 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
def test_document_type_settings_view_with_access(self):
self.grant_access(
permission=permission_document_type_file_metadata_setup,
obj=self.document.document_type
obj=self.document_type
)
response = self._request_document_type_settings_view()
self.assertEqual(response.status_code, 200)
def _request_document_type_submit_view(self):
return self.post(
viewname='file_metadata:document_type_submit', data={
'document_type': self.document_type.pk,
}
)
def test_document_type_submit_view_no_permission(self):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200)
self.assertEqual(
self.document.latest_version.file_metadata_drivers.count(), 0
)
def test_document_type_submit_view_with_access(self):
self.grant_access(
obj=self.document_type, permission=permission_file_metadata_submit,
)
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302)
self.assertEqual(
self.document.latest_version.file_metadata_drivers.count(), 1
)

View File

@@ -4,7 +4,7 @@ from django.conf.urls import url
from .views import (
DocumentDriverListView, DocumentSubmitView, DocumentTypeSettingsEditView,
DocumentVersionDriverEntryFileMetadataListView
DocumentTypeSubmitView, DocumentVersionDriverEntryFileMetadataListView
)
urlpatterns = [
@@ -25,6 +25,10 @@ urlpatterns = [
DocumentTypeSettingsEditView.as_view(),
name='document_type_settings'
),
url(
r'^document_types/submit/$', DocumentTypeSubmitView.as_view(),
name='document_type_submit'
),
url(
r'^document_version_driver/(?P<pk>\d+)/attributes/$',
DocumentVersionDriverEntryFileMetadataListView.as_view(),

View File

@@ -1,5 +1,7 @@
from __future__ import absolute_import, unicode_literals
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
@@ -7,9 +9,10 @@ from django.utils.translation import ungettext
from mayan.apps.acls.models import AccessControlList
from mayan.apps.common.views import (
MultipleObjectConfirmActionView, SingleObjectEditView,
FormView, MultipleObjectConfirmActionView, SingleObjectEditView,
SingleObjectListView
)
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
from mayan.apps.documents.models import Document, DocumentType
from .icons import icon_file_metadata
@@ -123,3 +126,38 @@ class DocumentTypeSettingsEditView(SingleObjectEditView):
def get_object(self, queryset=None):
return self.get_document_type().file_metadata_settings
class DocumentTypeSubmitView(FormView):
extra_context = {
'title': _(
'Submit all documents of a type for file metadata processing.'
)
}
form_class = DocumentTypeFilteredSelectForm
post_action_redirect = reverse_lazy('common:tools_list')
def get_form_extra_kwargs(self):
return {
'allow_multiple': True,
'permission': permission_file_metadata_submit,
'user': self.request.user
}
def form_valid(self, form):
count = 0
for document_type in form.cleaned_data['document_type']:
for document in document_type.documents.all():
document.submit_for_file_metadata_processing()
count += 1
messages.success(
self.request, _(
'%(count)d documents added to the file metadata processing '
'queue.'
) % {
'count': count,
}
)
return HttpResponseRedirect(self.get_success_url())

View File

@@ -121,7 +121,7 @@ class OCRApp(MayanAppConfig):
)
ModelPermission.register(
model=DocumentType, permissions=(
permission_document_type_ocr_setup,
permission_document_type_ocr_setup, permission_ocr_document
)
)
ModelPermission.register_inheritance(

View File

@@ -7,7 +7,6 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _, ugettext
from mayan.apps.common.widgets import TextAreaDiv
from mayan.apps.documents.models import DocumentType
from .models import DocumentPageOCRContent
@@ -80,9 +79,3 @@ class DocumentOCRContentForm(forms.Form):
)
self.fields['contents'].initial = mark_safe(''.join(content))
class DocumentTypeSelectForm(forms.Form):
document_type = forms.ModelChoiceField(
queryset=DocumentType.objects.all(), label=('Document type')
)

View File

@@ -3,8 +3,8 @@ from __future__ import unicode_literals
from mayan.apps.documents.tests import GenericDocumentViewTestCase
from ..permissions import (
permission_ocr_content_view, permission_ocr_document,
permission_document_type_ocr_setup
permission_document_type_ocr_setup, permission_ocr_content_view,
permission_ocr_document,
)
TEST_DOCUMENT_CONTENT = 'Mayan EDMS Documentation'
@@ -129,6 +129,16 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
response=response, content=self.document.ocr_content
)
class DocumentTypeViewsTestCase(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 setUp(self):
super(DocumentTypeViewsTestCase, self).setUp()
self.login_user()
def _request_document_type_ocr_settings_view(self):
return self.get(
viewname='ocr:document_type_ocr_settings',
@@ -147,3 +157,27 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_ocr_settings_view()
self.assertEqual(response.status_code, 200)
def _request_document_type_submit_view(self):
return self.post(
viewname='ocr:document_type_submit', data={
'document_type': self.document_type.pk,
}
)
def test_document_type_submit_view_no_permission(self):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200)
self.assertTrue(
TEST_DOCUMENT_CONTENT not in self.document.ocr_content
)
def test_document_type_submit_view_with_access(self):
self.grant_access(
obj=self.document_type, permission=permission_ocr_document,
)
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302)
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.ocr_content
)

View File

@@ -3,19 +3,17 @@ from __future__ import absolute_import, unicode_literals
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _, ungettext
from mayan.apps.common.generics import (
FormView, MultipleObjectConfirmActionView, SingleObjectDetailView,
SingleObjectDownloadView, SingleObjectEditView, SingleObjectListView
)
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
from mayan.apps.documents.models import Document, DocumentPage, DocumentType
from .forms import (
DocumentPageOCRContentForm, DocumentOCRContentForm,
DocumentTypeSelectForm
)
from .forms import DocumentPageOCRContentForm, DocumentOCRContentForm
from .models import DocumentVersionOCRError
from .permissions import (
permission_ocr_content_view, permission_ocr_document,
@@ -45,6 +43,36 @@ class DocumentOCRContentView(SingleObjectDetailView):
}
class DocumentOCRDownloadView(SingleObjectDownloadView):
model = Document
object_permission = permission_ocr_content_view
def get_file(self):
file_object = DocumentOCRDownloadView.TextIteratorIO(
iterator=get_document_ocr_content(document=self.get_object())
)
return DocumentOCRDownloadView.VirtualFile(
file=file_object, name='{}-OCR'.format(self.get_object())
)
class DocumentOCRErrorsListView(SingleObjectListView):
object_permission = permission_ocr_document
def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['pk'])
def get_extra_context(self):
return {
'hide_object': True,
'object': self.get_document(),
'title': _('OCR errors for document: %s') % self.get_document(),
}
def get_object_list(self):
return self.get_document().latest_version.ocr_errors.all()
class DocumentPageOCRContentView(SingleObjectDetailView):
form_class = DocumentPageOCRContentForm
model = DocumentPage
@@ -90,34 +118,6 @@ class DocumentSubmitView(MultipleObjectConfirmActionView):
instance.submit_for_ocr()
class DocumentTypeSubmitView(FormView):
extra_context = {
'title': _('Submit all documents of a type for OCR')
}
form_class = DocumentTypeSelectForm
def form_valid(self, form):
count = 0
for document in form.cleaned_data['document_type'].documents.all():
document.submit_for_ocr()
count += 1
messages.success(
self.request, _(
'%(count)d documents of type "%(document_type)s" added to the '
'OCR queue.'
) % {
'count': count,
'document_type': form.cleaned_data['document_type']
}
)
return HttpResponseRedirect(self.get_success_url())
def get_post_action_redirect(self):
return reverse('common:tools_list')
class DocumentTypeSettingsEditView(SingleObjectEditView):
fields = ('auto_ocr',)
object_permission = permission_document_type_ocr_setup
@@ -138,6 +138,38 @@ class DocumentTypeSettingsEditView(SingleObjectEditView):
return self.get_document_type().ocr_settings
class DocumentTypeSubmitView(FormView):
extra_context = {
'title': _('Submit all documents of a type for OCR')
}
form_class = DocumentTypeFilteredSelectForm
post_action_redirect = reverse_lazy('common:tools_list')
def get_form_extra_kwargs(self):
return {
'allow_multiple': True,
'permission': permission_ocr_document,
'user': self.request.user
}
def form_valid(self, form):
count = 0
for document_type in form.cleaned_data['document_type']:
for document in document_type.documents.all():
document.submit_for_ocr()
count += 1
messages.success(
self.request, _(
'%(count)d documents added to the OCR queue.'
) % {
'count': count,
}
)
return HttpResponseRedirect(self.get_success_url())
class EntryListView(SingleObjectListView):
extra_context = {
'hide_object': True,
@@ -147,33 +179,3 @@ class EntryListView(SingleObjectListView):
def get_object_list(self):
return DocumentVersionOCRError.objects.all()
class DocumentOCRErrorsListView(SingleObjectListView):
object_permission = permission_ocr_document
def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['pk'])
def get_extra_context(self):
return {
'hide_object': True,
'object': self.get_document(),
'title': _('OCR errors for document: %s') % self.get_document(),
}
def get_object_list(self):
return self.get_document().latest_version.ocr_errors.all()
class DocumentOCRDownloadView(SingleObjectDownloadView):
model = Document
object_permission = permission_ocr_content_view
def get_file(self):
file_object = DocumentOCRDownloadView.TextIteratorIO(
iterator=get_document_ocr_content(document=self.get_object())
)
return DocumentOCRDownloadView.VirtualFile(
file=file_object, name='{}-OCR'.format(self.get_object())
)

View File

@@ -11,7 +11,8 @@ from django.utils.translation import ugettext_lazy as _
from formtools.wizard.views import SessionWizardView
from mayan.apps.documents.forms import DocumentTypeSelectForm
from mayan.apps.documents.forms import DocumentTypeFilteredSelectForm
from mayan.apps.documents.permissions import permission_document_create
from .icons import icon_wizard_submit
@@ -92,7 +93,7 @@ class WizardStep(object):
class WizardStepDocumentType(WizardStep):
form_class = DocumentTypeSelectForm
form_class = DocumentTypeFilteredSelectForm
label = _('Select document type')
name = 'document_type_selection'
number = 0
@@ -111,7 +112,10 @@ class WizardStepDocumentType(WizardStep):
@classmethod
def get_form_kwargs(cls, wizard):
return {'user': wizard.request.user}
return {
'permission': permission_document_create,
'user': wizard.request.user
}
WizardStep.register(WizardStepDocumentType)