Compare commits
17 Commits
features/m
...
features/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e35cca704 | ||
|
|
661301f057 | ||
|
|
9448b148e9 | ||
|
|
a13f033104 | ||
|
|
968abe2cdb | ||
|
|
cca08d8103 | ||
|
|
3872db8c9f | ||
|
|
1315a74e27 | ||
|
|
8c6812203c | ||
|
|
4ed36e0114 | ||
|
|
c48fc203e3 | ||
|
|
af71ba927f | ||
|
|
894e009c2a | ||
|
|
f9ba08ee59 | ||
|
|
2819f9445b | ||
|
|
1b2ed08c7c | ||
|
|
c6c605e320 |
@@ -18,6 +18,7 @@ from mayan.apps.events.permissions import permission_events_view
|
||||
from mayan.apps.documents.search import document_page_search, document_search
|
||||
from mayan.apps.navigation.classes import SourceColumn
|
||||
|
||||
from .control_codes import * # NOQA
|
||||
from .dependencies import * # NOQA
|
||||
from .events import (
|
||||
event_cabinet_edited, event_cabinet_add_document,
|
||||
|
||||
31
mayan/apps/cabinets/control_codes.py
Normal file
31
mayan/apps/cabinets/control_codes.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
from mayan.apps.control_codes.classes import ControlCode
|
||||
|
||||
__all__ = ('ControlCodeCabinetDocumentAdd',)
|
||||
|
||||
|
||||
class ControlCodeCabinetDocumentAdd(ControlCode):
|
||||
arguments = ('label_path',)
|
||||
label = 'Add document to cabinet'
|
||||
name = 'cabinet_document_add_v1'
|
||||
|
||||
def execute(self, context):
|
||||
Cabinet = apps.get_model(
|
||||
app_label='cabinets', model_name='Cabinet'
|
||||
)
|
||||
|
||||
document = context['document_page'].document
|
||||
user = context.get('user', None)
|
||||
|
||||
queryset = Cabinet.objects.all()
|
||||
for label in self.kwargs['label_path']:
|
||||
cabinet = queryset.get(label=label)
|
||||
queryset = cabinet.get_children()
|
||||
|
||||
cabinet.document_add(document=document, _user=user)
|
||||
|
||||
|
||||
ControlCode.register(control_code=ControlCodeCabinetDocumentAdd)
|
||||
40
mayan/apps/cabinets/tests/test_control_codes.py
Normal file
40
mayan/apps/cabinets/tests/test_control_codes.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.documents.tests.base import GenericDocumentTestCase
|
||||
from mayan.apps.storage.utils import fs_cleanup, mkstemp
|
||||
|
||||
from ..control_codes import ControlCodeCabinetDocumentAdd
|
||||
|
||||
from .mixins import CabinetTestMixin, CabinetViewTestMixin
|
||||
|
||||
|
||||
class ControlCodeCabinetDocumentAddTestCase(
|
||||
CabinetTestMixin, CabinetViewTestMixin,
|
||||
GenericDocumentTestCase
|
||||
):
|
||||
auto_upload_document = False
|
||||
|
||||
def setUp(self):
|
||||
super(ControlCodeCabinetDocumentAddTestCase, self).setUp()
|
||||
self.test_document_path = mkstemp()[1]
|
||||
|
||||
self._create_test_cabinet()
|
||||
self._create_test_cabinet_child()
|
||||
|
||||
def tearDown(self):
|
||||
fs_cleanup(filename=self.test_document_path)
|
||||
super(ControlCodeCabinetDocumentAddTestCase, self).tearDown()
|
||||
|
||||
def test_control_code(self):
|
||||
with open(self.test_document_path, mode='wb') as file_object:
|
||||
control_code = ControlCodeCabinetDocumentAdd(
|
||||
label_path=(
|
||||
self.test_cabinet.label, self.test_cabinet_child.label
|
||||
),
|
||||
)
|
||||
control_code.get_image().save(file_object)
|
||||
|
||||
self.upload_document()
|
||||
self.assertEqual(
|
||||
self.test_document.cabinets.first(), self.test_cabinet_child
|
||||
)
|
||||
@@ -14,7 +14,9 @@ from .literals import TEST_CABINET_LABEL, TEST_CABINET_LABEL_EDITED
|
||||
from .mixins import CabinetTestMixin, CabinetViewTestMixin
|
||||
|
||||
|
||||
class CabinetViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericViewTestCase):
|
||||
class CabinetViewTestCase(
|
||||
CabinetTestMixin, CabinetViewTestMixin, GenericViewTestCase
|
||||
):
|
||||
def test_cabinet_create_view_no_permission(self):
|
||||
response = self._request_test_cabinet_create_view()
|
||||
self.assertEqual(response.status_code, 403)
|
||||
@@ -105,7 +107,9 @@ class CabinetViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericViewTes
|
||||
)
|
||||
|
||||
|
||||
class CabinetChildViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericViewTestCase):
|
||||
class CabinetChildViewTestCase(
|
||||
CabinetTestMixin, CabinetViewTestMixin, GenericViewTestCase
|
||||
):
|
||||
def setUp(self):
|
||||
super(CabinetChildViewTestCase, self).setUp()
|
||||
self._create_test_cabinet()
|
||||
@@ -157,7 +161,9 @@ class CabinetChildViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericVi
|
||||
self.assertEqual(Cabinet.objects.count(), cabinet_count - 1)
|
||||
|
||||
|
||||
class CabinetDocumentViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericDocumentViewTestCase):
|
||||
class CabinetDocumentViewTestCase(
|
||||
CabinetTestMixin, CabinetViewTestMixin, GenericDocumentViewTestCase
|
||||
):
|
||||
def _add_document_to_cabinet(self):
|
||||
return self.post(
|
||||
viewname='cabinets:document_cabinet_add', kwargs={
|
||||
|
||||
3
mayan/apps/control_codes/__init__.py
Normal file
3
mayan/apps/control_codes/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
default_app_config = 'mayan.apps.control_codes.apps.ControlCodesApp'
|
||||
23
mayan/apps/control_codes/admin.py
Normal file
23
mayan/apps/control_codes/admin.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import ControlSheet, ControlSheetCode
|
||||
|
||||
|
||||
class ControlSheetCodeInline(admin.StackedInline):
|
||||
allow_add = True
|
||||
classes = ('collapse-open',)
|
||||
extra = 1
|
||||
model = ControlSheetCode
|
||||
|
||||
|
||||
@admin.register(ControlSheet)
|
||||
class ControlSheetAdmin(admin.ModelAdmin):
|
||||
inlines = (ControlSheetCodeInline,)
|
||||
list_display = ('label', 'get_codes_count')
|
||||
|
||||
def get_codes_count(self, instance):
|
||||
return instance.codes.count()
|
||||
get_codes_count.short_description = _('Codes')
|
||||
186
mayan/apps/control_codes/api_views.py
Normal file
186
mayan/apps/control_codes/api_views.py
Normal file
@@ -0,0 +1,186 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.decorators.cache import cache_control, patch_cache_control
|
||||
|
||||
from rest_framework import generics
|
||||
|
||||
from mayan.apps.acls.models import AccessControlList
|
||||
from mayan.apps.rest_api.filters import MayanObjectPermissionsFilter
|
||||
from mayan.apps.rest_api.permissions import MayanPermission
|
||||
|
||||
from .literals import CONTROL_SHEET_CODE_IMAGE_TASK_TIMEOUT
|
||||
from .models import ControlSheet
|
||||
from .permissions import (
|
||||
permission_control_sheet_create, permission_control_sheet_delete,
|
||||
permission_control_sheet_edit, permission_control_sheet_view
|
||||
)
|
||||
from .serializers import (
|
||||
ControlSheetSerializer, ControlSheetCodeSerializer
|
||||
)
|
||||
|
||||
from .settings import settings_control_sheet_code_image_cache_time
|
||||
from .tasks import task_generate_control_sheet_code_image
|
||||
|
||||
|
||||
class ControlSheetAPIViewMixin(object):
|
||||
def get_control_sheet(self):
|
||||
if self.request.method == 'GET':
|
||||
permission = permission_control_sheet_view
|
||||
else:
|
||||
permission = permission_control_sheet_edit
|
||||
|
||||
queryset = AccessControlList.objects.restrict_queryset(
|
||||
permission=permission, queryset=ControlSheet.objects.all(),
|
||||
user=self.request.user
|
||||
)
|
||||
|
||||
return get_object_or_404(
|
||||
klass=queryset, pk=self.kwargs['control_sheet_id']
|
||||
)
|
||||
|
||||
|
||||
class APIControlSheetListView(generics.ListCreateAPIView):
|
||||
"""
|
||||
get: Returns a list of all the control sheets.
|
||||
post: Create a new control sheet.
|
||||
"""
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {'GET': (permission_control_sheet_view,)}
|
||||
mayan_view_permissions = {'POST': (permission_control_sheet_create,)}
|
||||
permission_classes = (MayanPermission,)
|
||||
queryset = ControlSheet.objects.all()
|
||||
serializer_class = ControlSheetSerializer
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
if not self.request:
|
||||
return None
|
||||
|
||||
return super(APIControlSheetListView, self).get_serializer(
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class APIControlSheetView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
delete: Delete the selected control sheet.
|
||||
get: Return the details of the selected control sheet.
|
||||
patch: Edit the selected control sheet.
|
||||
put: Edit the selected control sheet.
|
||||
"""
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {
|
||||
'DELETE': (permission_control_sheet_delete,),
|
||||
'GET': (permission_control_sheet_view,),
|
||||
'PATCH': (permission_control_sheet_edit,),
|
||||
'PUT': (permission_control_sheet_edit,)
|
||||
}
|
||||
lookup_url_kwarg = 'control_sheet_id'
|
||||
queryset = ControlSheet.objects.all()
|
||||
serializer_class = ControlSheetSerializer
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
if not self.request:
|
||||
return None
|
||||
|
||||
return super(APIControlSheetView, self).get_serializer(
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class APIControlSheetCodeListView(
|
||||
ControlSheetAPIViewMixin, generics.ListCreateAPIView
|
||||
):
|
||||
"""
|
||||
get: Returns a list of all the control sheet codes.
|
||||
post: Create a new control sheet code.
|
||||
"""
|
||||
serializer_class = ControlSheetCodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_control_sheet().codes.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""
|
||||
Extra context provided to the serializer class.
|
||||
"""
|
||||
context = super(
|
||||
APIControlSheetCodeListView, self
|
||||
).get_serializer_context()
|
||||
if self.kwargs:
|
||||
context.update(
|
||||
{
|
||||
'control_sheet': self.get_control_sheet(),
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class APIControlSheetCodeView(
|
||||
ControlSheetAPIViewMixin, generics.RetrieveUpdateDestroyAPIView
|
||||
):
|
||||
"""
|
||||
delete: Delete the selected control sheet code.
|
||||
get: Return the details of the selected control sheet code.
|
||||
patch: Edit the selected control sheet code.
|
||||
put: Edit the selected control sheet code.
|
||||
"""
|
||||
lookup_url_kwarg = 'control_sheet_code_id'
|
||||
serializer_class = ControlSheetCodeSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_control_sheet().codes.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""
|
||||
Extra context provided to the serializer class.
|
||||
"""
|
||||
context = super(APIControlSheetCodeView, self).get_serializer_context()
|
||||
if self.kwargs:
|
||||
context.update(
|
||||
{
|
||||
'control_sheet': self.get_control_sheet(),
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class APIControlSheetCodeImageView(
|
||||
ControlSheetAPIViewMixin, generics.RetrieveAPIView
|
||||
):
|
||||
"""
|
||||
get: Returns an image representation of the selected control_sheet.
|
||||
"""
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
lookup_url_kwarg = 'control_sheet_code_id'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_control_sheet().codes.all()
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
def get_serializer_class(self):
|
||||
return None
|
||||
|
||||
@cache_control(private=True)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
task = task_generate_control_sheet_code_image.apply_async(
|
||||
kwargs=dict(
|
||||
control_sheet_code_id=self.get_object().pk,
|
||||
)
|
||||
)
|
||||
|
||||
cache_filename = task.get(timeout=CONTROL_SHEET_CODE_IMAGE_TASK_TIMEOUT)
|
||||
cache_file = self.get_object().cache_partition.get_file(filename=cache_filename)
|
||||
with cache_file.open() as file_object:
|
||||
response = HttpResponse(file_object.read(), content_type='image')
|
||||
if '_hash' in request.GET:
|
||||
patch_cache_control(
|
||||
response,
|
||||
max_age=settings_control_sheet_code_image_cache_time.value
|
||||
)
|
||||
return response
|
||||
146
mayan/apps/control_codes/apps.py
Normal file
146
mayan/apps/control_codes/apps.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
from django.db.models.signals import post_migrate
|
||||
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_list_facet, menu_object, menu_secondary, menu_setup
|
||||
)
|
||||
from mayan.apps.navigation.classes import SourceColumn
|
||||
|
||||
from .control_codes import * # NOQA
|
||||
from .dependencies import * # NOQA
|
||||
from .handlers import (
|
||||
handler_create_control_sheet_codes_image_cache,
|
||||
handler_process_document_version
|
||||
)
|
||||
from .links import (
|
||||
link_control_sheet_create, link_control_sheet_delete,
|
||||
link_control_sheet_edit, link_control_sheet_list,
|
||||
link_control_sheet_preview, link_control_sheet_print,
|
||||
link_control_sheet_code_delete, link_control_sheet_code_edit,
|
||||
link_control_sheet_code_list, link_control_sheet_code_select
|
||||
)
|
||||
from .methods import method_document_submit, method_document_version_submit
|
||||
from .permissions import (
|
||||
permission_control_sheet_delete, permission_control_sheet_edit,
|
||||
permission_control_sheet_view
|
||||
)
|
||||
|
||||
|
||||
class ControlCodesApp(MayanAppConfig):
|
||||
app_namespace = 'control_codes'
|
||||
app_url = 'control_codes'
|
||||
has_rest_api = True
|
||||
has_tests = False
|
||||
name = 'mayan.apps.control_codes'
|
||||
verbose_name = _('Control codes')
|
||||
|
||||
def ready(self):
|
||||
super(ControlCodesApp, self).ready()
|
||||
|
||||
ControlSheet = self.get_model(model_name='ControlSheet')
|
||||
ControlSheetCode = self.get_model(model_name='ControlSheetCode')
|
||||
Document = apps.get_model(
|
||||
app_label='documents', model_name='Document'
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=ControlSheet, permissions=(
|
||||
permission_acl_edit, permission_acl_view,
|
||||
permission_control_sheet_delete,
|
||||
permission_control_sheet_edit,
|
||||
permission_control_sheet_view
|
||||
)
|
||||
)
|
||||
ModelPermission.register_inheritance(
|
||||
model=ControlSheetCode, related='control_sheet',
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='label', is_identifier=True, is_sortable=True,
|
||||
source=ControlSheet
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='get_label', is_identifier=True,
|
||||
source=ControlSheetCode
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='order', is_sortable=True, source=ControlSheetCode
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='arguments', source=ControlSheetCode
|
||||
)
|
||||
SourceColumn(
|
||||
attribute='enabled', is_sortable=True, source=ControlSheetCode,
|
||||
widget=TwoStateWidget
|
||||
)
|
||||
|
||||
menu_list_facet.bind_links(
|
||||
links=(link_acl_list,), sources=(ControlSheet,)
|
||||
)
|
||||
menu_list_facet.bind_links(
|
||||
links=(
|
||||
link_control_sheet_preview, link_control_sheet_code_list,
|
||||
),
|
||||
sources=(ControlSheet,)
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_control_sheet_edit, link_control_sheet_delete,
|
||||
link_control_sheet_print,
|
||||
),
|
||||
sources=(ControlSheet,)
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=(
|
||||
link_control_sheet_code_delete, link_control_sheet_code_edit,
|
||||
),
|
||||
sources=(ControlSheetCode,)
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=(link_control_sheet_list, link_control_sheet_create),
|
||||
sources=(
|
||||
ControlSheet, 'control_codes:control_sheet_list',
|
||||
'control_codes:control_sheet_create'
|
||||
)
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=(link_control_sheet_code_select,),
|
||||
sources=(
|
||||
'control_codes:control_sheet_code_create',
|
||||
'control_codes:control_sheet_code_list',
|
||||
'control_codes:control_sheet_code_select', ControlSheetCode
|
||||
)
|
||||
)
|
||||
menu_setup.bind_links(
|
||||
links=(link_control_sheet_list,),
|
||||
)
|
||||
|
||||
post_migrate.connect(
|
||||
dispatch_uid='control_codes_handler_create_control_sheet_codes_image_cache',
|
||||
receiver=handler_create_control_sheet_codes_image_cache,
|
||||
)
|
||||
post_version_upload.connect(
|
||||
dispatch_uid='control_codes_handler_process_document_version',
|
||||
receiver=handler_process_document_version, sender=DocumentVersion
|
||||
)
|
||||
138
mayan/apps/control_codes/classes.py
Normal file
138
mayan/apps/control_codes/classes.py
Normal file
@@ -0,0 +1,138 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from PIL import Image
|
||||
from pyzbar.pyzbar import decode
|
||||
import qrcode
|
||||
|
||||
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||
from django.utils.translation import string_concat
|
||||
|
||||
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
|
||||
|
||||
from .literals import (CONTROL_CODE_MAGIC_NUMBER, CONTROL_CODE_SEPARATOR)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ControlCode(object):
|
||||
_registry = {}
|
||||
arguments = ()
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
return cls._registry.values()
|
||||
|
||||
@classmethod
|
||||
def get(cls, name):
|
||||
return cls._registry[name]
|
||||
|
||||
@classmethod
|
||||
def get_label(cls):
|
||||
if cls.arguments:
|
||||
return string_concat(cls.label, ': ', ', '.join(cls.arguments))
|
||||
else:
|
||||
return cls.label
|
||||
|
||||
@classmethod
|
||||
def get_choices(cls):
|
||||
return sorted(
|
||||
[
|
||||
(klass.name, klass.get_label()) for klass in cls.all()
|
||||
]
|
||||
)
|
||||
|
||||
@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
|
||||
)
|
||||
|
||||
results = []
|
||||
|
||||
# Collect control codes per page
|
||||
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:
|
||||
try:
|
||||
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:])
|
||||
results.append(
|
||||
{
|
||||
'order': parts[1], 'name': parts[2],
|
||||
'arguments': arguments
|
||||
}
|
||||
)
|
||||
|
||||
# Sort control codes so that they are executed in the
|
||||
# specified order after the collection finishes.
|
||||
results.sort(key=lambda x: x['order'])
|
||||
|
||||
context = {'document_page': document_page}
|
||||
|
||||
for result in results:
|
||||
control_code_class = ControlCode.get(name=result['name'])
|
||||
control_code = control_code_class(
|
||||
**yaml_load(result['arguments'])
|
||||
)
|
||||
control_code.execute(context=context)
|
||||
|
||||
@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:
|
||||
self.kwargs[argument_name] = kwargs.get(argument_name)
|
||||
|
||||
def __str__(self):
|
||||
return '{} {}'.format(self.label, self.kwargs)
|
||||
|
||||
def get_image(self, order=0):
|
||||
return qrcode.make(self.get_qrcode_string(order=order))
|
||||
|
||||
def get_qrcode_string(self, order=0):
|
||||
result = []
|
||||
result.append(CONTROL_CODE_MAGIC_NUMBER)
|
||||
result.append(force_text(order))
|
||||
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, context):
|
||||
raise NotImplementedError(
|
||||
'Your %s class has not defined the required '
|
||||
'execue() method.' % self.__class__.__name__
|
||||
)
|
||||
21
mayan/apps/control_codes/control_codes.py
Normal file
21
mayan/apps/control_codes/control_codes.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .classes import ControlCode
|
||||
|
||||
__all__ = ('ControlCodeAttributeEdit',)
|
||||
|
||||
|
||||
class ControlCodeAttributeEdit(ControlCode):
|
||||
arguments = ('name', 'value')
|
||||
label = 'Change document property'
|
||||
name = 'document_property_edit_v1'
|
||||
|
||||
def execute(self, context):
|
||||
document = context['document_page'].document
|
||||
user = context.get('user', None)
|
||||
|
||||
setattr(document, self.kwargs['name'], self.kwargs['value'])
|
||||
document.save(_user=user)
|
||||
|
||||
|
||||
ControlCode.register(control_code=ControlCodeAttributeEdit)
|
||||
92
mayan/apps/control_codes/dependencies.py
Normal file
92
mayan/apps/control_codes/dependencies.py
Normal file
@@ -0,0 +1,92 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.dependencies.classes import PythonDependency
|
||||
|
||||
PythonDependency(
|
||||
copyright_text='''
|
||||
Copyright (c) 2014, Polyconseil
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
''', help_text=_(
|
||||
'Read one-dimensional barcodes and QR codes from Python 2 and 3.'
|
||||
), module=__name__, name='pyzbar', version_string='==0.1.8')
|
||||
|
||||
PythonDependency(
|
||||
copyright_text='''
|
||||
Copyright (c) 2011, Lincoln Loop
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the package name nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Original text and license from the pyqrnative package where this was forked
|
||||
from (http://code.google.com/p/pyqrnative):
|
||||
|
||||
#Ported from the Javascript library by Sam Curren
|
||||
#
|
||||
#QRCode for Javascript
|
||||
#http://d-project.googlecode.com/svn/trunk/misc/qrcode/js/qrcode.js
|
||||
#
|
||||
#Copyright (c) 2009 Kazuhiko Arase
|
||||
#
|
||||
#URL: http://www.d-project.com/
|
||||
#
|
||||
#Licensed under the MIT license:
|
||||
# http://www.opensource.org/licenses/mit-license.php
|
||||
#
|
||||
# The word "QR Code" is registered trademark of
|
||||
# DENSO WAVE INCORPORATED
|
||||
# http://www.denso-wave.com/qrcode/faqpatent-e.html
|
||||
''', help_text=_('Python QR Code image generator.'), module=__name__,
|
||||
name='qrcode', version_string='==6.1'
|
||||
)
|
||||
42
mayan/apps/control_codes/forms.py
Normal file
42
mayan/apps/control_codes/forms.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import yaml
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.serialization import yaml_load
|
||||
|
||||
from .classes import ControlCode
|
||||
from .models import ControlSheetCode
|
||||
|
||||
|
||||
class ControlSheetCodeClassSelectionForm(forms.Form):
|
||||
control_code_class_name = forms.ChoiceField(
|
||||
choices=(), help_text=_('Available control codes.'),
|
||||
label=_('Control code'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ControlSheetCodeClassSelectionForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields[
|
||||
'control_code_class_name'
|
||||
].choices = ControlCode.get_choices()
|
||||
|
||||
|
||||
class ControlSheetCodeForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ('arguments', 'order', 'enabled')
|
||||
model = ControlSheetCode
|
||||
|
||||
def clean(self):
|
||||
try:
|
||||
yaml_load(stream=self.cleaned_data['arguments'])
|
||||
except yaml.YAMLError:
|
||||
raise ValidationError(
|
||||
_(
|
||||
'"%s" not a valid entry.'
|
||||
) % self.cleaned_data['arguments']
|
||||
)
|
||||
25
mayan/apps/control_codes/handlers.py
Normal file
25
mayan/apps/control_codes/handlers.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .literals import (
|
||||
CONTROL_SHEET_CODE_IMAGE_CACHE_NAME,
|
||||
CONTROL_SHEET_CODE_IMAGE_CACHE_STORAGE_INSTANCE_PATH
|
||||
)
|
||||
from .settings import setting_control_sheet_code_image_cache_maximum_size
|
||||
|
||||
|
||||
def handler_create_control_sheet_codes_image_cache(sender, **kwargs):
|
||||
Cache = apps.get_model(app_label='file_caching', model_name='Cache')
|
||||
Cache.objects.update_or_create(
|
||||
defaults={
|
||||
'label': _('Control sheet codes'),
|
||||
'storage_instance_path': CONTROL_SHEET_CODE_IMAGE_CACHE_STORAGE_INSTANCE_PATH,
|
||||
'maximum_size': setting_control_sheet_code_image_cache_maximum_size.value,
|
||||
}, name=CONTROL_SHEET_CODE_IMAGE_CACHE_NAME,
|
||||
)
|
||||
|
||||
|
||||
def handler_process_document_version(sender, instance, **kwargs):
|
||||
instance.submit_for_control_codes_processing()
|
||||
38
mayan/apps/control_codes/icons.py
Normal file
38
mayan/apps/control_codes/icons.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from mayan.apps.appearance.classes import Icon
|
||||
|
||||
icon_control_sheet = Icon(
|
||||
driver_name='fontawesome-layers', data=[
|
||||
{'class': 'far fa-file'},
|
||||
{'class': 'fas fa-barcode', 'transform': 'shrink-6'},
|
||||
], shadow_class='far fa-file',
|
||||
)
|
||||
icon_control_sheet_create = Icon(
|
||||
driver_name='fontawesome-layers', data=[
|
||||
{'class': 'far fa-file ', 'transform': 'grow-4'},
|
||||
{'class': 'fas fa-barcode', 'transform': 'shrink-6'},
|
||||
{'class': 'far fa-circle', 'transform': 'down-5 right-10'},
|
||||
{'class': 'fas fa-plus', 'transform': 'shrink-4 down-5 right-10'},
|
||||
],
|
||||
)
|
||||
icon_control_sheet_delete = Icon(driver_name='fontawesome', symbol='times')
|
||||
icon_control_sheet_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
|
||||
icon_control_sheet_list = icon_control_sheet
|
||||
icon_control_sheet_preview = Icon(driver_name='fontawesome', symbol='eye')
|
||||
icon_control_sheet_print = Icon(driver_name='fontawesome', symbol='print')
|
||||
|
||||
icon_control_sheet_code = Icon(driver_name='fontawesome', symbol='barcode')
|
||||
icon_control_sheet_code_delete = Icon(
|
||||
driver_name='fontawesome', symbol='times'
|
||||
)
|
||||
icon_control_sheet_code_edit = Icon(
|
||||
driver_name='fontawesome', symbol='pencil-alt'
|
||||
)
|
||||
icon_control_sheet_code_list = Icon(
|
||||
driver_name='fontawesome', symbol='barcode'
|
||||
)
|
||||
icon_control_sheet_code_select = Icon(
|
||||
driver_name='fontawesome-dual', primary_symbol='barcode',
|
||||
secondary_symbol='plus'
|
||||
)
|
||||
74
mayan/apps/control_codes/links.py
Normal file
74
mayan/apps/control_codes/links.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.navigation.classes import Link
|
||||
|
||||
from .permissions import (
|
||||
permission_control_sheet_create, permission_control_sheet_delete,
|
||||
permission_control_sheet_edit, permission_control_sheet_view,
|
||||
)
|
||||
|
||||
link_control_sheet_create = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_create',
|
||||
text=_('Create control sheet'), permissions=(permission_control_sheet_create,),
|
||||
view='control_codes:control_sheet_create'
|
||||
)
|
||||
link_control_sheet_delete = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_delete',
|
||||
kwargs={'control_sheet_id': 'resolved_object.pk'}, text=_('Delete'),
|
||||
permissions=(permission_control_sheet_delete,), tags='dangerous',
|
||||
view='control_codes:control_sheet_delete'
|
||||
)
|
||||
link_control_sheet_edit = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_edit',
|
||||
kwargs={'control_sheet_id': 'resolved_object.pk'}, text=_('Edit'),
|
||||
permissions=(permission_control_sheet_edit,),
|
||||
view='control_codes:control_sheet_edit'
|
||||
)
|
||||
link_control_sheet_list = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_list',
|
||||
text=_('Control sheets'), view='control_codes:control_sheet_list'
|
||||
)
|
||||
link_control_sheet_preview = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_preview',
|
||||
kwargs={'control_sheet_id': 'resolved_object.pk'},
|
||||
text=_('Preview'), permissions=(permission_control_sheet_view,),
|
||||
view='control_codes:control_sheet_preview'
|
||||
)
|
||||
link_control_sheet_print = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_print',
|
||||
kwargs={'control_sheet_id': 'resolved_object.pk'},
|
||||
text=_('Print'), permissions=(permission_control_sheet_view,),
|
||||
view='control_codes:control_sheet_print'
|
||||
)
|
||||
|
||||
link_control_sheet_code_select = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_code_select',
|
||||
kwargs={'control_sheet_id': 'control_sheet.pk'}, text=_('Add new code'),
|
||||
permissions=(permission_control_sheet_edit,),
|
||||
view='control_codes:control_sheet_code_select'
|
||||
)
|
||||
link_control_sheet_code_delete = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_code_delete',
|
||||
kwargs={
|
||||
'control_sheet_id': 'control_sheet.pk',
|
||||
'control_sheet_code_id': 'resolved_object.pk'
|
||||
}, tags='dangerous', text=_('Delete'),
|
||||
permissions=(permission_control_sheet_delete,),
|
||||
view='control_codes:control_sheet_code_delete'
|
||||
)
|
||||
link_control_sheet_code_edit = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_code_edit',
|
||||
kwargs={
|
||||
'control_sheet_id': 'control_sheet.pk',
|
||||
'control_sheet_code_id': 'resolved_object.pk'
|
||||
}, text=_('Edit'), permissions=(permission_control_sheet_edit,),
|
||||
view='control_codes:control_sheet_code_edit'
|
||||
)
|
||||
link_control_sheet_code_list = Link(
|
||||
icon_class_path='mayan.apps.control_codes.icons.icon_control_sheet_code_list',
|
||||
kwargs={'control_sheet_id': 'resolved_object.pk'},
|
||||
text=_('Codes'), permissions=(permission_control_sheet_view,),
|
||||
view='control_codes:control_sheet_code_list'
|
||||
)
|
||||
10
mayan/apps/control_codes/literals.py
Normal file
10
mayan/apps/control_codes/literals.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
CONTROL_CODE_MAGIC_NUMBER = 'MCTRL1'
|
||||
CONTROL_CODE_SEPARATOR = ':'
|
||||
|
||||
CONTROL_SHEET_CODE_IMAGE_CACHE_NAME = 'control_sheet_codes'
|
||||
CONTROL_SHEET_CODE_IMAGE_CACHE_STORAGE_INSTANCE_PATH = 'mayan.apps.control_codes.storages.storage_controlsheetcodeimagecache'
|
||||
CONTROL_SHEET_CODE_IMAGE_TASK_TIMEOUT = 60
|
||||
|
||||
DEFAULT_CONTROL_SHEET_CODE_IMAGE_CACHE_MAXIMUM_SIZE = 50 * 2 ** 20 # 50 Megabytes
|
||||
8
mayan/apps/control_codes/managers.py
Normal file
8
mayan/apps/control_codes/managers.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class ControlSheetCodeBusinessLogicManager(models.Manager):
|
||||
def enabled(self):
|
||||
return self.filter(enabled=True)
|
||||
18
mayan/apps/control_codes/methods.py
Normal file
18
mayan/apps/control_codes/methods.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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,
|
||||
}
|
||||
)
|
||||
50
mayan/apps/control_codes/migrations/0001_initial.py
Normal file
50
mayan/apps/control_codes/migrations/0001_initial.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.22 on 2019-09-06 02:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.db.models.manager
|
||||
import mayan.apps.common.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ControlSheet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('label', models.CharField(help_text='Short text to describe the control sheet.', max_length=196, unique=True, verbose_name='Label')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('label',),
|
||||
'verbose_name': 'Control sheet',
|
||||
'verbose_name_plural': 'Control sheets',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ControlSheetCode',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveIntegerField(blank=True, db_index=True, default=0, help_text='Order in which the control sheet codes will be interpreted. If left unchanged, an automatic order value will be assigned.', verbose_name='Order')),
|
||||
('name', models.CharField(max_length=128, verbose_name='Name')),
|
||||
('arguments', models.TextField(blank=True, help_text='The arguments for the control sheet code as a YAML dictionary.', validators=[mayan.apps.common.validators.YAMLValidator()], verbose_name='Arguments')),
|
||||
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
|
||||
('control_sheet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='codes', to='control_codes.ControlSheet', verbose_name='Control sheet')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('order',),
|
||||
'verbose_name': 'Control sheet code',
|
||||
'verbose_name_plural': 'Control sheet codes',
|
||||
},
|
||||
managers=[
|
||||
('business_logic', django.db.models.manager.Manager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
mayan/apps/control_codes/migrations/__init__.py
Normal file
0
mayan/apps/control_codes/migrations/__init__.py
Normal file
171
mayan/apps/control_codes/models.py
Normal file
171
mayan/apps/control_codes/models.py
Normal file
@@ -0,0 +1,171 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from furl import furl
|
||||
|
||||
from django.apps import apps
|
||||
from django.core import serializers
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import (
|
||||
force_bytes, force_text, python_2_unicode_compatible
|
||||
)
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.serialization import yaml_load
|
||||
from mayan.apps.common.validators import YAMLValidator
|
||||
|
||||
from .classes import ControlCode
|
||||
from .literals import CONTROL_SHEET_CODE_IMAGE_CACHE_NAME
|
||||
from .managers import ControlSheetCodeBusinessLogicManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ControlSheet(models.Model):
|
||||
label = models.CharField(
|
||||
help_text=_('Short text to describe the control sheet.'),
|
||||
max_length=196, unique=True, verbose_name=_('Label')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('label',)
|
||||
verbose_name = _('Control sheet')
|
||||
verbose_name_plural = _('Control sheets')
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
viewname='control_codes:control_sheet_detail', kwargs={
|
||||
'control_sheet_id': self.pk
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ControlSheetCode(models.Model):
|
||||
control_sheet = models.ForeignKey(
|
||||
on_delete=models.CASCADE, related_name='codes', to=ControlSheet,
|
||||
verbose_name=_('Control sheet')
|
||||
)
|
||||
order = models.PositiveIntegerField(
|
||||
blank=True, db_index=True, default=0, help_text=_(
|
||||
'Order in which the control sheet codes will be interpreted. '
|
||||
'If left unchanged, an automatic order value will be assigned.'
|
||||
), verbose_name=_('Order')
|
||||
)
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
arguments = models.TextField(
|
||||
blank=True, help_text=_(
|
||||
'The arguments for the control sheet code as a YAML '
|
||||
'dictionary.'
|
||||
), validators=[YAMLValidator()], verbose_name=_('Arguments')
|
||||
)
|
||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||
|
||||
business_logic = ControlSheetCodeBusinessLogicManager()
|
||||
objects = models.Manager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('order',)
|
||||
verbose_name = _('Control sheet code')
|
||||
verbose_name_plural = _('Control sheet codes')
|
||||
|
||||
def __str__(self):
|
||||
return force_text(self.get_label())
|
||||
|
||||
@cached_property
|
||||
def cache(self):
|
||||
Cache = apps.get_model(app_label='file_caching', model_name='Cache')
|
||||
return Cache.objects.get(name=CONTROL_SHEET_CODE_IMAGE_CACHE_NAME)
|
||||
|
||||
@cached_property
|
||||
def cache_partition(self):
|
||||
partition, created = self.cache.partitions.get_or_create(
|
||||
name='{}'.format(self.pk)
|
||||
)
|
||||
return partition
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
self.cache_partition.delete()
|
||||
return super(ControlSheetCode, self).delete(*args, **kwargs)
|
||||
|
||||
def generate_image(self):
|
||||
cache_filename = '{}'.format(self.get_hash())
|
||||
|
||||
if self.cache_partition.get_file(filename=cache_filename):
|
||||
logger.debug(
|
||||
'workflow cache file "%s" found', cache_filename
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
'workflow cache file "%s" not found', cache_filename
|
||||
)
|
||||
|
||||
image = self.render()
|
||||
with self.cache_partition.create_file(filename=cache_filename) as file_object:
|
||||
image.save(file_object)
|
||||
|
||||
return cache_filename
|
||||
|
||||
def get_api_image_url(self, *args, **kwargs):
|
||||
final_url = furl()
|
||||
final_url.args = kwargs
|
||||
final_url.path = reverse(
|
||||
viewname='rest_api:controlsheet-code-image',
|
||||
kwargs={
|
||||
'control_sheet_id': self.control_sheet.pk,
|
||||
'control_sheet_code_id': self.pk
|
||||
}
|
||||
)
|
||||
final_url.args['_hash'] = self.get_hash()
|
||||
|
||||
return final_url.tostr()
|
||||
|
||||
def get_arguments(self):
|
||||
return yaml_load(self.arguments or '{}')
|
||||
|
||||
def get_control_code_class(self):
|
||||
return ControlCode.get(name=self.name)
|
||||
|
||||
def get_control_code_instance(self):
|
||||
return self.get_control_code_class()(
|
||||
**self.get_arguments()
|
||||
)
|
||||
|
||||
def get_display(self):
|
||||
return force_text(self.get_control_code_instance())
|
||||
|
||||
def get_hash(self):
|
||||
objects_lists = list(
|
||||
ControlSheetCode.objects.filter(pk=self.pk)
|
||||
)
|
||||
|
||||
return hashlib.sha256(
|
||||
force_bytes(
|
||||
serializers.serialize('json', objects_lists)
|
||||
)
|
||||
).hexdigest()
|
||||
|
||||
def get_label(self):
|
||||
return force_text(self.get_control_code_class().label)
|
||||
get_label.short_description = _('Label')
|
||||
|
||||
def render(self):
|
||||
return self.get_control_code_instance().get_image(order=self.order)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.order:
|
||||
last_order = ControlSheetCode.objects.filter(
|
||||
control_sheet=self.control_sheet
|
||||
).aggregate(Max('order'))['order__max']
|
||||
if last_order is not None:
|
||||
self.order = last_order + 1
|
||||
super(ControlSheetCode, self).save(*args, **kwargs)
|
||||
21
mayan/apps/control_codes/permissions.py
Normal file
21
mayan/apps/control_codes/permissions.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace(
|
||||
label=_('Control codes'), name='control_codes'
|
||||
)
|
||||
permission_control_sheet_create = namespace.add_permission(
|
||||
label=_('Create new control sheets'), name='control_sheet_create'
|
||||
)
|
||||
permission_control_sheet_delete = namespace.add_permission(
|
||||
label=_('Delete control sheets'), name='control_sheet_delete'
|
||||
)
|
||||
permission_control_sheet_edit = namespace.add_permission(
|
||||
label=_('Edit control sheets'), name='control_sheet_edit'
|
||||
)
|
||||
permission_control_sheet_view = namespace.add_permission(
|
||||
label=_('View existing control sheets'), name='control_sheet_view'
|
||||
)
|
||||
23
mayan/apps/control_codes/queues.py
Normal file
23
mayan/apps/control_codes/queues.py
Normal file
@@ -0,0 +1,23 @@
|
||||
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_fast, 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'
|
||||
)
|
||||
|
||||
queue = CeleryQueue(
|
||||
label=_('Control codes fast'), name='control_codes_fast',
|
||||
worker=worker_fast
|
||||
)
|
||||
queue.add_task_type(
|
||||
label=_('Generate control sheet code image'),
|
||||
dotted_path='mayan.apps.control_codes.tasks.task_generate_control_sheet_code_image'
|
||||
)
|
||||
65
mayan/apps/control_codes/serializers.py
Normal file
65
mayan/apps/control_codes/serializers.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from mayan.apps.rest_api.relations import MultiKwargHyperlinkedIdentityField
|
||||
|
||||
from .models import ControlSheet, ControlSheetCode
|
||||
|
||||
|
||||
class ControlSheetSerializer(serializers.HyperlinkedModelSerializer):
|
||||
code_list_url = serializers.HyperlinkedIdentityField(
|
||||
lookup_url_kwarg='control_sheet_id',
|
||||
view_name='rest_api:controlsheet-code-list'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
extra_kwargs = {
|
||||
'url': {
|
||||
'lookup_url_kwarg': 'control_sheet_id',
|
||||
'view_name': 'rest_api:controlsheet-detail'
|
||||
},
|
||||
}
|
||||
fields = ('code_list_url', 'id', 'label', 'url')
|
||||
model = ControlSheet
|
||||
|
||||
|
||||
class ControlSheetCodeSerializer(serializers.HyperlinkedModelSerializer):
|
||||
control_sheet = ControlSheetSerializer(read_only=True)
|
||||
image_url = MultiKwargHyperlinkedIdentityField(
|
||||
view_kwargs=(
|
||||
{
|
||||
'lookup_field': 'control_sheet_id',
|
||||
'lookup_url_kwarg': 'control_sheet_id',
|
||||
},
|
||||
{
|
||||
'lookup_field': 'pk',
|
||||
'lookup_url_kwarg': 'control_sheet_code_id',
|
||||
}
|
||||
),
|
||||
view_name='rest_api:controlsheet-code-image'
|
||||
)
|
||||
url = MultiKwargHyperlinkedIdentityField(
|
||||
view_kwargs=(
|
||||
{
|
||||
'lookup_field': 'control_sheet_id',
|
||||
'lookup_url_kwarg': 'control_sheet_id',
|
||||
},
|
||||
{
|
||||
'lookup_field': 'pk',
|
||||
'lookup_url_kwarg': 'control_sheet_code_id',
|
||||
}
|
||||
),
|
||||
view_name='rest_api:controlsheet-code-detail'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = (
|
||||
'arguments', 'control_sheet', 'id', 'image_url', 'name',
|
||||
'order', 'url'
|
||||
)
|
||||
model = ControlSheetCode
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['control_sheet'] = self.context['control_sheet']
|
||||
return super(ControlSheetCodeSerializer, self).create(validated_data)
|
||||
44
mayan/apps/control_codes/settings.py
Normal file
44
mayan/apps/control_codes/settings.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.smart_settings.classes import Namespace
|
||||
|
||||
from .literals import DEFAULT_CONTROL_SHEET_CODE_IMAGE_CACHE_MAXIMUM_SIZE
|
||||
from .utils import callback_update_control_sheet_image_cache_size
|
||||
|
||||
namespace = Namespace(label=_('Control codes'), name='control_codes')
|
||||
|
||||
setting_control_sheet_code_image_cache_maximum_size = namespace.add_setting(
|
||||
global_name='CONTROL_SHEET_CODE_IMAGE_CACHE_MAXIMUM_SIZE',
|
||||
default=DEFAULT_CONTROL_SHEET_CODE_IMAGE_CACHE_MAXIMUM_SIZE,
|
||||
help_text=_(
|
||||
'The threshold at which the CONTROL_SHEET_CODE_IMAGE_CACHE_STORAGE_BACKEND '
|
||||
'will start deleting the oldest control sheet code image cache files. '
|
||||
'Specify the size in bytes.'
|
||||
), post_edit_function=callback_update_control_sheet_image_cache_size
|
||||
)
|
||||
settings_control_sheet_code_image_cache_time = namespace.add_setting(
|
||||
global_name='CONTROL_SHEETS_CODE_IMAGE_CACHE_TIME', default='31556926',
|
||||
help_text=_(
|
||||
'Time in seconds that the browser should cache the supplied control sheet '
|
||||
'code images. The default of 31559626 seconds corresponde to 1 year.'
|
||||
)
|
||||
)
|
||||
setting_control_sheet_code_image_cache_storage_dotted_path = namespace.add_setting(
|
||||
global_name='CONTROL_SHEETS_CODE_IMAGE_CACHE_STORAGE_BACKEND',
|
||||
default='django.core.files.storage.FileSystemStorage', help_text=_(
|
||||
'Path to the Storage subclass to use when storing the cached '
|
||||
'control sheet code image files.'
|
||||
)
|
||||
)
|
||||
setting_control_sheet_code_image_cache_storage_arguments = namespace.add_setting(
|
||||
global_name='CONTROL_SHEETS_CODE_IMAGE_CACHE_STORAGE_BACKEND_ARGUMENTS',
|
||||
default={'location': os.path.join(settings.MEDIA_ROOT, 'control_sheets')},
|
||||
help_text=_(
|
||||
'Arguments to pass to the CONTROL_SHEETS_CODE_IMAGE_CACHE_STORAGE_BACKEND.'
|
||||
)
|
||||
)
|
||||
12
mayan/apps/control_codes/storages.py
Normal file
12
mayan/apps/control_codes/storages.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.storage.utils import get_storage_subclass
|
||||
|
||||
from .settings import (
|
||||
setting_control_sheet_code_image_cache_storage_dotted_path,
|
||||
setting_control_sheet_code_image_cache_storage_arguments
|
||||
)
|
||||
|
||||
storage_controlsheetcodeimagecache = get_storage_subclass(
|
||||
dotted_path=setting_control_sheet_code_image_cache_storage_dotted_path.value
|
||||
)(**setting_control_sheet_code_image_cache_storage_arguments.value)
|
||||
35
mayan/apps/control_codes/tasks.py
Normal file
35
mayan/apps/control_codes/tasks.py
Normal file
@@ -0,0 +1,35 @@
|
||||
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()
|
||||
def task_generate_control_sheet_code_image(control_sheet_code_id):
|
||||
ControlSheetCode = apps.get_model(
|
||||
app_label='control_codes', model_name='ControlSheetCode'
|
||||
)
|
||||
|
||||
control_sheet_code = ControlSheetCode.objects.get(pk=control_sheet_code_id)
|
||||
|
||||
return control_sheet_code.generate_image()
|
||||
|
||||
|
||||
@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
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
<ul class="list-inline text-center">
|
||||
{% for code in object.codes.enabled %}
|
||||
<li class="list-inline-item">
|
||||
<img class="img-responsive" src="{{ code.get_api_image_url }}" />
|
||||
<p class="text-center">{{ code.get_label }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'appearance/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'control_codes/control_sheet_content.html' %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,29 @@
|
||||
{% extends 'appearance/base_plain.html' %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock title %}
|
||||
|
||||
{% block content_plain %}
|
||||
<style>
|
||||
.img-responsive {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.list-inline {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
margin-left: -5px;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.list-inline > li {
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1 class="text-center">{{ title }}</h1>
|
||||
{% include 'control_codes/control_sheet_content.html' %}
|
||||
{% endblock %}
|
||||
0
mayan/apps/control_codes/tests/__init__.py
Normal file
0
mayan/apps/control_codes/tests/__init__.py
Normal file
15
mayan/apps/control_codes/tests/control_codes.py
Normal file
15
mayan/apps/control_codes/tests/control_codes.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..classes import ControlCode
|
||||
|
||||
|
||||
class ControlCodeTest(ControlCode):
|
||||
arguments = ('argument_1',)
|
||||
label = 'Test'
|
||||
name = 'test'
|
||||
|
||||
def execute(self, context):
|
||||
pass
|
||||
|
||||
|
||||
ControlCode.register(control_code=ControlCodeTest)
|
||||
6
mayan/apps/control_codes/tests/literals.py
Normal file
6
mayan/apps/control_codes/tests/literals.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
TEST_CONTROL_SHEET_CODE_ARGUMENTS = '{"argument_1": ""}'
|
||||
TEST_CONTROL_SHEET_CODE_ARGUMENTS_EDITED = '{"argument_1": "edited"}'
|
||||
TEST_CONTROL_SHEET_LABEL = 'test control sheet'
|
||||
TEST_CONTROL_SHEET_LABEL_EDITED = 'test control sheet edited'
|
||||
157
mayan/apps/control_codes/tests/mixins.py
Normal file
157
mayan/apps/control_codes/tests/mixins.py
Normal file
@@ -0,0 +1,157 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from ..models import ControlSheet
|
||||
|
||||
from .control_codes import ControlCodeTest
|
||||
from .literals import (
|
||||
TEST_CONTROL_SHEET_CODE_ARGUMENTS,
|
||||
TEST_CONTROL_SHEET_CODE_ARGUMENTS_EDITED, TEST_CONTROL_SHEET_LABEL,
|
||||
TEST_CONTROL_SHEET_LABEL_EDITED
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetAPIViewTestMixin(object):
|
||||
def _request_test_control_sheet_create_api_view(self):
|
||||
return self.post(
|
||||
viewname='rest_api:control_sheet-list', data={
|
||||
'label': TEST_CONTROL_SHEET_LABEL,
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_delete_api_view(self):
|
||||
return self.delete(
|
||||
viewname='rest_api:control_sheet-detail', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_edit_api_view(self, extra_data=None, verb='patch'):
|
||||
data = {
|
||||
'label': TEST_CONTROL_SHEET_LABEL_EDITED,
|
||||
}
|
||||
|
||||
if extra_data:
|
||||
data.update(extra_data)
|
||||
|
||||
return getattr(self, verb)(
|
||||
viewname='rest_api:control_sheet-detail', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}, data=data
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetTestMixin(object):
|
||||
def _create_test_control_sheet(self):
|
||||
self.test_control_sheet = ControlSheet.objects.create(
|
||||
label=TEST_CONTROL_SHEET_LABEL
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetCodeTestMixin(object):
|
||||
_test_control_code_class = ControlCodeTest
|
||||
|
||||
def _create_test_control_sheet_code(self):
|
||||
self.test_control_sheet_code = self.test_control_sheet.codes.create(
|
||||
control_sheet=self.test_control_sheet,
|
||||
name=self._test_control_code_class.name,
|
||||
arguments=TEST_CONTROL_SHEET_CODE_ARGUMENTS
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetViewTestMixin(object):
|
||||
def _request_test_control_sheet_create_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_create', data={
|
||||
'label': TEST_CONTROL_SHEET_LABEL
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_delete_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_delete', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_delete_multiple_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_multiple_delete', data={
|
||||
'id_list': self.test_control_sheet.pk
|
||||
},
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_edit_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_edit', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}, data={
|
||||
'label': TEST_CONTROL_SHEET_LABEL_EDITED,
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_list_view(self):
|
||||
return self.get(viewname='control_codes:control_sheet_list')
|
||||
|
||||
def _request_test_control_sheet_preview_view(self):
|
||||
return self.get(
|
||||
viewname='control_codes:control_sheet_preview', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_print_view(self):
|
||||
return self.get(
|
||||
viewname='control_codes:control_sheet_print', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetCodeViewTestMixin(object):
|
||||
def _request_test_control_sheet_code_select_get_view(self):
|
||||
return self.get(
|
||||
viewname='control_codes:control_sheet_code_select', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk,
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_code_select_post_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_code_select', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk,
|
||||
}, data={'control_code_class_name': self._test_control_code_class.name}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_code_create_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_code_create', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk,
|
||||
'control_code_class_name': self._test_control_code_class.name
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_code_delete_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_code_delete',
|
||||
kwargs={
|
||||
'control_sheet_id': self.test_control_sheet_code.control_sheet.pk,
|
||||
'control_sheet_code_id': self.test_control_sheet_code.pk
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_code_edit_view(self):
|
||||
return self.post(
|
||||
viewname='control_codes:control_sheet_code_edit', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet_code.control_sheet.pk,
|
||||
'control_sheet_code_id': self.test_control_sheet_code.pk
|
||||
}, data={
|
||||
'arguments': TEST_CONTROL_SHEET_CODE_ARGUMENTS_EDITED,
|
||||
}
|
||||
)
|
||||
|
||||
def _request_test_control_sheet_code_list_view(self):
|
||||
return self.get(
|
||||
viewname='control_codes:control_sheet_code_list', kwargs={
|
||||
'control_sheet_id': self.test_control_sheet.pk
|
||||
}
|
||||
)
|
||||
31
mayan/apps/control_codes/tests/test_control_codes.py
Normal file
31
mayan/apps/control_codes/tests/test_control_codes.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.documents.tests.base import GenericDocumentTestCase
|
||||
from mayan.apps.storage.utils import fs_cleanup, mkstemp
|
||||
|
||||
from ..control_codes import ControlCodeAttributeEdit
|
||||
|
||||
TEST_ATTRIBUTE_VALUE = 'test value'
|
||||
|
||||
|
||||
class ControlCodeAttributeEditTestCase(GenericDocumentTestCase):
|
||||
auto_upload_document = False
|
||||
|
||||
def setUp(self):
|
||||
super(ControlCodeAttributeEditTestCase, self).setUp()
|
||||
self.test_document_path = mkstemp()[1]
|
||||
|
||||
def tearDown(self):
|
||||
fs_cleanup(filename=self.test_document_path)
|
||||
super(ControlCodeAttributeEditTestCase, self).tearDown()
|
||||
|
||||
def test_control_code(self):
|
||||
with open(self.test_document_path, mode='wb') as file_object:
|
||||
control_code = ControlCodeAttributeEdit(
|
||||
name='label', value=TEST_ATTRIBUTE_VALUE
|
||||
)
|
||||
control_code.get_image().save(file_object)
|
||||
|
||||
self.upload_document()
|
||||
self.test_document.refresh_from_db()
|
||||
self.assertEqual(self.test_document.label, TEST_ATTRIBUTE_VALUE)
|
||||
310
mayan/apps/control_codes/tests/test_views.py
Normal file
310
mayan/apps/control_codes/tests/test_views.py
Normal file
@@ -0,0 +1,310 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.common.tests.base import GenericViewTestCase
|
||||
|
||||
from ..models import ControlSheet, ControlSheetCode
|
||||
from ..permissions import (
|
||||
permission_control_sheet_create, permission_control_sheet_delete,
|
||||
permission_control_sheet_edit, permission_control_sheet_view
|
||||
)
|
||||
|
||||
from .mixins import (
|
||||
ControlSheetCodeTestMixin, ControlSheetCodeViewTestMixin,
|
||||
ControlSheetTestMixin, ControlSheetViewTestMixin
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetViewTestCase(
|
||||
ControlSheetTestMixin, ControlSheetViewTestMixin, GenericViewTestCase
|
||||
):
|
||||
_test_model = ControlSheet
|
||||
_test_permission_create = permission_control_sheet_create
|
||||
_test_permission_delete = permission_control_sheet_delete
|
||||
_test_permission_edit = permission_control_sheet_edit
|
||||
_test_permission_view = permission_control_sheet_view
|
||||
|
||||
def test_control_sheet_create_view_no_permissions(self):
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_create_view()
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count)
|
||||
|
||||
def test_control_sheet_create_view_with_permissions(self):
|
||||
self.grant_permission(permission=self._test_permission_create)
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_create_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count + 1)
|
||||
|
||||
def test_control_sheet_delete_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_delete_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count)
|
||||
|
||||
def test_control_sheet_delete_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet, permission=self._test_permission_delete
|
||||
)
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_delete_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count - 1)
|
||||
|
||||
def test_control_sheet_edit_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
control_sheet_label = self.test_control_sheet.label
|
||||
|
||||
response = self._request_test_control_sheet_edit_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.test_control_sheet.refresh_from_db()
|
||||
self.assertEqual(self.test_control_sheet.label, control_sheet_label)
|
||||
|
||||
def test_control_sheet_edit_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet, permission=self._test_permission_edit
|
||||
)
|
||||
|
||||
control_sheet_label = self.test_control_sheet.label
|
||||
|
||||
response = self._request_test_control_sheet_edit_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.test_control_sheet.refresh_from_db()
|
||||
self.assertNotEqual(self.test_control_sheet.label, control_sheet_label)
|
||||
|
||||
def test_control_sheet_list_view_no_permission(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
response = self._request_test_control_sheet_list_view()
|
||||
self.assertNotContains(
|
||||
response, text=self.test_control_sheet.label, status_code=200
|
||||
)
|
||||
|
||||
def test_control_sheet_list_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_view
|
||||
)
|
||||
|
||||
response = self._request_test_control_sheet_list_view()
|
||||
self.assertContains(
|
||||
response, text=self.test_control_sheet.label, status_code=200
|
||||
)
|
||||
|
||||
def test_control_sheet_preview_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
response = self._request_test_control_sheet_preview_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_control_sheet_preview_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet, permission=self._test_permission_view
|
||||
)
|
||||
|
||||
response = self._request_test_control_sheet_preview_view()
|
||||
self.assertContains(
|
||||
response=response, text=self.test_control_sheet.label,
|
||||
status_code=200
|
||||
)
|
||||
|
||||
def test_control_sheet_print_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
response = self._request_test_control_sheet_print_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_control_sheet_print_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet, permission=self._test_permission_view
|
||||
)
|
||||
|
||||
response = self._request_test_control_sheet_print_view()
|
||||
self.assertContains(
|
||||
response=response, text=self.test_control_sheet.label,
|
||||
status_code=200
|
||||
)
|
||||
|
||||
|
||||
class ControlSheetCodeViewTestCase(
|
||||
ControlSheetTestMixin, ControlSheetCodeTestMixin,
|
||||
ControlSheetCodeViewTestMixin, GenericViewTestCase
|
||||
):
|
||||
_test_model = ControlSheetCode
|
||||
_test_permission_create = permission_control_sheet_create
|
||||
_test_permission_delete = permission_control_sheet_delete
|
||||
_test_permission_edit = permission_control_sheet_edit
|
||||
_test_permission_view = permission_control_sheet_view
|
||||
|
||||
def test_control_sheet_code_select_get_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
response = self._request_test_control_sheet_code_select_get_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_control_sheet_code_select_get_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_edit
|
||||
)
|
||||
|
||||
response = self._request_test_control_sheet_code_select_get_view()
|
||||
|
||||
self.assertContains(
|
||||
response=response, text=self._test_control_code_class.label,
|
||||
status_code=200
|
||||
)
|
||||
|
||||
def test_control_sheet_code_select_post_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
response = self._request_test_control_sheet_code_select_post_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_control_sheet_code_select_post_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_edit
|
||||
)
|
||||
|
||||
response = self._request_test_control_sheet_code_select_post_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_control_sheet_code_create_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_code_create_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count)
|
||||
|
||||
def test_control_sheet_code_create_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_edit
|
||||
)
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_code_create_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count + 1)
|
||||
|
||||
def test_control_sheet_code_delete_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
self._create_test_control_sheet_code()
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_code_delete_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count)
|
||||
|
||||
def test_control_sheet_code_delete_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
self._create_test_control_sheet_code()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_edit
|
||||
)
|
||||
|
||||
object_count = self._test_model.objects.count()
|
||||
|
||||
response = self._request_test_control_sheet_code_delete_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.assertEqual(self._test_model.objects.count(), object_count - 1)
|
||||
|
||||
def test_control_sheet_code_edit_view_no_permissions(self):
|
||||
self._create_test_control_sheet()
|
||||
self._create_test_control_sheet_code()
|
||||
|
||||
control_sheet_code_arguments = self.test_control_sheet_code.arguments
|
||||
|
||||
response = self._request_test_control_sheet_code_edit_view()
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.test_control_sheet_code.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.test_control_sheet_code.arguments,
|
||||
control_sheet_code_arguments
|
||||
)
|
||||
|
||||
def test_control_sheet_code_edit_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
self._create_test_control_sheet_code()
|
||||
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_edit
|
||||
)
|
||||
|
||||
control_sheet_code_arguments = self.test_control_sheet_code.arguments
|
||||
|
||||
response = self._request_test_control_sheet_code_edit_view()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
self.test_control_sheet_code.refresh_from_db()
|
||||
self.assertNotEqual(
|
||||
self.test_control_sheet_code.arguments,
|
||||
control_sheet_code_arguments
|
||||
)
|
||||
|
||||
def test_control_sheet_code_list_view_no_permission(self):
|
||||
self._create_test_control_sheet()
|
||||
self._create_test_control_sheet_code()
|
||||
|
||||
response = self._request_test_control_sheet_code_list_view()
|
||||
self.assertNotContains(
|
||||
response, text=self.test_control_sheet_code.get_label(),
|
||||
status_code=404
|
||||
)
|
||||
|
||||
def test_control_sheet_code_list_view_with_access(self):
|
||||
self._create_test_control_sheet()
|
||||
self._create_test_control_sheet_code()
|
||||
self.grant_access(
|
||||
obj=self.test_control_sheet,
|
||||
permission=self._test_permission_view
|
||||
)
|
||||
|
||||
response = self._request_test_control_sheet_code_list_view()
|
||||
self.assertContains(
|
||||
response, text=self.test_control_sheet_code.get_label(),
|
||||
status_code=200
|
||||
)
|
||||
87
mayan/apps/control_codes/urls.py
Normal file
87
mayan/apps/control_codes/urls.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from .api_views import (
|
||||
APIControlSheetCodeListView, APIControlSheetCodeView,
|
||||
APIControlSheetListView, APIControlSheetView,
|
||||
APIControlSheetCodeImageView
|
||||
)
|
||||
from .views import (
|
||||
ControlSheetCreateView, ControlSheetDeleteView, ControlSheetEditView,
|
||||
ControlSheetListView, ControlSheetPreviewView, ControlSheetPrintView,
|
||||
ControlSheetCodeCreate, ControlSheetCodeDeleteView,
|
||||
ControlSheetCodeEditView, ControlSheetCodeListView,
|
||||
ControlSheetCodeSelectView
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
regex=r'^control_sheets/$',
|
||||
view=ControlSheetListView.as_view(), name='control_sheet_list'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/create/$',
|
||||
view=ControlSheetCreateView.as_view(), name='control_sheet_create'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/codes/$',
|
||||
view=ControlSheetCodeListView.as_view(), name='control_sheet_code_list'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/codes/select/$',
|
||||
view=ControlSheetCodeSelectView.as_view(), name='control_sheet_code_select'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/codes/(?P<control_code_class_name>[-_\w]+)/create/$',
|
||||
view=ControlSheetCodeCreate.as_view(), name='control_sheet_code_create'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/codes/(?P<control_sheet_code_id>\d+)/edit/$',
|
||||
view=ControlSheetCodeEditView.as_view(), name='control_sheet_code_edit'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/codes/(?P<control_sheet_code_id>\d+)/delete/$',
|
||||
view=ControlSheetCodeDeleteView.as_view(), name='control_sheet_code_delete'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/delete/$',
|
||||
view=ControlSheetDeleteView.as_view(), name='control_sheet_delete'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/edit/$',
|
||||
view=ControlSheetEditView.as_view(), name='control_sheet_edit'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/preview/$',
|
||||
view=ControlSheetPreviewView.as_view(), name='control_sheet_preview'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/print/$',
|
||||
view=ControlSheetPrintView.as_view(), name='control_sheet_print'
|
||||
),
|
||||
]
|
||||
|
||||
api_urls = [
|
||||
url(
|
||||
regex=r'^control_sheets/$', view=APIControlSheetListView.as_view(),
|
||||
name='controlsheet-list'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>[0-9]+)/$',
|
||||
view=APIControlSheetView.as_view(),
|
||||
name='controlsheet-detail'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>[0-9]+)/codes/$',
|
||||
view=APIControlSheetCodeListView.as_view(), name='controlsheet-code-list'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>[0-9]+)/codes/(?P<control_sheet_code_id>[0-9]+)/$',
|
||||
view=APIControlSheetCodeView.as_view(), name='controlsheet-code-detail'
|
||||
),
|
||||
url(
|
||||
regex=r'^control_sheets/(?P<control_sheet_id>[0-9]+)/codes/(?P<control_sheet_code_id>[0-9]+)/image/$',
|
||||
name='controlsheet-code-image', view=APIControlSheetCodeImageView.as_view()
|
||||
),
|
||||
]
|
||||
12
mayan/apps/control_codes/utils.py
Normal file
12
mayan/apps/control_codes/utils.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
from .literals import CONTROL_SHEET_CODE_IMAGE_CACHE_NAME
|
||||
|
||||
|
||||
def callback_update_control_sheet_image_cache_size(setting):
|
||||
Cache = apps.get_model(app_label='file_caching', model_name='Cache')
|
||||
cache = Cache.objects.get(name=CONTROL_SHEET_CODE_IMAGE_CACHE_NAME)
|
||||
cache.maximum_size = setting.value
|
||||
cache.save()
|
||||
264
mayan/apps/control_codes/views.py
Normal file
264
mayan/apps/control_codes/views.py
Normal file
@@ -0,0 +1,264 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.template import RequestContext
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.generics import (
|
||||
FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||
SingleObjectDetailView, SingleObjectEditView, SingleObjectListView
|
||||
)
|
||||
from mayan.apps.common.mixins import ExternalObjectMixin
|
||||
|
||||
from .classes import ControlCode
|
||||
from .forms import ControlSheetCodeClassSelectionForm, ControlSheetCodeForm
|
||||
from .icons import icon_control_sheet, icon_control_sheet_code
|
||||
from .links import link_control_sheet_create, link_control_sheet_code_select
|
||||
from .models import ControlSheet
|
||||
from .permissions import (
|
||||
permission_control_sheet_create, permission_control_sheet_delete,
|
||||
permission_control_sheet_edit, permission_control_sheet_view
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ControlSheetCreateView(SingleObjectCreateView):
|
||||
fields = ('label',)
|
||||
model = ControlSheet
|
||||
view_permission = permission_control_sheet_create
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'title': _('Create new control sheet')
|
||||
}
|
||||
|
||||
|
||||
class ControlSheetDeleteView(SingleObjectDeleteView):
|
||||
model = ControlSheet
|
||||
object_permission = permission_control_sheet_delete
|
||||
pk_url_kwarg = 'control_sheet_id'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.object,
|
||||
'title': _(
|
||||
'Delete control sheet: %s?'
|
||||
) % self.object
|
||||
}
|
||||
|
||||
|
||||
class ControlSheetEditView(SingleObjectEditView):
|
||||
fields = ('label',)
|
||||
model = ControlSheet
|
||||
object_permission = permission_control_sheet_edit
|
||||
pk_url_kwarg = 'control_sheet_id'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.object,
|
||||
'title': _(
|
||||
'Edit control sheet: %s'
|
||||
) % self.object
|
||||
}
|
||||
|
||||
|
||||
class ControlSheetListView(SingleObjectListView):
|
||||
model = ControlSheet
|
||||
object_permission = permission_control_sheet_view
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_object': True,
|
||||
'no_results_icon': icon_control_sheet,
|
||||
'no_results_main_link': link_control_sheet_create.resolve(
|
||||
context=RequestContext(request=self.request,)
|
||||
),
|
||||
'no_results_text': _(
|
||||
'Control sheets contain barcodes that are scanned together '
|
||||
'with the document to automate how that document will be '
|
||||
'processed.'
|
||||
),
|
||||
'no_results_title': _('There are no control sheets'),
|
||||
'title': _('Control sheets')
|
||||
}
|
||||
|
||||
|
||||
class ControlSheetPreviewView(SingleObjectDetailView):
|
||||
fields = ('label',)
|
||||
pk_url_kwarg = 'control_sheet_id'
|
||||
model = ControlSheet
|
||||
object_permission = permission_control_sheet_view
|
||||
template_name = 'control_codes/control_sheet_preview.html'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.object,
|
||||
'title': _(
|
||||
'Preview of control sheet: %s'
|
||||
) % self.object
|
||||
}
|
||||
|
||||
|
||||
class ControlSheetPrintView(ControlSheetPreviewView):
|
||||
template_name = 'control_codes/control_sheet_print.html'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.object,
|
||||
'title': _(
|
||||
'Control sheet: %s'
|
||||
) % self.object
|
||||
}
|
||||
|
||||
|
||||
class ControlSheetCodeCreate(ExternalObjectMixin, SingleObjectCreateView):
|
||||
external_object_class = ControlSheet
|
||||
external_object_permission = permission_control_sheet_edit
|
||||
external_object_pk_url_kwarg = 'control_sheet_id'
|
||||
form_class = ControlSheetCodeForm
|
||||
|
||||
def form_valid(self, form):
|
||||
instance = form.save(commit=False)
|
||||
instance.control_sheet = self.external_object
|
||||
try:
|
||||
instance.name = self.get_control_code_class().name
|
||||
instance.full_clean()
|
||||
instance.save()
|
||||
except Exception as exception:
|
||||
logger.error('Invalid form, exception: %s', exception)
|
||||
return super(ControlSheetCodeCreate, self).form_invalid(form=form)
|
||||
else:
|
||||
return super(ControlSheetCodeCreate, self).form_valid(form=form)
|
||||
|
||||
def get_control_code_class(self):
|
||||
return ControlCode.get(name=self.kwargs['control_code_class_name'])
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'control_sheet': self.external_object,
|
||||
'navigation_object_list': ('control_sheet',),
|
||||
'title': _(
|
||||
'Create code "%(control_code)s" for: %(control_sheet)s'
|
||||
) % {
|
||||
'control_code': self.get_control_code_class().label,
|
||||
'control_sheet': self.external_object,
|
||||
}
|
||||
}
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
return reverse(
|
||||
viewname='control_codes:control_sheet_code_list', kwargs={
|
||||
'control_sheet_id': self.external_object.pk,
|
||||
}
|
||||
)
|
||||
|
||||
def get_source_queryset(self):
|
||||
return self.external_object.codes.all()
|
||||
|
||||
|
||||
class ControlSheetCodeDeleteView(ExternalObjectMixin, SingleObjectDeleteView):
|
||||
form_class = ControlSheetCodeForm
|
||||
external_object_class = ControlSheet
|
||||
external_object_permission = permission_control_sheet_edit
|
||||
external_object_pk_url_kwarg = 'control_sheet_id'
|
||||
pk_url_kwarg = 'control_sheet_code_id'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'control_sheet': self.external_object,
|
||||
'hide_object': True,
|
||||
'navigation_object_list': ('control_sheet', 'object',),
|
||||
'object': self.object,
|
||||
'title': _('Delete control sheet code: %s') % self.object,
|
||||
}
|
||||
|
||||
def get_source_queryset(self):
|
||||
return self.external_object.codes.all()
|
||||
|
||||
|
||||
class ControlSheetCodeEditView(ExternalObjectMixin, SingleObjectEditView):
|
||||
form_class = ControlSheetCodeForm
|
||||
external_object_class = ControlSheet
|
||||
external_object_permission = permission_control_sheet_edit
|
||||
external_object_pk_url_kwarg = 'control_sheet_id'
|
||||
pk_url_kwarg = 'control_sheet_code_id'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'control_sheet': self.external_object,
|
||||
'hide_object': True,
|
||||
'navigation_object_list': ('control_sheet', 'object',),
|
||||
'object': self.object,
|
||||
'title': _('Edit control sheet code: %s') % self.object,
|
||||
}
|
||||
|
||||
def get_source_queryset(self):
|
||||
return self.external_object.codes.all()
|
||||
|
||||
|
||||
class ControlSheetCodeListView(ExternalObjectMixin, SingleObjectListView):
|
||||
external_object_class = ControlSheet
|
||||
external_object_permission = permission_control_sheet_view
|
||||
external_object_pk_url_kwarg = 'control_sheet_id'
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'control_sheet': self.external_object,
|
||||
'hide_object': True,
|
||||
'navigation_object_list': ('control_sheet',),
|
||||
'no_results_icon': icon_control_sheet_code,
|
||||
'no_results_main_link': link_control_sheet_code_select.resolve(
|
||||
context=RequestContext(
|
||||
request=self.request, dict_={
|
||||
'control_sheet': self.external_object,
|
||||
}
|
||||
)
|
||||
),
|
||||
'no_results_text': _(
|
||||
'Control sheet codes are barcodes that trigger a specific '
|
||||
'process when they are scanned.'
|
||||
),
|
||||
'no_results_title': _('There are no control sheet codes'),
|
||||
'title': _('Codes of control sheet: %s') % self.external_object,
|
||||
}
|
||||
|
||||
def get_source_queryset(self):
|
||||
return self.external_object.codes.all()
|
||||
|
||||
|
||||
class ControlSheetCodeSelectView(ExternalObjectMixin, FormView):
|
||||
external_object_class = ControlSheet
|
||||
external_object_permission = permission_control_sheet_edit
|
||||
external_object_pk_url_kwarg = 'control_sheet_id'
|
||||
form_class = ControlSheetCodeClassSelectionForm
|
||||
template_name = 'appearance/generic_form.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
return HttpResponseRedirect(
|
||||
redirect_to=reverse(
|
||||
viewname='control_codes:control_sheet_code_create',
|
||||
kwargs={
|
||||
'control_sheet_id': self.external_object.pk,
|
||||
'control_code_class_name': form.cleaned_data[
|
||||
'control_code_class_name'
|
||||
]
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'control_sheet': self.external_object,
|
||||
'navigation_object_list': ('control_sheet',),
|
||||
'submit_label': _('Select'),
|
||||
'title': _(
|
||||
'Select new control sheet code '
|
||||
'for: %(control_sheet)s'
|
||||
) % {
|
||||
'control_sheet': self.external_object,
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,8 @@ class FileCachingConfig(MayanAppConfig):
|
||||
)
|
||||
)
|
||||
|
||||
SourceColumn(attribute='name', is_sortable=True, source=Cache)
|
||||
SourceColumn(attribute='label', is_sortable=True, source=Cache)
|
||||
SourceColumn(attribute='name', is_sortable=True, source=Cache)
|
||||
SourceColumn(
|
||||
attribute='storage_instance_path', is_sortable=True, source=Cache
|
||||
)
|
||||
|
||||
@@ -25,8 +25,8 @@ logger = logging.getLogger(__name__)
|
||||
@python_2_unicode_compatible
|
||||
class Cache(models.Model):
|
||||
name = models.CharField(
|
||||
help_text=_('Internal name of the cache.'), max_length=128,
|
||||
unique=True, verbose_name=_('Name')
|
||||
db_index=True, help_text=_('Internal name of the cache.'),
|
||||
max_length=128, unique=True, verbose_name=_('Name')
|
||||
)
|
||||
label = models.CharField(
|
||||
help_text=_('A short text describing the cache.'), max_length=128,
|
||||
@@ -55,6 +55,9 @@ class Cache(models.Model):
|
||||
def get_maximum_size_display(self):
|
||||
return filesizeformat(bytes_=self.maximum_size)
|
||||
|
||||
get_maximum_size_display.help_text = _(
|
||||
'Size at which the cache will start deleting old entries.'
|
||||
)
|
||||
get_maximum_size_display.short_description = _('Maximum size')
|
||||
|
||||
def get_total_size(self):
|
||||
@@ -69,6 +72,7 @@ class Cache(models.Model):
|
||||
return filesizeformat(bytes_=self.get_total_size())
|
||||
|
||||
get_total_size_display.short_description = _('Total size')
|
||||
get_total_size_display.help_text = _('Current size of the cache.')
|
||||
|
||||
def prune(self):
|
||||
"""
|
||||
|
||||
@@ -26,6 +26,7 @@ from mayan.apps.events.permissions import permission_events_view
|
||||
from mayan.apps.navigation.classes import SourceColumn
|
||||
|
||||
from .classes import DocumentMetadataHelper
|
||||
from .control_codes import * # NOQA
|
||||
from .dependencies import * # NOQA
|
||||
from .events import (
|
||||
event_document_metadata_added, event_document_metadata_edited,
|
||||
|
||||
34
mayan/apps/metadata/control_codes.py
Normal file
34
mayan/apps/metadata/control_codes.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
from mayan.apps.control_codes.classes import ControlCode
|
||||
|
||||
__all__ = ('ControlCodeDocumentMetadataAdd',)
|
||||
|
||||
|
||||
class ControlCodeDocumentMetadataAdd(ControlCode):
|
||||
arguments = ('name', 'value')
|
||||
label = 'Add document metadata'
|
||||
name = 'document_metadata_add_v1'
|
||||
|
||||
def execute(self, context):
|
||||
DocumentMetadata = apps.get_model(
|
||||
app_label='metadata', model_name='DocumentMetadata'
|
||||
)
|
||||
MetadataType = apps.get_model(
|
||||
app_label='metadata', model_name='MetadataType'
|
||||
)
|
||||
|
||||
document = context['document_page'].document
|
||||
user = context.get('user', None)
|
||||
|
||||
metadata_type = MetadataType.objects.get(name=self.kwargs['name'])
|
||||
document_metadata = DocumentMetadata(
|
||||
document=document, metadata_type=metadata_type,
|
||||
value=self.kwargs['value']
|
||||
)
|
||||
document_metadata.save(_user=user)
|
||||
|
||||
|
||||
ControlCode.register(control_code=ControlCodeDocumentMetadataAdd)
|
||||
42
mayan/apps/metadata/tests/test_control_codes.py
Normal file
42
mayan/apps/metadata/tests/test_control_codes.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.documents.tests.base import GenericDocumentTestCase
|
||||
from mayan.apps.storage.utils import fs_cleanup, mkstemp
|
||||
|
||||
from ..control_codes import ControlCodeDocumentMetadataAdd
|
||||
|
||||
from .literals import TEST_DOCUMENT_METADATA_VALUE_2
|
||||
from .mixins import DocumentMetadataViewTestMixin, MetadataTypeTestMixin
|
||||
|
||||
|
||||
class ControlCodeDocumentMetadataAddTestCase(
|
||||
DocumentMetadataViewTestMixin, MetadataTypeTestMixin,
|
||||
GenericDocumentTestCase
|
||||
):
|
||||
auto_upload_document = False
|
||||
|
||||
def setUp(self):
|
||||
super(ControlCodeDocumentMetadataAddTestCase, self).setUp()
|
||||
self.test_document_path = mkstemp()[1]
|
||||
|
||||
self._create_test_metadata_type()
|
||||
self.test_document_type.metadata.create(
|
||||
metadata_type=self.test_metadata_type
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
fs_cleanup(filename=self.test_document_path)
|
||||
super(ControlCodeDocumentMetadataAddTestCase, self).tearDown()
|
||||
|
||||
def test_control_code(self):
|
||||
with open(self.test_document_path, mode='wb') as file_object:
|
||||
control_code = ControlCodeDocumentMetadataAdd(
|
||||
name=self.test_metadata_type.name,
|
||||
value=TEST_DOCUMENT_METADATA_VALUE_2
|
||||
)
|
||||
control_code.get_image().save(file_object)
|
||||
|
||||
self.upload_document()
|
||||
self.assertEqual(
|
||||
self.test_document.metadata.first().value, TEST_DOCUMENT_METADATA_VALUE_2
|
||||
)
|
||||
91
mayan/apps/rest_api/relations.py
Normal file
91
mayan/apps/rest_api/relations.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.models import Manager
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.relations import HyperlinkedIdentityField
|
||||
|
||||
from mayan.apps.common.utils import resolve_attribute
|
||||
|
||||
|
||||
class FilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
|
||||
def __init__(self, **kwargs):
|
||||
self.source_model = kwargs.pop('source_model', None)
|
||||
self.source_permission = kwargs.pop('source_permission', None)
|
||||
self.source_queryset = kwargs.pop('source_queryset', None)
|
||||
self.source_queryset_method = kwargs.pop('source_queryset_method', None)
|
||||
super(FilteredPrimaryKeyRelatedField, self).__init__(**kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
AccessControlList = apps.get_model(
|
||||
app_label='acls', model_name='AccessControlList'
|
||||
)
|
||||
|
||||
if self.source_model:
|
||||
queryset = self.source_model._meta.default_manager.all()
|
||||
elif self.source_queryset:
|
||||
queryset = self.source_queryset
|
||||
if isinstance(queryset, (QuerySet, Manager)):
|
||||
# Ensure queryset is re-evaluated whenever used.
|
||||
queryset = queryset.all()
|
||||
else:
|
||||
method_name = self.source_queryset_method or 'get_{}_queryset'.format(
|
||||
self.field_name
|
||||
)
|
||||
try:
|
||||
queryset = getattr(self.parent, method_name)()
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
'Need to provide a source_model, a '
|
||||
'source_queryset, a source_queryset_method, or '
|
||||
'a method named "%s".' % method_name
|
||||
)
|
||||
|
||||
assert 'request' in self.context, (
|
||||
"`%s` requires the request in the serializer"
|
||||
" context. Add `context={'request': request}` when instantiating "
|
||||
"the serializer." % self.__class__.__name__
|
||||
)
|
||||
|
||||
request = self.context['request']
|
||||
|
||||
if self.source_permission:
|
||||
return AccessControlList.objects.restrict_queryset(
|
||||
permission=self.source_permission, queryset=queryset,
|
||||
user=request.user
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class MultiKwargHyperlinkedIdentityField(HyperlinkedIdentityField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.view_kwargs = kwargs.pop('view_kwargs', [])
|
||||
super(MultiKwargHyperlinkedIdentityField, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_url(self, obj, view_name, request, format):
|
||||
"""
|
||||
Extends HyperlinkedRelatedField to allow passing more than one view
|
||||
keyword argument.
|
||||
----
|
||||
Given an object, return the URL that hyperlinks to the object.
|
||||
|
||||
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
|
||||
attributes are not configured to correctly match the URL conf.
|
||||
"""
|
||||
# Unsaved objects will not yet have a valid URL.
|
||||
if hasattr(obj, 'pk') and obj.pk in (None, ''):
|
||||
return None
|
||||
|
||||
kwargs = {}
|
||||
for entry in self.view_kwargs:
|
||||
kwargs[entry['lookup_url_kwarg']] = resolve_attribute(
|
||||
obj=obj, attribute=entry['lookup_field']
|
||||
)
|
||||
|
||||
return self.reverse(
|
||||
viewname=view_name, kwargs=kwargs, request=request, format=format
|
||||
)
|
||||
@@ -16,6 +16,7 @@ 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 .events import event_web_link_edited
|
||||
from .links import (
|
||||
link_document_type_web_links, link_document_web_link_list,
|
||||
@@ -33,7 +34,7 @@ class WebLinksApp(MayanAppConfig):
|
||||
app_namespace = 'web_links'
|
||||
app_url = 'web_links'
|
||||
has_rest_api = False
|
||||
has_tests = False
|
||||
has_tests = True
|
||||
name = 'mayan.apps.web_links'
|
||||
verbose_name = _('Links')
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user