Compare commits

...

17 Commits

Author SHA1 Message Date
Roberto Rosario
0e35cca704 Add cabinets and metadata control codes
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-06 03:25:56 -04:00
Roberto Rosario
661301f057 Finalize icon
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-06 02:44:13 -04:00
Roberto Rosario
9448b148e9 Improve tests, update migrations
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-05 22:18:59 -04:00
Roberto Rosario
a13f033104 Add first control code
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-05 01:40:08 -04:00
Roberto Rosario
968abe2cdb Finish view tests
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-05 01:24:54 -04:00
Roberto Rosario
cca08d8103 Add initial tests
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-04 16:23:12 -04:00
HGWells
3872db8c9f Add code selecton and create views
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-04 07:41:11 -04:00
Roberto Rosario
1315a74e27 Add control sheet codes views
Add list, edit, delete views.

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-03 16:15:08 -04:00
Roberto Rosario
8c6812203c Add permissions, setup views, icons
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-02 01:20:03 -04:00
Roberto Rosario
4ed36e0114 Add contol sheet preview
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-01 17:55:18 -04:00
Roberto Rosario
c48fc203e3 Add dependencies and cache handling
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-01 15:34:59 -04:00
Roberto Rosario
af71ba927f Put file cache label column first
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-01 15:34:31 -04:00
Roberto Rosario
894e009c2a Update file cache model
Index the name field. Add help texts for maximum size and current
size methods.

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-01 15:33:41 -04:00
Roberto Rosario
f9ba08ee59 Add control code preview generation
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-09-01 14:35:04 -04:00
Roberto Rosario
2819f9445b Add control codes app proof of concept
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-08-30 05:22:13 -04:00
Roberto Rosario
1b2ed08c7c Enable web links app tests
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-08-30 02:36:54 -04:00
Roberto Rosario
c6c605e320 Add missing line
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
2019-08-30 02:34:01 -04:00
47 changed files with 2429 additions and 7 deletions

View File

@@ -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.documents.search import document_page_search, document_search
from mayan.apps.navigation.classes import SourceColumn from mayan.apps.navigation.classes import SourceColumn
from .control_codes import * # NOQA
from .dependencies import * # NOQA from .dependencies import * # NOQA
from .events import ( from .events import (
event_cabinet_edited, event_cabinet_add_document, event_cabinet_edited, event_cabinet_add_document,

View 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)

View 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
)

View File

@@ -14,7 +14,9 @@ from .literals import TEST_CABINET_LABEL, TEST_CABINET_LABEL_EDITED
from .mixins import CabinetTestMixin, CabinetViewTestMixin from .mixins import CabinetTestMixin, CabinetViewTestMixin
class CabinetViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericViewTestCase): class CabinetViewTestCase(
CabinetTestMixin, CabinetViewTestMixin, GenericViewTestCase
):
def test_cabinet_create_view_no_permission(self): def test_cabinet_create_view_no_permission(self):
response = self._request_test_cabinet_create_view() response = self._request_test_cabinet_create_view()
self.assertEqual(response.status_code, 403) 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): def setUp(self):
super(CabinetChildViewTestCase, self).setUp() super(CabinetChildViewTestCase, self).setUp()
self._create_test_cabinet() self._create_test_cabinet()
@@ -157,7 +161,9 @@ class CabinetChildViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericVi
self.assertEqual(Cabinet.objects.count(), cabinet_count - 1) self.assertEqual(Cabinet.objects.count(), cabinet_count - 1)
class CabinetDocumentViewTestCase(CabinetTestMixin, CabinetViewTestMixin, GenericDocumentViewTestCase): class CabinetDocumentViewTestCase(
CabinetTestMixin, CabinetViewTestMixin, GenericDocumentViewTestCase
):
def _add_document_to_cabinet(self): def _add_document_to_cabinet(self):
return self.post( return self.post(
viewname='cabinets:document_cabinet_add', kwargs={ viewname='cabinets:document_cabinet_add', kwargs={

View File

@@ -0,0 +1,3 @@
from __future__ import unicode_literals
default_app_config = 'mayan.apps.control_codes.apps.ControlCodesApp'

View 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')

View 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

View 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
)

View 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__
)

View 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)

View 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'
)

View 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']
)

View 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()

View 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'
)

View 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'
)

View 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

View 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)

View 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,
}
)

View 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()),
],
),
]

View 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)

View 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'
)

View 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'
)

View 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)

View 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.'
)
)

View 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)

View 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
)

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
{% extends 'appearance/base.html' %}
{% block content %}
{% include 'control_codes/control_sheet_content.html' %}
{% endblock %}

View File

@@ -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 %}

View 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)

View 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'

View 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
}
)

View 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)

View 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
)

View 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()
),
]

View 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()

View 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,
}
}

View File

@@ -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='label', is_sortable=True, source=Cache)
SourceColumn(attribute='name', is_sortable=True, source=Cache)
SourceColumn( SourceColumn(
attribute='storage_instance_path', is_sortable=True, source=Cache attribute='storage_instance_path', is_sortable=True, source=Cache
) )

View File

@@ -25,8 +25,8 @@ logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
class Cache(models.Model): class Cache(models.Model):
name = models.CharField( name = models.CharField(
help_text=_('Internal name of the cache.'), max_length=128, db_index=True, help_text=_('Internal name of the cache.'),
unique=True, verbose_name=_('Name') max_length=128, unique=True, verbose_name=_('Name')
) )
label = models.CharField( label = models.CharField(
help_text=_('A short text describing the cache.'), max_length=128, 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): def get_maximum_size_display(self):
return filesizeformat(bytes_=self.maximum_size) 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') get_maximum_size_display.short_description = _('Maximum size')
def get_total_size(self): def get_total_size(self):
@@ -69,6 +72,7 @@ class Cache(models.Model):
return filesizeformat(bytes_=self.get_total_size()) return filesizeformat(bytes_=self.get_total_size())
get_total_size_display.short_description = _('Total size') get_total_size_display.short_description = _('Total size')
get_total_size_display.help_text = _('Current size of the cache.')
def prune(self): def prune(self):
""" """

View File

@@ -26,6 +26,7 @@ from mayan.apps.events.permissions import permission_events_view
from mayan.apps.navigation.classes import SourceColumn from mayan.apps.navigation.classes import SourceColumn
from .classes import DocumentMetadataHelper from .classes import DocumentMetadataHelper
from .control_codes import * # NOQA
from .dependencies import * # NOQA from .dependencies import * # NOQA
from .events import ( from .events import (
event_document_metadata_added, event_document_metadata_edited, event_document_metadata_added, event_document_metadata_edited,

View 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)

View 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
)

View 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
)

View File

@@ -16,6 +16,7 @@ from mayan.apps.events.links import (
link_events_for_object, link_object_event_types_user_subcriptions_list link_events_for_object, link_object_event_types_user_subcriptions_list
) )
from mayan.apps.navigation.classes import SourceColumn from mayan.apps.navigation.classes import SourceColumn
from .events import event_web_link_edited from .events import event_web_link_edited
from .links import ( from .links import (
link_document_type_web_links, link_document_web_link_list, link_document_type_web_links, link_document_web_link_list,
@@ -33,7 +34,7 @@ class WebLinksApp(MayanAppConfig):
app_namespace = 'web_links' app_namespace = 'web_links'
app_url = 'web_links' app_url = 'web_links'
has_rest_api = False has_rest_api = False
has_tests = False has_tests = True
name = 'mayan.apps.web_links' name = 'mayan.apps.web_links'
verbose_name = _('Links') verbose_name = _('Links')

View File

@@ -89,6 +89,7 @@ INSTALLED_APPS = (
'mayan.apps.authentication', 'mayan.apps.authentication',
'mayan.apps.autoadmin', 'mayan.apps.autoadmin',
'mayan.apps.common', 'mayan.apps.common',
'mayan.apps.control_codes',
'mayan.apps.converter', 'mayan.apps.converter',
'mayan.apps.dashboards', 'mayan.apps.dashboards',
'mayan.apps.dependencies', 'mayan.apps.dependencies',