Complete conversion of download views to CBV views using django-downloadview.

This also removes dependency on the filetransfers library.
This commit is contained in:
Roberto Rosario
2016-10-31 01:00:35 -04:00
parent 24ef702e9c
commit 6e3d99670c
37 changed files with 437 additions and 340 deletions

View File

@@ -3,6 +3,7 @@
- Remove the installation app
- Add support for document page search
- Remove recent searches feature
- Remove dependency on the django-filetransfer library
2.1.4 (2016-10-28)
==================

View File

@@ -13,6 +13,7 @@ Other changes
- Remove the installation app
- Add support for page search
- Remove recent searches feature
- Remove dependency on the django-filetransfer library
Removals
--------
@@ -43,6 +44,7 @@ existing installation.
Next upgrade/add the new requirements::
$ pip uninstall -y django-filetransfers
$ pip install --upgrade -r requirements.txt
Common steps

View File

@@ -10,7 +10,9 @@ from .permissions import permission_acl_view, permission_acl_edit
def get_kwargs_factory(variable_name):
def get_kwargs(context):
ContentType = apps.get_model(app_label='django', model_name='ContentType')
ContentType = apps.get_model(
app_label='contenttypes', model_name='ContentType'
)
content_type = ContentType.objects.get_for_model(
context[variable_name]

View File

@@ -23,7 +23,9 @@
<form action="{% url 'common:multi_object_action_view' %}" class="pure-form" method="get">
{% if object_list %}
{% get_multi_item_links_form object_list %}
{% if not hide_multi_item_actions %}
{% get_multi_item_links_form object_list %}
{% endif %}
{% if multi_item_actions %}
<fieldset>
{{ multi_item_form }}

View File

@@ -482,36 +482,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
''')
Package(label='django-filetransfers', license_text='''
Copyright (c) Waldemar Kornewald, Thomas Wanschik, and all contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of All Buttons Pressed nor
the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
''')
Package(label='django-pure-pagination', license_text='''
Copyright (c) James Pacileo and contributors.
All rights reserved.

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
import zipfile
try:
import zlib
import zlib # NOQA
COMPRESSION = zipfile.ZIP_DEFLATED
except:
COMPRESSION = zipfile.ZIP_STORED

View File

@@ -14,8 +14,7 @@ from django.views.generic.edit import (
)
from django.views.generic.list import ListView
from django_downloadview import VirtualDownloadView
from django_downloadview import VirtualFile
from django_downloadview import VirtualDownloadView, VirtualFile
from pure_pagination.mixins import PaginationMixin
from .forms import ChoiceForm

View File

@@ -12,9 +12,16 @@ class Command(management.BaseCommand):
def handle(self, *args, **options):
management.call_command('createsettings', interactive=False)
try:
result = management.call_command('migrate', interactive=False)
except OperationalError as exception:
self.stderr.write(self.style.NOTICE('Unable to migrate the database. The initialsetup command is to be used only on new installations. To upgrade existing installations use the performupgrade command.'))
management.call_command('migrate', interactive=False)
except OperationalError:
self.stderr.write(
self.style.NOTICE(
'Unable to migrate the database. The initialsetup '
'command is to be used only on new installations. To '
'upgrade existing installations use the performupgrade '
'command.'
)
)
raise
management.call_command('createautoadmin', interactive=False)
post_initial_setup.send(sender=self)

View File

@@ -159,12 +159,6 @@ class RedirectionMixin(object):
post_action_redirect = None
action_cancel_redirect = None
def get_post_action_redirect(self):
return self.post_action_redirect
def get_action_cancel_redirect(self):
return self.action_cancel_redirect
def dispatch(self, request, *args, **kwargs):
post_action_redirect = self.get_post_action_redirect()
action_cancel_redirect = self.get_action_cancel_redirect()
@@ -188,6 +182,9 @@ class RedirectionMixin(object):
RedirectionMixin, self
).dispatch(request, *args, **kwargs)
def get_action_cancel_redirect(self):
return self.action_cancel_redirect
def get_context_data(self, **kwargs):
context = super(RedirectionMixin, self).get_context_data(**kwargs)
context.update(
@@ -199,6 +196,9 @@ class RedirectionMixin(object):
return context
def get_post_action_redirect(self):
return self.post_action_redirect
def get_success_url(self):
return self.next_url or self.previous_url

View File

@@ -1,5 +1,5 @@
def skip_file_descriptor_check(func):
def func_wrapper(item):
item._skip_file_descriptor_test = True
return func(item)
return func_wrapper
def func_wrapper(item):
item._skip_file_descriptor_test = True
return func(item)
return func_wrapper

View File

@@ -66,7 +66,7 @@ class TempfileCheckMixin(object):
msg='Orphan temporary file. The number of temporary files and/or '
'directories at the start and at the end of the test are not the '
'same. Orphan entries: {}'.format(
','.join(final_temporary_items-self._temporary_items)
','.join(final_temporary_items - self._temporary_items)
)
)
super(TempfileCheckMixin, self).tearDown()

View File

@@ -6,7 +6,6 @@ from django.contrib.auth.models import Group
from django.core.urlresolvers import clear_url_caches, reverse
from django.http import HttpResponse
from django.template import Context, Template
from django.test import TestCase
from permissions import Permission
from permissions.models import Role

View File

@@ -13,7 +13,9 @@ from .permissions import (
def get_kwargs_factory(variable_name):
def get_kwargs(context):
ContentType = apps.get_model(app_label='django', model_name='ContentType')
ContentType = apps.get_model(
app_label='contenttypes', model_name='ContentType'
)
content_type = ContentType.objects.get_for_model(
context[variable_name]

View File

@@ -5,7 +5,6 @@ import logging
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from filetransfers.api import serve_file
from rest_framework import generics, status
from rest_framework.response import Response
@@ -87,7 +86,16 @@ class APIDeletedDocumentRestoreView(generics.GenericAPIView):
return Response(status=status.HTTP_200_OK)
class APIDocumentDownloadView(generics.RetrieveAPIView):
##############
from django_downloadview import VirtualDownloadView
from django_downloadview import VirtualFile
from django_downloadview import DownloadMixin
#class SingleObjectDownloadView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, VirtualDownloadView, SingleObjectMixin):
# VirtualFile = VirtualFile
class APIDocumentDownloadView(DownloadMixin, generics.RetrieveAPIView):
"""
Download the latest version of a document.
---
@@ -105,17 +113,15 @@ class APIDocumentDownloadView(generics.RetrieveAPIView):
permission_classes = (MayanPermission,)
queryset = Document.objects.all()
def get_file(self):
instance = self.get_object()
return VirtualFile(instance.latest_version.file, name=instance.label)
def get_serializer_class(self):
return None
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
return serve_file(
request,
instance.latest_version.file,
save_as='"%s"' % instance.label,
content_type=instance.latest_version.mimetype if instance.latest_version.mimetype else 'application/octet-stream'
)
return self.render_to_response()
class APIDocumentListView(generics.ListCreateAPIView):
@@ -146,7 +152,7 @@ class APIDocumentListView(generics.ListCreateAPIView):
return super(APIDocumentListView, self).post(*args, **kwargs)
class APIDocumentVersionDownloadView(generics.RetrieveAPIView):
class APIDocumentVersionDownloadView(DownloadMixin, generics.RetrieveAPIView):
"""
Download a document version.
---
@@ -164,17 +170,15 @@ class APIDocumentVersionDownloadView(generics.RetrieveAPIView):
permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all()
def get_file(self):
instance = self.get_object()
return VirtualFile(instance.file, name=unicode(instance))
def get_serializer_class(self):
return None
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
return serve_file(
request,
instance.file,
save_as='"%s"' % instance.document.label,
content_type=instance.mimetype if instance.mimetype else 'application/octet-stream'
)
return self.render_to_response()
class APIDocumentView(generics.RetrieveUpdateDestroyAPIView):

View File

@@ -66,7 +66,8 @@ from .permissions import (
permission_document_trash, permission_document_version_revert,
permission_document_view
)
from .search import document_search, document_page_search
# Just import to initialize the search models
from .search import document_search, document_page_search # NOQA
from .settings import setting_thumbnail_size
from .statistics import (
new_documents_per_month, new_document_pages_per_month,

View File

@@ -85,7 +85,7 @@ link_document_document_type_edit = Link(
)
link_document_download = Link(
permissions=(permission_document_download,), text=_('Download'),
view='documents:document_download', args='resolved_object.id'
view='documents:document_download_form', args='resolved_object.id'
)
link_document_print = Link(
permissions=(permission_document_print,), text=_('Print'),
@@ -118,7 +118,7 @@ link_document_multiple_document_type_edit = Link(
view='documents:document_multiple_document_type_edit'
)
link_document_multiple_download = Link(
text=_('Download'), view='documents:document_multiple_download'
text=_('Download'), view='documents:document_multiple_download_form'
)
link_document_multiple_update_page_count = Link(
text=_('Recalculate page count'),
@@ -129,7 +129,7 @@ link_document_multiple_restore = Link(
)
link_document_version_download = Link(
args='resolved_object.pk', permissions=(permission_document_download,),
text=_('Download version'), view='documents:document_version_download'
text=_('Download version'), view='documents:document_version_download_form'
)
# Views

View File

@@ -46,11 +46,14 @@ from .signals import (
post_document_created, post_document_type_change, post_version_upload
)
# document image cache name hash function
HASH_FUNCTION = lambda x: hashlib.sha256(x).hexdigest()
logger = logging.getLogger(__name__)
# document image cache name hash function
def HASH_FUNCTION(data):
return hashlib.sha256(data).hexdigest()
def UUID_FUNCTION(*args, **kwargs):
return unicode(uuid.uuid4())

View File

@@ -7,11 +7,11 @@ import time
from json import loads
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import override_settings
from django.utils.six import BytesIO
from django_downloadview import assert_download_response
from rest_framework import status
from rest_framework.test import APITestCase
@@ -21,7 +21,7 @@ from user_management.tests.literals import (
from .literals import (
TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
TEST_SMALL_DOCUMENT_CHECKSUM, TEST_SMALL_DOCUMENT_PATH,
TEST_SMALL_DOCUMENT_FILENAME, TEST_SMALL_DOCUMENT_PATH,
)
from ..models import Document, DocumentType, HASH_FUNCTION
@@ -263,14 +263,13 @@ class DocumentAPITestCase(APITestCase):
'rest_api:document-download', args=(document.pk,)
)
)
buf = BytesIO()
buf.write(response.content)
self.assertEqual(
HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM
)
del(buf)
with document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type='{}; charset=utf-8'.format(document.file_mimetype)
)
def test_document_version_download(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
@@ -278,20 +277,22 @@ class DocumentAPITestCase(APITestCase):
file_object=file_object,
)
latest_version = document.latest_version
response = self.client.get(
reverse(
'rest_api:documentversion-download',
args=(document.latest_version.pk,)
args=(latest_version.pk,)
)
)
buf = BytesIO()
buf.write(response.content)
self.assertEqual(
HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM
)
del(buf)
with latest_version.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename='{} - {}'.format(
TEST_SMALL_DOCUMENT_FILENAME,
latest_version.timestamp
), mime_type='application/octet-stream; charset=utf-8'
)
# TODO: def test_document_set_document_type(self):
# pass

View File

@@ -3,8 +3,8 @@
from __future__ import unicode_literals
from actstream.models import Action
from django_downloadview import assert_download_response
from common.tests import skip_file_descriptor_check
from user_management.tests.literals import (
TEST_USER_PASSWORD, TEST_USER_USERNAME
)
@@ -14,7 +14,6 @@ from ..permissions import (
permission_document_download, permission_document_view
)
from .test_views import GenericDocumentViewTestCase
@@ -32,17 +31,14 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
Action.objects.all().delete()
response = self.post(
response = self.get(
'documents:document_download', args=(self.document.pk,)
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 403)
self.assertEqual(list(Action.objects.any(obj=self.document)), [])
@skip_file_descriptor_check
def test_document_download_event_with_permissions(self):
# TODO: Skip this test's file descriptor check until it gets migrate
# SingleObjectDownloadView CBV
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
@@ -53,12 +49,19 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
permission_document_download.stored_permission
)
self.expected_content_type = 'image/png'
self.expected_content_type = 'image/png; charset=utf-8'
self.post(
response = self.get(
'documents:document_download', args=(self.document.pk,),
)
# Download the file to close the file descriptor
with self.document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
mime_type=self.document.file_mimetype
)
event = Action.objects.any(obj=self.document).first()
self.assertEqual(event.verb, event_document_download.name)

View File

@@ -94,7 +94,7 @@ class DocumentsLinksTestCase(GenericDocumentViewTestCase):
self.assertEqual(
resolved_link.url,
reverse(
'documents:document_version_download',
'documents:document_version_download_form',
args=(self.document.latest_version.pk,)
)
)

View File

@@ -4,7 +4,7 @@ from datetime import timedelta
import time
from common.tests import BaseTestCase
from django.test import TestCase, override_settings
from django.test import override_settings
from ..exceptions import NewDocumentVersionNotAllowed
from ..literals import STUB_EXPIRATION_INTERVAL

View File

@@ -4,9 +4,9 @@ from __future__ import unicode_literals
from django.contrib.contenttypes.models import ContentType
from django.test import override_settings
from django.utils.six import BytesIO
from common.tests import skip_file_descriptor_check
from django_downloadview import assert_download_response
from common.tests.test_views import GenericViewTestCase
from converter.models import Transformation
from converter.permissions import permission_transformation_delete
@@ -15,9 +15,7 @@ from user_management.tests.literals import (
)
from ..literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
from ..models import (
DeletedDocument, Document, DocumentType, HASH_FUNCTION
)
from ..models import DeletedDocument, Document, DocumentType
from ..permissions import (
permission_document_create, permission_document_delete,
permission_document_download, permission_document_properties_edit,
@@ -30,7 +28,7 @@ from ..permissions import (
from .literals import (
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_QUICK_LABEL,
TEST_SMALL_DOCUMENT_CHECKSUM, TEST_SMALL_DOCUMENT_PATH
TEST_SMALL_DOCUMENT_FILENAME, TEST_SMALL_DOCUMENT_PATH
)
@@ -226,113 +224,113 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
Document.objects.first().document_type, document_type
)
@skip_file_descriptor_check
def test_document_download_user_view(self):
# TODO: Skip this test's file descriptor check until it gets migrate
# SingleObjectDownloadView CBV
def test_document_download_view_no_permission(self):
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
self.assertEqual(Document.objects.count(), 1)
response = self.post(
response = self.get(
'documents:document_download', args=(self.document.pk,)
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 403)
def test_document_download_view_with_permission(self):
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
self.role.permissions.add(
permission_document_download.stored_permission
)
# Set the expected_content_type for common.tests.mixins.ContentTypeCheckMixin
self.expected_content_type = self.document.file_mimetype
# Set the expected_content_type for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_type = '{}; charset=utf-8'.format(
self.document.file_mimetype
)
response = self.post(
response = self.get(
'documents:document_download', args=(self.document.pk,)
)
self.assertEqual(response.status_code, 200)
buf = BytesIO()
buf.write(response.content)
self.assertEqual(
HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM
)
del(buf)
@skip_file_descriptor_check
def test_document_multiple_download_user_view(self):
# TODO: Skip this test's file descriptor check until it gets migrate
# SingleObjectDownloadView CBV
with self.document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type=self.document.file_mimetype
)
def test_document_multiple_download_view_no_permission(self):
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
self.assertEqual(Document.objects.count(), 1)
response = self.post(
response = self.get(
'documents:document_multiple_download',
data={'id_list': self.document.pk}
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 403)
def test_document_multiple_download_view_with_permission(self):
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
self.role.permissions.add(
permission_document_download.stored_permission
)
# Set the expected_content_type for common.tests.mixins.ContentTypeCheckMixin
self.expected_content_type = self.document.file_mimetype
# Set the expected_content_type for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_type = '{}; charset=utf-8'.format(
self.document.file_mimetype
)
response = self.post(
response = self.get(
'documents:document_multiple_download',
data={'id_list': self.document.pk}
)
self.assertEqual(response.status_code, 200)
buf = BytesIO()
buf.write(response.content)
self.assertEqual(
HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM
)
del(buf)
@skip_file_descriptor_check
def test_document_version_download_user_view(self):
# TODO: Skip this test's file descriptor check until it gets migrate
# SingleObjectDownloadView CBV
with self.document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename=TEST_SMALL_DOCUMENT_FILENAME,
mime_type=self.document.file_mimetype
)
def test_document_version_download_view_no_permission(self):
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
self.assertEqual(Document.objects.count(), 1)
response = self.post(
response = self.get(
'documents:document_version_download', args=(
self.document.latest_version.pk,
)
)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.status_code, 403)
def test_document_version_download_view_with_permission(self):
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
)
self.role.permissions.add(
permission_document_download.stored_permission
)
# Set the expected_content_type for common.tests.mixins.ContentTypeCheckMixin
self.expected_content_type = self.document.file_mimetype
# Set the expected_content_type for
# common.tests.mixins.ContentTypeCheckMixin
self.expected_content_type = 'application/octet-stream; charset=utf-8'
response = self.post(
response = self.get(
'documents:document_version_download', args=(
self.document.latest_version.pk,
)
@@ -340,14 +338,14 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 200)
buf = BytesIO()
buf.write(response.content)
self.assertEqual(
HASH_FUNCTION(buf.getvalue()), TEST_SMALL_DOCUMENT_CHECKSUM
)
del(buf)
with self.document.open() as file_object:
assert_download_response(
self, response, content=file_object.read(),
basename='{} - {}'.format(
TEST_SMALL_DOCUMENT_FILENAME,
self.document.latest_version.timestamp
), mime_type='application/octet-stream; charset=utf-8'
)
def test_document_update_page_count_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
@@ -380,7 +378,6 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertContains(response, text='queued', status_code=200)
self.assertEqual(self.document.pages.count(), page_count)
def test_document_multiple_update_page_count_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)

View File

@@ -15,7 +15,8 @@ from .api_views import (
from .settings import setting_print_size, setting_display_size
from .views import (
ClearImageCacheView, DeletedDocumentDeleteView,
DeletedDocumentDeleteManyView, DeletedDocumentListView, DocumentEditView,
DeletedDocumentDeleteManyView, DeletedDocumentListView,
DocumentDownloadFormView, DocumentDownloadView, DocumentEditView,
DocumentListView, DocumentPageView, DocumentPageListView,
DocumentPageViewResetView, DocumentPreviewView, DocumentRestoreView,
DocumentRestoreManyView, DocumentTrashView, DocumentTrashManyView,
@@ -23,6 +24,7 @@ from .views import (
DocumentTypeDocumentListView, DocumentTypeFilenameCreateView,
DocumentTypeFilenameDeleteView, DocumentTypeFilenameEditView,
DocumentTypeFilenameListView, DocumentTypeListView, DocumentTypeEditView,
DocumentVersionDownloadFormView, DocumentVersionDownloadView,
DocumentVersionListView, DocumentVersionRevertView, DocumentView,
EmptyTrashCanView, RecentDocumentListView
)
@@ -107,13 +109,20 @@ urlpatterns = patterns(
'size': setting_print_size.value
}, 'document_display_print'
),
url(
r'^(?P<document_id>\d+)/download/$', 'document_download',
r'^(?P<pk>\d+)/download/form/$',
DocumentDownloadFormView.as_view(), name='document_download_form'
),
url(
r'^(?P<pk>\d+)/download/$', DocumentDownloadView.as_view(),
name='document_download'
),
url(
r'^multiple/download/$', 'document_multiple_download',
r'^multiple/download/form/$', DocumentDownloadFormView.as_view(),
name='document_multiple_download_form'
),
url(
r'^multiple/download/$', DocumentDownloadView.as_view(),
name='document_multiple_download'
),
url(
@@ -127,8 +136,13 @@ urlpatterns = patterns(
name='document_version_list'
),
url(
r'^document/version/(?P<document_version_pk>\d+)/download/$',
'document_download', name='document_version_download'
r'^document/version/(?P<pk>\d+)/download/form/$',
DocumentVersionDownloadFormView.as_view(),
name='document_version_download_form'
),
url(
r'^document/version/(?P<pk>\d+)/download/$',
DocumentVersionDownloadView.as_view(), name='document_version_download'
),
url(
r'^document/version/(?P<pk>\d+)/revert/$',

View File

@@ -18,8 +18,9 @@ from django.views.generic import RedirectView
from acls.models import AccessControlList
from common.compressed_files import CompressedFile
from common.generics import (
ConfirmView, SimpleView, SingleObjectCreateView, SingleObjectDeleteView,
SingleObjectDetailView, SingleObjectEditView, SingleObjectListView
ConfirmView, FormView, SimpleView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectDetailView, SingleObjectDownloadView,
SingleObjectEditView, SingleObjectListView
)
from common.mixins import MultipleInstanceActionMixin
from converter.literals import (
@@ -27,7 +28,6 @@ from converter.literals import (
)
from converter.models import Transformation
from converter.permissions import permission_transformation_delete
from filetransfers.api import serve_file
from permissions import Permission
from .events import event_document_download, event_document_view
@@ -36,7 +36,9 @@ from .forms import (
DocumentPropertiesForm, DocumentTypeSelectForm,
DocumentTypeFilenameForm_create, PrintForm
)
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT, PAGE_RANGE_RANGE
from .literals import (
DOCUMENT_IMAGE_TASK_TIMEOUT, PAGE_RANGE_RANGE, DEFAULT_ZIP_FILENAME
)
from .models import (
DeletedDocument, Document, DocumentType, DocumentPage,
DocumentTypeFilename, DocumentVersion, RecentDocument
@@ -771,154 +773,209 @@ def get_document_image(request, document_id, size=setting_preview_size.value):
return HttpResponse(base64.b64decode(data.partition('base64,')[2]), content_type='image')
def document_download(request, document_id=None, document_id_list=None, document_version_pk=None):
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
class DocumentDownloadFormView(FormView):
form_class = DocumentDownloadForm
model = Document
multiple_download_view = 'documents:document_multiple_download'
single_download_view = 'documents:document_download'
if document_id:
documents = Document.objects.filter(pk=document_id)
elif document_id_list:
documents = Document.objects.filter(pk__in=document_id_list)
elif document_version_pk:
documents = Document.objects.filter(
pk=get_object_or_404(
DocumentVersion, pk=document_version_pk
).document.pk
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
try:
Permission.check_permissions(
request.user, (permission_document_download,)
)
except PermissionDenied:
documents = AccessControlList.objects.filter_by_access(
permission_document_download, request.user, documents
)
if not id_list:
id_list = self.kwargs['pk']
if not documents:
messages.error(
request, _('Must provide at least one document or version.')
)
return HttpResponseRedirect(
request.META.get(
'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
)
)
return self.model.objects.filter(
pk__in=id_list.split(',')
).filter(is_stub=False)
if document_version_pk:
queryset = DocumentVersion.objects.filter(pk=document_version_pk)
else:
queryset = DocumentVersion.objects.filter(
pk__in=[document.latest_version.pk for document in documents]
)
subtemplates_list = []
subtemplates_list.append(
{
'name': 'appearance/generic_list_subtemplate.html',
'context': {
'title': _('Documents to be downloaded'),
'object_list': queryset,
'hide_link': True,
'hide_object': True,
'hide_links': True,
'scrollable_content': True,
'scrollable_content_height': '200px',
'extra_columns': (
{'name': _('Document'), 'attribute': 'document'},
{'name': _('Date and time'), 'attribute': 'timestamp'},
{'name': _('MIME type'), 'attribute': 'mimetype'},
{'name': _('Encoding'), 'attribute': 'encoding'},
),
def get_extra_context(self):
subtemplates_list = [
{
'name': 'appearance/generic_list_subtemplate.html',
'context': {
'object_list': self.queryset,
'hide_link': True,
'hide_links': True,
'hide_multi_item_actions': True,
}
}
]
context = {
'submit_label': _('Download'),
'subtemplates_list': subtemplates_list,
'title': _('Download documents'),
}
)
if request.method == 'POST':
form = DocumentDownloadForm(request.POST, queryset=queryset)
if form.is_valid():
if form.cleaned_data['compressed'] or queryset.count() > 1:
try:
compressed_file = CompressedFile()
for document_version in queryset:
descriptor = document_version.open()
compressed_file.add_file(
descriptor,
arcname=document_version.document.label
)
descriptor.close()
event_document_download.commit(
actor=request.user,
target=document_version.document
)
if self.queryset.count() == 1:
context['object'] = self.queryset.first()
compressed_file.close()
return context
return serve_file(
request,
compressed_file.as_file(
form.cleaned_data['zip_filename']
),
save_as='"%s"' % form.cleaned_data['zip_filename'],
content_type='application/zip'
)
except Exception as exception:
if settings.DEBUG:
raise
else:
messages.error(request, exception)
return HttpResponseRedirect(
request.META['HTTP_REFERER']
)
def get_form_kwargs(self):
kwargs = super(DocumentDownloadFormView, self).get_form_kwargs()
self.queryset = self.get_queryset()
kwargs.update({'queryset': self.queryset})
return kwargs
def form_valid(self, form):
querystring = urlencode(
{
'compressed': form.cleaned_data['compressed'],
'zip_filename': form.cleaned_data['zip_filename'],
'id_list': ','.join(
map(str, self.queryset.values_list('pk', flat=True))
)
}, doseq=True
)
if self.queryset.count() > 1:
url = reverse(self.multiple_download_view)
else:
url = reverse(
self.single_download_view, args=(self.queryset.first().pk,)
)
return HttpResponseRedirect('{}?{}'.format(url, querystring))
def get_post_action_redirect(self):
return self.post_action_redirect
def get_queryset(self):
queryset = self.get_document_queryset()
try:
Permission.check_permissions(
self.request.user, (permission_document_download,)
)
except PermissionDenied:
return AccessControlList.objects.filter_by_access(
permission_document_download, self.request.user, queryset
)
else:
return queryset
class DocumentDownloadView(SingleObjectDownloadView):
model = Document
# Set to None to disable the .get_object call
object_permission = None
@staticmethod
def commit_event(item, request):
if isinstance(item, Document):
event_document_download.commit(
actor=request.user,
target=item
)
else:
# TODO: Improve by adding a document version download event
event_document_download.commit(
actor=request.user,
target=item.document
)
@staticmethod
def get_item_file(item):
if isinstance(item, Document):
return item.open()
else:
return item.file
@staticmethod
def get_item_label(item):
if isinstance(item, Document):
return item.label
else:
return unicode(item)
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
if not id_list:
id_list = self.kwargs['pk']
queryset = self.model.objects.filter(pk__in=id_list.split(','))
try:
Permission.check_permissions(
self.request.user, (permission_document_download,)
)
except PermissionDenied:
return AccessControlList.objects.filter_by_access(
permission_document_download, self.request.user, queryset
)
else:
return queryset
def get_file(self):
queryset = self.get_document_queryset()
zip_filename = self.request.GET.get(
'zip_filename', DEFAULT_ZIP_FILENAME
)
if self.request.GET.get('compressed') == 'True' or queryset.count() > 1:
compressed_file = CompressedFile()
for item in queryset:
descriptor = item.open()
compressed_file.add_file(
descriptor,
arcname=DocumentDownloadView.get_item_label(item=item)
)
descriptor.close()
DocumentDownloadView.commit_event(
item=item, request=self.request
)
compressed_file.close()
return DocumentDownloadView.VirtualFile(
compressed_file.as_file(zip_filename),
name=zip_filename
)
else:
item = queryset.first()
if item:
DocumentDownloadView.commit_event(
item=item, request=self.request
)
else:
try:
# Test permissions and trigger exception
fd = queryset.first().open()
fd.close()
event_document_download.commit(
actor=request.user, target=queryset.first().document
)
return serve_file(
request,
queryset.first().file,
save_as='"%s"' % queryset.first().document.label,
content_type=queryset.first().mimetype if queryset.first().mimetype else 'application/octet-stream'
)
except Exception as exception:
if settings.DEBUG:
raise
else:
messages.error(request, exception)
return HttpResponseRedirect(
request.META['HTTP_REFERER']
)
raise PermissionDenied
else:
form = DocumentDownloadForm(queryset=queryset)
context = {
'form': form,
'previous': previous,
'submit_label': _('Download'),
'subtemplates_list': subtemplates_list,
'title': _('Download documents'),
}
if queryset.count() == 1:
context['object'] = queryset.first().document
return render_to_response(
'appearance/generic_form.html',
context,
context_instance=RequestContext(request)
)
return DocumentDownloadView.VirtualFile(
DocumentDownloadView.get_item_file(item=item),
name=DocumentDownloadView.get_item_label(
item=item
)
)
def document_multiple_download(request):
return document_download(
request, document_id_list=request.GET.get(
'id_list', request.POST.get('id_list', '')
).split(',')
)
class DocumentVersionDownloadFormView(DocumentDownloadFormView):
model = DocumentVersion
multiple_download_view = None
single_download_view = 'documents:document_version_download'
def get_document_queryset(self):
id_list = self.request.GET.get(
'id_list', self.request.POST.get('id_list', '')
)
if not id_list:
id_list = self.kwargs['pk']
return self.model.objects.filter(
pk__in=id_list.split(',')
)
class DocumentVersionDownloadView(DocumentDownloadView):
model = DocumentVersion
object_permission = permission_document_download
def document_update_page_count(request, document_id=None, document_id_list=None):

View File

@@ -11,7 +11,7 @@ from .permissions import permission_events_view
def get_kwargs_factory(variable_name):
def get_kwargs(context):
ContentType = apps.get_model(
app_label='django', model_name='ContentType'
app_label='contenttypes', model_name='ContentType'
)
content_type = ContentType.objects.get_for_model(

View File

@@ -2,7 +2,6 @@ from __future__ import absolute_import, unicode_literals
from django.contrib.contenttypes.models import ContentType
from acls.models import AccessControlList
from documents.tests.test_views import GenericDocumentViewTestCase
from user_management.tests import (
TEST_USER_USERNAME, TEST_USER_PASSWORD

View File

@@ -13,12 +13,37 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='MessageOfTheDay',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('label', models.CharField(max_length=32, verbose_name='Label')),
('message', models.TextField(verbose_name='Message', blank=True)),
('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
('start_datetime', models.DateTimeField(verbose_name='Start date time', blank=True)),
('end_datetime', models.DateTimeField(verbose_name='End date time', blank=True)),
(
'id', models.AutoField(
verbose_name='ID', serialize=False, auto_created=True,
primary_key=True
)
),
(
'label', models.CharField(
max_length=32, verbose_name='Label'
)
),
(
'message', models.TextField(
verbose_name='Message', blank=True
)
),
(
'enabled', models.BooleanField(
default=True, verbose_name='Enabled'
)
),
(
'start_datetime', models.DateTimeField(
verbose_name='Start date time', blank=True
)
),
(
'end_datetime', models.DateTimeField(
verbose_name='End date time', blank=True
)
),
],
options={
'verbose_name': 'Message of the day',

View File

@@ -14,11 +14,15 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='messageoftheday',
name='end_datetime',
field=models.DateTimeField(null=True, verbose_name='End date time', blank=True),
field=models.DateTimeField(
null=True, verbose_name='End date time', blank=True
),
),
migrations.AlterField(
model_name='messageoftheday',
name='start_datetime',
field=models.DateTimeField(null=True, verbose_name='Start date time', blank=True),
field=models.DateTimeField(
null=True, verbose_name='Start date time', blank=True
),
),
]

View File

@@ -14,21 +14,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='message',
name='end_datetime',
field=models.DateTimeField(help_text='Date and time until when this message is to be displayed.', null=True, verbose_name='End date time', blank=True),
field=models.DateTimeField(
help_text='Date and time until when this message is to be '
'displayed.', null=True, verbose_name='End date time',
blank=True
),
),
migrations.AlterField(
model_name='message',
name='label',
field=models.CharField(help_text='Short description of this message.', max_length=32, verbose_name='Label'),
field=models.CharField(
help_text='Short description of this message.', max_length=32,
verbose_name='Label'
),
),
migrations.AlterField(
model_name='message',
name='message',
field=models.TextField(help_text='The actual message to be displayed.', verbose_name='Message'),
field=models.TextField(
help_text='The actual message to be displayed.',
verbose_name='Message'
),
),
migrations.AlterField(
model_name='message',
name='start_datetime',
field=models.DateTimeField(help_text='Date and time after which this message will be displayed.', null=True, verbose_name='Start date time', blank=True),
field=models.DateTimeField(
help_text='Date and time after which this message will be '
'displayed.', null=True, verbose_name='Start date time',
blank=True
),
),
]

View File

@@ -10,7 +10,9 @@ urlpatterns = patterns(
'',
url(r'^list/$', MessageListView.as_view(), name='message_list'),
url(r'^create/$', MessageCreateView.as_view(), name='message_create'),
url(r'^(?P<pk>\d+)/edit/$', MessageEditView.as_view(), name='message_edit'),
url(
r'^(?P<pk>\d+)/edit/$', MessageEditView.as_view(), name='message_edit'
),
url(
r'^(?P<pk>\d+)/delete/$', MessageDeleteView.as_view(),
name='message_delete'

View File

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
import logging
from kombu import Exchange, Queue
import sh
from django.apps import apps
from django.db.models.signals import post_save
@@ -29,9 +28,6 @@ from .links import (
link_document_type_submit, link_entry_list
)
from .permissions import permission_ocr_document, permission_ocr_content_view
from .settings import (
setting_pdftotext_path, setting_tesseract_path
)
logger = logging.getLogger(__name__)

View File

@@ -11,7 +11,6 @@ import subprocess
from django.utils.translation import ugettext_lazy as _
from common.settings import setting_temporary_directory
from common.utils import copyfile, fs_cleanup, mkstemp
from .exceptions import ParserError, NoMIMETypeMatch

View File

@@ -1,11 +1,8 @@
from __future__ import unicode_literals
import psutil
from django.core.files.base import File
from django.test import override_settings
from common.settings import setting_temporary_directory
from common.tests import BaseTestCase
from documents.models import DocumentType
from documents.tests import (

View File

@@ -35,7 +35,6 @@ class StatisticsViewTestCase(GenericViewTestCase):
self.assertEqual(response.status_code, 200)
def test_statistic_namespace_list_view_no_permissions(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)

View File

@@ -4,7 +4,7 @@ import os
import zipfile
try:
import zlib
import zlib # NOQA
COMPRESSION = zipfile.ZIP_DEFLATED
except:
COMPRESSION = zipfile.ZIP_STORED

View File

@@ -55,7 +55,6 @@ INSTALLED_APPS = (
'compressor',
'corsheaders',
'djcelery',
'filetransfers',
'formtools',
'mptt',
'pure_pagination',

View File

@@ -11,7 +11,6 @@ django-colorful==1.2
django-compressor==2.1
django-cors-headers==1.2.2
django-downloadview==1.9
django-filetransfers==0.1.0
django-formtools==1.0
django-pure-pagination==0.3.0
django-model-utils==2.6