diff --git a/mayan/apps/control_codes/__init__.py b/mayan/apps/control_codes/__init__.py new file mode 100644 index 0000000000..6bf14b2f07 --- /dev/null +++ b/mayan/apps/control_codes/__init__.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals + +default_app_config = 'mayan.apps.control_codes.apps.ControlCodesApp' diff --git a/mayan/apps/control_codes/apps.py b/mayan/apps/control_codes/apps.py new file mode 100644 index 0000000000..2cad57f417 --- /dev/null +++ b/mayan/apps/control_codes/apps.py @@ -0,0 +1,60 @@ +from __future__ import unicode_literals + +from django.apps import apps +from django.db.models.signals import post_save +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.acls.classes import ModelPermission +from mayan.apps.acls.links import link_acl_list +from mayan.apps.acls.permissions import permission_acl_edit, permission_acl_view +from mayan.apps.documents.signals import post_version_upload +from mayan.apps.common.apps import MayanAppConfig +from mayan.apps.common.html_widgets import TwoStateWidget +from mayan.apps.common.menus import ( + menu_facet, menu_list_facet, menu_object, menu_secondary, menu_setup +) +from mayan.apps.events.classes import ModelEventType +from mayan.apps.events.links import ( + link_events_for_object, link_object_event_types_user_subcriptions_list +) +from mayan.apps.navigation.classes import SourceColumn + +from .handlers import handler_process_document_version +from .methods import method_document_submit, method_document_version_submit + + +class ControlCodesApp(MayanAppConfig): + app_namespace = 'control_codes' + app_url = 'control_codes' + has_rest_api = False + has_tests = False + name = 'mayan.apps.control_codes' + verbose_name = _('Control codes') + + def ready(self): + super(ControlCodesApp, self).ready() + from actstream import registry + + Document = apps.get_model( + app_label='documents', model_name='Document' + ) + DocumentType = apps.get_model( + app_label='documents', model_name='DocumentType' + ) + DocumentVersion = apps.get_model( + app_label='documents', model_name='DocumentVersion' + ) + + Document.add_to_class( + name='submit_for_control_codes_processing', + value=method_document_submit + ) + DocumentVersion.add_to_class( + name='submit_for_control_codes_processing', + value=method_document_version_submit + ) + + post_version_upload.connect( + dispatch_uid='control_codes_handler_process_document_version', + receiver=handler_process_document_version, sender=DocumentVersion + ) diff --git a/mayan/apps/control_codes/classes.py b/mayan/apps/control_codes/classes.py new file mode 100644 index 0000000000..c91837a241 --- /dev/null +++ b/mayan/apps/control_codes/classes.py @@ -0,0 +1,101 @@ +from __future__ import unicode_literals + +import logging + +from PIL import Image +from pyzbar.pyzbar import decode +import qrcode + +from django.apps import apps +from django.db import transaction + +from mayan.apps.common.serialization import yaml_dump, yaml_load +from mayan.apps.documents.literals import DOCUMENT_IMAGE_TASK_TIMEOUT +from mayan.apps.documents.tasks import task_generate_document_page_image + +CONTROL_CODE_MAGIC_NUMBER = 'MCTRL' +CONTROL_CODE_SEPARATOR = ':' +CONTROL_CODE_VERSION = '1' +logger = logging.getLogger(__name__) + + +class ControlCode(object): + _registry = {} + arguments = () + + @classmethod + def get(cls, name): + return cls._registry[name] + + @classmethod + def process_document_version(cls, document_version): + logger.info( + 'Starting processing document version: %s', document_version + ) + + for document_page in document_version.pages.all(): + task = task_generate_document_page_image.apply_async( + kwargs=dict( + document_page_id=document_page.pk + ) + ) + + cache_filename = task.get( + timeout=DOCUMENT_IMAGE_TASK_TIMEOUT, disable_sync_subtasks=False + ) + + with document_page.cache_partition.get_file(filename=cache_filename).open() as file_object: + image = Image.open(file_object) + for code in decode(image): + logger.debug('code found: %s', code) + parts = code.data.split(CONTROL_CODE_SEPARATOR) + + if parts[0] == CONTROL_CODE_MAGIC_NUMBER: + # Version + if parts[1] == '1': + try: + control_code_class = ControlCode.get(name=parts[2]) + except KeyError: + # Unknown control code name + pass + else: + document_page.enabled = False + document_page.save() + arguments = CONTROL_CODE_SEPARATOR.join(parts[3:]) + control_code = control_code_class( + **yaml_load(arguments) + ) + control_code.execute() + + @classmethod + def register(cls, control_code): + cls._registry[control_code.name] = control_code + + def __init__(self, **kwargs): + self.kwargs = {} + for argument_name in self.arguments: + setattr(self, argument_name, kwargs.get(argument_name)) + self.kwargs[argument_name] = kwargs.get(argument_name) + + @property + def image(self): + return qrcode.make(self.get_qrcode_string()) + + def get_qrcode_string(self): + result = [] + result.append(CONTROL_CODE_MAGIC_NUMBER) + result.append(CONTROL_CODE_VERSION) + result.append(self.name) + result.append( + yaml_dump( + data=self.kwargs, allow_unicode=True, default_flow_style=True + ) + ) + + return CONTROL_CODE_SEPARATOR.join(result) + + def execute(self): + raise NotImplementedError( + 'Your %s class has not defined the required ' + 'execue() method.' % self.__class__.__name__ + ) diff --git a/mayan/apps/control_codes/handlers.py b/mayan/apps/control_codes/handlers.py new file mode 100644 index 0000000000..5e61ac7ff5 --- /dev/null +++ b/mayan/apps/control_codes/handlers.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals + +from django.apps import apps + +#from .settings import setting_auto_process + + +def handler_initialize_new_document_type_settings(sender, instance, **kwargs): + DocumentTypeSettings = apps.get_model( + app_label='file_metadata', model_name='DocumentTypeSettings' + ) + + if kwargs['created']: + DocumentTypeSettings.objects.create( + auto_process=setting_auto_process.value, document_type=instance + ) + + +def handler_process_document_version(sender, instance, **kwargs): + #if instance.document.document_type.file_metadata_settings.auto_process: + instance.submit_for_control_codes_processing() diff --git a/mayan/apps/control_codes/icons.py b/mayan/apps/control_codes/icons.py new file mode 100644 index 0000000000..75b7c2025b --- /dev/null +++ b/mayan/apps/control_codes/icons.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, unicode_literals + +from mayan.apps.appearance.classes import Icon +from mayan.apps.documents.icons import icon_document_type + +icon_control_code = Icon(driver_name='fontawesome', symbol='gear') diff --git a/mayan/apps/control_codes/links.py b/mayan/apps/control_codes/links.py new file mode 100644 index 0000000000..0e403f512a --- /dev/null +++ b/mayan/apps/control_codes/links.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.documents.permissions import permission_document_type_edit +from mayan.apps.navigation.classes import Link + +from .icons import icon_control_code +from .permissions import ( + permission_web_link_create, permission_web_link_delete, + permission_web_link_edit, permission_web_link_instance_view, +) + +link_control_code_tools = Link( + icon_class=icon_control_code, text=_('Control codes'), + view='control_codes:control_code_create' +) diff --git a/mayan/apps/control_codes/methods.py b/mayan/apps/control_codes/methods.py new file mode 100644 index 0000000000..8103489075 --- /dev/null +++ b/mayan/apps/control_codes/methods.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from .tasks import task_process_document_version + + +def method_document_submit(self): + latest_version = self.latest_version + # Don't error out if document has no version + if latest_version: + latest_version.submit_for_control_codes_processing() + + +def method_document_version_submit(self): + task_process_document_version.apply_async( + kwargs={ + 'document_version_id': self.pk, + } + ) diff --git a/mayan/apps/control_codes/queues.py b/mayan/apps/control_codes/queues.py new file mode 100644 index 0000000000..bed92fa395 --- /dev/null +++ b/mayan/apps/control_codes/queues.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.task_manager.classes import CeleryQueue +from mayan.apps.task_manager.workers import worker_medium + +queue = CeleryQueue( + label=_('Control codes'), name='control_codes', worker=worker_medium +) +queue.add_task_type( + label=_('Process document version'), + dotted_path='mayan.apps.control_codes.tasks.task_process_document_version' +) diff --git a/mayan/apps/control_codes/tasks.py b/mayan/apps/control_codes/tasks.py new file mode 100644 index 0000000000..b945e686a5 --- /dev/null +++ b/mayan/apps/control_codes/tasks.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals + +import logging + +from django.apps import apps + +from mayan.celery import app + +from .classes import ControlCode + +logger = logging.getLogger(__name__) + + +@app.task(ignore_result=True) +def task_process_document_version(document_version_id): + DocumentVersion = apps.get_model( + app_label='documents', model_name='DocumentVersion' + ) + + document_version = DocumentVersion.objects.get(pk=document_version_id) + + ControlCode.process_document_version( + document_version=document_version + ) diff --git a/mayan/apps/control_codes/tests/__init__.py b/mayan/apps/control_codes/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mayan/apps/control_codes/tests/literals.py b/mayan/apps/control_codes/tests/literals.py new file mode 100644 index 0000000000..76232389bc --- /dev/null +++ b/mayan/apps/control_codes/tests/literals.py @@ -0,0 +1,2 @@ +from __future__ import unicode_literals + diff --git a/mayan/apps/control_codes/tests/mixins.py b/mayan/apps/control_codes/tests/mixins.py new file mode 100644 index 0000000000..3b5ac19ca6 --- /dev/null +++ b/mayan/apps/control_codes/tests/mixins.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + + + + diff --git a/mayan/apps/control_codes/tests/test_models.py b/mayan/apps/control_codes/tests/test_models.py new file mode 100644 index 0000000000..7bee662dcd --- /dev/null +++ b/mayan/apps/control_codes/tests/test_models.py @@ -0,0 +1,38 @@ +from __future__ import unicode_literals + +from django.test import override_settings + +from mayan.apps.common.tests.base import BaseTestCase +from mayan.apps.documents.tests.base import GenericDocumentTestCase +from mayan.apps.documents.tests.mixins import DocumentTestMixin + +from ..classes import ControlCode + +TEST_CONTROL_CODE_DOCUMENT_PATH = '/tmp/test_control_code.png' + + +class ControlCodeTest(ControlCode): + arguments = ('argument_1',) + label = 'Test' + name = 'test' + + def execute(self): + pass + + +ControlCode.register(control_code=ControlCodeTest) + + +class ControlCodeTestCase(GenericDocumentTestCase): + auto_upload_document = False + test_document_path = TEST_CONTROL_CODE_DOCUMENT_PATH + + def test_control_code_detection(self): + + with open(TEST_CONTROL_CODE_DOCUMENT_PATH, mode='wb') as file_object: + control_code = ControlCodeTest(argument_1='test argument value') + control_code.image.save(file_object) + + self.upload_document() + + print self.test_document.pages.count() diff --git a/mayan/apps/control_codes/urls.py b/mayan/apps/control_codes/urls.py new file mode 100644 index 0000000000..dd0f21c3d4 --- /dev/null +++ b/mayan/apps/control_codes/urls.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + +from django.conf.urls import url + +urlpatterns = [] diff --git a/mayan/settings/base.py b/mayan/settings/base.py index 816dc34c77..65f66cefa7 100644 --- a/mayan/settings/base.py +++ b/mayan/settings/base.py @@ -89,6 +89,7 @@ INSTALLED_APPS = ( 'mayan.apps.authentication', 'mayan.apps.autoadmin', 'mayan.apps.common', + 'mayan.apps.control_codes', 'mayan.apps.converter', 'mayan.apps.dashboards', 'mayan.apps.dependencies',