diff --git a/apps/converter/api.py b/apps/converter/api.py index 57260072dc..856c0c4c14 100755 --- a/apps/converter/api.py +++ b/apps/converter/api.py @@ -81,7 +81,7 @@ def cache_cleanup(input_filepath, size, page=0, format='jpg'): pass -def create_image_cache_filename(input_filepath, quality=QUALITY_DEFAULT, *args, **kwargs): +def create_image_cache_filename(input_filepath, quality=QUALITY_DEFAULT, extra_options='', *args, **kwargs): if input_filepath: temp_filename, separator = os.path.splitext(os.path.basename(input_filepath)) temp_path = os.path.join(TEMPORARY_DIRECTORY, temp_filename) @@ -90,6 +90,7 @@ def create_image_cache_filename(input_filepath, quality=QUALITY_DEFAULT, *args, [final_filepath.append(str(arg)) for arg in args] final_filepath.extend(['%s_%s' % (key, value) for key, value in kwargs.items()]) final_filepath.append(QUALITY_SETTINGS[quality]) + final_filepath.append(extra_options) temp_path += slugify('_'.join(final_filepath)) @@ -97,17 +98,17 @@ def create_image_cache_filename(input_filepath, quality=QUALITY_DEFAULT, *args, else: return None -def in_image_cache(input_filepath, size, page=0, format='jpg', quality=QUALITY_DEFAULT): - output_filepath = create_image_cache_filename(input_filepath, size=size, page=page, format=format, quality=quality) +def in_image_cache(input_filepath, size, page=0, format='jpg', quality=QUALITY_DEFAULT, extra_options=''): + output_filepath = create_image_cache_filename(input_filepath, size=size, page=page, format=format, quality=quality, extra_options=extra_options) if os.path.exists(output_filepath): return output_filepath else: return None -def convert(input_filepath, size, quality=QUALITY_DEFAULT, cache=True, page=0, format='jpg', mimetype=None, extension=None): +def convert(input_filepath, size, quality=QUALITY_DEFAULT, cache=True, page=0, format='jpg', extra_options='', mimetype=None, extension=None): unoconv_output = None - output_filepath = create_image_cache_filename(input_filepath, size=size, page=page, format=format, quality=quality) + output_filepath = create_image_cache_filename(input_filepath, size=size, page=page, format=format, quality=quality, extra_options=extra_options) if os.path.exists(output_filepath): return output_filepath ''' @@ -124,7 +125,9 @@ def convert(input_filepath, size, quality=QUALITY_DEFAULT, cache=True, page=0, f #TODO: Check mimetype and use corresponding utility try: input_arg = '%s[%s]' % (input_filepath, page) - status, error_string = execute_convert(input_arg, '-resize %s' % size, '%s:%s' % (format, output_filepath), quality=quality) + extra_options += ' -resize %s' % size + print 'extra_options', extra_options + status, error_string = execute_convert(input_arg, extra_options, '%s:%s' % (format, output_filepath), quality=quality) if status: errors = get_errors(error_string) raise ConvertError(status, errors) diff --git a/apps/converter/conf/settings.py b/apps/converter/conf/settings.py index 6ef7cd996f..d843670ba3 100755 --- a/apps/converter/conf/settings.py +++ b/apps/converter/conf/settings.py @@ -1,7 +1,16 @@ from django.conf import settings + +ugettext = lambda s: s + + CONVERT_PATH = getattr(settings, 'CONVERTER_CONVERT_PATH', u'/usr/bin/convert') OCR_OPTIONS = getattr(settings, 'CONVERTER_OCR_OPTIONS', u'-colorspace Gray -depth 8 -resample 200x200') DEFAULT_OPTIONS = getattr(settings, 'CONVERTER_DEFAULT_OPTIONS', u'') LOW_QUALITY_OPTIONS = getattr(settings, 'CONVERTER_LOW_QUALITY_OPTIONS', u'') HIGH_QUALITY_OPTIONS = getattr(settings, 'CONVERTER_HIGH_QUALITY_OPTIONS', u'-density 400') + +TRANFORMATION_ROTATE = (u'-rotate %(degrees)d', ugettext(u'Rotation, arguments: degrees')) +TRANFORMATION_CHOICES = getattr(settings, 'CONVERTER_TRANSFORMATION_LIST', [ + TRANFORMATION_ROTATE, + ]) diff --git a/apps/converter/locale/es/LC_MESSAGES/django.mo b/apps/converter/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..e0e9c162cc Binary files /dev/null and b/apps/converter/locale/es/LC_MESSAGES/django.mo differ diff --git a/apps/converter/locale/es/LC_MESSAGES/django.po b/apps/converter/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000000..6b9ab4afab --- /dev/null +++ b/apps/converter/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-02-13 16:00-0400\n" +"PO-Revision-Date: 2011-02-13 16:00\n" +"Last-Translator: \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: \n" +"X-Translated-Using: django-rosetta 0.5.6\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: conf/settings.py:17 +msgid "Rotation" +msgstr "Rotación" diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index 7f616f1cf6..c59d518355 100755 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -9,7 +9,7 @@ from common.utils import pretty_size from permissions.api import register_permissions -from models import Document +from models import Document, DocumentTransformation from staging import StagingFile from documents.conf import settings as documents_settings @@ -20,6 +20,7 @@ PERMISSION_DOCUMENT_METADATA_EDIT = 'document_metadata_edit' PERMISSION_DOCUMENT_VIEW = 'document_view' PERMISSION_DOCUMENT_DELETE = 'document_delete' PERMISSION_DOCUMENT_DOWNLOAD = 'document_download' +PERMISSION_DOCUMENT_TRANSFORM = 'document_transform' register_permissions('documents', [ {'name':PERMISSION_DOCUMENT_CREATE, 'label':_(u'Create document')}, @@ -28,6 +29,7 @@ register_permissions('documents', [ {'name':PERMISSION_DOCUMENT_VIEW, 'label':_(u'View document')}, {'name':PERMISSION_DOCUMENT_DELETE, 'label':_(u'Delete document')}, {'name':PERMISSION_DOCUMENT_DOWNLOAD, 'label':_(u'Download document')}, + {'name':PERMISSION_DOCUMENT_TRANSFORM, 'label':_(u'Transform document')}, ]) document_list = {'text':_(u'documents list'), 'view':'document_list', 'famfam':'page', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_VIEW]}} @@ -41,13 +43,21 @@ document_edit_metadata = {'text':_('edit metadata'), 'view':'document_edit_metad document_preview = {'text':_('preview'), 'class':'fancybox', 'view':'document_preview', 'args':'object.id', 'famfam':'magnifier', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_VIEW]}} document_download = {'text':_('download'), 'view':'document_download', 'args':'object.id', 'famfam':'page_save', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_DOWNLOAD]}} +document_transformation_list = {'text':_(u'transformations'), 'view':'document_transformation_list', 'args':'object.id', 'famfam':'page_paintbrush', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_TRANSFORM]}} +document_transformation_delete = {'text':_('delete'), 'view':'document_transformation_delete', 'args':'object.id', 'famfam':'delete'}#, 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_TRANSFORM]}} + + staging_file_preview = {'text':_('preview'), 'class':'fancybox', 'view':'staging_file_preview', 'args':'object.id', 'famfam':'drive_magnify'} staging_file_delete = {'text':_('delete'), 'view':'staging_file_delete', 'args':'object.id', 'famfam':'drive_delete'} -register_links(Document, [document_view, document_edit, document_edit_metadata, document_delete, document_download], menu_name='sidebar') +register_links(Document, [document_view, document_edit, document_edit_metadata, document_delete, document_download, document_transformation_list], menu_name='sidebar') register_links(Document, [document_list, document_create, document_create_multiple, document_create_sibling], menu_name='sidebar') register_links(['document_list', 'document_create', 'document_create_multiple', 'upload_document_with_type', 'upload_multiple_documents_with_type'], [document_list, document_create, document_create_multiple], menu_name='sidebar') +register_links(DocumentTransformation, [document_transformation_delete]) + + + register_links(StagingFile, [staging_file_preview, staging_file_delete]) register_model_list_columns(Document, [ diff --git a/apps/documents/admin.py b/apps/documents/admin.py index ee067e2295..863d77ac4d 100755 --- a/apps/documents/admin.py +++ b/apps/documents/admin.py @@ -3,7 +3,7 @@ from django.contrib import admin from models import MetadataType, DocumentType, Document, \ DocumentTypeMetadataType, DocumentMetadata, DocumentTypeFilename, \ MetadataIndex, DocumentMetadataIndex, DocumentPage, MetadataGroup, \ - MetadataGroupItem + MetadataGroupItem, DocumentTransformation class MetadataTypeAdmin(admin.ModelAdmin): @@ -50,6 +50,7 @@ class DocumentMetadataIndexInline(admin.StackedInline): allow_add = True readonly_fields = ('metadata_index', 'filename') + class DocumentPageInline(admin.StackedInline): model = DocumentPage extra = 1 @@ -57,8 +58,16 @@ class DocumentPageInline(admin.StackedInline): allow_add = True +class DocumentTransformationline(admin.StackedInline): + model = DocumentTransformation + extra = 1 + classes = ('collapse-open',) + allow_add = True + + class DocumentAdmin(admin.ModelAdmin): - inlines = [DocumentMetadataInline, DocumentMetadataIndexInline, DocumentPageInline] + inlines = [DocumentMetadataInline, DocumentMetadataIndexInline, + DocumentTransformationline, DocumentPageInline] list_display = ('uuid', 'file_filename', 'file_extension') diff --git a/apps/documents/models.py b/apps/documents/models.py index 790d50fd8d..281618e2ba 100755 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -14,6 +14,8 @@ from django.db.models import Q from dynamic_search.api import register +from converter.conf.settings import TRANFORMATION_CHOICES + from documents.conf.settings import AVAILABLE_FUNCTIONS from documents.conf.settings import AVAILABLE_MODELS from documents.conf.settings import CHECKSUM_FUNCTION @@ -411,4 +413,27 @@ class MetadataGroupItem(models.Model): verbose_name_plural = _(u'metadata group items') +class DocumentTransformation(models.Model): + document = models.ForeignKey(Document, verbose_name=_(u'document')) + order = models.PositiveIntegerField(blank=True, null=True, verbose_name=_(u'order')) + transformation = models.CharField(choices=TRANFORMATION_CHOICES, max_length=128, verbose_name=_(u'transformation')) + arguments = models.TextField(blank=True, null=True, verbose_name=_(u'arguments'), help_text=_(u'Use directories to indentify arguments, example: {\'degrees\':90}')) + + def __unicode__(self): + return self.get_transformation_display() + + def get_transformation(self): + try: + return self.transformation % eval(self.arguments) + except Exception, e: + raise Exception(e) + + class Meta: + ordering = ('order',) + verbose_name = _(u'document transformation') + verbose_name_plural = _(u'document transformations') + + + + register(Document, _(u'document'), ['document_type__name', 'file_mimetype', 'file_filename', 'file_extension', 'documentmetadata__value', 'documentpage__content']) diff --git a/apps/documents/urls.py b/apps/documents/urls.py index fb3156e15b..62714155d5 100755 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -24,6 +24,10 @@ urlpatterns = patterns('documents.views', url(r'^document/(?P\d+)/display/$', 'get_document_image', {'size':DISPLAY_SIZE,'quality':QUALITY_HIGH}, 'document_display'), url(r'^document/(?P\d+)/download/$', 'document_download', (), 'document_download'), url(r'^document/(?P\d+)/create/siblings/$', 'document_create_sibling', {'multiple':False}, 'document_create_sibling'), + + url(r'^document/(?P\d+)/tranformation/list/$', 'document_transformation_list', (), 'document_transformation_list'), + url(r'^document/tranformation/(?P\d+)/delete/$', 'document_transformation_delete', (), 'document_transformation_delete'), + url(r'^staging_file/(?P\w+)/preview/$', 'staging_file_preview', (), 'staging_file_preview'), url(r'^staging_file/(?P\w+)/delete/$', 'staging_file_delete', (), 'staging_file_delete'), diff --git a/apps/documents/views.py b/apps/documents/views.py index 065edbcfa0..7fc7abee4d 100755 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -38,7 +38,8 @@ from documents.conf.settings import GROUP_SHOW_EMPTY from documents import PERMISSION_DOCUMENT_CREATE, \ PERMISSION_DOCUMENT_CREATE, PERMISSION_DOCUMENT_PROPERTIES_EDIT, \ PERMISSION_DOCUMENT_METADATA_EDIT, PERMISSION_DOCUMENT_VIEW, \ - PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_DOWNLOAD + PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_DOWNLOAD, \ + PERMISSION_DOCUMENT_TRANSFORM from utils import save_metadata, save_metadata_list, decode_metadata_from_url @@ -435,9 +436,19 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_ raise Http404(e) document = get_object_or_404(Document, pk=document_id) - + transformation_list = [] + for tranformation in document.documenttransformation_set.all(): + try: + transformation_list.append(tranformation.get_transformation()) + except Exception, e: + if request.user.is_staff: + messages.warning(request, _(u'Transformation %s error: %s' % (tranformation, e))) + else: + pass + + tranformation_string = ' '.join(transformation_list) try: - filepath = in_image_cache(document.checksum, size=size, quality=quality) + filepath = in_image_cache(document.checksum, size=size, quality=quality, extra_options=tranformation_string) if filepath: return serve_file(request, File(file=open(filepath, 'r'))) @@ -445,7 +456,7 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_ document.file.open() desc = document.file.storage.open(document.file.path) filepath = from_descriptor_to_tempfile(desc, document.checksum) - output_file = convert(filepath, size=size, format='jpg', quality=quality) + output_file = convert(filepath, size=size, format='jpg', quality=quality, extra_options=tranformation_string) return serve_file(request, File(file=open(output_file, 'r')), content_type='image/jpeg') except Exception, e: if size == THUMBNAIL_SIZE: @@ -501,3 +512,40 @@ def staging_file_delete(request, staging_file_id): 'next':next, 'previous':previous, }, context_instance=RequestContext(request)) + + +def document_transformation_list(request, document_id): + permissions = [PERMISSION_DOCUMENT_TRANSFORM] + try: + check_permissions(request.user, 'documents', permissions) + except Unauthorized, e: + raise Http404(e) + + document = get_object_or_404(Document, pk=document_id) + + return object_list( + request, + queryset=document.documenttransformation_set.all(), + template_name='generic_list.html', + extra_context={ + 'title':_(u'document transformations'), + }, + ) + +def document_transformation_delete(request, document_transformation_id): + permissions = [PERMISSION_DOCUMENT_TRANSFORM] + try: + check_permissions(request.user, 'documents', permissions) + except Unauthorized, e: + raise Http404(e) + + document_transformation = get_object_or_404(DocumentTransformation, pk=document_transformation_id) + + return delete_object(request, model=DocumentTransformation, object_id=document_transformation_id, + template_name='generic_confirm.html', + post_delete_redirect=reverse('document_transformation_list'), + extra_context={ + 'delete_view':True, + 'object':document_transformation, + 'object_name':_(u'document transformation'), + }) diff --git a/docs/TODO b/docs/TODO index c26f8a18ec..6f4901e708 100755 --- a/docs/TODO +++ b/docs/TODO @@ -28,6 +28,7 @@ * Group documents by metadata - DONE * Permissions - DONE * Roles - DONE +* Assign default role to new users - DONE * Document list filtering by metadata * Filterform date filtering widget * Validate GET data before saving file @@ -62,4 +63,10 @@ * Handle ziped or rar archives * Display preferences 'document transformations' (Rotation, default zoom) * Gallery view for document groups -* Assign default role to new users +* Role editing view under setup +* Download metadata group documents as a single zip file +* Download original document or transformed document +* Include annotations in transformed documents downloads +* DB stored transformations +* Document view temp transformations +* Implement permissions decorators