From dfcf2b8b17fd4b9903ff79862f32f2384acd0fc3 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 4 Jan 2012 02:08:50 -0400 Subject: [PATCH 1/2] Add generic class for handling reading, appending and creating Zip files --- apps/common/compressed_files.py | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 apps/common/compressed_files.py diff --git a/apps/common/compressed_files.py b/apps/common/compressed_files.py new file mode 100644 index 0000000000..6737bbc9ed --- /dev/null +++ b/apps/common/compressed_files.py @@ -0,0 +1,84 @@ +import os, tempfile, zipfile + +try: + import zlib + COMPRESSION = zipfile.ZIP_DEFLATED +except: + COMPRESSION = zipfile.ZIP_STORED + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +from django.core.files.uploadedfile import SimpleUploadedFile + + +class NotACompressedFile(Exception): + pass + + +class CompressedFile(object): + def __init__(self, file_input=None): + if file_input: + self._open(file_input) + else: + self._create() + + def _create(self): + self.descriptor = StringIO() + self.zf = zipfile.ZipFile(self.descriptor, mode='w') + + def _open(self, file_input): + try: + # Is it a file like object? + file_input.seek(0) + except AttributeError: + # If not, try open it. + self.descriptor = open(file_input, 'r+b') + else: + self.descriptor = file_input + + try: + test = zipfile.ZipFile(self.descriptor, mode='r') + except zipfile.BadZipfile: + raise NotACompressedFile + else: + test.close() + self.descriptor.seek(0) + self.zf = zipfile.ZipFile(self.descriptor, mode='a') + + def add_file(self, file_input, arcname=None): + try: + # Is it a file like object? + file_input.seek(0) + except AttributeError: + # If not, keep it + self.zf.write(filename, arcname=arcname, compress_type=COMPRESSION) + else: + self.zf.writestr(arcname, file_input.read()) + + def contents(self): + return [filename for filename in self.zf.namelist() if not filename.endswith('/')] + + def get_content(self, filename): + return self.zf.read(filename) + + def write(self, filename=None): + # fix for Linux zip files read in Windows + for file in self.zf.filelist: + file.create_system = 0 + + self.descriptor.seek(0) + + if filename: + descriptor = open(filename, 'w') + descriptor.write(self.descriptor.read()) + else: + return self.descriptor + + def as_file(self, filename): + return SimpleUploadedFile(name=filename, content=self.write().read()) + + def close(self): + self.zf.close() From 3a517036d1f3d546f82a2250eccbfea247442901 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Wed, 4 Jan 2012 02:09:30 -0400 Subject: [PATCH 2/2] Add bulk download support to the document app --- apps/documents/__init__.py | 9 +++-- apps/documents/urls.py | 1 + apps/documents/views.py | 81 +++++++++++++++++++++++++++++--------- 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index 0fe9f1aecb..aa8ec09269 100644 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -5,9 +5,9 @@ import tempfile from django.utils.translation import ugettext_lazy as _ from common.utils import validate_path, encapsulate -from navigation.api import register_links, register_top_menu, \ - register_model_list_columns, register_multi_item_links, \ - register_sidebar_template +from navigation.api import (register_links, register_top_menu, + register_model_list_columns, register_multi_item_links, + register_sidebar_template) from main.api import register_diagnostic, register_maintenance_links from tags.widgets import get_tags_inline_widget_simple from history.api import register_history_type @@ -70,6 +70,7 @@ document_multiple_delete = {'text': _(u'delete'), 'view': 'document_multiple_del document_edit = {'text': _(u'edit'), 'view': 'document_edit', 'args': 'object.id', 'famfam': 'page_edit', 'permissions': [PERMISSION_DOCUMENT_PROPERTIES_EDIT]} document_preview = {'text': _(u'preview'), 'class': 'fancybox', 'view': 'document_preview', 'args': 'object.id', 'famfam': 'magnifier', 'permissions': [PERMISSION_DOCUMENT_VIEW]} document_download = {'text': _(u'download'), 'view': 'document_download', 'args': 'object.id', 'famfam': 'page_save', 'permissions': [PERMISSION_DOCUMENT_DOWNLOAD]} +document_multiple_download = {'text': _(u'download'), 'view': 'document_multiple_download', 'famfam': 'page_save', 'permissions': [PERMISSION_DOCUMENT_DOWNLOAD]} document_version_download = {'text': _(u'download'), 'view': 'document_version_download', 'args': 'object.pk', 'famfam': 'page_save', 'permissions': [PERMISSION_DOCUMENT_DOWNLOAD]} document_find_duplicates = {'text': _(u'find duplicates'), 'view': 'document_find_duplicates', 'args': 'object.id', 'famfam': 'page_white_copy', 'permissions': [PERMISSION_DOCUMENT_VIEW]} document_find_all_duplicates = {'text': _(u'find all duplicates'), 'view': 'document_find_all_duplicates', 'famfam': 'page_white_copy', 'permissions': [PERMISSION_DOCUMENT_VIEW], 'description': _(u'Search all the documents\' checksums and return a list of the exact matches.')} @@ -130,7 +131,7 @@ register_links(['document_type_filename_create', 'document_type_filename_list', # Register document links register_links(Document, [document_edit, document_print, document_delete, document_download, document_find_duplicates, document_clear_transformations, document_create_siblings]) -register_multi_item_links(['document_find_duplicates', 'folder_view', 'index_instance_list', 'document_type_document_list', 'search', 'results', 'document_group_view', 'document_list', 'document_list_recent'], [document_multiple_clear_transformations, document_multiple_delete]) +register_multi_item_links(['document_find_duplicates', 'folder_view', 'index_instance_list', 'document_type_document_list', 'search', 'results', 'document_group_view', 'document_list', 'document_list_recent'], [document_multiple_clear_transformations, document_multiple_delete, document_multiple_download]) # Document Version links register_links(DocumentVersion, [document_version_revert, document_version_download]) diff --git a/apps/documents/urls.py b/apps/documents/urls.py index 8818543380..046d274dd8 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -29,6 +29,7 @@ urlpatterns = patterns('documents.views', url(r'^(?P\d+)/display/thumbnail/base64/$', 'get_document_image', {'size': THUMBNAIL_SIZE, 'base64_version': True}, 'document_thumbnail_base64'), url(r'^(?P\d+)/download/$', 'document_download', (), 'document_download'), + url(r'^multiple/download/$', 'document_multiple_download', (), 'document_multiple_download'), url(r'^(?P\d+)/create/siblings/$', 'document_create_siblings', (), 'document_create_siblings'), url(r'^(?P\d+)/find_duplicates/$', 'document_find_duplicates', (), 'document_find_duplicates'), url(r'^(?P\d+)/clear_transformations/$', 'document_clear_transformations', (), 'document_clear_transformations'), diff --git a/apps/documents/views.py b/apps/documents/views.py index 14ec65e855..a5deae4f53 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -13,6 +13,7 @@ from django.views.generic.list_detail import object_list from django.core.urlresolvers import reverse from django.utils.http import urlencode from django.core.exceptions import PermissionDenied +from django.conf import settings import sendfile from common.utils import pretty_size, parse_range, urlquote, \ @@ -31,6 +32,7 @@ from permissions.models import Permission from document_indexing.api import update_indexes, delete_indexes from history.api import create_history from acls.models import AccessEntry +from common.compressed_files import CompressedFile from .conf.settings import (PREVIEW_SIZE, STORAGE_BACKEND, ZOOM_PERCENT_STEP, ZOOM_MAX_LEVEL, ZOOM_MIN_LEVEL, ROTATION_STEP, PRINT_SIZE, @@ -317,31 +319,72 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, base64_version=F return sendfile.sendfile(request, document.get_image(size=size, page=page, zoom=zoom, rotation=rotation, version=version), mimetype=DEFAULT_FILE_FORMAT_MIMETYPE) -def document_download(request, document_id=None, document_version_pk=None): - if document_version_pk: +def document_download(request, document_id=None, document_id_list=None, document_version_pk=None): + document_version = None + documents = [] + + if document_id: + documents = [get_object_or_404(Document, pk=document_id)] + post_action_redirect = reverse('document_list') + elif document_id_list: + documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')] + elif document_version_pk: document_version = get_object_or_404(DocumentVersion, pk=document_version_pk) - else: - document_version = get_object_or_404(Document, pk=document_id).latest_version try: Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_DOWNLOAD]) except PermissionDenied: - AccessEntry.objects.check_access(PERMISSION_DOCUMENT_DOWNLOAD, request.user, document_version.document) - - try: - # Test permissions and trigger exception - fd = document_version.open() - fd.close() - return serve_file( - request, - document_version.file, - save_as=u'"%s"' % document_version.filename, - content_type=document_version.mimetype if document_version.mimetype else 'application/octet-stream' - ) - except Exception, e: - messages.error(request, e) - return HttpResponseRedirect(request.META['HTTP_REFERER']) + documents = AccessEntry.objects.filter_objects_by_access(PERMISSION_DOCUMENT_DOWNLOAD, request.user, documents, exception_on_empty=True) + if len(documents) == 1: + document_version = documents[0].latest_version + + if document_version: + try: + # Test permissions and trigger exception + fd = document_version.open() + fd.close() + return serve_file( + request, + document_version.file, + save_as=u'"%s"' % document_version.filename, + content_type=document_version.mimetype if document_version.mimetype else 'application/octet-stream' + ) + except Exception, e: + if settings.DEBUG: + raise + else: + messages.error(request, e) + return HttpResponseRedirect(request.META['HTTP_REFERER']) + else: + try: + compressed_file = CompressedFile() + for document in documents: + descriptor = document.open() + compressed_file.add_file(descriptor, arcname=document.filename) + descriptor.close() + + compressed_file.close() + + return serve_file( + request, + compressed_file.as_file('document_bundle.zip'), + save_as=u'"document_bundle.zip"', + content_type='application/zip' + ) + # TODO: DO a redirection afterwards + except Exception, e: + if settings.DEBUG: + raise + else: + messages.error(request, e) + return HttpResponseRedirect(request.META['HTTP_REFERER']) + + +def document_multiple_download(request): + return document_download( + request, document_id_list=request.GET.get('id_list', []) + ) def document_page_transformation_list(request, document_page_id): document_page = get_object_or_404(DocumentPage, pk=document_page_id)