Add control code preview generation

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-09-01 14:35:04 -04:00
parent 2819f9445b
commit f9ba08ee59
19 changed files with 1154 additions and 6 deletions

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,231 @@
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.documents.models import Document, DocumentType
from mayan.apps.documents.permissions import permission_document_type_view
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_workflow_create, permission_workflow_delete,
# permission_workflow_edit, permission_workflow_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 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
)
#def get_serializer_class(self):
# if self.request.method == 'GET':
# return ControlSheetSerializer
# else:
# return WritableControlSheetSerializer
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
)
#def get_serializer_class(self):
# if self.request.method == 'GET':
# return ControlSheetSerializer
# else:
# return WritableControlSheetSerializer
class APIControlSheetCodeListView(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
def get_control_sheet(self):
#if self.request.method == 'GET':
# permission_required = permission_control_sheet_view
#else:
# permission_required = permission_control_sheet_edit
control_sheet = get_object_or_404(
klass=ControlSheet, pk=self.kwargs['control_sheet_id']
)
#AccessControlList.objects.check_access(
# obj=control_sheet, permissions=(permission_required,),
# user=self.request.user
#)
return control_sheet
class APIControlSheetCodeView(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
def get_control_sheet(self):
#if self.request.method == 'GET':
# permission_required = permission_control_sheet_view
#else:
# permission_required = permission_control_sheet_edit
control_sheet = get_object_or_404(
klass=ControlSheet, pk=self.kwargs['control_sheet_id']
)
#AccessControlList.objects.check_access(
# obj=control_sheet, permissions=(permission_required,),
# user=self.request.user
#)
return control_sheet
class APIControlSheetCodeImageView(generics.RetrieveAPIView):
"""
get: Returns an image representation of the selected control_sheet.
"""
filter_backends = (MayanObjectPermissionsFilter,)
#mayan_object_permissions = {
# 'GET': (permission_control_sheet_view,),
#}
lookup_url_kwarg = 'control_sheet_code_id'
#queryset = ControlSheetCode.objects.all()
def get_queryset(self):
return self.get_control_sheet().codes.all()
def get_control_sheet(self):
#if self.request.method == 'GET':
# permission_required = permission_control_sheet_view
#else:
# permission_required = permission_control_sheet_edit
control_sheet = get_object_or_404(
klass=ControlSheet, pk=self.kwargs['control_sheet_id']
)
#AccessControlList.objects.check_access(
# obj=control_sheet, permissions=(permission_required,),
# user=self.request.user
#)
return control_sheet
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_image_cache_time.value
)
return response

View File

@@ -19,6 +19,7 @@ from mayan.apps.events.links import (
)
from mayan.apps.navigation.classes import SourceColumn
from .control_codes import *
from .handlers import handler_process_document_version
from .methods import method_document_submit, method_document_version_submit
@@ -26,7 +27,7 @@ from .methods import method_document_submit, method_document_version_submit
class ControlCodesApp(MayanAppConfig):
app_namespace = 'control_codes'
app_url = 'control_codes'
has_rest_api = False
has_rest_api = True
has_tests = False
name = 'mayan.apps.control_codes'
verbose_name = _('Control codes')

View File

@@ -8,14 +8,16 @@ import qrcode
from django.apps import apps
from django.db import transaction
from django.utils.translation import string_concat, ugettext_lazy as _
from mayan.apps.common.serialization import yaml_dump, yaml_load
from mayan.apps.documents.literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from mayan.apps.documents.tasks import task_generate_document_page_image
CONTROL_CODE_MAGIC_NUMBER = 'MCTRL'
CONTROL_CODE_SEPARATOR = ':'
CONTROL_CODE_VERSION = '1'
from .literals import (
CONTROL_CODE_MAGIC_NUMBER, CONTROL_CODE_SEPARATOR, CONTROL_CODE_VERSION
)
logger = logging.getLogger(__name__)
@@ -27,6 +29,28 @@ class ControlCode(object):
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):
#if layer:
# transformation_list = [
# (transformation.name, transformation) for transformation in cls._layer_transformations[layer]
# ]
#else:
#control_code_list = cls._registry.items()
return sorted(
[
(name, klass.get_label()) for name, klass in cls._registry.items()#transformation_list
]
)
@classmethod
def process_document_version(cls, document_version):
logger.info(

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):
pass
ControlCode.register(control_code=ControlCodeTest)

View File

@@ -0,0 +1,65 @@
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 ControlCodeClassSelectionForm(forms.Form):
control_code = forms.ChoiceField(
choices=(), help_text=_('Available control codes.'),
label=_('Control code'),
)
def __init__(self, *args, **kwargs):
super(ControlCodeClassSelectionForm, self).__init__(*args, **kwargs)
self.fields[
'control_code'
].choices = ControlCode.get_choices()
"""
class ControlSheetCodeForm(forms.ModelForm):
class Meta:
fields = ('arguments', 'order')
model = LayerTransformation
def __init__(self, *args, **kwargs):
transformation_name = kwargs.pop('transformation_name', None)
super(LayerTransformationForm, self).__init__(*args, **kwargs)
if not transformation_name:
# Get the template name when the transformation is being edited.
template_name = getattr(
self.instance.get_transformation_class(), 'template_name',
None
)
else:
# Get the template name when the transformation is being created
template_name = getattr(
BaseTransformation.get(name=transformation_name),
'template_name', None
)
if template_name:
self.fields['arguments'].widget.attrs['class'] = 'hidden'
self.fields['order'].widget.attrs['class'] = 'hidden'
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

@@ -17,5 +17,5 @@ def handler_initialize_new_document_type_settings(sender, instance, **kwargs):
def handler_process_document_version(sender, instance, **kwargs):
#if instance.document.document_type.file_metadata_settings.auto_process:
#if instance.document.document_type.control_codes_settings.auto_process:
instance.submit_for_control_codes_processing()

View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
CONTROL_CODE_MAGIC_NUMBER = 'MCTRL'
CONTROL_CODE_SEPARATOR = ':'
CONTROL_CODE_VERSION = '1'
CONTROL_SHEET_CODE_IMAGE_CACHE_NAME = 'workflow_images'
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,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.22 on 2019-09-01 08:20
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
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(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 transformations will be executed. If left unchanged, an automatic order value will be assigned.', verbose_name='Order')),
('name', models.CharField(choices=[('test', 'Test: argument_1')], max_length=128, verbose_name='Name')),
('arguments', models.TextField(blank=True, help_text='Enter the arguments for the control 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={
'verbose_name': 'Control sheet code',
'verbose_name_plural': 'Control sheet codes',
},
),
]

View File

@@ -0,0 +1,154 @@
from __future__ import unicode_literals
import hashlib
import json
import logging
from furl import furl
from django.apps import apps
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
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
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class ControlSheet(models.Model):
label = models.CharField(
max_length=196, unique=True, verbose_name=_('Label')
)
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
}
)
class Meta:
ordering = ('label',)
verbose_name = _('Control sheet')
verbose_name_plural = _('Control sheets')
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 transformations will be executed. If left '
'unchanged, an automatic order value will be assigned.'
), verbose_name=_('Order')
)
name = models.CharField(
choices=ControlCode.get_choices(),
max_length=128, verbose_name=_('Name')
)
arguments = models.TextField(
blank=True, help_text=_(
'Enter the arguments for the control code as a YAML '
'dictionary.'
), validators=[YAMLValidator()], verbose_name=_('Arguments')
)
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
class Meta:
verbose_name = _('Control sheet code')
verbose_name_plural = _('Control sheet codes')
@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(Workflow, 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:
#file_object.write(image)
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:workflow-image',
kwargs={'pk': 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_hash(self):
objects_lists = list(
ControlSheetCode.objects.filter(pk=self.pk)
)
return hashlib.sha256(
force_bytes(
serializers.serialize('json', objects_lists)
)
).hexdigest()
def render(self):
return self.get_control_code_class()(**self.get_arguments()).image
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,79 @@
from __future__ import unicode_literals
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.reverse import reverse
from mayan.apps.documents.models import DocumentType
from mayan.apps.documents.serializers import DocumentTypeSerializer
from mayan.apps.rest_api.relations import (
FilteredPrimaryKeyRelatedField, MultiKwargHyperlinkedIdentityField
)
from mayan.apps.user_management.serializers import UserSerializer
from .models import ControlSheet, ControlSheetCode
class ControlSheetSerializer(serializers.HyperlinkedModelSerializer):
#states = ControlSheetStateSerializer(many=True, required=False)
#transitions = ControlSheetTransitionSerializer(many=True, required=False)
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 = 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,
setting_control_sheet_code_image_storage_arguments
)
storage_controlsheetcodeimagecache = get_storage_subclass(
dotted_path=setting_control_sheet_code_image_cache_storage.value
)(**setting_control_sheet_code_image_storage_arguments.value)

View File

@@ -11,6 +11,17 @@ 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(

View File

@@ -2,4 +2,40 @@ from __future__ import unicode_literals
from django.conf.urls import url
urlpatterns = []
from .api_views import (
APIControlSheetCodeListView, APIControlSheetCodeView,
APIControlSheetListView, APIControlSheetView,
APIControlSheetCodeImageView
)
from .views import ControlSheetDetailView
urlpatterns = [
url(
regex=r'^control_sheets/(?P<control_sheet_id>\d+)/$',
view=ControlSheetDetailView.as_view(), name='control_sheet_detail'
),
]
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,294 @@
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, SimpleView, SingleObjectCreateView, SingleObjectDeleteView,
SingleObjectDetailView, SingleObjectEditView, SingleObjectListView
)
from mayan.apps.common.mixins import ExternalContentTypeObjectMixin
from .forms import ControlCodeClassSelectionForm
#from .links import link_transformation_select
from .models import ControlSheet
#from .transformations import BaseTransformation
logger = logging.getLogger(__name__)
class ControlSheetDetailView(SingleObjectDetailView):
fields = ('label',)
pk_url_kwarg = 'control_sheet_id'
model = ControlSheet
def get_extra_context(self):
return {
'object': self.object,
'title': _(
'Details for control sheet: %s'
) % self.object
}
"""
class TransformationCreateView(
LayerViewMixin, ExternalContentTypeObjectMixin, SingleObjectCreateView
):
form_class = LayerTransformationForm
def form_valid(self, form):
layer = self.layer
content_type = self.get_content_type()
object_layer, created = ObjectLayer.objects.get_or_create(
content_type=content_type, object_id=self.external_object.pk,
stored_layer=layer.stored_layer
)
instance = form.save(commit=False)
instance.content_object = self.external_object
instance.name = self.kwargs['transformation_name']
instance.object_layer = object_layer
try:
instance.full_clean()
instance.save()
except Exception as exception:
logger.debug('Invalid form, exception: %s', exception)
return super(TransformationCreateView, self).form_invalid(form)
else:
return super(TransformationCreateView, self).form_valid(form)
def get_extra_context(self):
return {
'content_object': self.external_object,
'form_field_css_classes': 'hidden' if hasattr(
self.get_transformation_class(), 'template_name'
) else '',
'layer': self.layer,
'layer_name': self.layer.name,
'navigation_object_list': ('content_object',),
'title': _(
'Create layer "%(layer)s" transformation '
'"%(transformation)s" for: %(object)s'
) % {
'layer': self.layer,
'transformation': self.get_transformation_class(),
'object': self.external_object,
}
}
def get_form_extra_kwargs(self):
return {
'transformation_name': self.kwargs['transformation_name']
}
def get_external_object_permission(self):
return self.layer.permissions.get('create', None)
def get_post_action_redirect(self):
return reverse(
viewname='converter:transformation_list', kwargs={
'app_label': self.kwargs['app_label'],
'model': self.kwargs['model'],
'object_id': self.kwargs['object_id'],
'layer_name': self.kwargs['layer_name']
}
)
def get_queryset(self):
return self.layer.get_transformations_for(
obj=self.content_object
)
def get_template_names(self):
return [
getattr(
self.get_transformation_class(), 'template_name',
self.template_name
)
]
def get_transformation_class(self):
return BaseTransformation.get(name=self.kwargs['transformation_name'])
class TransformationDeleteView(LayerViewMixin, SingleObjectDeleteView):
model = LayerTransformation
def get_extra_context(self):
return {
'content_object': self.object.object_layer.content_object,
'layer_name': self.layer.name,
'navigation_object_list': ('content_object', 'transformation'),
'previous': reverse(
viewname='converter:transformation_list', kwargs={
'app_label': self.object.object_layer.content_type.app_label,
'model': self.object.object_layer.content_type.model,
'object_id': self.object.object_layer.object_id,
'layer_name': self.object.object_layer.stored_layer.name
}
),
'title': _(
'Delete transformation "%(transformation)s" for: '
'%(content_object)s?'
) % {
'transformation': self.object,
'content_object': self.object.object_layer.content_object
},
'transformation': self.object,
}
def get_object_permission(self):
return self.layer.permissions.get('delete', None)
def get_post_action_redirect(self):
return reverse(
viewname='converter:transformation_list', kwargs={
'app_label': self.object.object_layer.content_type.app_label,
'model': self.object.object_layer.content_type.model,
'object_id': self.object.object_layer.object_id,
'layer_name': self.object.object_layer.stored_layer.name
}
)
class TransformationEditView(LayerViewMixin, SingleObjectEditView):
form_class = LayerTransformationForm
model = LayerTransformation
def form_valid(self, form):
instance = form.save(commit=False)
try:
instance.full_clean()
instance.save()
except Exception as exception:
logger.debug('Invalid form, exception: %s', exception)
return super(TransformationEditView, self).form_invalid(form=form)
else:
return super(TransformationEditView, self).form_valid(form=form)
def get_extra_context(self):
return {
'content_object': self.object.object_layer.content_object,
'form_field_css_classes': 'hidden' if hasattr(
self.object.get_transformation_class(), 'template_name'
) else '',
'layer': self.layer,
'layer_name': self.layer.name,
'navigation_object_list': ('content_object', 'transformation'),
'title': _(
'Edit transformation "%(transformation)s" '
'for: %(content_object)s'
) % {
'transformation': self.object,
'content_object': self.object.object_layer.content_object
},
'transformation': self.object,
}
def get_object_permission(self):
return self.layer.permissions.get('edit', None)
def get_post_action_redirect(self):
return reverse(
viewname='converter:transformation_list', kwargs={
'app_label': self.object.object_layer.content_type.app_label,
'model': self.object.object_layer.content_type.model,
'object_id': self.object.object_layer.object_id,
'layer_name': self.object.object_layer.stored_layer.name
}
)
def get_template_names(self):
return [
getattr(
self.object.get_transformation_class(), 'template_name',
self.template_name
)
]
class TransformationListView(
LayerViewMixin, ExternalContentTypeObjectMixin, SingleObjectListView
):
def get_external_object_permission(self):
return self.layer.permissions.get('view', None)
def get_extra_context(self):
return {
'object': self.external_object,
'hide_link': True,
'hide_object': True,
'layer_name': self.layer.name,
'no_results_icon': self.layer.get_icon(),
'no_results_main_link': link_transformation_select.resolve(
context=RequestContext(
request=self.request, dict_={
'resolved_object': self.external_object,
'layer_name': self.kwargs['layer_name'],
}
)
),
'no_results_text': self.layer.get_empty_results_text(),
'no_results_title': _(
'There are no entries for layer "%(layer_name)s"'
) % {'layer_name': self.layer.label},
'title': _(
'Layer "%(layer)s" transformations for: %(object)s'
) % {
'layer': self.layer,
'object': self.external_object,
}
}
def get_source_queryset(self):
return self.layer.get_transformations_for(obj=self.external_object)
class TransformationSelectView(
ExternalContentTypeObjectMixin, LayerViewMixin, FormView
):
form_class = LayerTransformationSelectForm
template_name = 'appearance/generic_form.html'
def form_valid(self, form):
return HttpResponseRedirect(
redirect_to=reverse(
viewname='converter:transformation_create',
kwargs={
'app_label': self.kwargs['app_label'],
'model': self.kwargs['model'],
'object_id': self.kwargs['object_id'],
'layer_name': self.kwargs['layer_name'],
'transformation_name': form.cleaned_data[
'transformation'
]
}
)
)
def get_extra_context(self):
return {
'layer': self.layer,
'layer_name': self.kwargs['layer_name'],
'navigation_object_list': ('content_object',),
'content_object': self.external_object,
'submit_label': _('Select'),
'title': _(
'Select new layer "%(layer)s" transformation '
'for: %(object)s'
) % {
'layer': self.layer,
'object': self.external_object,
}
}
def get_form_extra_kwargs(self):
return {
'layer': self.layer
}
"""

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
)