Add converter layers, redactions app
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
@@ -45,7 +45,7 @@
|
|||||||
{{ field }}
|
{{ field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
<div class="form-group {% if field.errors %}has-error{% endif %}">
|
<div class="form-group {% if field.errors %}has-error{% endif %} {{ form_field_css_classes }}">
|
||||||
{# We display the label then the field for all except checkboxes #}
|
{# We display the label then the field for all except checkboxes #}
|
||||||
{% if field|widget_type != 'checkboxinput' and not field.field.widget.attrs.hidden %}
|
{% if field|widget_type != 'checkboxinput' and not field.field.widget.attrs.hidden %}
|
||||||
{% if not hide_labels %}{{ field.label_tag }}{% if field.field.required and not read_only %} ({% trans 'required' %}){% endif %}{% endif %}
|
{% if not hide_labels %}{{ field.label_tag }}{% if field.field.required and not read_only %} ({% trans 'required' %}){% endif %}{% endif %}
|
||||||
|
|||||||
@@ -355,8 +355,8 @@ class DocumentCheckoutViewTestCase(
|
|||||||
|
|
||||||
class NewVersionBlockViewTestCase(
|
class NewVersionBlockViewTestCase(
|
||||||
DocumentCheckoutTestMixin, DocumentCheckoutViewTestMixin,
|
DocumentCheckoutTestMixin, DocumentCheckoutViewTestMixin,
|
||||||
GenericDocumentViewTestCase):
|
GenericDocumentViewTestCase
|
||||||
|
):
|
||||||
def test_document_check_out_new_version(self):
|
def test_document_check_out_new_version(self):
|
||||||
"""
|
"""
|
||||||
Gitlab issue #231
|
Gitlab issue #231
|
||||||
|
|||||||
@@ -430,6 +430,21 @@ class RestrictedQuerysetMixin(object):
|
|||||||
object_permission = None
|
object_permission = None
|
||||||
source_queryset = None
|
source_queryset = None
|
||||||
|
|
||||||
|
def get_object_permission(self):
|
||||||
|
return self.object_permission
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = self.get_source_queryset()
|
||||||
|
object_permission = self.get_object_permission()
|
||||||
|
|
||||||
|
if object_permission:
|
||||||
|
queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=object_permission, queryset=queryset,
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_source_queryset(self):
|
def get_source_queryset(self):
|
||||||
if self.source_queryset is None:
|
if self.source_queryset is None:
|
||||||
if self.model:
|
if self.model:
|
||||||
@@ -445,17 +460,6 @@ class RestrictedQuerysetMixin(object):
|
|||||||
|
|
||||||
return self.source_queryset.all()
|
return self.source_queryset.all()
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
queryset = self.get_source_queryset()
|
|
||||||
|
|
||||||
if self.object_permission:
|
|
||||||
queryset = AccessControlList.objects.restrict_queryset(
|
|
||||||
permission=self.object_permission, queryset=queryset,
|
|
||||||
user=self.request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class ViewPermissionCheckMixin(object):
|
class ViewPermissionCheckMixin(object):
|
||||||
"""
|
"""
|
||||||
@@ -467,11 +471,16 @@ class ViewPermissionCheckMixin(object):
|
|||||||
view_permission = None
|
view_permission = None
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if self.view_permission:
|
view_permission = self.get_view_permission()
|
||||||
|
if view_permission:
|
||||||
Permission.check_user_permissions(
|
Permission.check_user_permissions(
|
||||||
permissions=(self.view_permission,), user=self.request.user
|
permissions=(view_permission,),
|
||||||
|
user=self.request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
return super(
|
return super(
|
||||||
ViewPermissionCheckMixin, self
|
ViewPermissionCheckMixin, self
|
||||||
).dispatch(request, *args, **kwargs)
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_view_permission(self):
|
||||||
|
return self.view_permission
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ from __future__ import unicode_literals
|
|||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.acls.classes import ModelPermission
|
||||||
from mayan.apps.common.apps import MayanAppConfig
|
from mayan.apps.common.apps import MayanAppConfig
|
||||||
from mayan.apps.common.menus import menu_object, menu_secondary
|
from mayan.apps.common.menus import menu_object, menu_secondary
|
||||||
from mayan.apps.navigation.classes import SourceColumn
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
|
|
||||||
from .dependencies import * # NOQA
|
from .dependencies import * # NOQA
|
||||||
from .links import (
|
from .links import (
|
||||||
link_transformation_create, link_transformation_delete,
|
link_transformation_delete, link_transformation_edit,
|
||||||
link_transformation_edit
|
link_transformation_select
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -24,26 +25,31 @@ class ConverterApp(MayanAppConfig):
|
|||||||
def ready(self):
|
def ready(self):
|
||||||
super(ConverterApp, self).ready()
|
super(ConverterApp, self).ready()
|
||||||
|
|
||||||
Transformation = self.get_model(model_name='Transformation')
|
LayerTransformation = self.get_model(model_name='LayerTransformation')
|
||||||
|
|
||||||
SourceColumn(attribute='order', source=Transformation)
|
ModelPermission.register_inheritance(
|
||||||
|
model=LayerTransformation,
|
||||||
|
related='object_layer__content_object',
|
||||||
|
)
|
||||||
|
|
||||||
|
SourceColumn(attribute='order', source=LayerTransformation)
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=Transformation, label=_('Transformation'),
|
source=LayerTransformation, label=_('Transformation'),
|
||||||
func=lambda context: force_text(context['object'])
|
func=lambda context: force_text(context['object'])
|
||||||
)
|
)
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
attribute='arguments', source=Transformation
|
attribute='arguments', source=LayerTransformation
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=(link_transformation_edit, link_transformation_delete),
|
links=(link_transformation_edit, link_transformation_delete),
|
||||||
sources=(Transformation,)
|
sources=(LayerTransformation,)
|
||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_transformation_create,), sources=(Transformation,)
|
links=(link_transformation_select,), sources=(LayerTransformation,)
|
||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_transformation_create,),
|
links=(link_transformation_select,),
|
||||||
sources=(
|
sources=(
|
||||||
'converter:transformation_create',
|
'converter:transformation_create',
|
||||||
'converter:transformation_list'
|
'converter:transformation_list'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import copy
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -8,9 +9,16 @@ import shutil
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
import sh
|
import sh
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.db import transaction
|
||||||
|
from django.utils.encoding import force_text, python_2_unicode_compatible
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.appearance.classes import Icon
|
||||||
from mayan.apps.mimetype.api import get_mimetype
|
from mayan.apps.mimetype.api import get_mimetype
|
||||||
|
from mayan.apps.navigation.classes import Link
|
||||||
from mayan.apps.storage.settings import setting_temporary_directory
|
from mayan.apps.storage.settings import setting_temporary_directory
|
||||||
from mayan.apps.storage.utils import (
|
from mayan.apps.storage.utils import (
|
||||||
NamedTemporaryFile, fs_cleanup, mkdtemp
|
NamedTemporaryFile, fs_cleanup, mkdtemp
|
||||||
@@ -202,3 +210,228 @@ class ConverterBase(object):
|
|||||||
|
|
||||||
for transformation in transformations:
|
for transformation in transformations:
|
||||||
self.image = transformation.execute_on(image=self.image)
|
self.image = transformation.execute_on(image=self.image)
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class Layer(object):
|
||||||
|
_registry = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def all(cls):
|
||||||
|
return cls._registry.values()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, name):
|
||||||
|
return cls._registry[name]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_value(cls, key, value):
|
||||||
|
for name, layer in cls._registry.items():
|
||||||
|
if getattr(layer, key) == value:
|
||||||
|
return layer
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def invalidate_cache(cls):
|
||||||
|
for layer in cls.all():
|
||||||
|
layer.__dict__.pop('stored_layer', None)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls):
|
||||||
|
for layer in cls.all():
|
||||||
|
layer.stored_layer
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, label, name, order, permissions, default=False,
|
||||||
|
empty_results_text=None, symbol=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
access_permission is the permission necessary to view the layer.
|
||||||
|
exclude_permission is the permission necessary to discard the layer.
|
||||||
|
"""
|
||||||
|
self.default = default
|
||||||
|
self.empty_results_text = empty_results_text
|
||||||
|
self.label = label
|
||||||
|
self.name = name
|
||||||
|
self.order = order
|
||||||
|
self.permissions = permissions
|
||||||
|
self.symbol = symbol
|
||||||
|
|
||||||
|
# Check order
|
||||||
|
layer = self.__class__.get_by_value(key='order', value=self.order)
|
||||||
|
|
||||||
|
if layer:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'Layer "{}" already has order "{}" requested by layer "{}"'.format(
|
||||||
|
layer.name, order, self.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check default
|
||||||
|
if default:
|
||||||
|
layer = self.__class__.get_by_value(key='default', value=True)
|
||||||
|
if layer:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'Layer "{}" is already the default layer; "{}"'.format(
|
||||||
|
layer.name, self.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.__class__._registry[name] = self
|
||||||
|
|
||||||
|
def get_permission(self, name):
|
||||||
|
return self.permissions.get(name, None)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return force_text(self.label)
|
||||||
|
|
||||||
|
def add_transformation_to(self, obj, transformation_class, arguments=None):
|
||||||
|
ContentType = apps.get_model(
|
||||||
|
app_label='contenttypes', model_name='ContentType'
|
||||||
|
)
|
||||||
|
content_type = ContentType.objects.get_for_model(model=obj)
|
||||||
|
object_layer, created = self.stored_layer.object_layers.get_or_create(
|
||||||
|
content_type=content_type, object_id=obj.pk
|
||||||
|
)
|
||||||
|
object_layer.transformations.create(
|
||||||
|
name=transformation_class.name, arguments=arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
def copy_transformations(self, source, targets):
|
||||||
|
"""
|
||||||
|
Copy transformation from source to all targets
|
||||||
|
"""
|
||||||
|
ContentType = apps.get_model(
|
||||||
|
app_label='contenttypes', model_name='ContentType'
|
||||||
|
)
|
||||||
|
|
||||||
|
transformations = self.get_transformations_for(obj=source)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for target in targets:
|
||||||
|
content_type = ContentType.objects.get_for_model(model=target)
|
||||||
|
object_layer, created = self.stored_layer.object_layers.get_or_create(
|
||||||
|
content_type=content_type, object_id=target.pk
|
||||||
|
)
|
||||||
|
for transformation in transformations:
|
||||||
|
object_layer.transformations.create(
|
||||||
|
order=transformation.order,
|
||||||
|
name=transformation.name,
|
||||||
|
arguments=transformation.arguments,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_empty_results_text(self):
|
||||||
|
if self.empty_results_text:
|
||||||
|
return self.empty_results_text
|
||||||
|
else:
|
||||||
|
return _(
|
||||||
|
'Transformations allow changing the visual appearance '
|
||||||
|
'of documents without making permanent changes to the '
|
||||||
|
'document file themselves.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_icon(self):
|
||||||
|
return Icon(driver_name='fontawesome', symbol=self.symbol)
|
||||||
|
|
||||||
|
def get_model_instance(self):
|
||||||
|
StoredLayer = apps.get_model(
|
||||||
|
app_label='converter', model_name='StoredLayer'
|
||||||
|
)
|
||||||
|
stored_layer, created = StoredLayer.objects.update_or_create(
|
||||||
|
name=self.name, defaults={'order': self.order}
|
||||||
|
)
|
||||||
|
|
||||||
|
return stored_layer
|
||||||
|
|
||||||
|
def get_transformations_for(self, obj, as_classes=False):
|
||||||
|
"""
|
||||||
|
as_classes == True returns the transformation classes from .classes
|
||||||
|
ready to be feed to the converter class
|
||||||
|
"""
|
||||||
|
LayerTransformation = apps.get_model(
|
||||||
|
app_label='converter', model_name='LayerTransformation'
|
||||||
|
)
|
||||||
|
|
||||||
|
return LayerTransformation.objects.get_for_object(
|
||||||
|
obj=obj, as_classes=as_classes,
|
||||||
|
only_stored_layer=self.stored_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def stored_layer(self):
|
||||||
|
return self.get_model_instance()
|
||||||
|
|
||||||
|
|
||||||
|
class LayerLink(Link):
|
||||||
|
@staticmethod
|
||||||
|
def set_icon(instance, layer):
|
||||||
|
if instance.action == 'list':
|
||||||
|
if layer.symbol:
|
||||||
|
instance.icon_class = layer.get_icon()
|
||||||
|
|
||||||
|
def __init__(self, action, layer, object_name=None, **kwargs):
|
||||||
|
super(LayerLink, self).__init__(**kwargs)
|
||||||
|
self.action = action
|
||||||
|
self.layer = layer
|
||||||
|
self.object_name = object_name or _('transformation')
|
||||||
|
|
||||||
|
permission = layer.permissions.get(action, None)
|
||||||
|
if permission:
|
||||||
|
self.permissions = (permission,)
|
||||||
|
|
||||||
|
if action == 'list':
|
||||||
|
self.kwargs = LayerLinkKwargsFactory(
|
||||||
|
layer_name=layer.name
|
||||||
|
).get_kwargs_function()
|
||||||
|
|
||||||
|
if action in ('create', 'select'):
|
||||||
|
self.kwargs = LayerLinkKwargsFactory().get_kwargs_function()
|
||||||
|
|
||||||
|
LayerLink.set_icon(instance=self, layer=layer)
|
||||||
|
|
||||||
|
def copy(self, layer):
|
||||||
|
result = copy.copy(self)
|
||||||
|
result.kwargs = LayerLinkKwargsFactory(
|
||||||
|
layer_name=layer.name
|
||||||
|
).get_kwargs_function()
|
||||||
|
result._layer_name = layer.name
|
||||||
|
|
||||||
|
LayerLink.set_icon(instance=result, layer=layer)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def layer_name(self):
|
||||||
|
return getattr(
|
||||||
|
self, '_layer_name', Layer.get_by_value(
|
||||||
|
key='default', value=True
|
||||||
|
).name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LayerLinkKwargsFactory(object):
|
||||||
|
def __init__(self, layer_name=None, variable_name='resolved_object'):
|
||||||
|
self.layer_name = layer_name
|
||||||
|
self.variable_name = variable_name
|
||||||
|
|
||||||
|
def get_kwargs_function(self):
|
||||||
|
def get_kwargs(context):
|
||||||
|
ContentType = apps.get_model(
|
||||||
|
app_label='contenttypes', model_name='ContentType'
|
||||||
|
)
|
||||||
|
|
||||||
|
content_type = ContentType.objects.get_for_model(
|
||||||
|
context[self.variable_name]
|
||||||
|
)
|
||||||
|
default_layer = Layer.get_by_value(key='default', value=True)
|
||||||
|
return {
|
||||||
|
'app_label': '"{}"'.format(content_type.app_label),
|
||||||
|
'model': '"{}"'.format(content_type.model),
|
||||||
|
'object_id': '{}.pk'.format(self.variable_name),
|
||||||
|
'layer_name': '"{}"'.format(
|
||||||
|
self.layer_name or context.get(
|
||||||
|
'layer_name', default_layer.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return get_kwargs
|
||||||
|
|||||||
@@ -8,13 +8,49 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from mayan.apps.common.serialization import yaml_load
|
from mayan.apps.common.serialization import yaml_load
|
||||||
|
|
||||||
from .models import Transformation
|
from .models import LayerTransformation
|
||||||
|
from .transformations import BaseTransformation
|
||||||
|
|
||||||
|
|
||||||
class TransformationForm(forms.ModelForm):
|
class LayerTransformationSelectForm(forms.Form):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
layer = kwargs.pop('layer')
|
||||||
|
super(LayerTransformationSelectForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields[
|
||||||
|
'transformation'
|
||||||
|
].choices = BaseTransformation.get_transformation_choices(layer=layer)
|
||||||
|
|
||||||
|
transformation = forms.ChoiceField(
|
||||||
|
choices=(), help_text=_('Available transformations for this layer.'),
|
||||||
|
label=_('Transformation'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LayerTransformationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('name', 'arguments', 'order')
|
fields = ('arguments', 'order')
|
||||||
model = Transformation
|
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):
|
def clean(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ from mayan.apps.appearance.classes import Icon
|
|||||||
|
|
||||||
icon_transformations = Icon(driver_name='fontawesome', symbol='crop')
|
icon_transformations = Icon(driver_name='fontawesome', symbol='crop')
|
||||||
|
|
||||||
icon_transformation_create = Icon(
|
|
||||||
driver_name='fontawesome-dual', primary_symbol='crop',
|
|
||||||
secondary_symbol='plus'
|
|
||||||
)
|
|
||||||
icon_transformation_delete = Icon(driver_name='fontawesome', symbol='times')
|
icon_transformation_delete = Icon(driver_name='fontawesome', symbol='times')
|
||||||
icon_transformation_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
|
icon_transformation_edit = Icon(driver_name='fontawesome', symbol='pencil-alt')
|
||||||
icon_transformation_list = icon_transformations
|
icon_transformation_list = icon_transformations
|
||||||
|
icon_transformation_select = Icon(driver_name='fontawesome', symbol='plus')
|
||||||
|
|||||||
20
mayan/apps/converter/layers.py
Normal file
20
mayan/apps/converter/layers.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .classes import Layer
|
||||||
|
from .permissions import (
|
||||||
|
permission_transformation_create, permission_transformation_delete,
|
||||||
|
permission_transformation_edit, permission_transformation_view
|
||||||
|
)
|
||||||
|
|
||||||
|
layer_saved_transformations = Layer(
|
||||||
|
default=True, label=_('Saved transformations'),
|
||||||
|
name='saved_transformations', order=100, permissions={
|
||||||
|
'create': permission_transformation_create,
|
||||||
|
'delete': permission_transformation_delete,
|
||||||
|
'edit': permission_transformation_edit,
|
||||||
|
'select': permission_transformation_create,
|
||||||
|
'view': permission_transformation_view,
|
||||||
|
}, symbol='crop'
|
||||||
|
)
|
||||||
@@ -1,55 +1,37 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.navigation.classes import Link
|
from .classes import LayerLink
|
||||||
|
from .layers import layer_saved_transformations
|
||||||
from .permissions import (
|
|
||||||
permission_transformation_create, permission_transformation_delete,
|
|
||||||
permission_transformation_edit, permission_transformation_view
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_kwargs_factory(variable_name):
|
def conditional_active(context, resolved_link):
|
||||||
def get_kwargs(context):
|
return resolved_link.link.view == resolved_link.current_view_name and context.get('layer_name', None) == resolved_link.link.layer_name
|
||||||
ContentType = apps.get_model(
|
|
||||||
app_label='contenttypes', model_name='ContentType'
|
|
||||||
)
|
|
||||||
|
|
||||||
content_type = ContentType.objects.get_for_model(
|
|
||||||
context[variable_name]
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
'app_label': '"{}"'.format(content_type.app_label),
|
|
||||||
'model': '"{}"'.format(content_type.model),
|
|
||||||
'object_id': '{}.pk'.format(variable_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return get_kwargs
|
|
||||||
|
|
||||||
|
|
||||||
link_transformation_create = Link(
|
link_transformation_delete = LayerLink(
|
||||||
icon_class_path='mayan.apps.converter.icons.icon_transformation_create',
|
action='delete',
|
||||||
kwargs=get_kwargs_factory('content_object'),
|
kwargs={'layer_name': 'layer_name', 'pk': 'resolved_object.pk'},
|
||||||
permissions=(permission_transformation_create,),
|
|
||||||
text=_('Create new transformation'), view='converter:transformation_create'
|
|
||||||
)
|
|
||||||
link_transformation_delete = Link(
|
|
||||||
args='resolved_object.pk',
|
|
||||||
icon_class_path='mayan.apps.converter.icons.icon_transformation_delete',
|
icon_class_path='mayan.apps.converter.icons.icon_transformation_delete',
|
||||||
permissions=(permission_transformation_delete,),
|
layer=layer_saved_transformations,
|
||||||
tags='dangerous', text=_('Delete'), view='converter:transformation_delete'
|
tags='dangerous', text=_('Delete'), view='converter:transformation_delete'
|
||||||
)
|
)
|
||||||
link_transformation_edit = Link(
|
link_transformation_edit = LayerLink(
|
||||||
args='resolved_object.pk',
|
action='edit',
|
||||||
|
kwargs={'layer_name': 'layer_name', 'pk': 'resolved_object.pk'},
|
||||||
icon_class_path='mayan.apps.converter.icons.icon_transformation_edit',
|
icon_class_path='mayan.apps.converter.icons.icon_transformation_edit',
|
||||||
permissions=(permission_transformation_edit,),
|
layer=layer_saved_transformations,
|
||||||
text=_('Edit'), view='converter:transformation_edit'
|
text=_('Edit'), view='converter:transformation_edit'
|
||||||
)
|
)
|
||||||
link_transformation_list = Link(
|
link_transformation_list = LayerLink(
|
||||||
icon_class_path='mayan.apps.converter.icons.icon_transformation_list',
|
action='list', conditional_active=conditional_active,
|
||||||
kwargs=get_kwargs_factory('resolved_object'),
|
layer=layer_saved_transformations, text=_('Transformations'),
|
||||||
permissions=(permission_transformation_view,), text=_('Transformations'),
|
|
||||||
view='converter:transformation_list'
|
view='converter:transformation_list'
|
||||||
)
|
)
|
||||||
|
link_transformation_select = LayerLink(
|
||||||
|
action='select',
|
||||||
|
icon_class_path='mayan.apps.converter.icons.icon_transformation_select',
|
||||||
|
layer=layer_saved_transformations, text=_('Select new transformation'),
|
||||||
|
view='converter:transformation_select'
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,75 +2,89 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models, transaction
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
from mayan.apps.common.serialization import yaml_dump, yaml_load
|
from mayan.apps.acls.models import AccessControlList
|
||||||
|
from mayan.apps.common.serialization import yaml_load
|
||||||
|
|
||||||
|
from .classes import Layer
|
||||||
from .transformations import BaseTransformation
|
from .transformations import BaseTransformation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TransformationManager(models.Manager):
|
class LayerTransformationManager(models.Manager):
|
||||||
def add_to_object(self, obj, transformation, arguments=None):
|
def get_for_object(
|
||||||
content_type = ContentType.objects.get_for_model(model=obj)
|
self, obj, as_classes=False, maximum_layer_order=None,
|
||||||
|
only_stored_layer=None, user=None
|
||||||
self.create(
|
):
|
||||||
content_type=content_type, object_id=obj.pk,
|
|
||||||
name=transformation.name, arguments=yaml_dump(
|
|
||||||
data=arguments
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def copy(self, source, targets):
|
|
||||||
"""
|
|
||||||
Copy transformation from source to all targets
|
|
||||||
"""
|
|
||||||
content_type = ContentType.objects.get_for_model(model=source)
|
|
||||||
|
|
||||||
# Get transformations
|
|
||||||
transformations = self.filter(
|
|
||||||
content_type=content_type, object_id=source.pk
|
|
||||||
).values('name', 'arguments', 'order')
|
|
||||||
logger.debug('source transformations: %s', transformations)
|
|
||||||
|
|
||||||
# Get all targets from target QS
|
|
||||||
targets_dict = map(
|
|
||||||
lambda entry: {
|
|
||||||
'content_type': entry[0], 'object_id': entry[1]
|
|
||||||
}, zip(
|
|
||||||
ContentType.objects.get_for_models(models=targets).values(),
|
|
||||||
targets.values_list('pk', flat=True)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.debug('targets: %s', targets_dict)
|
|
||||||
|
|
||||||
# Combine the two
|
|
||||||
results = []
|
|
||||||
for instance in targets_dict:
|
|
||||||
for transformation in transformations:
|
|
||||||
result = instance.copy()
|
|
||||||
result.update(transformation)
|
|
||||||
results.append(dict(result))
|
|
||||||
|
|
||||||
logger.debug('results: %s', results)
|
|
||||||
|
|
||||||
# Bulk create for a single DB query
|
|
||||||
with transaction.atomic():
|
|
||||||
self.bulk_create(
|
|
||||||
map(lambda entry: self.model(**entry), results),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_for_object(self, obj, as_classes=False):
|
|
||||||
"""
|
"""
|
||||||
as_classes == True returns the transformation classes from .classes
|
as_classes == True returns the transformation classes from .classes
|
||||||
ready to be feed to the converter class
|
ready to be feed to the converter class
|
||||||
"""
|
"""
|
||||||
|
Layer.update()
|
||||||
|
|
||||||
|
StoredLayer = apps.get_model(
|
||||||
|
app_label='converter', model_name='StoredLayer'
|
||||||
|
)
|
||||||
content_type = ContentType.objects.get_for_model(model=obj)
|
content_type = ContentType.objects.get_for_model(model=obj)
|
||||||
|
|
||||||
transformations = self.filter(
|
transformations = self.filter(
|
||||||
content_type=content_type, object_id=obj.pk
|
enabled=True, object_layer__content_type=content_type,
|
||||||
|
object_layer__object_id=obj.pk, object_layer__enabled=True
|
||||||
|
)
|
||||||
|
|
||||||
|
access_layers = StoredLayer.objects.all()
|
||||||
|
exclude_layers = StoredLayer.objects.none()
|
||||||
|
|
||||||
|
if maximum_layer_order:
|
||||||
|
access_layers = StoredLayer.objects.filter(
|
||||||
|
order__lte=maximum_layer_order
|
||||||
|
)
|
||||||
|
exclude_layers = StoredLayer.objects.filter(
|
||||||
|
order__gt=maximum_layer_order
|
||||||
|
)
|
||||||
|
|
||||||
|
for stored_layer in access_layers:
|
||||||
|
access_permission = stored_layer.get_layer().permissions.get(
|
||||||
|
'access_permission', None
|
||||||
|
)
|
||||||
|
if access_permission:
|
||||||
|
try:
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
obj=obj, permissions=(access_permission,), user=user
|
||||||
|
)
|
||||||
|
except PermissionDenied:
|
||||||
|
access_layers = access_layers.exclude(pk=stored_layer.pk)
|
||||||
|
|
||||||
|
for stored_layer in exclude_layers:
|
||||||
|
exclude_permission = stored_layer.get_layer().permissions.get(
|
||||||
|
'exclude_permission', None
|
||||||
|
)
|
||||||
|
if exclude_permission:
|
||||||
|
try:
|
||||||
|
AccessControlList.objects.check_access(
|
||||||
|
obj=obj, permissions=(exclude_permission,), user=user
|
||||||
|
)
|
||||||
|
except PermissionDenied:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
exclude_layers = exclude_layers.exclude(pk=stored_layer.pk)
|
||||||
|
|
||||||
|
if only_stored_layer:
|
||||||
|
transformations = transformations.filter(
|
||||||
|
object_layer__stored_layer=only_stored_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
transformations = transformations.filter(
|
||||||
|
object_layer__stored_layer__in=access_layers
|
||||||
|
)
|
||||||
|
|
||||||
|
transformations = transformations.exclude(
|
||||||
|
object_layer__stored_layer__in=exclude_layers
|
||||||
)
|
)
|
||||||
|
|
||||||
if as_classes:
|
if as_classes:
|
||||||
|
|||||||
81
mayan/apps/converter/migrations/0014_auto_20190814_0013.py
Normal file
81
mayan/apps/converter/migrations/0014_auto_20190814_0013.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import mayan.apps.converter.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('converter', '0013_auto_20180823_2353'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LayerTransformation',
|
||||||
|
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=[('crop', 'Crop: left, top, right, bottom'), ('draw_rectangle', 'Draw rectangle: left, top, right, bottom, fillcolor, outlinecolor, outlinewidth'), ('draw_rectangle_percent', 'Draw rectangle (percents coordinates): left, top, right, bottom, fillcolor, outlinecolor, outlinewidth'), ('flip', 'Flip'), ('gaussianblur', 'Gaussian blur: radius'), ('lineart', 'Line art'), ('mirror', 'Mirror'), ('redaction_percent', 'Redaction: left, top, right, bottom'), ('resize', 'Resize: width, height'), ('rotate', 'Rotate: degrees, fillcolor'), ('rotate180', 'Rotate 180 degrees'), ('rotate270', 'Rotate 270 degrees'), ('rotate90', 'Rotate 90 degrees'), ('unsharpmask', 'Unsharp masking: radius, percent, threshold'), ('zoom', 'Zoom: percent')], max_length=128, verbose_name='Name')),
|
||||||
|
('arguments', models.TextField(blank=True, help_text='Enter the arguments for the transformation as a YAML dictionary. ie: {"degrees": 180}', validators=[mayan.apps.converter.validators.YAMLValidator()], verbose_name='Arguments')),
|
||||||
|
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('object_layer__stored_layer__order', 'order'),
|
||||||
|
'verbose_name': 'Layer transformation',
|
||||||
|
'verbose_name_plural': 'Layer transformations',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ObjectLayer',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('object_id', models.PositiveIntegerField()),
|
||||||
|
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
|
||||||
|
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('stored_layer__order',),
|
||||||
|
'verbose_name': 'Object layer',
|
||||||
|
'verbose_name_plural': 'Object layers',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StoredLayer',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=64, unique=True, verbose_name='Name')),
|
||||||
|
('order', models.PositiveIntegerField(db_index=True, unique=True, verbose_name='Order')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('order',),
|
||||||
|
'verbose_name': 'Stored layer',
|
||||||
|
'verbose_name_plural': 'Stored layers',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transformation',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[('crop', 'Crop: left, top, right, bottom'), ('draw_rectangle', 'Draw rectangle: left, top, right, bottom, fillcolor, outlinecolor, outlinewidth'), ('draw_rectangle_percent', 'Draw rectangle (percents coordinates): left, top, right, bottom, fillcolor, outlinecolor, outlinewidth'), ('flip', 'Flip'), ('gaussianblur', 'Gaussian blur: radius'), ('lineart', 'Line art'), ('mirror', 'Mirror'), ('redaction_percent', 'Redaction: left, top, right, bottom'), ('resize', 'Resize: width, height'), ('rotate', 'Rotate: degrees, fillcolor'), ('rotate180', 'Rotate 180 degrees'), ('rotate270', 'Rotate 270 degrees'), ('rotate90', 'Rotate 90 degrees'), ('unsharpmask', 'Unsharp masking: radius, percent, threshold'), ('zoom', 'Zoom: percent')], max_length=128, verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='objectlayer',
|
||||||
|
name='stored_layer',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='object_layers', to='converter.StoredLayer', verbose_name='Stored layer'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='layertransformation',
|
||||||
|
name='object_layer',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transformations', to='converter.ObjectLayer', verbose_name='Object layer'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='objectlayer',
|
||||||
|
unique_together=set([('content_type', 'object_id', 'stored_layer')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='layertransformation',
|
||||||
|
unique_together=set([('object_layer', 'order')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
76
mayan/apps/converter/migrations/0015_auto_20190814_0014.py
Normal file
76
mayan/apps/converter/migrations/0015_auto_20190814_0014.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
from ..layers import layer_saved_transformations
|
||||||
|
|
||||||
|
|
||||||
|
def code_copy_transformations(apps, schema_editor):
|
||||||
|
ObjectLayer = apps.get_model(
|
||||||
|
app_label='converter', model_name='ObjectLayer'
|
||||||
|
)
|
||||||
|
StoredLayer = apps.get_model(
|
||||||
|
app_label='converter', model_name='StoredLayer'
|
||||||
|
)
|
||||||
|
Transformation = apps.get_model(
|
||||||
|
app_label='converter', model_name='Transformation'
|
||||||
|
)
|
||||||
|
|
||||||
|
stored_layer, created = StoredLayer.objects.using(schema_editor.connection.alias).update_or_create(
|
||||||
|
name=layer_saved_transformations.name, defaults={'order': layer_saved_transformations.order}
|
||||||
|
)
|
||||||
|
|
||||||
|
for transformation in Transformation.objects.using(schema_editor.connection.alias).all():
|
||||||
|
object_layer, created = ObjectLayer.objects.get_or_create(
|
||||||
|
content_type=transformation.content_type,
|
||||||
|
object_id=transformation.object_id,
|
||||||
|
stored_layer=stored_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
object_layer.transformations.create(
|
||||||
|
order=transformation.order, name=transformation.name,
|
||||||
|
arguments=transformation.arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def code_copy_transformations_reverse(apps, schema_editor):
|
||||||
|
LayerTransformation = apps.get_model(
|
||||||
|
app_label='converter', model_name='LayerTransformation'
|
||||||
|
)
|
||||||
|
ObjectLayer = apps.get_model(
|
||||||
|
app_label='converter', model_name='ObjectLayer'
|
||||||
|
)
|
||||||
|
StoredLayer = apps.get_model(
|
||||||
|
app_label='converter', model_name='StoredLayer'
|
||||||
|
)
|
||||||
|
Transformation = apps.get_model(
|
||||||
|
app_label='converter', model_name='Transformation'
|
||||||
|
)
|
||||||
|
|
||||||
|
stored_layer, created = StoredLayer.objects.using(schema_editor.connection.alias).update_or_create(
|
||||||
|
name=layer_saved_transformations.name, defaults={'order': layer_saved_transformations.order}
|
||||||
|
)
|
||||||
|
|
||||||
|
for object_layer in ObjectLayer.objects.using(schema_editor.connection.alias).filter(stored_layer=stored_layer):
|
||||||
|
for layer_transformation in LayerTransformation.objects.using(schema_editor.connection.alias).filter(object_layer=object_layer):
|
||||||
|
Transformation.objects.using(schema_editor.connection.alias).create(
|
||||||
|
content_type=object_layer.content_type,
|
||||||
|
object_id=object_layer.object_id,
|
||||||
|
order=layer_transformation.order,
|
||||||
|
name=layer_transformation.name,
|
||||||
|
arguments=layer_transformation.arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('converter', '0014_auto_20190814_0013'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
code=code_copy_transformations,
|
||||||
|
reverse_code=code_copy_transformations_reverse
|
||||||
|
)
|
||||||
|
]
|
||||||
26
mayan/apps/converter/migrations/0016_auto_20190814_0510.py
Normal file
26
mayan/apps/converter/migrations/0016_auto_20190814_0510.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.22 on 2019-08-14 05:10
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('converter', '0015_auto_20190814_0014'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='transformation',
|
||||||
|
unique_together=set([]),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='transformation',
|
||||||
|
name='content_type',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Transformation',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -9,7 +9,8 @@ from django.db.models import Max
|
|||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .managers import TransformationManager
|
from .classes import Layer
|
||||||
|
from .managers import LayerTransformationManager
|
||||||
from .transformations import BaseTransformation
|
from .transformations import BaseTransformation
|
||||||
from .validators import YAMLValidator
|
from .validators import YAMLValidator
|
||||||
|
|
||||||
@@ -17,7 +18,47 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Transformation(models.Model):
|
class StoredLayer(models.Model):
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=64, unique=True, verbose_name=_('Name')
|
||||||
|
)
|
||||||
|
order = models.PositiveIntegerField(
|
||||||
|
db_index=True, unique=True, verbose_name=_('Order')
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('order',)
|
||||||
|
verbose_name = _('Stored layer')
|
||||||
|
verbose_name_plural = _('Stored layers')
|
||||||
|
|
||||||
|
def get_layer(self):
|
||||||
|
return Layer.get(name=self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectLayer(models.Model):
|
||||||
|
content_type = models.ForeignKey(on_delete=models.CASCADE, to=ContentType)
|
||||||
|
object_id = models.PositiveIntegerField()
|
||||||
|
content_object = GenericForeignKey(
|
||||||
|
ct_field='content_type', fk_field='object_id'
|
||||||
|
)
|
||||||
|
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||||
|
stored_layer = models.ForeignKey(
|
||||||
|
on_delete=models.CASCADE, related_name='object_layers', to=StoredLayer,
|
||||||
|
verbose_name=_('Stored layer')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('stored_layer__order',)
|
||||||
|
unique_together = ('content_type', 'object_id', 'stored_layer')
|
||||||
|
verbose_name = _('Object layer')
|
||||||
|
verbose_name_plural = _('Object layers')
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class LayerTransformation(models.Model):
|
||||||
"""
|
"""
|
||||||
Model that stores the transformation and transformation arguments
|
Model that stores the transformation and transformation arguments
|
||||||
for a given object
|
for a given object
|
||||||
@@ -29,9 +70,10 @@ class Transformation(models.Model):
|
|||||||
transformation argument. Example: if a page is rotated with the Rotation
|
transformation argument. Example: if a page is rotated with the Rotation
|
||||||
transformation, this field will show by how many degrees it was rotated.
|
transformation, this field will show by how many degrees it was rotated.
|
||||||
"""
|
"""
|
||||||
content_type = models.ForeignKey(on_delete=models.CASCADE, to=ContentType)
|
object_layer = models.ForeignKey(
|
||||||
object_id = models.PositiveIntegerField()
|
on_delete=models.CASCADE, related_name='transformations',
|
||||||
content_object = GenericForeignKey('content_type', 'object_id')
|
to=ObjectLayer, verbose_name=_('Object layer')
|
||||||
|
)
|
||||||
order = models.PositiveIntegerField(
|
order = models.PositiveIntegerField(
|
||||||
blank=True, db_index=True, default=0, help_text=_(
|
blank=True, db_index=True, default=0, help_text=_(
|
||||||
'Order in which the transformations will be executed. If left '
|
'Order in which the transformations will be executed. If left '
|
||||||
@@ -48,23 +90,27 @@ class Transformation(models.Model):
|
|||||||
'dictionary. ie: {"degrees": 180}'
|
'dictionary. ie: {"degrees": 180}'
|
||||||
), validators=[YAMLValidator()], verbose_name=_('Arguments')
|
), validators=[YAMLValidator()], verbose_name=_('Arguments')
|
||||||
)
|
)
|
||||||
|
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||||
|
|
||||||
objects = TransformationManager()
|
objects = LayerTransformationManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('order',)
|
ordering = ('object_layer__stored_layer__order', 'order',)
|
||||||
unique_together = ('content_type', 'object_id', 'order')
|
unique_together = ('object_layer', 'order')
|
||||||
verbose_name = _('Transformation')
|
verbose_name = _('Layer transformation')
|
||||||
verbose_name_plural = _('Transformations')
|
verbose_name_plural = _('Layer transformations')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.get_name_display()
|
return self.get_name_display()
|
||||||
|
|
||||||
|
def get_transformation_class(self):
|
||||||
|
return BaseTransformation.get(name=self.name)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if not self.order:
|
if not self.order:
|
||||||
last_order = Transformation.objects.filter(
|
last_order = LayerTransformation.objects.filter(
|
||||||
content_type=self.content_type, object_id=self.object_id
|
object_layer=self.object_layer
|
||||||
).aggregate(Max('order'))['order__max']
|
).aggregate(Max('order'))['order__max']
|
||||||
if last_order is not None:
|
if last_order is not None:
|
||||||
self.order = last_order + 1
|
self.order = last_order + 1
|
||||||
super(Transformation, self).save(*args, **kwargs)
|
super(LayerTransformation, self).save(*args, **kwargs)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
TEST_TRANSFORMATION_NAME = 'rotate'
|
TEST_TRANSFORMATION_NAME = 'rotate'
|
||||||
TEST_TRANSFORMATION_ARGUMENT = 'degrees: 180'
|
TEST_TRANSFORMATION_ARGUMENT = 'degrees: 180'
|
||||||
|
TEST_TRANSFORMATION_ARGUMENT_EDITED = 'degrees: 270'
|
||||||
TEST_TRANSFORMATION_COMBINED_CACHE_HASH = '384bf78014d2aed7255d9e548a0694c70af0b22545653214bcceb1ac6286b5f7'
|
TEST_TRANSFORMATION_COMBINED_CACHE_HASH = '384bf78014d2aed7255d9e548a0694c70af0b22545653214bcceb1ac6286b5f7'
|
||||||
TEST_TRANSFORMATION_RESIZE_CACHE_HASH = b'4aa319f5a6950985a19380a1f279a66769d04138bd1583844270fe8c269260fc'
|
TEST_TRANSFORMATION_RESIZE_CACHE_HASH = b'4aa319f5a6950985a19380a1f279a66769d04138bd1583844270fe8c269260fc'
|
||||||
TEST_TRANSFORMATION_RESIZE_CACHE_HASH_2 = b'cc8d220d40e810b995181c0c69b44b7a61c3bb039c0be96a5465fcaf698ca99a'
|
TEST_TRANSFORMATION_RESIZE_CACHE_HASH_2 = b'cc8d220d40e810b995181c0c69b44b7a61c3bb039c0be96a5465fcaf698ca99a'
|
||||||
|
|||||||
97
mayan/apps/converter/tests/mixins.py
Normal file
97
mayan/apps/converter/tests/mixins.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
from mayan.apps.acls.classes import ModelPermission
|
||||||
|
from mayan.apps.permissions.tests.mixins import PermissionTestMixin
|
||||||
|
|
||||||
|
from ..classes import Layer
|
||||||
|
from ..models import ObjectLayer
|
||||||
|
|
||||||
|
from .literals import (
|
||||||
|
TEST_TRANSFORMATION_NAME, TEST_TRANSFORMATION_ARGUMENT,
|
||||||
|
TEST_TRANSFORMATION_ARGUMENT_EDITED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LayerTestMixin(PermissionTestMixin):
|
||||||
|
test_layer = Layer(
|
||||||
|
label='Test layer', name='test_layer', order=1000,
|
||||||
|
permissions={}
|
||||||
|
)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(LayerTestMixin, self).setUp()
|
||||||
|
self._create_test_permission()
|
||||||
|
|
||||||
|
self.test_layer_permission = self.test_permission
|
||||||
|
ModelPermission.register(
|
||||||
|
model=self.test_document._meta.model, permissions=(
|
||||||
|
self.test_permission,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.test_layer.permissions = {
|
||||||
|
'create': self.test_layer_permission,
|
||||||
|
'delete': self.test_layer_permission,
|
||||||
|
'edit': self.test_layer_permission,
|
||||||
|
'select': self.test_layer_permission,
|
||||||
|
'view': self.test_layer_permission,
|
||||||
|
}
|
||||||
|
Layer.invalidate_cache()
|
||||||
|
Layer.update()
|
||||||
|
|
||||||
|
|
||||||
|
class TransformationTestMixin(LayerTestMixin):
|
||||||
|
def _create_test_transformation(self):
|
||||||
|
content_type = ContentType.objects.get_for_model(model=self.test_document)
|
||||||
|
object_layer, created = ObjectLayer.objects.get_or_create(
|
||||||
|
content_type=content_type, object_id=self.test_document.pk,
|
||||||
|
stored_layer=self.test_layer.stored_layer
|
||||||
|
)
|
||||||
|
|
||||||
|
self.test_transformation = object_layer.transformations.create(
|
||||||
|
name=TEST_TRANSFORMATION_NAME,
|
||||||
|
arguments=TEST_TRANSFORMATION_ARGUMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TransformationViewsTestMixin(object):
|
||||||
|
def _request_transformation_create_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='converter:transformation_create', kwargs={
|
||||||
|
'app_label': 'documents', 'model': 'document',
|
||||||
|
'object_id': self.test_document.pk,
|
||||||
|
'layer_name': self.test_layer.name,
|
||||||
|
'transformation_name': TEST_TRANSFORMATION_NAME,
|
||||||
|
}, data={
|
||||||
|
'arguments': TEST_TRANSFORMATION_ARGUMENT
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_transformation_delete_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='converter:transformation_delete', kwargs={
|
||||||
|
'layer_name': self.test_layer.name,
|
||||||
|
'pk': self.test_transformation.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_transformation_edit_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='converter:transformation_edit', kwargs={
|
||||||
|
'layer_name': self.test_layer.name,
|
||||||
|
'pk': self.test_transformation.pk
|
||||||
|
}, data={
|
||||||
|
'arguments': TEST_TRANSFORMATION_ARGUMENT_EDITED
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _request_transformation_list_view(self):
|
||||||
|
return self.get(
|
||||||
|
viewname='converter:transformation_list', kwargs={
|
||||||
|
'app_label': 'documents', 'model': 'document',
|
||||||
|
'object_id': self.test_document.pk,
|
||||||
|
'layer_name': self.test_layer.name
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -4,7 +4,6 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from mayan.apps.documents.tests import GenericDocumentTestCase
|
from mayan.apps.documents.tests import GenericDocumentTestCase
|
||||||
|
|
||||||
from ..models import Transformation
|
|
||||||
from ..transformations import (
|
from ..transformations import (
|
||||||
BaseTransformation, TransformationCrop, TransformationLineArt,
|
BaseTransformation, TransformationCrop, TransformationLineArt,
|
||||||
TransformationResize, TransformationRotate, TransformationRotate90,
|
TransformationResize, TransformationRotate, TransformationRotate90,
|
||||||
@@ -24,6 +23,7 @@ from .literals import (
|
|||||||
TEST_TRANSFORMATION_ZOOM_CACHE_HASH,
|
TEST_TRANSFORMATION_ZOOM_CACHE_HASH,
|
||||||
TEST_TRANSFORMATION_ZOOM_PERCENT,
|
TEST_TRANSFORMATION_ZOOM_PERCENT,
|
||||||
)
|
)
|
||||||
|
from .mixins import LayerTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TransformationBaseTestCase(TestCase):
|
class TransformationBaseTestCase(TestCase):
|
||||||
@@ -110,14 +110,14 @@ class TransformationBaseTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TransformationTestCase(GenericDocumentTestCase):
|
class TransformationTestCase(LayerTestMixin, GenericDocumentTestCase):
|
||||||
def test_crop_transformation_optional_arguments(self):
|
def test_crop_transformation_optional_arguments(self):
|
||||||
self._silence_logger(name='mayan.apps.converter.managers')
|
self._silence_logger(name='mayan.apps.converter.managers')
|
||||||
|
|
||||||
document_page = self.test_document.pages.first()
|
document_page = self.test_document.pages.first()
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationCrop,
|
obj=document_page, transformation_class=TransformationCrop,
|
||||||
arguments={'top': '10'}
|
arguments={'top': '10'}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,8 +128,8 @@ class TransformationTestCase(GenericDocumentTestCase):
|
|||||||
|
|
||||||
document_page = self.test_document.pages.first()
|
document_page = self.test_document.pages.first()
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationCrop,
|
obj=document_page, transformation_class=TransformationCrop,
|
||||||
arguments={'top': 'x', 'left': '-'}
|
arguments={'top': 'x', 'left': '-'}
|
||||||
)
|
)
|
||||||
self.assertTrue(document_page.generate_image())
|
self.assertTrue(document_page.generate_image())
|
||||||
@@ -139,8 +139,8 @@ class TransformationTestCase(GenericDocumentTestCase):
|
|||||||
|
|
||||||
document_page = self.test_document.pages.first()
|
document_page = self.test_document.pages.first()
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationCrop,
|
obj=document_page, transformation_class=TransformationCrop,
|
||||||
arguments={'top': '-1000', 'bottom': '100000000'}
|
arguments={'top': '-1000', 'bottom': '100000000'}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,13 +151,13 @@ class TransformationTestCase(GenericDocumentTestCase):
|
|||||||
|
|
||||||
document_page = self.test_document.pages.first()
|
document_page = self.test_document.pages.first()
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationCrop,
|
obj=document_page, transformation_class=TransformationCrop,
|
||||||
arguments={'top': '1000', 'bottom': '1000'}
|
arguments={'top': '1000', 'bottom': '1000'}
|
||||||
)
|
)
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationCrop,
|
obj=document_page, transformation_class=TransformationCrop,
|
||||||
arguments={'left': '1000', 'right': '10000'}
|
arguments={'left': '1000', 'right': '10000'}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,8 +166,8 @@ class TransformationTestCase(GenericDocumentTestCase):
|
|||||||
def test_lineart_transformations(self):
|
def test_lineart_transformations(self):
|
||||||
document_page = self.test_document.pages.first()
|
document_page = self.test_document.pages.first()
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationLineArt,
|
obj=document_page, transformation_class=TransformationLineArt,
|
||||||
arguments={}
|
arguments={}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -176,22 +176,22 @@ class TransformationTestCase(GenericDocumentTestCase):
|
|||||||
def test_rotate_transformations(self):
|
def test_rotate_transformations(self):
|
||||||
document_page = self.test_document.pages.first()
|
document_page = self.test_document.pages.first()
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationRotate90,
|
obj=document_page, transformation_class=TransformationRotate90,
|
||||||
arguments={}
|
arguments={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(document_page.generate_image())
|
self.assertTrue(document_page.generate_image())
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationRotate180,
|
obj=document_page, transformation_class=TransformationRotate180,
|
||||||
arguments={}
|
arguments={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(document_page.generate_image())
|
self.assertTrue(document_page.generate_image())
|
||||||
|
|
||||||
Transformation.objects.add_to_object(
|
self.test_layer.add_transformation_to(
|
||||||
obj=document_page, transformation=TransformationRotate270,
|
obj=document_page, transformation_class=TransformationRotate270,
|
||||||
arguments={}
|
arguments={}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,110 +1,123 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
||||||
|
|
||||||
from ..models import Transformation
|
from ..models import LayerTransformation
|
||||||
from ..permissions import (
|
|
||||||
permission_transformation_create, permission_transformation_delete,
|
|
||||||
permission_transformation_view
|
|
||||||
)
|
|
||||||
|
|
||||||
from .literals import TEST_TRANSFORMATION_NAME, TEST_TRANSFORMATION_ARGUMENT
|
from .mixins import TransformationTestMixin, TransformationViewsTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TransformationViewsTestCase(GenericDocumentViewTestCase):
|
class TransformationViewsTestCase(
|
||||||
def _transformation_create_view(self):
|
TransformationTestMixin, TransformationViewsTestMixin,
|
||||||
return self.post(
|
GenericDocumentViewTestCase
|
||||||
viewname='converter:transformation_create', kwargs={
|
):
|
||||||
'app_label': 'documents', 'model': 'document',
|
def test_transformation_create_view_no_permission(self):
|
||||||
'object_id': self.test_document.pk
|
transformation_count = LayerTransformation.objects.count()
|
||||||
}, data={
|
|
||||||
'name': TEST_TRANSFORMATION_NAME,
|
|
||||||
'arguments': TEST_TRANSFORMATION_ARGUMENT
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_transformation_create_view_no_permissions(self):
|
response = self._request_transformation_create_view()
|
||||||
transformation_count = Transformation.objects.count()
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
response = self._transformation_create_view()
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
self.assertEqual(Transformation.objects.count(), transformation_count)
|
|
||||||
|
|
||||||
def test_transformation_create_view_with_permissions(self):
|
|
||||||
self.grant_permission(permission=permission_transformation_create)
|
|
||||||
|
|
||||||
transformation_count = Transformation.objects.count()
|
|
||||||
|
|
||||||
response = self._transformation_create_view()
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
Transformation.objects.count(), transformation_count + 1
|
LayerTransformation.objects.count(), transformation_count
|
||||||
)
|
)
|
||||||
|
|
||||||
def _request_transformation_delete_view(self):
|
def test_transformation_create_view_with_permission(self):
|
||||||
return self.post(
|
|
||||||
viewname='converter:transformation_delete', kwargs={
|
|
||||||
'pk': self.test_transformation.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _create_test_transformation(self):
|
|
||||||
content_type = ContentType.objects.get_for_model(model=self.test_document)
|
|
||||||
|
|
||||||
self.test_transformation = Transformation.objects.create(
|
|
||||||
content_type=content_type, object_id=self.test_document.pk,
|
|
||||||
name=TEST_TRANSFORMATION_NAME,
|
|
||||||
arguments=TEST_TRANSFORMATION_ARGUMENT
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_transformation_delete_view_no_permissions(self):
|
|
||||||
self._create_test_transformation()
|
|
||||||
|
|
||||||
transformation_count = Transformation.objects.count()
|
|
||||||
|
|
||||||
response = self._request_transformation_delete_view()
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
Transformation.objects.count(), transformation_count
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_transformation_delete_view_with_permissions(self):
|
|
||||||
self._create_test_transformation()
|
|
||||||
|
|
||||||
self.grant_permission(permission=permission_transformation_delete)
|
|
||||||
|
|
||||||
transformation_count = Transformation.objects.count()
|
|
||||||
|
|
||||||
response = self._request_transformation_delete_view()
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
Transformation.objects.count(), transformation_count - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
def _transformation_list_view(self):
|
|
||||||
return self.get(
|
|
||||||
viewname='converter:transformation_list', kwargs={
|
|
||||||
'app_label': 'documents', 'model': 'document',
|
|
||||||
'object_id': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_transformation_list_view_no_permissions(self):
|
|
||||||
response = self._transformation_list_view()
|
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
def test_transformation_list_view_with_permissions(self):
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_transformation_view
|
obj=self.test_document, permission=self.test_permission
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self._transformation_list_view()
|
transformation_count = LayerTransformation.objects.count()
|
||||||
|
|
||||||
|
response = self._request_transformation_create_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
LayerTransformation.objects.count(), transformation_count + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transformation_delete_view_no_permission(self):
|
||||||
|
self._create_test_transformation()
|
||||||
|
|
||||||
|
transformation_count = LayerTransformation.objects.count()
|
||||||
|
|
||||||
|
response = self._request_transformation_delete_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
LayerTransformation.objects.count(), transformation_count
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transformation_delete_view_with_access(self):
|
||||||
|
self._create_test_transformation()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=self.test_layer_permission
|
||||||
|
)
|
||||||
|
|
||||||
|
transformation_count = LayerTransformation.objects.count()
|
||||||
|
|
||||||
|
response = self._request_transformation_delete_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
LayerTransformation.objects.count(), transformation_count - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transformation_edit_view_no_permission(self):
|
||||||
|
self._create_test_transformation()
|
||||||
|
|
||||||
|
transformation_arguments = self.test_transformation.arguments
|
||||||
|
|
||||||
|
response = self._request_transformation_edit_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.test_transformation.refresh_from_db()
|
||||||
|
self.assertEqual(
|
||||||
|
transformation_arguments, self.test_transformation.arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transformation_edit_view_with_access(self):
|
||||||
|
self._create_test_transformation()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=self.test_layer_permission
|
||||||
|
)
|
||||||
|
|
||||||
|
transformation_arguments = self.test_transformation.arguments
|
||||||
|
response = self._request_transformation_edit_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.test_transformation.refresh_from_db()
|
||||||
|
self.assertNotEqual(
|
||||||
|
transformation_arguments, self.test_transformation.arguments
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transformation_list_view_no_permission(self):
|
||||||
|
self._create_test_transformation()
|
||||||
|
|
||||||
|
response = self._request_transformation_list_view()
|
||||||
|
self.assertNotContains(
|
||||||
|
response=response, text=self.test_document.label, status_code=404
|
||||||
|
)
|
||||||
|
self.assertNotContains(
|
||||||
|
response=response,
|
||||||
|
text=self.test_transformation.get_transformation_class().label,
|
||||||
|
status_code=404
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transformation_list_view_with_access(self):
|
||||||
|
self._create_test_transformation()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=self.test_permission
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_transformation_list_view()
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=200
|
||||||
)
|
)
|
||||||
|
self.assertContains(
|
||||||
|
response=response,
|
||||||
|
text=self.test_transformation.get_transformation_class().label,
|
||||||
|
status_code=200
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,12 +5,19 @@ import logging
|
|||||||
|
|
||||||
from PIL import Image, ImageColor, ImageDraw, ImageFilter
|
from PIL import Image, ImageColor, ImageDraw, ImageFilter
|
||||||
|
|
||||||
|
from django.utils.encoding import force_bytes, force_text
|
||||||
from django.utils.translation import string_concat, ugettext_lazy as _
|
from django.utils.translation import string_concat, ugettext_lazy as _
|
||||||
from django.utils.encoding import force_bytes
|
|
||||||
|
from .layers import layer_saved_transformations
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTransformationType(type):
|
||||||
|
def __str__(self):
|
||||||
|
return force_text(self.label)
|
||||||
|
|
||||||
|
|
||||||
class BaseTransformation(object):
|
class BaseTransformation(object):
|
||||||
"""
|
"""
|
||||||
Transformation can modify the appearance of the document's page preview.
|
Transformation can modify the appearance of the document's page preview.
|
||||||
@@ -18,7 +25,9 @@ class BaseTransformation(object):
|
|||||||
"""
|
"""
|
||||||
arguments = ()
|
arguments = ()
|
||||||
name = 'base_transformation'
|
name = 'base_transformation'
|
||||||
|
_layer_transformations = {}
|
||||||
_registry = {}
|
_registry = {}
|
||||||
|
__metaclass__ = BaseTransformationType
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def combine(transformations):
|
def combine(transformations):
|
||||||
@@ -44,16 +53,25 @@ class BaseTransformation(object):
|
|||||||
return cls.label
|
return cls.label
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_transformation_choices(cls):
|
def get_transformation_choices(cls, layer=None):
|
||||||
|
if layer:
|
||||||
|
transformation_list = [
|
||||||
|
(transformation.name, transformation) for transformation in cls._layer_transformations[layer]
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
transformation_list = cls._registry.items()
|
||||||
|
|
||||||
return sorted(
|
return sorted(
|
||||||
[
|
[
|
||||||
(name, klass.get_label()) for name, klass in cls._registry.items()
|
(name, klass.get_label()) for name, klass in transformation_list
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register(cls, transformation):
|
def register(cls, layer, transformation):
|
||||||
cls._registry[transformation.name] = transformation
|
cls._registry[transformation.name] = transformation
|
||||||
|
cls._layer_transformations.setdefault(layer, [])
|
||||||
|
cls._layer_transformations[layer].append(transformation)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.kwargs = {}
|
self.kwargs = {}
|
||||||
@@ -517,19 +535,19 @@ class TransformationZoom(BaseTransformation):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
BaseTransformation.register(transformation=TransformationCrop)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationCrop)
|
||||||
BaseTransformation.register(transformation=TransformationDrawRectangle)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationDrawRectangle)
|
||||||
BaseTransformation.register(
|
BaseTransformation.register(
|
||||||
transformation=TransformationDrawRectanglePercent
|
layer=layer_saved_transformations, transformation=TransformationDrawRectanglePercent
|
||||||
)
|
)
|
||||||
BaseTransformation.register(transformation=TransformationFlip)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationFlip)
|
||||||
BaseTransformation.register(transformation=TransformationGaussianBlur)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationGaussianBlur)
|
||||||
BaseTransformation.register(transformation=TransformationLineArt)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationLineArt)
|
||||||
BaseTransformation.register(transformation=TransformationMirror)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationMirror)
|
||||||
BaseTransformation.register(transformation=TransformationResize)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationResize)
|
||||||
BaseTransformation.register(transformation=TransformationRotate)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationRotate)
|
||||||
BaseTransformation.register(transformation=TransformationRotate90)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationRotate90)
|
||||||
BaseTransformation.register(transformation=TransformationRotate180)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationRotate180)
|
||||||
BaseTransformation.register(transformation=TransformationRotate270)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationRotate270)
|
||||||
BaseTransformation.register(transformation=TransformationUnsharpMask)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationUnsharpMask)
|
||||||
BaseTransformation.register(transformation=TransformationZoom)
|
BaseTransformation.register(layer=layer_saved_transformations, transformation=TransformationZoom)
|
||||||
|
|||||||
@@ -3,25 +3,29 @@ from __future__ import unicode_literals
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
TransformationCreateView, TransformationDeleteView, TransformationEditView,
|
TransformationCreateView, TransformationDeleteView,
|
||||||
TransformationListView
|
TransformationEditView, TransformationListView, TransformationSelectView
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
url(
|
||||||
regex=r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/transformations/$',
|
regex=r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/layers/(?P<layer_name>[-_\w]+)/transformations/$',
|
||||||
view=TransformationListView.as_view(), name='transformation_list'
|
view=TransformationListView.as_view(), name='transformation_list'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/transformations/create/$',
|
regex=r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/layers/(?P<layer_name>[-_\w]+)/transformations/select/$',
|
||||||
|
view=TransformationSelectView.as_view(), name='transformation_select'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/layers/(?P<layer_name>[-_\w]+)/transformations/(?P<transformation_name>[-_\w]+)/create/$',
|
||||||
view=TransformationCreateView.as_view(), name='transformation_create'
|
view=TransformationCreateView.as_view(), name='transformation_create'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^transformations/(?P<pk>\d+)/delete/$', view=TransformationDeleteView.as_view(),
|
regex=r'^layers/(?P<layer_name>[-_\w]+)/transformations/(?P<pk>\d+)/delete/$',
|
||||||
name='transformation_delete'
|
view=TransformationDeleteView.as_view(), name='transformation_delete'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
regex=r'^transformations/(?P<pk>\d+)/edit/$', view=TransformationEditView.as_view(),
|
regex=r'^layers/(?P<layer_name>[-_\w]+)/transformations/(?P<pk>\d+)/edit/$',
|
||||||
name='transformation_edit'
|
view=TransformationEditView.as_view(), name='transformation_edit'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,59 +2,56 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.http import HttpResponseRedirect
|
||||||
from django.http import Http404
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.acls.models import AccessControlList
|
|
||||||
from mayan.apps.common.generics import (
|
from mayan.apps.common.generics import (
|
||||||
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView,
|
FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||||
SingleObjectListView
|
SingleObjectEditView, SingleObjectListView
|
||||||
)
|
)
|
||||||
|
from mayan.apps.common.mixins import ExternalContentTypeObjectMixin
|
||||||
|
|
||||||
from .forms import TransformationForm
|
from .classes import Layer
|
||||||
from .icons import icon_transformation_list
|
from .forms import LayerTransformationForm, LayerTransformationSelectForm
|
||||||
from .links import link_transformation_create
|
from .links import link_transformation_select
|
||||||
from .models import Transformation
|
from .models import LayerTransformation, ObjectLayer
|
||||||
from .permissions import (
|
from .transformations import BaseTransformation
|
||||||
permission_transformation_create, permission_transformation_delete,
|
|
||||||
permission_transformation_edit, permission_transformation_view
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TransformationCreateView(SingleObjectCreateView):
|
class LayerViewMixin(object):
|
||||||
form_class = TransformationForm
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
content_type = get_object_or_404(
|
self.layer = self.get_layer()
|
||||||
klass=ContentType, app_label=self.kwargs['app_label'],
|
return super(LayerViewMixin, self).dispatch(
|
||||||
model=self.kwargs['model']
|
request=request, *args, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
def get_layer(self):
|
||||||
self.content_object = content_type.get_object_for_this_type(
|
return Layer.get(
|
||||||
pk=self.kwargs['object_id']
|
name=self.kwargs['layer_name']
|
||||||
)
|
|
||||||
except content_type.model_class().DoesNotExist:
|
|
||||||
raise Http404
|
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=self.content_object,
|
|
||||||
permissions=(permission_transformation_create,), user=request.user
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return super(TransformationCreateView, self).dispatch(
|
|
||||||
request, *args, **kwargs
|
class TransformationCreateView(
|
||||||
)
|
LayerViewMixin, ExternalContentTypeObjectMixin, SingleObjectCreateView
|
||||||
|
):
|
||||||
|
form_class = LayerTransformationForm
|
||||||
|
|
||||||
def form_valid(self, form):
|
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 = form.save(commit=False)
|
||||||
instance.content_object = self.content_object
|
instance.content_object = self.external_object
|
||||||
|
instance.name = self.kwargs['transformation_name']
|
||||||
|
instance.object_layer = object_layer
|
||||||
try:
|
try:
|
||||||
instance.full_clean()
|
instance.full_clean()
|
||||||
instance.save()
|
instance.save()
|
||||||
@@ -66,91 +63,101 @@ class TransformationCreateView(SingleObjectCreateView):
|
|||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'content_object': self.content_object,
|
'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',),
|
'navigation_object_list': ('content_object',),
|
||||||
'title': _(
|
'title': _(
|
||||||
'Create new transformation for: %s'
|
'Create layer "%(layer)s" transformation '
|
||||||
) % self.content_object,
|
'"%(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):
|
def get_post_action_redirect(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='converter:transformation_list', kwargs={
|
viewname='converter:transformation_list', kwargs={
|
||||||
'app_label': self.kwargs['app_label'],
|
'app_label': self.kwargs['app_label'],
|
||||||
'model': self.kwargs['model'],
|
'model': self.kwargs['model'],
|
||||||
'object_id': self.kwargs['object_id']
|
'object_id': self.kwargs['object_id'],
|
||||||
|
'layer_name': self.kwargs['layer_name']
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Transformation.objects.get_for_object(obj=self.content_object)
|
return self.layer.get_transformations_for(
|
||||||
|
obj=self.content_object
|
||||||
|
|
||||||
class TransformationDeleteView(SingleObjectDeleteView):
|
|
||||||
model = Transformation
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
self.transformation = get_object_or_404(
|
|
||||||
klass=Transformation, pk=self.kwargs['pk']
|
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
def get_template_names(self):
|
||||||
obj=self.transformation.content_object,
|
return [
|
||||||
permissions=(permission_transformation_delete,), user=request.user
|
getattr(
|
||||||
)
|
self.get_transformation_class(), 'template_name',
|
||||||
|
self.template_name
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
return super(TransformationDeleteView, self).dispatch(
|
def get_transformation_class(self):
|
||||||
request, *args, **kwargs
|
return BaseTransformation.get(name=self.kwargs['transformation_name'])
|
||||||
)
|
|
||||||
|
|
||||||
def get_post_action_redirect(self):
|
|
||||||
return reverse(
|
class TransformationDeleteView(LayerViewMixin, SingleObjectDeleteView):
|
||||||
viewname='converter:transformation_list', kwargs={
|
model = LayerTransformation
|
||||||
'app_label': self.transformation.content_type.app_label,
|
|
||||||
'model': self.transformation.content_type.model,
|
|
||||||
'object_id': self.transformation.object_id
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'content_object': self.transformation.content_object,
|
'content_object': self.object.object_layer.content_object,
|
||||||
|
'layer_name': self.layer.name,
|
||||||
'navigation_object_list': ('content_object', 'transformation'),
|
'navigation_object_list': ('content_object', 'transformation'),
|
||||||
'previous': reverse(
|
'previous': reverse(
|
||||||
viewname='converter:transformation_list', kwargs={
|
viewname='converter:transformation_list', kwargs={
|
||||||
'app_label': self.transformation.content_type.app_label,
|
'app_label': self.object.object_layer.content_type.app_label,
|
||||||
'model': self.transformation.content_type.model,
|
'model': self.object.object_layer.content_type.model,
|
||||||
'object_id': self.transformation.object_id
|
'object_id': self.object.object_layer.object_id,
|
||||||
|
'layer_name': self.object.object_layer.stored_layer.name
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'title': _(
|
'title': _(
|
||||||
'Delete transformation "%(transformation)s" for: '
|
'Delete transformation "%(transformation)s" for: '
|
||||||
'%(content_object)s?'
|
'%(content_object)s?'
|
||||||
) % {
|
) % {
|
||||||
'transformation': self.transformation,
|
'transformation': self.object,
|
||||||
'content_object': self.transformation.content_object
|
'content_object': self.object.object_layer.content_object
|
||||||
},
|
},
|
||||||
'transformation': self.transformation,
|
'transformation': self.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_object_permission(self):
|
||||||
|
return self.layer.permissions.get('delete', None)
|
||||||
|
|
||||||
class TransformationEditView(SingleObjectEditView):
|
def get_post_action_redirect(self):
|
||||||
form_class = TransformationForm
|
return reverse(
|
||||||
model = Transformation
|
viewname='converter:transformation_list', kwargs={
|
||||||
|
'app_label': self.object.object_layer.content_type.app_label,
|
||||||
def dispatch(self, request, *args, **kwargs):
|
'model': self.object.object_layer.content_type.model,
|
||||||
self.transformation = get_object_or_404(
|
'object_id': self.object.object_layer.object_id,
|
||||||
klass=Transformation, pk=self.kwargs['pk']
|
'layer_name': self.object.object_layer.stored_layer.name
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=self.transformation.content_object,
|
|
||||||
permissions=(permission_transformation_edit,), user=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return super(TransformationEditView, self).dispatch(
|
class TransformationEditView(LayerViewMixin, SingleObjectEditView):
|
||||||
request, *args, **kwargs
|
form_class = LayerTransformationForm
|
||||||
)
|
model = LayerTransformation
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
instance = form.save(commit=False)
|
instance = form.save(commit=False)
|
||||||
@@ -165,72 +172,121 @@ class TransformationEditView(SingleObjectEditView):
|
|||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'content_object': self.transformation.content_object,
|
'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'),
|
'navigation_object_list': ('content_object', 'transformation'),
|
||||||
'title': _(
|
'title': _(
|
||||||
'Edit transformation "%(transformation)s" for: %(content_object)s'
|
'Edit transformation "%(transformation)s" '
|
||||||
|
'for: %(content_object)s'
|
||||||
) % {
|
) % {
|
||||||
'transformation': self.transformation,
|
'transformation': self.object,
|
||||||
'content_object': self.transformation.content_object
|
'content_object': self.object.object_layer.content_object
|
||||||
},
|
},
|
||||||
'transformation': self.transformation,
|
'transformation': self.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_object_permission(self):
|
||||||
|
return self.layer.permissions.get('edit', None)
|
||||||
|
|
||||||
def get_post_action_redirect(self):
|
def get_post_action_redirect(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
viewname='converter:transformation_list', kwargs={
|
viewname='converter:transformation_list', kwargs={
|
||||||
'app_label': self.transformation.content_type.app_label,
|
'app_label': self.object.object_layer.content_type.app_label,
|
||||||
'model': self.transformation.content_type.model,
|
'model': self.object.object_layer.content_type.model,
|
||||||
'object_id': self.transformation.object_id
|
'object_id': self.object.object_layer.object_id,
|
||||||
|
'layer_name': self.object.object_layer.stored_layer.name
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_template_names(self):
|
||||||
class TransformationListView(SingleObjectListView):
|
return [
|
||||||
def dispatch(self, request, *args, **kwargs):
|
getattr(
|
||||||
content_type = get_object_or_404(
|
self.object.get_transformation_class(), 'template_name',
|
||||||
klass=ContentType, app_label=self.kwargs['app_label'],
|
self.template_name
|
||||||
model=self.kwargs['model']
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.content_object = content_type.get_object_for_this_type(
|
|
||||||
pk=self.kwargs['object_id']
|
|
||||||
)
|
)
|
||||||
except content_type.model_class().DoesNotExist:
|
]
|
||||||
raise Http404
|
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=self.content_object,
|
|
||||||
permissions=(permission_transformation_view,), user=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return super(TransformationListView, self).dispatch(
|
class TransformationListView(
|
||||||
request, *args, **kwargs
|
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):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'content_object': self.content_object,
|
'layer': self.layer,
|
||||||
'hide_link': True,
|
'layer_name': self.kwargs['layer_name'],
|
||||||
'hide_object': True,
|
|
||||||
'navigation_object_list': ('content_object',),
|
'navigation_object_list': ('content_object',),
|
||||||
'no_results_icon': icon_transformation_list,
|
'content_object': self.external_object,
|
||||||
'no_results_main_link': link_transformation_create.resolve(
|
'submit_label': _('Select'),
|
||||||
context=RequestContext(
|
'title': _(
|
||||||
request=self.request, dict_={
|
'Select new layer "%(layer)s" transformation '
|
||||||
'content_object': self.content_object
|
'for: %(object)s'
|
||||||
}
|
) % {
|
||||||
)
|
'layer': self.layer,
|
||||||
),
|
'object': self.external_object,
|
||||||
'no_results_text': _(
|
}
|
||||||
'Transformations allow changing the visual appearance '
|
|
||||||
'of documents without making permanent changes to the '
|
|
||||||
'document file themselves.'
|
|
||||||
),
|
|
||||||
'no_results_title': _('No transformations'),
|
|
||||||
'title': _('Transformations for: %s') % self.content_object,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_source_queryset(self):
|
def get_form_extra_kwargs(self):
|
||||||
return Transformation.objects.get_for_object(obj=self.content_object)
|
return {
|
||||||
|
'layer': self.layer
|
||||||
|
}
|
||||||
|
|||||||
@@ -196,10 +196,16 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
|
|||||||
if rotation:
|
if rotation:
|
||||||
rotation = int(rotation)
|
rotation = int(rotation)
|
||||||
|
|
||||||
|
maximum_layer_order = request.GET.get('maximum_layer_order')
|
||||||
|
if maximum_layer_order:
|
||||||
|
maximum_layer_order = int(maximum_layer_order)
|
||||||
|
|
||||||
task = task_generate_document_page_image.apply_async(
|
task = task_generate_document_page_image.apply_async(
|
||||||
kwargs=dict(
|
kwargs=dict(
|
||||||
document_page_id=self.get_object().pk, width=width,
|
document_page_id=self.get_object().pk, width=width,
|
||||||
height=height, zoom=zoom, rotation=rotation
|
height=height, zoom=zoom, rotation=rotation,
|
||||||
|
maximum_layer_order=maximum_layer_order,
|
||||||
|
user_id=request.user.pk
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ def is_document_page_enabled(context):
|
|||||||
return context['object'].enabled
|
return context['object'].enabled
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentsApp(MayanAppConfig):
|
class DocumentsApp(MayanAppConfig):
|
||||||
app_namespace = 'documents'
|
app_namespace = 'documents'
|
||||||
app_url = 'documents'
|
app_url = 'documents'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from ..storages import storage_documentimagecache
|
|||||||
|
|
||||||
def operation_clear_old_cache(apps, schema_editor):
|
def operation_clear_old_cache(apps, schema_editor):
|
||||||
DocumentPageCachedImage = apps.get_model(
|
DocumentPageCachedImage = apps.get_model(
|
||||||
'documents', 'DocumentPageCachedImage'
|
app_label='documents', model_name='DocumentPageCachedImage'
|
||||||
)
|
)
|
||||||
|
|
||||||
for cached_image in DocumentPageCachedImage.objects.using(schema_editor.connection.alias).all():
|
for cached_image in DocumentPageCachedImage.objects.using(schema_editor.connection.alias).all():
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
from mayan.apps.converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
||||||
|
|
||||||
from mayan.apps.converter.models import Transformation
|
from mayan.apps.converter.models import LayerTransformation
|
||||||
from mayan.apps.converter.transformations import (
|
from mayan.apps.converter.transformations import (
|
||||||
BaseTransformation, TransformationResize, TransformationRotate,
|
BaseTransformation, TransformationResize, TransformationRotate,
|
||||||
TransformationZoom
|
TransformationZoom
|
||||||
@@ -83,8 +83,8 @@ class DocumentPage(models.Model):
|
|||||||
def document(self):
|
def document(self):
|
||||||
return self.document_version.document
|
return self.document_version.document
|
||||||
|
|
||||||
def generate_image(self, *args, **kwargs):
|
def generate_image(self, user=None, **kwargs):
|
||||||
transformation_list = self.get_combined_transformation_list(*args, **kwargs)
|
transformation_list = self.get_combined_transformation_list(user=user, **kwargs)
|
||||||
combined_cache_filename = BaseTransformation.combine(transformation_list)
|
combined_cache_filename = BaseTransformation.combine(transformation_list)
|
||||||
|
|
||||||
# Check is transformed image is available
|
# Check is transformed image is available
|
||||||
@@ -136,7 +136,7 @@ class DocumentPage(models.Model):
|
|||||||
|
|
||||||
return final_url.tostr()
|
return final_url.tostr()
|
||||||
|
|
||||||
def get_combined_transformation_list(self, *args, **kwargs):
|
def get_combined_transformation_list(self, user=None, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Return a list of transformation containing the server side
|
Return a list of transformation containing the server side
|
||||||
document page transformation as well as tranformations created
|
document page transformation as well as tranformations created
|
||||||
@@ -161,8 +161,13 @@ class DocumentPage(models.Model):
|
|||||||
# Generate transformation hash
|
# Generate transformation hash
|
||||||
transformation_list = []
|
transformation_list = []
|
||||||
|
|
||||||
|
maximum_layer_order = kwargs.get('maximum_layer_order', None)
|
||||||
|
|
||||||
# Stored transformations first
|
# Stored transformations first
|
||||||
for stored_transformation in Transformation.objects.get_for_object(self, as_classes=True):
|
for stored_transformation in LayerTransformation.objects.get_for_object(
|
||||||
|
self, maximum_layer_order=maximum_layer_order, as_classes=True,
|
||||||
|
user=user
|
||||||
|
):
|
||||||
transformation_list.append(stored_transformation)
|
transformation_list.append(stored_transformation)
|
||||||
|
|
||||||
# Interactive transformations second
|
# Interactive transformations second
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.converter.exceptions import InvalidOfficeFormat, PageCountError
|
from mayan.apps.converter.exceptions import InvalidOfficeFormat, PageCountError
|
||||||
from mayan.apps.converter.models import Transformation
|
from mayan.apps.converter.layers import layer_saved_transformations
|
||||||
from mayan.apps.converter.transformations import TransformationRotate
|
from mayan.apps.converter.transformations import TransformationRotate
|
||||||
from mayan.apps.converter.utils import get_converter_class
|
from mayan.apps.converter.utils import get_converter_class
|
||||||
from mayan.apps.mimetype.api import get_mimetype
|
from mayan.apps.mimetype.api import get_mimetype
|
||||||
@@ -156,7 +156,7 @@ class DocumentVersion(models.Model):
|
|||||||
for page in self.pages.all():
|
for page in self.pages.all():
|
||||||
degrees = page.detect_orientation()
|
degrees = page.detect_orientation()
|
||||||
if degrees:
|
if degrees:
|
||||||
Transformation.objects.add_to_object(
|
layer_saved_transformations.add_to_object(
|
||||||
obj=page, transformation=TransformationRotate,
|
obj=page, transformation=TransformationRotate,
|
||||||
arguments='{{"degrees": {}}}'.format(360 - degrees)
|
arguments='{{"degrees": {}}}'.format(360 - degrees)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -65,13 +65,19 @@ def task_delete_stubs():
|
|||||||
|
|
||||||
|
|
||||||
@app.task()
|
@app.task()
|
||||||
def task_generate_document_page_image(document_page_id, *args, **kwargs):
|
def task_generate_document_page_image(document_page_id, user_id=None, **kwargs):
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentPage'
|
||||||
)
|
)
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
if user_id:
|
||||||
|
user = User.objects.get(pk=user_id)
|
||||||
|
else:
|
||||||
|
user = None
|
||||||
|
|
||||||
document_page = DocumentPage.passthrough.get(pk=document_page_id)
|
document_page = DocumentPage.passthrough.get(pk=document_page_id)
|
||||||
return document_page.generate_image(*args, **kwargs)
|
return document_page.generate_image(user=user, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.task(ignore_result=True)
|
@app.task(ignore_result=True)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -17,7 +19,7 @@ from mayan.apps.common.generics import (
|
|||||||
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectEditView,
|
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectEditView,
|
||||||
SingleObjectListView
|
SingleObjectListView
|
||||||
)
|
)
|
||||||
from mayan.apps.converter.models import Transformation
|
from mayan.apps.converter.layers import layer_saved_transformations
|
||||||
from mayan.apps.converter.permissions import (
|
from mayan.apps.converter.permissions import (
|
||||||
permission_transformation_delete, permission_transformation_edit
|
permission_transformation_delete, permission_transformation_edit
|
||||||
)
|
)
|
||||||
@@ -522,7 +524,7 @@ class DocumentTransformationsClearView(MultipleObjectConfirmActionView):
|
|||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
try:
|
try:
|
||||||
for page in instance.pages.all():
|
for page in instance.pages.all():
|
||||||
Transformation.objects.get_for_object(obj=page).delete()
|
layer_saved_transformations.get_transformations_for(obj=page).delete()
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
messages.error(
|
messages.error(
|
||||||
self.request, _(
|
self.request, _(
|
||||||
@@ -545,24 +547,29 @@ class DocumentTransformationsCloneView(FormView):
|
|||||||
pk=form.cleaned_data['page'].pk
|
pk=form.cleaned_data['page'].pk
|
||||||
)
|
)
|
||||||
|
|
||||||
for page in target_pages:
|
with transaction.atomic():
|
||||||
Transformation.objects.get_for_object(obj=page).delete()
|
for page in target_pages:
|
||||||
|
layer_saved_transformations.get_transformations_for(obj=page).delete()
|
||||||
|
|
||||||
Transformation.objects.copy(
|
layer_saved_transformations.copy_transformations(
|
||||||
source=form.cleaned_data['page'], targets=target_pages
|
source=form.cleaned_data['page'], targets=target_pages
|
||||||
)
|
)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
messages.error(
|
if settings.DEBUG:
|
||||||
self.request, _(
|
raise
|
||||||
'Error deleting the page transformations for '
|
else:
|
||||||
'document: %(document)s; %(error)s.'
|
messages.error(
|
||||||
) % {
|
message=_(
|
||||||
'document': instance, 'error': exception
|
'Error cloning the page transformations for '
|
||||||
}
|
'document: %(document)s; %(error)s.'
|
||||||
)
|
) % {
|
||||||
|
'document': instance, 'error': exception
|
||||||
|
}, request=self.request
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request, _('Transformations cloned successfully.')
|
message=_('Transformations cloned successfully.'),
|
||||||
|
request=self.request
|
||||||
)
|
)
|
||||||
|
|
||||||
return super(DocumentTransformationsCloneView, self).form_valid(form=form)
|
return super(DocumentTransformationsCloneView, self).form_valid(form=form)
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ class ScanDuplicatedDocuments(ConfirmView):
|
|||||||
def view_action(self):
|
def view_action(self):
|
||||||
task_scan_duplicates_all.apply_async()
|
task_scan_duplicates_all.apply_async()
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request, _('Duplicated document scan queued successfully.')
|
message=_('Duplicated document scan queued successfully.'),
|
||||||
|
request=self.request
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,19 +48,21 @@ class Link(object):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, text=None, view=None, args=None, badge_text=None, condition=None,
|
self, text=None, view=None, args=None, badge_text=None, condition=None,
|
||||||
conditional_disable=None, description=None, html_data=None,
|
conditional_active=None, conditional_disable=None, description=None,
|
||||||
html_extra_classes=None, icon_class=None, icon_class_path=None,
|
html_data=None, html_extra_classes=None, icon_class=None,
|
||||||
keep_query=False, kwargs=None, name=None, permissions=None,
|
icon_class_path=None, keep_query=False, kwargs=None, name=None,
|
||||||
remove_from_query=None, tags=None, url=None
|
permissions=None, remove_from_query=None, tags=None, url=None
|
||||||
):
|
):
|
||||||
self.args = args or []
|
self.args = args or []
|
||||||
self.badge_text = badge_text
|
self.badge_text = badge_text
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
|
self.conditional_active = conditional_active
|
||||||
self.conditional_disable = conditional_disable
|
self.conditional_disable = conditional_disable
|
||||||
self.description = description
|
self.description = description
|
||||||
self.html_data = html_data
|
self.html_data = html_data
|
||||||
self.html_extra_classes = html_extra_classes
|
self.html_extra_classes = html_extra_classes
|
||||||
self.icon_class = icon_class
|
self.icon_class = icon_class
|
||||||
|
self.icon_class_path = icon_class_path
|
||||||
self.keep_query = keep_query
|
self.keep_query = keep_query
|
||||||
self.kwargs = kwargs or {}
|
self.kwargs = kwargs or {}
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -71,7 +73,13 @@ class Link(object):
|
|||||||
self.view = view
|
self.view = view
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
||||||
if icon_class_path:
|
self.process_icon()
|
||||||
|
|
||||||
|
if name:
|
||||||
|
self.__class__._registry[name] = self
|
||||||
|
|
||||||
|
def process_icon(self):
|
||||||
|
if self.icon_class_path:
|
||||||
if self.icon_class:
|
if self.icon_class:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
'Specify the icon_class or the icon_class_path but not '
|
'Specify the icon_class or the icon_class_path but not '
|
||||||
@@ -79,17 +87,14 @@ class Link(object):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.icon_class = import_string(dotted_path=icon_class_path)
|
self.icon_class = import_string(dotted_path=self.icon_class_path)
|
||||||
except ImportError as exception:
|
except ImportError as exception:
|
||||||
logger.error(
|
logger.error(
|
||||||
'Exception importing icon: %s; %s', icon_class_path,
|
'Exception importing icon: %s; %s', self.icon_class_path,
|
||||||
exception
|
exception
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if name:
|
|
||||||
self.__class__._registry[name] = self
|
|
||||||
|
|
||||||
def resolve(self, context=None, request=None, resolved_object=None):
|
def resolve(self, context=None, request=None, resolved_object=None):
|
||||||
if not context and not request:
|
if not context and not request:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
@@ -525,7 +530,13 @@ class ResolvedLink(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
return self.link.view == self.current_view_name
|
conditional_active = self.link.conditional_active
|
||||||
|
if conditional_active:
|
||||||
|
return conditional_active(
|
||||||
|
context=self.context, resolved_link=self
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return self.link.view == self.current_view_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def badge_text(self):
|
def badge_text(self):
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ class SourceColumnLinkWidget(object):
|
|||||||
'column': self.column, 'value': value
|
'column': self.column, 'value': value
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
3
mayan/apps/redactions/__init__.py
Normal file
3
mayan/apps/redactions/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
default_app_config = 'mayan.apps.redactions.apps.RedactionsApp'
|
||||||
41
mayan/apps/redactions/apps.py
Normal file
41
mayan/apps/redactions/apps.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.common.apps import MayanAppConfig
|
||||||
|
from mayan.apps.converter.links import link_transformation_list
|
||||||
|
from mayan.apps.common.menus import menu_list_facet
|
||||||
|
|
||||||
|
from .dependencies import * # NOQA
|
||||||
|
from .layers import layer_redactions # NOQA
|
||||||
|
from .transformations import * # NOQA
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RedactionsApp(MayanAppConfig):
|
||||||
|
app_namespace = 'redactions'
|
||||||
|
app_url = 'redactions'
|
||||||
|
has_rest_api = False
|
||||||
|
has_tests = False
|
||||||
|
name = 'mayan.apps.redactions'
|
||||||
|
verbose_name = _('Redactions')
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
super(RedactionsApp, self).ready()
|
||||||
|
|
||||||
|
DocumentPage = apps.get_model(
|
||||||
|
app_label='documents', model_name='DocumentPage'
|
||||||
|
)
|
||||||
|
|
||||||
|
link_redaction_list = link_transformation_list.copy(
|
||||||
|
layer=layer_redactions
|
||||||
|
)
|
||||||
|
link_redaction_list.text = _('Redactions')
|
||||||
|
|
||||||
|
menu_list_facet.bind_links(
|
||||||
|
links=(link_redaction_list,), sources=(DocumentPage,)
|
||||||
|
)
|
||||||
13
mayan/apps/redactions/dependencies.py
Normal file
13
mayan/apps/redactions/dependencies.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.dependencies.classes import JavaScriptDependency
|
||||||
|
|
||||||
|
JavaScriptDependency(
|
||||||
|
label=_('JavaScript image cropper'), module=__name__, name='cropperjs',
|
||||||
|
version_string='=1.4.1'
|
||||||
|
)
|
||||||
|
JavaScriptDependency(
|
||||||
|
module=__name__, name='jquery-cropper', version_string='=1.0.0'
|
||||||
|
)
|
||||||
27
mayan/apps/redactions/layers.py
Normal file
27
mayan/apps/redactions/layers.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.converter.classes import Layer
|
||||||
|
from mayan.apps.converter.layers import layer_saved_transformations
|
||||||
|
|
||||||
|
from .permissions import (
|
||||||
|
permission_redaction_create, permission_redaction_delete,
|
||||||
|
permission_redaction_edit, permission_redaction_exclude,
|
||||||
|
permission_redaction_view
|
||||||
|
)
|
||||||
|
|
||||||
|
layer_redactions = Layer(
|
||||||
|
empty_results_text=_(
|
||||||
|
'Redactions allow removing access to confidential and '
|
||||||
|
'sensitive information without having to modify the document.'
|
||||||
|
), label=_('Redactions'), name='redactions',
|
||||||
|
order=layer_saved_transformations.order - 1, permissions={
|
||||||
|
'create': permission_redaction_create,
|
||||||
|
'delete': permission_redaction_delete,
|
||||||
|
'exclude': permission_redaction_exclude,
|
||||||
|
'edit': permission_redaction_edit,
|
||||||
|
'select': permission_redaction_create,
|
||||||
|
'view': permission_redaction_view,
|
||||||
|
}, symbol='highlighter'
|
||||||
|
)
|
||||||
0
mayan/apps/redactions/migrations/__init__.py
Normal file
0
mayan/apps/redactions/migrations/__init__.py
Normal file
23
mayan/apps/redactions/permissions.py
Normal file
23
mayan/apps/redactions/permissions.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.permissions import PermissionNamespace
|
||||||
|
|
||||||
|
namespace = PermissionNamespace(label=_('Redactions'), name='redactions')
|
||||||
|
|
||||||
|
permission_redaction_create = namespace.add_permission(
|
||||||
|
label=_('Create new redactions'), name='redaction_create'
|
||||||
|
)
|
||||||
|
permission_redaction_delete = namespace.add_permission(
|
||||||
|
label=_('Delete redactions'), name='redaction_delete'
|
||||||
|
)
|
||||||
|
permission_redaction_edit = namespace.add_permission(
|
||||||
|
label=_('Edit redactions'), name='redaction_edit'
|
||||||
|
)
|
||||||
|
permission_redaction_exclude = namespace.add_permission(
|
||||||
|
label=_('Exclude redactions'), name='redaction_exclude'
|
||||||
|
)
|
||||||
|
permission_redaction_view = namespace.add_permission(
|
||||||
|
label=_('View existing redactions'), name='redaction_view'
|
||||||
|
)
|
||||||
127
mayan/apps/redactions/templates/redactions/cropper.html
Normal file
127
mayan/apps/redactions/templates/redactions/cropper.html
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
{% extends 'appearance/base.html' %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% load common_tags %}
|
||||||
|
{% load documents_tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link href="{% static 'redactions/node_modules/cropperjs/dist/cropper.css' %}" rel="stylesheet">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cropper-invisible {
|
||||||
|
background: #000;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropper-main {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropper-main img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropper-modal {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="cropper-main">
|
||||||
|
<img id="cropper-img" src="{% get_api_image_url obj=content_object maximum_layer_order=layer.order %}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
{% with '' as title %}
|
||||||
|
{% include 'appearance/generic_form_subtemplate.html' %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script>
|
||||||
|
var crop_left, crop_top, crop_right, crop_bottom;
|
||||||
|
var pic_real_width, pic_real_height;
|
||||||
|
var canvasData;
|
||||||
|
var containerData;
|
||||||
|
var $image = $('#cropper-img');
|
||||||
|
var image = document.getElementById('cropper-img');
|
||||||
|
var defaultArguments = {
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
bottom: 10,
|
||||||
|
}
|
||||||
|
var initialArguments = JSON.parse(
|
||||||
|
$('#id_arguments').text() || JSON.stringify(defaultArguments)
|
||||||
|
);
|
||||||
|
|
||||||
|
var callbackCrop = function (data) {
|
||||||
|
var crop_left = (data.detail.x / pic_real_width * 100).toFixed(2);
|
||||||
|
var crop_top = (data.detail.y / pic_real_height * 100).toFixed(2);
|
||||||
|
var crop_right = (
|
||||||
|
100.001 - (data.detail.x + data.detail.width) / pic_real_width * 100
|
||||||
|
).toFixed(2);
|
||||||
|
var crop_bottom = (
|
||||||
|
100.001 - (data.detail.y + data.detail.height) / pic_real_height * 100
|
||||||
|
).toFixed(2);
|
||||||
|
|
||||||
|
var arguments = {
|
||||||
|
'left': parseFloat(crop_left),
|
||||||
|
'top': parseFloat(crop_top),
|
||||||
|
'right': parseFloat(crop_right),
|
||||||
|
'bottom': parseFloat(crop_bottom),
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#id_arguments').text(JSON.stringify(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery(document).ready(function() {
|
||||||
|
$('.help-block').hide();
|
||||||
|
$('label').hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$.getScript("{% static 'redactions/node_modules/cropperjs/dist/cropper.js' %}")
|
||||||
|
.done(function (script, textStatus) {
|
||||||
|
$.getScript("{% static 'redactions/node_modules/jquery-cropper/dist/jquery-cropper.js' %}")
|
||||||
|
.done(function (script, textStatus) {
|
||||||
|
jQuery(document).ready(function () {
|
||||||
|
// Create DOM new image to get the real
|
||||||
|
// (unscaled) image size
|
||||||
|
$('<img/>')
|
||||||
|
.attr('src', $image.attr('src'))
|
||||||
|
.on('load', function () {
|
||||||
|
pic_real_width = this.width;
|
||||||
|
pic_real_height = this.height;
|
||||||
|
});
|
||||||
|
|
||||||
|
new Cropper(
|
||||||
|
image, {
|
||||||
|
crop: callbackCrop,
|
||||||
|
highlight: false,
|
||||||
|
mouseWheelZoom: false,
|
||||||
|
movable: false,
|
||||||
|
ready: function () {
|
||||||
|
canvasData = this.cropper.getCanvasData();
|
||||||
|
containerData = this.cropper.getContainerData();
|
||||||
|
this.cropper.setCropBoxData({
|
||||||
|
left: initialArguments.left / 100.0 * canvasData.width + canvasData.left,
|
||||||
|
top: initialArguments.top / 100.0 * canvasData.height + canvasData.top,
|
||||||
|
width: (100.0 - initialArguments.right - initialArguments.left) / 100.0 * canvasData.width,
|
||||||
|
height: (100.0 - initialArguments.bottom - initialArguments.top) / 100.0 * canvasData.height,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rotatable: false,
|
||||||
|
touchDragZoom: false,
|
||||||
|
viewMode: 1,
|
||||||
|
zoomable: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
21
mayan/apps/redactions/transformations.py
Normal file
21
mayan/apps/redactions/transformations.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.converter.transformations import (
|
||||||
|
BaseTransformation, TransformationDrawRectanglePercent
|
||||||
|
)
|
||||||
|
|
||||||
|
from .layers import layer_redactions
|
||||||
|
|
||||||
|
|
||||||
|
class TransformationRedactionPercent(TransformationDrawRectanglePercent):
|
||||||
|
arguments = ('left', 'top', 'right', 'bottom')
|
||||||
|
label = _('Redaction')
|
||||||
|
name = 'redaction_percent'
|
||||||
|
template_name = 'redactions/cropper.html'
|
||||||
|
|
||||||
|
|
||||||
|
BaseTransformation.register(
|
||||||
|
layer=layer_redactions, transformation=TransformationRedactionPercent
|
||||||
|
)
|
||||||
@@ -17,8 +17,8 @@ from mayan.apps.navigation.classes import SourceColumn
|
|||||||
from .classes import StagingFile
|
from .classes import StagingFile
|
||||||
from .dependencies import * # NOQA
|
from .dependencies import * # NOQA
|
||||||
from .handlers import (
|
from .handlers import (
|
||||||
handler_copy_transformations_to_version, handler_create_default_document_source,
|
handler_copy_transformations_to_version,
|
||||||
handler_initialize_periodic_tasks
|
handler_create_default_document_source, handler_initialize_periodic_tasks
|
||||||
)
|
)
|
||||||
from .links import (
|
from .links import (
|
||||||
link_document_create_multiple, link_setup_sources,
|
link_document_create_multiple, link_setup_sources,
|
||||||
|
|||||||
@@ -3,19 +3,16 @@ from __future__ import unicode_literals
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from mayan.apps.converter.layers import layer_saved_transformations
|
||||||
|
|
||||||
from .literals import SOURCE_UNCOMPRESS_CHOICE_ASK
|
from .literals import SOURCE_UNCOMPRESS_CHOICE_ASK
|
||||||
|
|
||||||
|
|
||||||
def handler_copy_transformations_to_version(sender, **kwargs):
|
def handler_copy_transformations_to_version(sender, instance, **kwargs):
|
||||||
Transformation = apps.get_model(
|
|
||||||
app_label='converter', model_name='Transformation'
|
|
||||||
)
|
|
||||||
|
|
||||||
instance = kwargs['instance']
|
|
||||||
|
|
||||||
# TODO: Fix this, source should be previous version
|
# TODO: Fix this, source should be previous version
|
||||||
# TODO: Fix this, shouldn't this be at the documents app
|
# TODO: Fix this, shouldn't this be at the documents app
|
||||||
Transformation.objects.copy(
|
|
||||||
|
layer_saved_transformations.copy_transformations(
|
||||||
source=instance.document, targets=instance.pages.all()
|
source=instance.document, targets=instance.pages.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from model_utils.managers import InheritanceManager
|
|||||||
|
|
||||||
from mayan.apps.common.compressed_files import Archive
|
from mayan.apps.common.compressed_files import Archive
|
||||||
from mayan.apps.common.exceptions import NoMIMETypeMatch
|
from mayan.apps.common.exceptions import NoMIMETypeMatch
|
||||||
from mayan.apps.converter.models import Transformation
|
from mayan.apps.converter.layers import layer_saved_transformations
|
||||||
from mayan.apps.documents.models import Document, DocumentType
|
from mayan.apps.documents.models import Document, DocumentType
|
||||||
from mayan.apps.documents.settings import setting_language
|
from mayan.apps.documents.settings import setting_language
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ class Source(models.Model):
|
|||||||
if user:
|
if user:
|
||||||
document.add_as_recent_document_for_user(user=user)
|
document.add_as_recent_document_for_user(user=user)
|
||||||
|
|
||||||
Transformation.objects.copy(
|
layer_saved_transformations.copy_transformations(
|
||||||
source=self, targets=document_version.pages.all()
|
source=self, targets=document_version.pages.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ INSTALLED_APPS = (
|
|||||||
'mayan.apps.metadata',
|
'mayan.apps.metadata',
|
||||||
'mayan.apps.mirroring',
|
'mayan.apps.mirroring',
|
||||||
'mayan.apps.ocr',
|
'mayan.apps.ocr',
|
||||||
|
'mayan.apps.redactions',
|
||||||
'mayan.apps.sources',
|
'mayan.apps.sources',
|
||||||
'mayan.apps.storage',
|
'mayan.apps.storage',
|
||||||
'mayan.apps.tags',
|
'mayan.apps.tags',
|
||||||
|
|||||||
Reference in New Issue
Block a user