diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index 35007412d7..162abfd605 100644 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -2,6 +2,7 @@ from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse from django.conf import settings +from common.utils import validate_path from navigation.api import register_links, register_top_menu, \ register_model_list_columns, register_multi_item_links, \ register_sidebar_template @@ -24,8 +25,25 @@ from documents.literals import HISTORY_DOCUMENT_CREATED, \ HISTORY_DOCUMENT_EDITED, HISTORY_DOCUMENT_DELETED from documents.conf.settings import ZOOM_MAX_LEVEL from documents.conf.settings import ZOOM_MIN_LEVEL +from documents.conf.settings import CACHE_PATH from documents.widgets import document_thumbnail +# Document page links expressions +def is_first_page(context): + return context['object'].page_number <= 1 + + +def is_last_page(context): + return context['object'].page_number >= context['object'].document.documentpage_set.count() + + +def is_min_zoom(context): + return context['zoom'] <= ZOOM_MIN_LEVEL + + +def is_max_zoom(context): + return context['zoom'] >= ZOOM_MAX_LEVEL + # Permission setup set_namespace_title('documents', _(u'Documents')) register_permission(PERMISSION_DOCUMENT_CREATE) @@ -48,23 +66,6 @@ register_history_type(HISTORY_DOCUMENT_CREATED) register_history_type(HISTORY_DOCUMENT_EDITED) register_history_type(HISTORY_DOCUMENT_DELETED) - -# Document page links expressions -def is_first_page(context): - return context['object'].page_number <= 1 - - -def is_last_page(context): - return context['object'].page_number >= context['object'].document.documentpage_set.count() - - -def is_min_zoom(context): - return context['zoom'] <= ZOOM_MIN_LEVEL - - -def is_max_zoom(context): - return context['zoom'] >= ZOOM_MAX_LEVEL - document_list = {'text': _(u'all documents'), 'view': 'document_list', 'famfam': 'page', 'permissions': [PERMISSION_DOCUMENT_VIEW]} document_list_recent = {'text': _(u'recent documents'), 'view': 'document_list_recent', 'famfam': 'page', 'permissions': [PERMISSION_DOCUMENT_VIEW]} document_create_multiple = {'text': _(u'upload new documents'), 'view': 'document_create_multiple', 'famfam': 'page_add', 'permissions': [PERMISSION_DOCUMENT_CREATE]} @@ -198,3 +199,5 @@ register_sidebar_template(['document_type_list'], 'document_types_help.html') register_links(Document, [document_view_simple], menu_name='form_header', position=0) register_links(Document, [document_view_advanced], menu_name='form_header', position=1) register_links(Document, [document_history_view], menu_name='form_header') + +validate_path(CACHE_PATH) diff --git a/apps/documents/conf/settings.py b/apps/documents/conf/settings.py index 4c7749624c..5da5542b94 100644 --- a/apps/documents/conf/settings.py +++ b/apps/documents/conf/settings.py @@ -2,8 +2,10 @@ import hashlib import uuid +import os from django.utils.translation import ugettext_lazy as _ +from django.conf import settings from storage.backends.filebasedstorage import FileBasedStorage from smart_settings.api import register_settings @@ -38,5 +40,7 @@ register_settings( {'name': u'ZOOM_MAX_LEVEL', 'global_name': u'DOCUMENTS_ZOOM_MAX_LEVEL', 'default': 200, 'description': _(u'Maximum amount in percent (%) to allow user to zoom in a document page interactively.')}, {'name': u'ZOOM_MIN_LEVEL', 'global_name': u'DOCUMENTS_ZOOM_MIN_LEVEL', 'default': 50, 'description': _(u'Minimum amount in percent (%) to allow user to zoom out a document page interactively.')}, {'name': u'ROTATION_STEP', 'global_name': u'DOCUMENTS_ROTATION_STEP', 'default': 90, 'description': _(u'Amount in degrees to rotate a document page per user interaction.')}, + # + {'name': u'CACHE_PATH', 'global_name': u'DOCUMENTS_CACHE_PATH', 'default': os.path.join(settings.PROJECT_ROOT, 'image_cache'), 'exists': True}, ] ) diff --git a/apps/documents/models.py b/apps/documents/models.py index b3eadb08e7..d33bf1112d 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -1,11 +1,13 @@ import os import tempfile +import hashlib from django.db import models from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.contrib.contenttypes import generic from django.contrib.comments.models import Comment +from django.conf import settings from python_magic import magic @@ -13,12 +15,26 @@ from taggit.managers import TaggableManager from dynamic_search.api import register from converter.api import get_page_count from converter.api import get_available_transformations_choices +from converter.api import create_image_cache_filename, convert +from converter.exceptions import UnknownFormat, UnkownConvertError from documents.conf.settings import CHECKSUM_FUNCTION from documents.conf.settings import UUID_FUNCTION from documents.conf.settings import STORAGE_BACKEND +from documents.conf.settings import PREVIEW_SIZE +from documents.conf.settings import THUMBNAIL_SIZE +from documents.conf.settings import CACHE_PATH + from documents.managers import RecentDocumentManager, \ DocumentPageTransformationManager +from documents.utils import document_save_to_temp_dir +from documents.literals import PICTURE_ERROR_SMALL, PICTURE_ERROR_MEDIUM, \ + PICTURE_UNKNOWN_SMALL, PICTURE_UNKNOWN_MEDIUM +from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \ + DEFAULT_FILE_FORMAT, DEFAULT_PAGE_NUMBER + +# document image cache name hash function +HASH_FUNCTION = lambda x: hashlib.sha256(x).hexdigest() def get_filename_from_uuid(instance, filename): @@ -201,8 +217,7 @@ class Document(models.Model): exists in storage """ return self.file.storage.exists(self.file.path) - - + def apply_default_transformations(self, transformations): #Only apply default transformations on new documents if reduce(lambda x, y: x + y, [page.documentpagetransformation_set.count() for page in self.documentpage_set.all()]) == 0: @@ -216,6 +231,29 @@ class Document(models.Model): ) page_transformation.save() + + def get_image_cache_name(self, page): + document_page = self.documentpage_set.get(page_number=page) + transformations, warnings = document_page.get_transformation_list() + hash_value = HASH_FUNCTION(u''.join([self.checksum, unicode(page), unicode(transformations)])) + cache_file_path = os.path.join(CACHE_PATH, hash_value) + if os.path.exists(cache_file_path): + return cache_file_path + else: + document_file = document_save_to_temp_dir(self, self.checksum) + return convert(document_file, output_filepath=cache_file_path, page=page, transformations=transformations) + + def get_image(self, size=PREVIEW_SIZE, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION): + try: + image_cache_name = self.get_image_cache_name(page=page) + output_file = convert(image_cache_name, cleanup_files=False, size=size, zoom=zoom, rotation=rotation) + except UnknownFormat: + output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_UNKNOWN_SMALL) + except UnkownConvertError: + output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_ERROR_SMALL) + except Exception, e: + output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_ERROR_SMALL) + return output_file class DocumentTypeFilename(models.Model): diff --git a/apps/documents/urls.py b/apps/documents/urls.py index 4a8dcd2d46..19020a3448 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -1,7 +1,5 @@ from django.conf.urls.defaults import patterns, url -from converter.literals import QUALITY_HIGH, QUALITY_PRINT - from documents.conf.settings import PREVIEW_SIZE from documents.conf.settings import PRINT_SIZE from documents.conf.settings import THUMBNAIL_SIZE @@ -24,8 +22,8 @@ urlpatterns = patterns('documents.views', url(r'^(?P\d+)/display/preview/$', 'get_document_image', {'size': PREVIEW_SIZE}, 'document_preview'), url(r'^(?P\d+)/display/preview/multipage/$', 'get_document_image', {'size': MULTIPAGE_PREVIEW_SIZE}, 'document_preview_multipage'), url(r'^(?P\d+)/display/thumbnail/$', 'get_document_image', {'size': THUMBNAIL_SIZE}, 'document_thumbnail'), - url(r'^(?P\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE, 'quality': QUALITY_HIGH}, 'document_display'), - url(r'^(?P\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE, 'quality': QUALITY_PRINT}, 'document_display_print'), + url(r'^(?P\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE}, 'document_display'), + url(r'^(?P\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE}, 'document_display_print'), url(r'^(?P\d+)/download/$', 'document_download', (), 'document_download'), url(r'^(?P\d+)/create/siblings/$', 'document_create_siblings', (), 'document_create_siblings'), diff --git a/apps/documents/views.py b/apps/documents/views.py index ee9be82b3e..f86a73f3d8 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -20,11 +20,8 @@ from common.widgets import two_state_template from common.literals import PAGE_SIZE_DIMENSIONS, \ PAGE_ORIENTATION_PORTRAIT, PAGE_ORIENTATION_LANDSCAPE from common.conf.settings import DEFAULT_PAPER_SIZE -from converter.api import convert_document -from converter.exceptions import UnkownConvertError, UnknownFormat from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \ - DEFAULT_FILE_FORMAT, QUALITY_PRINT, QUALITY_DEFAULT, \ - DEFAULT_PAGE_NUMBER + DEFAULT_FILE_FORMAT, DEFAULT_PAGE_NUMBER from filetransfers.api import serve_file from grouping.utils import get_document_group_subtemplate from metadata.api import save_metadata_list, \ @@ -287,7 +284,7 @@ def document_edit(request, document_id): }, context_instance=RequestContext(request)) -def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_DEFAULT): +def get_document_image(request, document_id, size=PREVIEW_SIZE): check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW]) document = get_object_or_404(Document, pk=document_id) @@ -304,36 +301,7 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_ rotation = int(request.GET.get('rotation', DEFAULT_ROTATION)) % 360 - document_page = get_object_or_404(document.documentpage_set, page_number=page) - transformations, warnings = document_page.get_transformation_list() - - if warnings and (request.user.is_staff or request.user.is_superuser): - for warning in warnings: - messages.warning(request, _(u'Page transformation error: %s') % warning) - - try: - output_file = convert_document(document, size=size, file_format=DEFAULT_FILE_FORMAT, quality=quality, page=page, zoom=zoom, rotation=rotation, transformations=transformations) - except UnkownConvertError, e: - if request.user.is_staff or request.user.is_superuser: - messages.error(request, e) - if size == THUMBNAIL_SIZE: - output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_ERROR_SMALL) - else: - output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_ERROR_MEDIUM) - except UnknownFormat: - if size == THUMBNAIL_SIZE: - output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_UNKNOWN_SMALL) - else: - output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_UNKNOWN_MEDIUM) - except Exception, e: - if request.user.is_staff or request.user.is_superuser: - messages.error(request, e) - if size == THUMBNAIL_SIZE: - output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_ERROR_SMALL) - else: - output_file = os.path.join(settings.MEDIA_ROOT, u'images', PICTURE_ERROR_MEDIUM) - finally: - return sendfile.sendfile(request, output_file) + return sendfile.sendfile(request, document.get_image(size=size, page=page, zoom=zoom, rotation=rotation)) def document_download(request, document_id): @@ -804,13 +772,14 @@ def document_print(request, document_id): def document_hard_copy(request, document_id): + #TODO: FIXME check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW]) document = get_object_or_404(Document, pk=document_id) RecentDocument.objects.add_document_for_user(request.user, document) - arguments, warnings = calculate_converter_arguments(document, size=PRINT_SIZE, file_format=DEFAULT_FILE_FORMAT, quality=QUALITY_PRINT) + arguments, warnings = calculate_converter_arguments(document, size=PRINT_SIZE, file_format=DEFAULT_FILE_FORMAT) # Pre-generate convert_document(document, **arguments)