Move transformation storage to the converter app via content types

This commit is contained in:
Roberto Rosario
2015-06-08 15:53:25 -04:00
parent 86bfd76141
commit d678431c97
14 changed files with 382 additions and 19 deletions

View File

@@ -3,7 +3,20 @@ from __future__ import unicode_literals
from django import apps
from django.utils.translation import ugettext_lazy as _
from common import menu_object, menu_sidebar
from .links import (
link_transformation_create, link_transformation_delete,
link_transformation_edit
)
from .models import Transformation
class ConverterApp(apps.AppConfig):
name = 'converter'
verbose_name = _('Converter')
def ready(self):
menu_sidebar.bind_links(links=[link_transformation_create], sources=[Transformation])
menu_sidebar.bind_links(links=[link_transformation_create], sources=['converter:transformation_create', 'converter:transformation_list'])
menu_object.bind_links(links=[link_transformation_edit, link_transformation_delete], sources=[Transformation])

View File

@@ -1 +0,0 @@

View File

@@ -4,7 +4,7 @@ import logging
import io
import os
import subprocess
from tempfile import mkstemp
import tempfile
try:
from cStringIO import StringIO
@@ -13,8 +13,6 @@ except ImportError:
from PIL import Image
from django.utils.encoding import smart_str
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from common.settings import TEMPORARY_DIRECTORY
@@ -22,12 +20,8 @@ from common.utils import fs_cleanup
from mimetype.api import get_mimetype
from .exceptions import OfficeConversionError, UnknownFileFormat
from .literals import (
DEFAULT_PAGE_NUMBER, DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION,
DEFAULT_FILE_FORMAT, TRANSFORMATION_CHOICES, TRANSFORMATION_RESIZE,
TRANSFORMATION_ROTATE, TRANSFORMATION_ZOOM, DIMENSION_SEPARATOR
)
from .settings import GRAPHICS_BACKEND, LIBREOFFICE_PATH
from .literals import DEFAULT_PAGE_NUMBER, DEFAULT_FILE_FORMAT
from .settings import LIBREOFFICE_PATH
CONVERTER_OFFICE_FILE_MIMETYPES = [
'application/msword',
@@ -111,7 +105,7 @@ class ConverterBase(object):
readline = proc.stderr.readline()
logger.debug('stderr: %s', readline)
if return_code != 0:
raise OfficeBackendError(readline)
raise OfficeConversionError(readline)
filename, extension = os.path.splitext(os.path.basename(input_filepath))
logger.debug('filename: %s', filename)
@@ -158,7 +152,7 @@ class ConverterBase(object):
if self.mime_type in CONVERTER_OFFICE_FILE_MIMETYPES:
if os.path.exists(LIBREOFFICE_PATH):
if not self.soffice_file_object:
converted_output = Converter.soffice(self.file_object)
converted_output = ConverterBase.soffice(self.file_object)
self.file_object.seek(0)
self.soffice_file_object = open(converted_output)
self.mime_type = 'application/pdf'
@@ -187,12 +181,16 @@ class BaseTransformation(object):
_registry = {}
@classmethod
def get_transformations_classes(cls):
return map(lambda name: getattr(cls, name), filter(lambda entry: entry.startswith('Transform'), dir(cls)))
def register(cls, transformation):
cls._registry[transformation.name] = transformation
@classmethod
def get_transformations_choices(cls):
return [(transformation.name, transformation.label) for transformation in cls.get_transformations_classes()]
def get_transformation_choices(cls):
return [(name, klass.label) for name, klass in cls._registry.items()]
@classmethod
def get(cls, name):
return cls._registry[name]
def __init__(self, **kwargs):
for argument_name in self.arguments:
@@ -206,7 +204,7 @@ class BaseTransformation(object):
class TransformationResize(BaseTransformation):
name = 'resize'
arguments = ('width', 'height')
label = _('Resize')
label = _('Resize <width, height>')
def execute_on(self, *args, **kwargs):
super(TransformationResize, self).execute_on(*args, **kwargs)
@@ -244,7 +242,7 @@ class TransformationResize(BaseTransformation):
class TransformationRotate(BaseTransformation):
name = 'rotate'
arguments = ('degrees',)
label = _('Rotate')
label = _('Rotate <degrees>')
def execute_on(self, *args, **kwargs):
super(TransformationRotate, self).execute_on(*args, **kwargs)
@@ -255,10 +253,15 @@ class TransformationRotate(BaseTransformation):
class TransformationZoom(BaseTransformation):
name = 'zoom'
arguments = ('percent',)
label = _('Zoom')
label = _('Zoom <percent>')
def execute_on(self, *args, **kwargs):
super(TransformationZoom, self).execute_on(*args, **kwargs)
decimal_value = float(self.percent) / 100
return self.image.resize((int(self.image.size[0] * decimal_value), int(self.image.size[1] * decimal_value)), Image.ANTIALIAS)
BaseTransformation.register(TransformationResize)
BaseTransformation.register(TransformationRotate)
BaseTransformation.register(TransformationZoom)

View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
from django import forms
from .models import Transformation
class TransformationForm(forms.ModelForm):
class Meta:
fields = ('order', 'name', 'arguments')
model = Transformation

View File

@@ -0,0 +1,25 @@
from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
from navigation import Link
from .permissions import (
PERMISSION_TRANSFORMATION_CREATE, PERMISSION_TRANSFORMATION_DELETE,
PERMISSION_TRANSFORMATION_EDIT, PERMISSION_TRANSFORMATION_VIEW
)
def get_kwargs_factory(variable_name):
def get_kwargs(context):
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(kwargs=get_kwargs_factory('content_object'), permissions=[PERMISSION_TRANSFORMATION_CREATE], text=_('Create new transformation'), view='converter:transformation_create')
link_transformation_delete = Link(args='resolved_object.pk', permissions=[PERMISSION_TRANSFORMATION_DELETE], tags='dangerous', text=_('Delete'), view='converter:transformation_delete')
link_transformation_edit = Link(args='resolved_object.pk', permissions=[PERMISSION_TRANSFORMATION_EDIT], text=_('Edit'), view='converter:transformation_edit')
link_transformation_list = Link(kwargs=get_kwargs_factory('resolved_object'), permissions=[PERMISSION_TRANSFORMATION_VIEW], text=_('Transformations'), view='converter:transformation_list')

View File

@@ -0,0 +1,42 @@
from __future__ import unicode_literals
from ast import literal_eval
import logging
from django.contrib.contenttypes.models import ContentType
from django.db import models
from .classes import BaseTransformation
logger = logging.getLogger(__name__)
class TransformationManager(models.Manager):
def get_for_model(self, obj, as_classes=False):
"""
as_classes == True returns the transformation classes from .classes
ready to be feed to the converter class
"""
content_type = ContentType.objects.get_for_model(obj)
transformations = self.filter(content_type=content_type, object_id=obj.pk)
if as_classes:
result = []
for transformation in transformations:
try:
transformation_class = BaseTransformation.get(transformation.name)
except KeyError:
# Non existant transformation, but we don't raise an error
logger.error('Non existant transformation: %s for %s', transformation.name, obj)
else:
# TODO: what to do if literal_eval fails?
result.append(transformation_class(**literal_eval(transformation.arguments)))
return result
else:
return transformations

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import converter.models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Transformation',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.PositiveIntegerField()),
('order', models.PositiveIntegerField(default=0, null=True, verbose_name='Order', db_index=True, blank=True)),
('transformation', models.CharField(max_length=128, verbose_name='Transformation', choices=[('rotate', 'Rotate'), ('zoom', 'Zoom'), ('resize', 'Resize')])),
('arguments', models.TextField(blank=True, null=True, verbose_name='Arguments', validators=[converter.models.argument_validator])),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
],
options={
'ordering': ('order',),
'verbose_name': 'Transformation',
'verbose_name_plural': 'Transformations',
},
bases=(models.Model,),
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('converter', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='transformation',
old_name='transformation',
new_name='name',
),
]

View File

@@ -0,0 +1,54 @@
from __future__ import unicode_literals
from ast import literal_eval
import logging
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from .classes import BaseTransformation
from .managers import TransformationManager
logger = logging.getLogger(__name__)
def argument_validator(value):
"""
Validates that the input evaluates correctly.
"""
value = value.strip()
try:
literal_eval(value)
except (ValueError, SyntaxError):
raise ValidationError(_('Enter a valid value.'), code='invalid')
@python_2_unicode_compatible
class Transformation(models.Model):
"""
Model that stores the transformation and transformation arguments
for a given object
"""
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
order = models.PositiveIntegerField(default=0, blank=True, null=True, verbose_name=_('Order'), db_index=True)
name = models.CharField(choices=BaseTransformation.get_transformation_choices(), max_length=128, verbose_name=_('Transformation'))
arguments = models.TextField(blank=True, null=True, verbose_name=_('Arguments'), validators=[argument_validator])
objects = TransformationManager()
def __str__(self):
return self.get_transformation_display()
class Meta:
ordering = ('order',)
verbose_name = _('Transformation')
verbose_name_plural = _('Transformations')

View File

@@ -0,0 +1,11 @@
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from permissions.models import Permission, PermissionNamespace
namespace = PermissionNamespace('converter', _('Converter'))
PERMISSION_TRANSFORMATION_CREATE = Permission.objects.register(namespace, 'transformation_create', _('Create new transformations'))
PERMISSION_TRANSFORMATION_DELETE = Permission.objects.register(namespace, 'transformation_delete', _('Delete transformations'))
PERMISSION_TRANSFORMATION_EDIT = Permission.objects.register(namespace, 'transformation_edit', _('Edit transformations'))
PERMISSION_TRANSFORMATION_VIEW = Permission.objects.register(namespace, 'transformation_view', _('View existing transformations'))

View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
from django.conf.urls import patterns, url
urlpatterns = patterns(
'converter.views',
url(r'^create_for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', 'transformation_create', (), 'transformation_create'),
url(r'^list_for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', 'transformation_list', (), 'transformation_list'),
url(r'^delete/(?P<object_id>\d+)/$', 'transformation_delete', (), 'transformation_delete'),
url(r'^edit/(?P<object_id>\d+)/$', 'transformation_edit', (), 'transformation_edit'),
)

View File

@@ -0,0 +1,142 @@
from __future__ import absolute_import, unicode_literals
import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _
from common.utils import encapsulate
from permissions.models import Permission
from .forms import TransformationForm
from .models import Transformation
from .permissions import (
PERMISSION_TRANSFORMATION_CREATE, PERMISSION_TRANSFORMATION_DELETE,
PERMISSION_TRANSFORMATION_EDIT, PERMISSION_TRANSFORMATION_VIEW
)
logger = logging.getLogger(__name__)
def transformation_list(request, app_label, model, object_id):
content_type = get_object_or_404(ContentType, app_label=app_label, model=model)
try:
content_object = content_type.get_object_for_this_type(pk=object_id)
except content_type.model_class().DoesNotExist:
raise Http404
try:
Permission.objects.check_permissions(request.user, [PERMISSION_TRANSFORMATION_VIEW])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_TRANSFORMATION_VIEW, request.user, content_object)
context = {
'object_list': Transformation.objects.get_for_model(content_object),
'content_object': content_object,
'navigation_object_list': ['content_object'],
'title': _('Transformations for: %s') % content_object,
'extra_columns': [
{'name': _('Order'), 'attribute': 'order'},
{'name': _('Transformation'), 'attribute': encapsulate(lambda x: x.get_name_display())},
{'name': _('Arguments'), 'attribute': 'arguments'}
],
'hide_link': True,
'hide_object': True,
}
return render_to_response(
'appearance/generic_list.html', context, context_instance=RequestContext(request)
)
def transformation_create(request, app_label, model, object_id):
content_type = get_object_or_404(ContentType, app_label=app_label, model=model)
try:
content_object = content_type.get_object_for_this_type(pk=object_id)
except content_type.model_class().DoesNotExist:
raise Http404
try:
Permission.objects.check_permissions(request.user, [PERMISSION_TRANSFORMATION_CREATE])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_TRANSFORMATION_CREATE, request.user, content_object)
if request.method == 'POST':
form = TransformationForm(request.POST, initial={'content_object': content_object})
if form.is_valid():
instance = form.save(commit=False)
instance.content_object = content_object
instance.save()
messages.success(request, _('Transformation created successfully.'))
return HttpResponseRedirect(reverse('converter:transformation_list', args=[app_label, model, object_id]))
else:
form = TransformationForm(initial={'content_object': content_object})
return render_to_response('appearance/generic_form.html', {
'form': form,
'content_object': content_object,
'navigation_object_list': ['content_object'],
'title': _('Create new transformation for: %s') % content_object,
}, context_instance=RequestContext(request))
def transformation_delete(request, object_id):
transformation = get_object_or_404(Transformation, pk=object_id)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_TRANSFORMATION_DELETE])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_TRANSFORMATION_DELETE, request.user, transformation.content_object)
if request.method == 'POST':
transformation.delete()
messages.success(request, _('Transformation deleted successfully.'))
return HttpResponseRedirect(reverse('converter:transformation_list', args=[transformation.content_type.app_label, transformation.content_type.model, transformation.object_id]))
return render_to_response('appearance/generic_confirm.html', {
'content_object': transformation.content_object,
'delete_view': True,
'navigation_object_list': ['content_object', 'transformation'],
'previous': reverse('converter:transformation_list', args=[transformation.content_type.app_label, transformation.content_type.model, transformation.object_id]),
'title': _('Are you sure you wish to delete transformation "%(transformation)s" for: %(content_object)s') % {
'transformation': transformation,
'content_object': transformation.content_object},
'transformation': transformation,
}, context_instance=RequestContext(request))
def transformation_edit(request, object_id):
transformation = get_object_or_404(Transformation, pk=object_id)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_TRANSFORMATION_EDIT])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_TRANSFORMATION_EDIT, request.user, transformation.content_object)
if request.method == 'POST':
form = TransformationForm(request.POST, instance=transformation)
if form.is_valid():
form.save()
messages.success(request, _('Transformation edited successfully.'))
return HttpResponseRedirect(reverse('converter:transformation_list', args=[transformation.content_type.app_label, transformation.content_type.model, transformation.object_id]))
else:
form = TransformationForm(instance=transformation)
return render_to_response('appearance/generic_form.html', {
'content_object': transformation.content_object,
'form': form,
'navigation_object_list': ['content_object', 'transformation'],
'title': _('Edit transformation "%(transformation)s" for: %(content_object)s') % {
'transformation': transformation,
'content_object': transformation.content_object},
'transformation': transformation,
}, context_instance=RequestContext(request))

View File

@@ -16,6 +16,7 @@ urlpatterns = patterns(
url(r'^authentication/', include('authentication.urls', namespace='authentication')),
url(r'^checkouts/', include('checkouts.urls', namespace='checkouts')),
url(r'^comments/', include('document_comments.urls', namespace='comments')),
url(r'^converter/', include('converter.urls', namespace='converter')),
url(r'^document/signatures/', include('document_signatures.urls', namespace='signatures')),
url(r'^document/states/', include('document_states.urls', namespace='document_states')),
url(r'^documents/', include('documents.urls', namespace='documents')),