Refactor the model accesors

Refactor the accesors to behave like methods instead of properties.
This means all accesors will be prepended with the string
"get_" and will include a set of parenthesis.

Improve the ModeAttribute class to use the method's
short_description. This commit also adds support for a
new method .help_text attribute has been added.

Move accessors to their own module, named "methods.py".

Remove the PropertyHelper class as the accessors no longer
need it.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2018-12-15 04:49:40 -04:00
parent 8c63ef4c69
commit 0e86f2ad8a
42 changed files with 434 additions and 282 deletions

View File

@@ -1,4 +1,4 @@
3.2 (2018-XX-XX)
4.0 (2019-XX-XX)
================
- Documents: Add a server side template for invalid documents.
The new template can be accessed via the templates API.
@@ -177,6 +177,16 @@
BROKER_URL to CELERY_BROKER_URL.
- Internal change. Add support to the SourceColumn class to resolve
related fields using the double underscore as separator.
- Refactored the model accesors to behave like methods instead of
properties. This means all accessors will be prepended with the
string "get_" and will include a set of parenthesis.
- The ModeAttribute class has been improved to use the method's
short_description. Support for a new method .help_text attribute
has been added.
- Add accessors have been moved to their own module, named
"methods.py".
- The PropertyHelper class has been removed as the accessors
no longer need it.
3.1.9 (2018-11-01)
==================

View File

@@ -8,23 +8,9 @@ logger = logging.getLogger(__name__)
class ModelPermission(object):
_registry = {}
_proxies = {}
_inheritances = {}
@classmethod
def register(cls, model, permissions):
from django.contrib.contenttypes.fields import GenericRelation
cls._registry.setdefault(model, [])
for permission in permissions:
cls._registry[model].append(permission)
AccessControlList = apps.get_model(
app_label='acls', model_name='AccessControlList'
)
model.add_to_class(name='acls', value=GenericRelation(AccessControlList))
_proxies = {}
_registry = {}
@classmethod
def get_classes(cls, as_content_type=False):
@@ -72,13 +58,29 @@ class ModelPermission(object):
return StoredPermission.objects.filter(pk__in=pks)
@classmethod
def register_proxy(cls, source, model):
cls._proxies[model] = source
def get_inheritance(cls, model):
return cls._inheritances[model]
@classmethod
def register(cls, model, permissions):
from django.contrib.contenttypes.fields import GenericRelation
cls._registry.setdefault(model, [])
for permission in permissions:
cls._registry[model].append(permission)
AccessControlList = apps.get_model(
app_label='acls', model_name='AccessControlList'
)
model.add_to_class(
name='acls', value=GenericRelation(to=AccessControlList)
)
@classmethod
def register_inheritance(cls, model, related):
cls._inheritances[model] = related
@classmethod
def get_inheritance(cls, model):
return cls._inheritances[model]
def register_proxy(cls, source, model):
cls._proxies[model] = source

View File

@@ -22,6 +22,7 @@ from .links import (
link_document_cabinet_remove, link_multiple_document_cabinet_remove
)
from .menus import menu_cabinets
from .methods import method_get_document_cabinets
from .permissions import (
permission_cabinet_add_document, permission_cabinet_delete,
permission_cabinet_edit, permission_cabinet_remove_document,
@@ -53,8 +54,7 @@ class CabinetsApp(MayanAppConfig):
# Add explicit order_by as DocumentCabinet ordering Meta option has no
# effect.
Document.add_to_class(
name='document_cabinets',
value=lambda document: DocumentCabinet.objects.filter(documents=document).order_by('parent__label', 'label')
name='get_document_cabinets', value=method_get_document_cabinets
)
ModelPermission.register(

View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.apps import apps
def method_get_document_cabinets(self):
DocumentCabinet = apps.get_models(
app_label='cabinets', model_name='DocumentCabinet'
)
return DocumentCabinet.objects.filter(documents=self).order_by(
'parent__label', 'label'
)

View File

@@ -25,14 +25,6 @@ class WizardStepCabinets(WizardStep):
)
return Cabinet.objects.exists()
@classmethod
def get_form_kwargs(self, wizard):
return {
'help_text': _('Cabinets to which the document will be added.'),
'permission': permission_cabinet_add_document,
'user': wizard.request.user
}
@classmethod
def done(cls, wizard):
result = {}
@@ -44,6 +36,14 @@ class WizardStepCabinets(WizardStep):
return result
@classmethod
def get_form_kwargs(self, wizard):
return {
'help_text': _('Cabinets to which the document will be added.'),
'permission': permission_cabinet_add_document,
'user': wizard.request.user
}
@classmethod
def step_post_upload_process(cls, document, querystring=None):
furl_instance = furl(querystring)

View File

@@ -27,6 +27,10 @@ from .links import (
link_checkout_list
)
from .literals import CHECK_EXPIRED_CHECK_OUTS_INTERVAL
from .methods import (
method_check_in, method_get_checkout_info, method_get_checkout_state,
method_is_checked_out
)
from .permissions import (
permission_document_checkin, permission_document_checkin_override,
permission_document_checkout, permission_document_checkout_detail_view
@@ -54,29 +58,15 @@ class CheckoutsApp(MayanAppConfig):
app_label='documents', model_name='DocumentVersion'
)
DocumentCheckout = self.get_model('DocumentCheckout')
Document.add_to_class(name='check_in', value=method_check_in)
Document.add_to_class(
name='check_in',
value=lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user)
name='get_checkout_info', value=method_get_checkout_info
)
Document.add_to_class(
name='checkout_info',
value=lambda document: DocumentCheckout.objects.document_checkout_info(
document
)
name='get_checkout_state', value=method_get_checkout_state
)
Document.add_to_class(
name='checkout_state',
value=lambda document: DocumentCheckout.objects.document_checkout_state(
document
)
)
Document.add_to_class(
name='is_checked_out',
value=lambda document: DocumentCheckout.objects.is_document_checked_out(
document
)
name='is_checked_out', value=method_is_checked_out
)
ModelEventType.register(

View File

@@ -62,27 +62,6 @@ class DocumentCheckoutManager(models.Manager):
)
)
def document_checkout_info(self, document):
try:
return self.model.objects.get(document=document)
except self.model.DoesNotExist:
raise DocumentNotCheckedOut
def document_checkout_state(self, document):
if self.is_document_checked_out(document):
return STATE_CHECKED_OUT
else:
return STATE_CHECKED_IN
def expired_check_outs(self):
expired_list = Document.objects.filter(
pk__in=self.model.objects.filter(
expiration_datetime__lte=now()
).values_list('document__pk', flat=True)
)
logger.debug('expired_list: %s', expired_list)
return expired_list
def get_by_natural_key(self, document_natural_key):
Document = apps.get_model(
app_label='documents', model_name='Document'
@@ -94,6 +73,27 @@ class DocumentCheckoutManager(models.Manager):
return self.get(document__pk=document.pk)
def get_document_checkout_info(self, document):
try:
return self.model.objects.get(document=document)
except self.model.DoesNotExist:
raise DocumentNotCheckedOut
def get_document_checkout_state(self, document):
if self.is_document_checked_out(document):
return STATE_CHECKED_OUT
else:
return STATE_CHECKED_IN
def get_expired_check_outs(self):
expired_list = Document.objects.filter(
pk__in=self.model.objects.filter(
expiration_datetime__lte=now()
).values_list('document__pk', flat=True)
)
logger.debug('expired_list: %s', expired_list)
return expired_list
def is_document_checked_out(self, document):
if self.model.objects.filter(document=document):
return True

View File

@@ -0,0 +1,33 @@
from __future__ import unicode_literals
from django.apps import apps
def method_check_in(self, user=None):
DocumentCheckout = apps.get_model(
app_label='checkouts', model_name='DocumentCheckout'
)
return DocumentCheckout.objects.check_in_document(
document=self, user=user
)
def method_get_checkout_info(self):
DocumentCheckout = apps.get_model(
app_label='checkouts', model_name='DocumentCheckout'
)
return DocumentCheckout.objects.get_document_checkout_info(document=self)
def method_get_checkout_state(self):
DocumentCheckout = apps.get_model(
app_label='checkouts', model_name='DocumentCheckout'
)
return DocumentCheckout.objects.get_document_checkout_state(document=self)
def method_is_checked_out(self):
DocumentCheckout = apps.get_model(
app_label='checkouts', model_name='DocumentCheckout'
)
return DocumentCheckout.objects.is_document_checked_out(document=self)

View File

@@ -86,19 +86,19 @@ class CheckoutListView(DocumentListView):
{
'name': _('User'),
'attribute': encapsulate(
lambda document: document.checkout_info().user.get_full_name() or document.checkout_info().user
lambda document: document.get_checkout_info().user.get_full_name() or document.get_checkout_info().user
)
},
{
'name': _('Checkout time and date'),
'attribute': encapsulate(
lambda document: document.checkout_info().checkout_datetime
lambda document: document.get_checkout_info().checkout_datetime
)
},
{
'name': _('Checkout expiration'),
'attribute': encapsulate(
lambda document: document.checkout_info().expiration_datetime
lambda document: document.get_checkout_info().expiration_datetime
)
},
),
@@ -140,7 +140,7 @@ class DocumentCheckinView(ConfirmView):
'object': document,
}
if document.checkout_info().user != self.request.user:
if document.get_checkout_info().user != self.request.user:
context['title'] = _(
'You didn\'t originally checked out this document. '
'Forcefully check in the document: %s?'
@@ -159,7 +159,7 @@ class DocumentCheckinView(ConfirmView):
def view_action(self):
document = self.get_object()
if document.checkout_info().user == self.request.user:
if document.get_checkout_info().user == self.request.user:
AccessControlList.objects.check_access(
permissions=permission_document_checkin,
user=self.request.user, obj=document

View File

@@ -226,13 +226,27 @@ class ModelAttribute(object):
def __str__(self):
return self.get_display()
def get_display(self, show_name=False):
def get_label(self):
if self.label:
return self.label
else:
return getattr(
getattr(self.model, self.name), 'short_description', self.name
)
def get_description(self):
if self.description:
return self.description
else:
return getattr(getattr(self.model, self.name), 'help_text', None)
def get_display(self, show_name=False):
if self.get_description():
return '{} - {}'.format(
self.name if show_name else self.label, self.description
self.name if show_name else self.get_label(), self.get_description()
)
else:
return force_text(self.name if show_name else self.label)
return force_text(self.name if show_name else self.get_label())
class ModelField(ModelAttribute):
@@ -344,31 +358,6 @@ class Package(object):
self.__class__._registry.append(self)
class PropertyHelper(object):
"""
Makes adding fields using __class__.add_to_class easier.
Each subclass must implement the `constructor` and the `get_result`
method.
"""
@staticmethod
@property
def constructor(source_object):
return PropertyHelper(source_object)
def __init__(self, instance):
self.instance = instance
def __getattr__(self, name):
return self.get_result(name=name)
def get_result(self, name):
"""
The method that produces the actual result. Must be implemented
by each subclass.
"""
raise NotImplementedError
class Template(object):
_registry = {}

View File

@@ -12,7 +12,7 @@ class ErrorLogEntryManager(models.Manager):
app_label='common', model_name='ErrorLogEntry'
)
model.add_to_class(
name='error_logs', value=GenericRelation(ErrorLogEntry)
name='error_logs', value=GenericRelation(to=ErrorLogEntry)
)

View File

@@ -5,6 +5,6 @@ TEST_INDEX_LABEL_EDITED = 'test edited label'
TEST_INDEX_SLUG = 'test_slug'
TEST_METADATA_TYPE_LABEL = 'test metadata label'
TEST_METADATA_TYPE_NAME = 'test_metadata_name'
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{ document.metadata_value_of.%s }}' % TEST_METADATA_TYPE_NAME
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{ document.get_metadata("%s").value }}' % TEST_METADATA_TYPE_NAME
TEST_INDEX_TEMPLATE_DOCUMENT_LABEL_EXPRESSION = '{{ document.label }}'
TEST_INDEX_TEMPLATE_DOCUMENT_DESCRIPTION_EXPRESSION = '{{ document.description }}'

View File

@@ -1,11 +1,9 @@
from __future__ import unicode_literals
import logging
from datetime import timedelta
from django.apps import apps
from django.db.models.signals import post_save
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from kombu import Exchange, Queue
@@ -16,14 +14,12 @@ from mayan.apps.common import (
menu_tools
)
from mayan.apps.common.classes import ModelAttribute, ModelField
from mayan.apps.common.settings import settings_db_sync_task_delay
from mayan.apps.documents.search import document_page_search, document_search
from mayan.apps.documents.signals import post_version_upload
from mayan.apps.documents.widgets import document_link
from mayan.apps.navigation import SourceColumn
from mayan.celery import app
from .events import event_parsing_document_version_submit
from .handlers import (
handler_index_document, handler_initialize_new_parsing_settings,
handler_parse_document_version
@@ -35,36 +31,20 @@ from .links import (
link_document_type_parsing_settings, link_document_type_submit,
link_error_list
)
from .methods import (
method_document_submit_for_parsing,
method_document_version_submit_for_parsing,
method_get_document_content, method_get_document_version_content
)
from .permissions import (
permission_content_view, permission_document_type_parsing_setup,
permission_parse_document
)
from .signals import post_document_version_parsing
from .utils import document_property_content, get_document_content
logger = logging.getLogger(__name__)
def document_parsing_submit(self):
latest_version = self.latest_version
# Don't error out if document has no version
if latest_version:
latest_version.submit_for_parsing()
def document_version_parsing_submit(self):
from .tasks import task_parse_document_version
event_parsing_document_version_submit.commit(
action_object=self.document, target=self
)
task_parse_document_version.apply_async(
eta=now() + timedelta(seconds=settings_db_sync_task_delay.value),
kwargs={'document_version_pk': self.pk},
)
class DocumentParsingApp(MayanAppConfig):
app_namespace = 'document_parsing'
app_url = 'parsing'
@@ -96,26 +76,24 @@ class DocumentParsingApp(MayanAppConfig):
)
Document.add_to_class(
name='submit_for_parsing', value=document_parsing_submit
name='submit_for_parsing',
value=method_document_submit_for_parsing
)
Document.add_to_class(
name='content', value=document_property_content
name='get_content', value=method_get_document_content
)
DocumentVersion.add_to_class(
name='content', value=get_document_content
name='get_content', value=method_get_document_version_content
)
DocumentVersion.add_to_class(
name='submit_for_parsing', value=document_version_parsing_submit
name='submit_for_parsing',
value=method_document_version_submit_for_parsing
)
ModelAttribute(
model=Document, name='content', description=_(
'The parsed content of the document.'
)
)
ModelAttribute(model=Document, name='get_content')
ModelField(
Document, name='versions__pages__content__content'
model=Document, name='versions__pages__content__content'
)
ModelPermission.register(

View File

@@ -0,0 +1,56 @@
from __future__ import unicode_literals
from datetime import timedelta
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.settings import settings_db_sync_task_delay
from .events import event_parsing_document_version_submit
from .tasks import task_parse_document_version
from .utils import get_document_version_content_iterator
def method_document_submit_for_parsing(self):
latest_version = self.latest_version
# Don't error out if document has no version
if latest_version:
latest_version.submit_for_parsing()
def method_document_version_submit_for_parsing(self):
event_parsing_document_version_submit.commit(
action_object=self.document, target=self
)
task_parse_document_version.apply_async(
eta=now() + timedelta(seconds=settings_db_sync_task_delay.value),
kwargs={'document_version_pk': self.pk},
)
def method_get_document_content(self):
latest_version = self.latest_version
if latest_version:
return latest_version.get_content()
method_get_document_content.help_text = _(
'Return the parsed content of the document.'
)
method_get_document_content.short_description = _(
'get_content()'
)
def method_get_document_version_content(self):
return ' '.join(
get_document_version_content_iterator(document_version=self)
)
method_get_document_version_content.help_text = _(
'Return the parsed content of the document version.'
)

View File

@@ -1,4 +1,4 @@
from __future__ import unicode_literals
TEST_DOCUMENT_CONTENT = 'Sample text'
TEST_PARSING_INDEX_NODE_TEMPLATE = '{% if "sample" in document.content.lower() %}sample{% endif %}'
TEST_PARSING_INDEX_NODE_TEMPLATE = '{% if "sample" in document.get_content().lower() %}sample{% endif %}'

View File

@@ -25,5 +25,5 @@ class DocumentAutoParsingTestCase(GenericDocumentTestCase):
self._create_document_type()
self.document = self.upload_document()
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.content
TEST_DOCUMENT_CONTENT in self.document.get_content()
)

View File

@@ -10,7 +10,7 @@ from ..permissions import (
permission_content_view, permission_document_type_parsing_setup,
permission_parse_document
)
from ..utils import get_document_content
from ..utils import get_document_content_iterator
from .literals import TEST_DOCUMENT_CONTENT
@@ -89,7 +89,7 @@ class DocumentContentViewsTestCase(GenericDocumentViewTestCase):
self.assert_download_response(
response=response, content=(
''.join(get_document_content(document=self.document))
''.join(get_document_content_iterator(document=self.document))
),
)
@@ -132,7 +132,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200)
self.assertTrue(
TEST_DOCUMENT_CONTENT not in self.document.content
TEST_DOCUMENT_CONTENT not in self.document.get_content()
)
def test_document_type_submit_view_with_access(self):
@@ -142,5 +142,5 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302)
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.content
TEST_DOCUMENT_CONTENT in self.document.get_content()
)

View File

@@ -5,20 +5,24 @@ from django.utils.encoding import force_text
from django.utils.html import conditional_escape
def get_document_content(document):
def get_document_content_iterator(document):
latest_version = document.latest_version
if latest_version:
return get_document_version_content_iterator(
document_version=latest_version
)
def get_document_version_content_iterator(document_version):
DocumentPageContent = apps.get_model(
app_label='document_parsing', model_name='DocumentPageContent'
)
for page in document.pages.all():
for page in document_version.pages.all():
try:
page_content = page.content.content
except DocumentPageContent.DoesNotExist:
return
else:
yield conditional_escape(force_text(page_content))
@property
def document_property_content(self):
return ' '.join(get_document_content(self))

View File

@@ -2,6 +2,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 _
@@ -20,7 +21,7 @@ from .permissions import (
permission_content_view, permission_document_type_parsing_setup,
permission_parse_document
)
from .utils import get_document_content
from .utils import get_document_content_iterator
class DocumentContentView(SingleObjectDetailView):
@@ -50,7 +51,7 @@ class DocumentContentDownloadView(SingleObjectDownloadView):
def get_file(self):
file_object = DocumentContentDownloadView.TextIteratorIO(
iterator=get_document_content(document=self.get_object())
iterator=get_document_content_iterator(document=self.get_object())
)
return DocumentContentDownloadView.VirtualFile(
file=file_object, name='{}-content'.format(self.get_object())

View File

@@ -19,10 +19,11 @@ from mayan.apps.common.widgets import TwoStateWidget
from mayan.apps.navigation import SourceColumn
from mayan.celery import app
from .classes import DocumentStateHelper, WorkflowAction
from .classes import WorkflowAction
from .handlers import (
handler_index_document, handler_trigger_transition, launch_workflow
)
from .methods import method_get_workflow
from .links import (
link_document_workflow_instance_list, link_setup_workflow_create,
link_setup_workflow_delete, link_setup_workflow_document_types,
@@ -84,28 +85,14 @@ class DocumentStatesApp(MayanAppConfig):
)
Document.add_to_class(
name='workflow', value=DocumentStateHelper.constructor
name='get_workflow', value=method_get_workflow
)
ErrorLogEntry.objects.register(model=WorkflowStateAction)
WorkflowAction.initialize()
ModelAttribute(
model=Document,
name='workflow.< workflow internal name >.get_current_state()',
label=_('Current state of a workflow'), description=_(
'Return the current state of the selected workflow'
)
)
ModelAttribute(
model=Document,
name='workflow.< workflow internal name >.get_current_state().completion',
label=_('Current state of a workflow'), description=_(
'Return the completion value of the current state of the '
'selected workflow'
)
)
ModelAttribute(model=Document, name='get_workflow')
ModelPermission.register(
model=Document, permissions=(permission_workflow_view,)

View File

@@ -7,22 +7,10 @@ from django.apps import apps
from django.utils import six
from django.utils.encoding import force_text
from mayan.apps.common.classes import PropertyHelper
__all__ = ('WorkflowAction',)
logger = logging.getLogger(__name__)
class DocumentStateHelper(PropertyHelper):
@staticmethod
@property
def constructor(*args, **kwargs):
return DocumentStateHelper(*args, **kwargs)
def get_result(self, name):
return self.instance.workflows.get(workflow__internal_name=name)
class WorkflowActionMetaclass(type):
_registry = {}

View File

@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
def method_get_workflow(self, name):
return self.workflows.get(workflow__internal_name=name)
method_get_workflow.short_description = _(
'get_workflow(< workflow internal name >)'
)
method_get_workflow.help_text = _(
'Return the current state of the selected workflow.'
)

View File

@@ -15,6 +15,6 @@ TEST_WORKFLOW_TRANSITION_LABEL = 'test transition label'
TEST_WORKFLOW_TRANSITION_LABEL_2 = 'test transition label 2'
TEST_WORKFLOW_TRANSITION_LABEL_EDITED = 'test transition label edited'
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{{{ document.workflow.{}.get_current_state() }}}}'.format(
TEST_INDEX_TEMPLATE_METADATA_EXPRESSION = '{{{{ document.get_workflow.("{}").get_current_state() }}}}'.format(
TEST_WORKFLOW_INTERNAL_NAME
)

View File

@@ -10,6 +10,7 @@ from mayan.apps.acls import ModelPermission
from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_tools
)
from mayan.apps.common.classes import ModelAttribute, ModelField
from mayan.apps.document_indexing.handlers import handler_index_document
from mayan.apps.documents.search import document_page_search, document_search
from mayan.apps.documents.signals import post_version_upload
@@ -31,16 +32,16 @@ from .links import (
link_document_submit, link_document_submit_multiple,
link_document_type_file_metadata_settings, link_document_type_submit
)
from .methods import (
method_document_submit, method_document_version_submit,
method_get_document_file_metadata,
method_get_document_version_file_metadata
)
from .permissions import (
permission_document_type_file_metadata_setup,
permission_file_metadata_submit, permission_file_metadata_view
)
from .signals import post_document_version_file_metadata_processing
from .utils import (
method_document_submit, method_document_version_submit,
method_get_document_file_metadata,
method_get_document_version_file_metadata
)
class FileMetadataApp(MayanAppConfig):
@@ -78,14 +79,16 @@ class FileMetadataApp(MayanAppConfig):
name='get_file_metadata',
value=method_get_document_file_metadata
)
DocumentVersion.add_to_class(
name='submit_for_file_metadata_processing',
value=method_document_version_submit
)
DocumentVersion.add_to_class(
name='get_file_metadata',
value=method_get_document_version_file_metadata
)
DocumentVersion.add_to_class(
name='submit_for_file_metadata_processing',
value=method_document_version_submit
)
ModelAttribute(model=Document, name='get_file_metadata')
ModelEventType.register(
model=Document, event_types=(
@@ -94,6 +97,15 @@ class FileMetadataApp(MayanAppConfig):
)
)
ModelField(
label=_('File metadata key'), model=Document,
name='versions__file_metadata_drivers__entries__key',
)
ModelField(
label=_('File metadata key'), model=Document,
name='versions__file_metadata_drivers__entries__value',
)
ModelPermission.register(
model=Document, permissions=(
permission_file_metadata_submit, permission_file_metadata_view,

View File

@@ -1,5 +1,7 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from .events import event_file_metadata_document_version_submit
from .tasks import task_process_document_version
@@ -32,6 +34,14 @@ def method_get_document_file_metadata(self, dotted_name):
)
method_get_document_file_metadata.short_description=_(
'get_file_metadata(< file metadata dotted path >)'
)
method_get_document_file_metadata.help_text = _(
'Return the specified document file metadata entry.'
)
def method_get_document_version_file_metadata(self, dotted_name):
driver_internal_name, key = dotted_name.split('.')
@@ -46,3 +56,8 @@ def method_get_document_version_file_metadata(self, dotted_name):
return document_driver.entries.get(key=key).value
except document_driver.entries.model.DoesNotExist:
return
method_get_document_version_file_metadata.help_text = _(
'Return the specified document version file metadata entry.'
)

View File

@@ -100,10 +100,12 @@ class FileMetadataEntry(models.Model):
)
key = models.CharField(
db_index=True, max_length=255, verbose_name=_('Key')
db_index=True, help_text=_('Name of the file metadata entry.'),
max_length=255, verbose_name=_('Key')
)
value = models.CharField(
db_index=True, max_length=255, verbose_name=_('Value')
db_index=True, help_text=_('Value of the file metadata entry.'),
max_length=255, verbose_name=_('Value')
)
class Meta:

View File

@@ -27,7 +27,6 @@ from mayan.apps.events.permissions import permission_events_view
from mayan.celery import app
from mayan.apps.navigation import SourceColumn
from .classes import DocumentMetadataHelper
from .events import (
event_document_metadata_added, event_document_metadata_edited,
event_document_metadata_removed, event_metadata_type_edited,
@@ -46,13 +45,13 @@ from .links import (
link_setup_metadata_type_delete, link_setup_metadata_type_document_types,
link_setup_metadata_type_edit, link_setup_metadata_type_list,
)
from .methods import method_get_metadata
from .permissions import (
permission_metadata_document_add, permission_metadata_document_edit,
permission_metadata_document_remove, permission_metadata_document_view,
permission_metadata_type_delete, permission_metadata_type_edit,
permission_metadata_type_view
)
from .queues import * # NOQA
from .search import metadata_type_search # NOQA
from .widgets import get_metadata_string
@@ -90,23 +89,18 @@ class MetadataApp(MayanAppConfig):
MetadataType = self.get_model('MetadataType')
Document.add_to_class(
name='metadata_value_of',
value=DocumentMetadataHelper.constructor
name='get_metadata', value=method_get_metadata
)
ModelAttribute(
Document, 'metadata_value_of',
description=_(
'Return the value of a specific document metadata'
),
)
ModelAttribute(model=Document, name='get_metadata')
ModelField(
Document, 'metadata__metadata_type__name',
label=_('Metadata type name')
label=_('Metadata type name'), model=Document,
name='metadata__metadata_type__name'
)
ModelField(
Document, 'metadata__value', label=_('Metadata type value'),
label=_('Metadata type value'), model=Document,
name='metadata__value',
)
ModelEventType.register(

View File

@@ -1,17 +1,5 @@
from __future__ import unicode_literals
from mayan.apps.common.classes import PropertyHelper
class DocumentMetadataHelper(PropertyHelper):
@staticmethod
@property
def constructor(*args, **kwargs):
return DocumentMetadataHelper(*args, **kwargs)
def get_result(self, name):
return self.instance.metadata.get(metadata_type__name=name).value
class MetadataLookup(object):
_registry = []

View File

@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
def method_get_metadata(self, internal_name):
return self.metadata.get(metadata_type__name=internal_name)
method_get_metadata.short_description = _(
'get_metadata(< metadata type internal name >)'
)
method_get_metadata.help_text = _(
'Return the specified document metadata.'
)

View File

@@ -1,13 +1,11 @@
from __future__ import unicode_literals
from datetime import timedelta
import logging
from kombu import Exchange, Queue
from django.apps import apps
from django.db.models.signals import post_save
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls import ModelPermission
@@ -16,14 +14,12 @@ from mayan.apps.common import (
menu_tools
)
from mayan.apps.common.classes import ModelAttribute, ModelField
from mayan.apps.common.settings import settings_db_sync_task_delay
from mayan.apps.documents.search import document_search, document_page_search
from mayan.apps.documents.signals import post_version_upload
from mayan.apps.documents.widgets import document_link
from mayan.apps.navigation import SourceColumn
from mayan.celery import app
from .events import event_ocr_document_version_submit
from .handlers import (
handler_index_document, handler_initialize_new_ocr_settings,
handler_ocr_document_version,
@@ -35,37 +31,20 @@ from .links import (
link_document_type_ocr_settings, link_document_type_submit,
link_entry_list
)
from .methods import (
method_document_ocr_submit, method_document_version_ocr_submit,
method_get_document_ocr_content, method_get_document_version_ocr_content
)
from .permissions import (
permission_document_type_ocr_setup, permission_ocr_document,
permission_ocr_content_view
)
from .queues import * # NOQA
from .signals import post_document_version_ocr
from .utils import document_property_ocr_content
logger = logging.getLogger(__name__)
def document_ocr_submit(self):
latest_version = self.latest_version
# Don't error out if document has no version
if latest_version:
latest_version.submit_for_ocr()
def document_version_ocr_submit(self):
from .tasks import task_do_ocr
event_ocr_document_version_submit.commit(
action_object=self.document, target=self
)
task_do_ocr.apply_async(
eta=now() + timedelta(seconds=settings_db_sync_task_delay.value),
kwargs={'document_version_pk': self.pk},
)
class OCRApp(MayanAppConfig):
app_namespace = 'ocr'
app_url = 'ocr'
@@ -96,22 +75,24 @@ class OCRApp(MayanAppConfig):
DocumentVersionOCRError = self.get_model('DocumentVersionOCRError')
Document.add_to_class(
name='submit_for_ocr', value=document_ocr_submit)
name='get_ocr_content',
value=method_get_document_ocr_content
)
Document.add_to_class(
name='ocr_content', value=document_property_ocr_content
name='submit_for_ocr', value=method_document_ocr_submit
)
DocumentVersion.add_to_class(
name='submit_for_ocr', value=document_version_ocr_submit
name='get_ocr_content',
value=method_get_document_version_ocr_content
)
DocumentVersion.add_to_class(
name='submit_for_ocr', value=method_document_version_ocr_submit
)
ModelAttribute(
model=Document, name='ocr_content', description=_(
'The OCR content of the document.'
)
)
ModelAttribute(model=Document, name='get_ocr_content')
ModelField(
Document, name='versions__pages__ocr_content__content'
model=Document, name='versions__pages__ocr_content__content'
)
ModelPermission.register(

51
mayan/apps/ocr/methods.py Normal file
View File

@@ -0,0 +1,51 @@
from __future__ import unicode_literals
from datetime import timedelta
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from mayan.apps.common.settings import settings_db_sync_task_delay
from .events import event_ocr_document_version_submit
from .tasks import task_do_ocr
from .utils import get_document_version_content_iterator
def method_document_ocr_submit(self):
latest_version = self.latest_version
# Don't error out if document has no version
if latest_version:
latest_version.submit_for_ocr()
def method_document_version_ocr_submit(self):
event_ocr_document_version_submit.commit(
action_object=self.document, target=self
)
task_do_ocr.apply_async(
eta=now() + timedelta(seconds=settings_db_sync_task_delay.value),
kwargs={'document_version_pk': self.pk},
)
def method_get_document_ocr_content(self):
latest_version = self.latest_version
# Don't error out if document has no version
if latest_version:
latest_version.get_ocr_content()
method_get_document_ocr_content.short_description = _(
'get_ocr_content()'
)
method_get_document_ocr_content.help_text = _(
'Return the OCR content of the document.'
)
def method_get_document_version_ocr_content(self):
return ' '.join(
get_document_version_content_iterator(document_version=self)
)

View File

@@ -1,4 +1,4 @@
from __future__ import unicode_literals
TEST_OCR_INDEX_NODE_TEMPLATE = '{% if "mayan" in document.ocr_content.lower() %}mayan{% endif %}'
TEST_OCR_INDEX_NODE_TEMPLATE = '{% if "mayan" in document.get_ocr_content().lower() %}mayan{% endif %}'
TEST_OCR_INDEX_NODE_TEMPLATE_LEVEL = 'mayan'

View File

@@ -82,7 +82,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
)
self._request_document_submit_view()
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.ocr_content
TEST_DOCUMENT_CONTENT in self.document.get_ocr_content()
)
def _request_multiple_document_submit_view(self):
@@ -95,7 +95,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
def test_multiple_document_submit_view_no_permission(self):
self._request_multiple_document_submit_view()
self.assertEqual(self.document.ocr_content, '')
self.assertEqual(self.document.get_ocr_content(), '')
def test_multiple_document_submit_view_with_access(self):
self.grant_access(
@@ -103,7 +103,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
)
self._request_multiple_document_submit_view()
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.ocr_content
TEST_DOCUMENT_CONTENT in self.document.get_ocr_content()
)
def _request_document_ocr_download_view(self):
@@ -126,7 +126,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 200)
self.assert_download_response(
response=response, content=self.document.ocr_content
response=response, content=self.document.get_ocr_content()
)
@@ -169,7 +169,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200)
self.assertTrue(
TEST_DOCUMENT_CONTENT not in self.document.ocr_content
TEST_DOCUMENT_CONTENT not in self.document.get_ocr_content()
)
def test_document_type_submit_view_with_access(self):
@@ -179,5 +179,5 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302)
self.assertTrue(
TEST_DOCUMENT_CONTENT in self.document.ocr_content
TEST_DOCUMENT_CONTENT in self.document.get_ocr_content()
)

View File

@@ -4,20 +4,24 @@ from django.apps import apps
from django.utils.encoding import force_text
def get_document_ocr_content(document):
def get_document_content_iterator(document):
latest_version = document.latest_version
if latest_version:
return get_document_version_content_iterator(
document_version=latest_version
)
def get_document_version_content_iterator(document_version):
DocumentPageOCRContent = apps.get_model(
app_label='ocr', model_name='DocumentPageOCRContent'
)
for page in document.pages.all():
for page in document_version.pages.all():
try:
page_content = page.ocr_content.content
except DocumentPageOCRContent.DoesNotExist:
return
else:
yield force_text(page_content)
@property
def document_property_ocr_content(self):
return ' '.join(get_document_ocr_content(self))

View File

@@ -19,7 +19,7 @@ from .permissions import (
permission_ocr_content_view, permission_ocr_document,
permission_document_type_ocr_setup
)
from .utils import get_document_ocr_content
from .utils import get_document_content_iterator
class DocumentOCRContentView(SingleObjectDetailView):
@@ -49,7 +49,7 @@ class DocumentOCRDownloadView(SingleObjectDownloadView):
def get_file(self):
file_object = DocumentOCRDownloadView.TextIteratorIO(
iterator=get_document_ocr_content(document=self.get_object())
iterator=get_document_content_iterator(document=self.get_object())
)
return DocumentOCRDownloadView.VirtualFile(
file=file_object, name='{}-OCR'.format(self.get_object())

View File

@@ -117,7 +117,7 @@ class APIDocumentTagListView(generics.ListCreateAPIView):
obj=document
)
return document.attached_tags().all()
return document.get_tags().all()
def get_serializer(self, *args, **kwargs):
if not self.request:
@@ -171,7 +171,7 @@ class APIDocumentTagView(generics.RetrieveDestroyAPIView):
return document
def get_queryset(self):
return self.get_document().attached_tags().all()
return self.get_document().get_tags().all()
def get_serializer(self, *args, **kwargs):
if not self.request:

View File

@@ -11,7 +11,7 @@ from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_list_facet, menu_object, menu_main,
menu_multi_item, menu_sidebar
)
from mayan.apps.common.classes import ModelField
from mayan.apps.common.classes import ModelAttribute, ModelField
from mayan.apps.documents.search import document_page_search, document_search
from mayan.apps.events import ModelEventType
from mayan.apps.events.links import (
@@ -32,6 +32,7 @@ from .links import (
link_tag_multiple_delete, link_tag_tagged_item_list
)
from .menus import menu_tags
from .methods import method_get_tags
from .permissions import (
permission_tag_attach, permission_tag_delete, permission_tag_edit,
permission_tag_remove, permission_tag_view
@@ -65,10 +66,9 @@ class TagsApp(MayanAppConfig):
DocumentTag = self.get_model('DocumentTag')
Tag = self.get_model('Tag')
Document.add_to_class(
name='attached_tags',
value=lambda document: DocumentTag.objects.filter(documents=document)
)
Document.add_to_class(name='get_tags', value=method_get_tags)
ModelAttribute(model=Document, name='get_tags')
ModelEventType.register(
model=Tag, event_types=(

View File

@@ -0,0 +1,8 @@
from __future__ import unicode_literals
from django.db import models
class DocumentTagManager(models.Manager):
def get_for(self, document):
return self.filter(document=document)

View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.apps import apps
from django.utils.translation import ugettext_lazy as _
def method_get_tags(self):
DocumentTag = apps.get_model(app_label='tags', model_name='DocumentTag')
return DocumentTag.objects.filter(documents=self)
method_get_tags.help_text = _('Return a the tags attached to the document.')
method_get_tags.short_description = _('get_tags()')

View File

@@ -14,6 +14,7 @@ from mayan.apps.documents.permissions import permission_document_view
from .events import (
event_tag_attach, event_tag_created, event_tag_edited, event_tag_remove
)
from .managers import DocumentTagManager
from .widgets import widget_single_tag
@@ -95,6 +96,8 @@ class Tag(models.Model):
class DocumentTag(Tag):
objects = DocumentTagManager()
class Meta:
proxy = True
verbose_name = _('Document tag')

View File

@@ -8,7 +8,7 @@ TEST_TAG_COLOR_EDITED = '#221100'
TEST_TAG_INDEX_HAS_TAG = 'HAS_TAG'
TEST_TAG_INDEX_NO_TAG = 'NO_TAG'
TEST_TAG_INDEX_NODE_TEMPLATE = '''
{{% for tag in document.tags.all() %}}
{{% for tag in document.get_tags().all() %}}
{{% if tag.label == "{}" %}}
{}
{{% else %}}

View File

@@ -89,7 +89,7 @@ class TagAttachActionView(MultipleObjectFormActionView):
return super(TagAttachActionView, self).get_post_action_redirect()
def object_action(self, form, instance):
attached_tags = instance.attached_tags()
attached_tags = instance.get_tags()
for tag in form.cleaned_data['tags']:
AccessControlList.objects.check_access(
@@ -269,7 +269,7 @@ class DocumentTagListView(TagListView):
return context
def get_tag_queryset(self):
return self.document.attached_tags().all()
return self.document.get_tags().all()
class TagRemoveActionView(MultipleObjectFormActionView):
@@ -331,7 +331,7 @@ class TagRemoveActionView(MultipleObjectFormActionView):
return super(TagRemoveActionView, self).get_post_action_redirect()
def object_action(self, form, instance):
attached_tags = instance.attached_tags()
attached_tags = instance.get_tags()
for tag in form.cleaned_data['tags']:
AccessControlList.objects.check_access(