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. - Documents: Add a server side template for invalid documents.
The new template can be accessed via the templates API. The new template can be accessed via the templates API.
@@ -177,6 +177,16 @@
BROKER_URL to CELERY_BROKER_URL. BROKER_URL to CELERY_BROKER_URL.
- Internal change. Add support to the SourceColumn class to resolve - Internal change. Add support to the SourceColumn class to resolve
related fields using the double underscore as separator. 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) 3.1.9 (2018-11-01)
================== ==================

View File

@@ -8,23 +8,9 @@ logger = logging.getLogger(__name__)
class ModelPermission(object): class ModelPermission(object):
_registry = {}
_proxies = {}
_inheritances = {} _inheritances = {}
_proxies = {}
@classmethod _registry = {}
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))
@classmethod @classmethod
def get_classes(cls, as_content_type=False): def get_classes(cls, as_content_type=False):
@@ -72,13 +58,29 @@ class ModelPermission(object):
return StoredPermission.objects.filter(pk__in=pks) return StoredPermission.objects.filter(pk__in=pks)
@classmethod @classmethod
def register_proxy(cls, source, model): def get_inheritance(cls, model):
cls._proxies[model] = source 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 @classmethod
def register_inheritance(cls, model, related): def register_inheritance(cls, model, related):
cls._inheritances[model] = related cls._inheritances[model] = related
@classmethod @classmethod
def get_inheritance(cls, model): def register_proxy(cls, source, model):
return cls._inheritances[model] cls._proxies[model] = source

View File

@@ -22,6 +22,7 @@ from .links import (
link_document_cabinet_remove, link_multiple_document_cabinet_remove link_document_cabinet_remove, link_multiple_document_cabinet_remove
) )
from .menus import menu_cabinets from .menus import menu_cabinets
from .methods import method_get_document_cabinets
from .permissions import ( from .permissions import (
permission_cabinet_add_document, permission_cabinet_delete, permission_cabinet_add_document, permission_cabinet_delete,
permission_cabinet_edit, permission_cabinet_remove_document, 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 # Add explicit order_by as DocumentCabinet ordering Meta option has no
# effect. # effect.
Document.add_to_class( Document.add_to_class(
name='document_cabinets', name='get_document_cabinets', value=method_get_document_cabinets
value=lambda document: DocumentCabinet.objects.filter(documents=document).order_by('parent__label', 'label')
) )
ModelPermission.register( 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() 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 @classmethod
def done(cls, wizard): def done(cls, wizard):
result = {} result = {}
@@ -44,6 +36,14 @@ class WizardStepCabinets(WizardStep):
return result 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 @classmethod
def step_post_upload_process(cls, document, querystring=None): def step_post_upload_process(cls, document, querystring=None):
furl_instance = furl(querystring) furl_instance = furl(querystring)

View File

@@ -27,6 +27,10 @@ from .links import (
link_checkout_list link_checkout_list
) )
from .literals import CHECK_EXPIRED_CHECK_OUTS_INTERVAL 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 ( from .permissions import (
permission_document_checkin, permission_document_checkin_override, permission_document_checkin, permission_document_checkin_override,
permission_document_checkout, permission_document_checkout_detail_view permission_document_checkout, permission_document_checkout_detail_view
@@ -54,29 +58,15 @@ class CheckoutsApp(MayanAppConfig):
app_label='documents', model_name='DocumentVersion' 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( Document.add_to_class(
name='check_in', name='get_checkout_info', value=method_get_checkout_info
value=lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user)
) )
Document.add_to_class( Document.add_to_class(
name='checkout_info', name='get_checkout_state', value=method_get_checkout_state
value=lambda document: DocumentCheckout.objects.document_checkout_info(
document
)
) )
Document.add_to_class( Document.add_to_class(
name='checkout_state', name='is_checked_out', value=method_is_checked_out
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
)
) )
ModelEventType.register( 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): def get_by_natural_key(self, document_natural_key):
Document = apps.get_model( Document = apps.get_model(
app_label='documents', model_name='Document' app_label='documents', model_name='Document'
@@ -94,6 +73,27 @@ class DocumentCheckoutManager(models.Manager):
return self.get(document__pk=document.pk) 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): def is_document_checked_out(self, document):
if self.model.objects.filter(document=document): if self.model.objects.filter(document=document):
return True 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'), 'name': _('User'),
'attribute': encapsulate( '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'), 'name': _('Checkout time and date'),
'attribute': encapsulate( 'attribute': encapsulate(
lambda document: document.checkout_info().checkout_datetime lambda document: document.get_checkout_info().checkout_datetime
) )
}, },
{ {
'name': _('Checkout expiration'), 'name': _('Checkout expiration'),
'attribute': encapsulate( '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, 'object': document,
} }
if document.checkout_info().user != self.request.user: if document.get_checkout_info().user != self.request.user:
context['title'] = _( context['title'] = _(
'You didn\'t originally checked out this document. ' 'You didn\'t originally checked out this document. '
'Forcefully check in the document: %s?' 'Forcefully check in the document: %s?'
@@ -159,7 +159,7 @@ class DocumentCheckinView(ConfirmView):
def view_action(self): def view_action(self):
document = self.get_object() 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( AccessControlList.objects.check_access(
permissions=permission_document_checkin, permissions=permission_document_checkin,
user=self.request.user, obj=document user=self.request.user, obj=document

View File

@@ -226,13 +226,27 @@ class ModelAttribute(object):
def __str__(self): def __str__(self):
return self.get_display() 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: 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( 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: 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): class ModelField(ModelAttribute):
@@ -344,31 +358,6 @@ class Package(object):
self.__class__._registry.append(self) 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): class Template(object):
_registry = {} _registry = {}

View File

@@ -12,7 +12,7 @@ class ErrorLogEntryManager(models.Manager):
app_label='common', model_name='ErrorLogEntry' app_label='common', model_name='ErrorLogEntry'
) )
model.add_to_class( 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_INDEX_SLUG = 'test_slug'
TEST_METADATA_TYPE_LABEL = 'test metadata label' TEST_METADATA_TYPE_LABEL = 'test metadata label'
TEST_METADATA_TYPE_NAME = 'test_metadata_name' 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_LABEL_EXPRESSION = '{{ document.label }}'
TEST_INDEX_TEMPLATE_DOCUMENT_DESCRIPTION_EXPRESSION = '{{ document.description }}' TEST_INDEX_TEMPLATE_DOCUMENT_DESCRIPTION_EXPRESSION = '{{ document.description }}'

View File

@@ -1,11 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import logging import logging
from datetime import timedelta
from django.apps import apps from django.apps import apps
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from kombu import Exchange, Queue from kombu import Exchange, Queue
@@ -16,14 +14,12 @@ from mayan.apps.common import (
menu_tools menu_tools
) )
from mayan.apps.common.classes import ModelAttribute, ModelField 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.search import document_page_search, document_search
from mayan.apps.documents.signals import post_version_upload from mayan.apps.documents.signals import post_version_upload
from mayan.apps.documents.widgets import document_link from mayan.apps.documents.widgets import document_link
from mayan.apps.navigation import SourceColumn from mayan.apps.navigation import SourceColumn
from mayan.celery import app from mayan.celery import app
from .events import event_parsing_document_version_submit
from .handlers import ( from .handlers import (
handler_index_document, handler_initialize_new_parsing_settings, handler_index_document, handler_initialize_new_parsing_settings,
handler_parse_document_version handler_parse_document_version
@@ -35,36 +31,20 @@ from .links import (
link_document_type_parsing_settings, link_document_type_submit, link_document_type_parsing_settings, link_document_type_submit,
link_error_list 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 ( from .permissions import (
permission_content_view, permission_document_type_parsing_setup, permission_content_view, permission_document_type_parsing_setup,
permission_parse_document permission_parse_document
) )
from .signals import post_document_version_parsing from .signals import post_document_version_parsing
from .utils import document_property_content, get_document_content
logger = logging.getLogger(__name__) 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): class DocumentParsingApp(MayanAppConfig):
app_namespace = 'document_parsing' app_namespace = 'document_parsing'
app_url = 'parsing' app_url = 'parsing'
@@ -96,26 +76,24 @@ class DocumentParsingApp(MayanAppConfig):
) )
Document.add_to_class( 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( Document.add_to_class(
name='content', value=document_property_content name='get_content', value=method_get_document_content
) )
DocumentVersion.add_to_class( DocumentVersion.add_to_class(
name='content', value=get_document_content name='get_content', value=method_get_document_version_content
) )
DocumentVersion.add_to_class( 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( ModelAttribute(model=Document, name='get_content')
model=Document, name='content', description=_(
'The parsed content of the document.'
)
)
ModelField( ModelField(
Document, name='versions__pages__content__content' model=Document, name='versions__pages__content__content'
) )
ModelPermission.register( 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 from __future__ import unicode_literals
TEST_DOCUMENT_CONTENT = 'Sample text' 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._create_document_type()
self.document = self.upload_document() self.document = self.upload_document()
self.assertTrue( 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_content_view, permission_document_type_parsing_setup,
permission_parse_document permission_parse_document
) )
from ..utils import get_document_content from ..utils import get_document_content_iterator
from .literals import TEST_DOCUMENT_CONTENT from .literals import TEST_DOCUMENT_CONTENT
@@ -89,7 +89,7 @@ class DocumentContentViewsTestCase(GenericDocumentViewTestCase):
self.assert_download_response( self.assert_download_response(
response=response, content=( 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() response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue( 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): def test_document_type_submit_view_with_access(self):
@@ -142,5 +142,5 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_submit_view() response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertTrue( 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 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( DocumentPageContent = apps.get_model(
app_label='document_parsing', model_name='DocumentPageContent' app_label='document_parsing', model_name='DocumentPageContent'
) )
for page in document.pages.all(): for page in document_version.pages.all():
try: try:
page_content = page.content.content page_content = page.content.content
except DocumentPageContent.DoesNotExist: except DocumentPageContent.DoesNotExist:
return return
else: else:
yield conditional_escape(force_text(page_content)) 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.contrib import messages
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -20,7 +21,7 @@ from .permissions import (
permission_content_view, permission_document_type_parsing_setup, permission_content_view, permission_document_type_parsing_setup,
permission_parse_document permission_parse_document
) )
from .utils import get_document_content from .utils import get_document_content_iterator
class DocumentContentView(SingleObjectDetailView): class DocumentContentView(SingleObjectDetailView):
@@ -50,7 +51,7 @@ class DocumentContentDownloadView(SingleObjectDownloadView):
def get_file(self): def get_file(self):
file_object = DocumentContentDownloadView.TextIteratorIO( file_object = DocumentContentDownloadView.TextIteratorIO(
iterator=get_document_content(document=self.get_object()) iterator=get_document_content_iterator(document=self.get_object())
) )
return DocumentContentDownloadView.VirtualFile( return DocumentContentDownloadView.VirtualFile(
file=file_object, name='{}-content'.format(self.get_object()) 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.apps.navigation import SourceColumn
from mayan.celery import app from mayan.celery import app
from .classes import DocumentStateHelper, WorkflowAction from .classes import WorkflowAction
from .handlers import ( from .handlers import (
handler_index_document, handler_trigger_transition, launch_workflow handler_index_document, handler_trigger_transition, launch_workflow
) )
from .methods import method_get_workflow
from .links import ( from .links import (
link_document_workflow_instance_list, link_setup_workflow_create, link_document_workflow_instance_list, link_setup_workflow_create,
link_setup_workflow_delete, link_setup_workflow_document_types, link_setup_workflow_delete, link_setup_workflow_document_types,
@@ -84,28 +85,14 @@ class DocumentStatesApp(MayanAppConfig):
) )
Document.add_to_class( Document.add_to_class(
name='workflow', value=DocumentStateHelper.constructor name='get_workflow', value=method_get_workflow
) )
ErrorLogEntry.objects.register(model=WorkflowStateAction) ErrorLogEntry.objects.register(model=WorkflowStateAction)
WorkflowAction.initialize() WorkflowAction.initialize()
ModelAttribute( ModelAttribute(model=Document, name='get_workflow')
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'
)
)
ModelPermission.register( ModelPermission.register(
model=Document, permissions=(permission_workflow_view,) 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 import six
from django.utils.encoding import force_text from django.utils.encoding import force_text
from mayan.apps.common.classes import PropertyHelper
__all__ = ('WorkflowAction',) __all__ = ('WorkflowAction',)
logger = logging.getLogger(__name__) 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): class WorkflowActionMetaclass(type):
_registry = {} _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_2 = 'test transition label 2'
TEST_WORKFLOW_TRANSITION_LABEL_EDITED = 'test transition label edited' 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 TEST_WORKFLOW_INTERNAL_NAME
) )

View File

@@ -10,6 +10,7 @@ from mayan.apps.acls import ModelPermission
from mayan.apps.common import ( from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_tools 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.document_indexing.handlers import handler_index_document
from mayan.apps.documents.search import document_page_search, document_search from mayan.apps.documents.search import document_page_search, document_search
from mayan.apps.documents.signals import post_version_upload 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_submit, link_document_submit_multiple,
link_document_type_file_metadata_settings, link_document_type_submit 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 ( from .permissions import (
permission_document_type_file_metadata_setup, permission_document_type_file_metadata_setup,
permission_file_metadata_submit, permission_file_metadata_view permission_file_metadata_submit, permission_file_metadata_view
) )
from .signals import post_document_version_file_metadata_processing 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): class FileMetadataApp(MayanAppConfig):
@@ -78,14 +79,16 @@ class FileMetadataApp(MayanAppConfig):
name='get_file_metadata', name='get_file_metadata',
value=method_get_document_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( DocumentVersion.add_to_class(
name='get_file_metadata', name='get_file_metadata',
value=method_get_document_version_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( ModelEventType.register(
model=Document, event_types=( 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( ModelPermission.register(
model=Document, permissions=( model=Document, permissions=(
permission_file_metadata_submit, permission_file_metadata_view, permission_file_metadata_submit, permission_file_metadata_view,

View File

@@ -1,5 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from .events import event_file_metadata_document_version_submit from .events import event_file_metadata_document_version_submit
from .tasks import task_process_document_version 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): def method_get_document_version_file_metadata(self, dotted_name):
driver_internal_name, key = dotted_name.split('.') 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 return document_driver.entries.get(key=key).value
except document_driver.entries.model.DoesNotExist: except document_driver.entries.model.DoesNotExist:
return 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( 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( 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: class Meta:

View File

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

View File

@@ -1,17 +1,5 @@
from __future__ import unicode_literals 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): class MetadataLookup(object):
_registry = [] _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 __future__ import unicode_literals
from datetime import timedelta
import logging import logging
from kombu import Exchange, Queue from kombu import Exchange, Queue
from django.apps import apps from django.apps import apps
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls import ModelPermission from mayan.apps.acls import ModelPermission
@@ -16,14 +14,12 @@ from mayan.apps.common import (
menu_tools menu_tools
) )
from mayan.apps.common.classes import ModelAttribute, ModelField 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.search import document_search, document_page_search
from mayan.apps.documents.signals import post_version_upload from mayan.apps.documents.signals import post_version_upload
from mayan.apps.documents.widgets import document_link from mayan.apps.documents.widgets import document_link
from mayan.apps.navigation import SourceColumn from mayan.apps.navigation import SourceColumn
from mayan.celery import app from mayan.celery import app
from .events import event_ocr_document_version_submit
from .handlers import ( from .handlers import (
handler_index_document, handler_initialize_new_ocr_settings, handler_index_document, handler_initialize_new_ocr_settings,
handler_ocr_document_version, handler_ocr_document_version,
@@ -35,37 +31,20 @@ from .links import (
link_document_type_ocr_settings, link_document_type_submit, link_document_type_ocr_settings, link_document_type_submit,
link_entry_list 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 ( from .permissions import (
permission_document_type_ocr_setup, permission_ocr_document, permission_document_type_ocr_setup, permission_ocr_document,
permission_ocr_content_view permission_ocr_content_view
) )
from .queues import * # NOQA from .queues import * # NOQA
from .signals import post_document_version_ocr from .signals import post_document_version_ocr
from .utils import document_property_ocr_content
logger = logging.getLogger(__name__) 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): class OCRApp(MayanAppConfig):
app_namespace = 'ocr' app_namespace = 'ocr'
app_url = 'ocr' app_url = 'ocr'
@@ -96,22 +75,24 @@ class OCRApp(MayanAppConfig):
DocumentVersionOCRError = self.get_model('DocumentVersionOCRError') DocumentVersionOCRError = self.get_model('DocumentVersionOCRError')
Document.add_to_class( 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( 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( 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( ModelAttribute(model=Document, name='get_ocr_content')
model=Document, name='ocr_content', description=_(
'The OCR content of the document.'
)
)
ModelField( ModelField(
Document, name='versions__pages__ocr_content__content' model=Document, name='versions__pages__ocr_content__content'
) )
ModelPermission.register( 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 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' TEST_OCR_INDEX_NODE_TEMPLATE_LEVEL = 'mayan'

View File

@@ -82,7 +82,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
) )
self._request_document_submit_view() self._request_document_submit_view()
self.assertTrue( 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): def _request_multiple_document_submit_view(self):
@@ -95,7 +95,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
def test_multiple_document_submit_view_no_permission(self): def test_multiple_document_submit_view_no_permission(self):
self._request_multiple_document_submit_view() 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): def test_multiple_document_submit_view_with_access(self):
self.grant_access( self.grant_access(
@@ -103,7 +103,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
) )
self._request_multiple_document_submit_view() self._request_multiple_document_submit_view()
self.assertTrue( 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): def _request_document_ocr_download_view(self):
@@ -126,7 +126,7 @@ class OCRViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assert_download_response( 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() response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue( 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): def test_document_type_submit_view_with_access(self):
@@ -179,5 +179,5 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
response = self._request_document_type_submit_view() response = self._request_document_type_submit_view()
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertTrue( 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 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( DocumentPageOCRContent = apps.get_model(
app_label='ocr', model_name='DocumentPageOCRContent' app_label='ocr', model_name='DocumentPageOCRContent'
) )
for page in document.pages.all(): for page in document_version.pages.all():
try: try:
page_content = page.ocr_content.content page_content = page.ocr_content.content
except DocumentPageOCRContent.DoesNotExist: except DocumentPageOCRContent.DoesNotExist:
return return
else: else:
yield force_text(page_content) 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_ocr_content_view, permission_ocr_document,
permission_document_type_ocr_setup permission_document_type_ocr_setup
) )
from .utils import get_document_ocr_content from .utils import get_document_content_iterator
class DocumentOCRContentView(SingleObjectDetailView): class DocumentOCRContentView(SingleObjectDetailView):
@@ -49,7 +49,7 @@ class DocumentOCRDownloadView(SingleObjectDownloadView):
def get_file(self): def get_file(self):
file_object = DocumentOCRDownloadView.TextIteratorIO( 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( return DocumentOCRDownloadView.VirtualFile(
file=file_object, name='{}-OCR'.format(self.get_object()) file=file_object, name='{}-OCR'.format(self.get_object())

View File

@@ -117,7 +117,7 @@ class APIDocumentTagListView(generics.ListCreateAPIView):
obj=document obj=document
) )
return document.attached_tags().all() return document.get_tags().all()
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
if not self.request: if not self.request:
@@ -171,7 +171,7 @@ class APIDocumentTagView(generics.RetrieveDestroyAPIView):
return document return document
def get_queryset(self): def get_queryset(self):
return self.get_document().attached_tags().all() return self.get_document().get_tags().all()
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):
if not self.request: 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, MayanAppConfig, menu_facet, menu_list_facet, menu_object, menu_main,
menu_multi_item, menu_sidebar 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.documents.search import document_page_search, document_search
from mayan.apps.events import ModelEventType from mayan.apps.events import ModelEventType
from mayan.apps.events.links import ( from mayan.apps.events.links import (
@@ -32,6 +32,7 @@ from .links import (
link_tag_multiple_delete, link_tag_tagged_item_list link_tag_multiple_delete, link_tag_tagged_item_list
) )
from .menus import menu_tags from .menus import menu_tags
from .methods import method_get_tags
from .permissions import ( from .permissions import (
permission_tag_attach, permission_tag_delete, permission_tag_edit, permission_tag_attach, permission_tag_delete, permission_tag_edit,
permission_tag_remove, permission_tag_view permission_tag_remove, permission_tag_view
@@ -65,10 +66,9 @@ class TagsApp(MayanAppConfig):
DocumentTag = self.get_model('DocumentTag') DocumentTag = self.get_model('DocumentTag')
Tag = self.get_model('Tag') Tag = self.get_model('Tag')
Document.add_to_class( Document.add_to_class(name='get_tags', value=method_get_tags)
name='attached_tags',
value=lambda document: DocumentTag.objects.filter(documents=document) ModelAttribute(model=Document, name='get_tags')
)
ModelEventType.register( ModelEventType.register(
model=Tag, event_types=( 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 ( from .events import (
event_tag_attach, event_tag_created, event_tag_edited, event_tag_remove event_tag_attach, event_tag_created, event_tag_edited, event_tag_remove
) )
from .managers import DocumentTagManager
from .widgets import widget_single_tag from .widgets import widget_single_tag
@@ -95,6 +96,8 @@ class Tag(models.Model):
class DocumentTag(Tag): class DocumentTag(Tag):
objects = DocumentTagManager()
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _('Document tag') 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_HAS_TAG = 'HAS_TAG'
TEST_TAG_INDEX_NO_TAG = 'NO_TAG' TEST_TAG_INDEX_NO_TAG = 'NO_TAG'
TEST_TAG_INDEX_NODE_TEMPLATE = ''' TEST_TAG_INDEX_NODE_TEMPLATE = '''
{{% for tag in document.tags.all() %}} {{% for tag in document.get_tags().all() %}}
{{% if tag.label == "{}" %}} {{% if tag.label == "{}" %}}
{} {}
{{% else %}} {{% else %}}

View File

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