Refactor document page image generation and transformation classes
to cache all transformed versions of a document page.
This commit is contained in:
@@ -16,6 +16,7 @@ Other changes
|
|||||||
- Remove dependency on the django-filetransfer library
|
- Remove dependency on the django-filetransfer library
|
||||||
- Fix height calculation in resize transformation
|
- Fix height calculation in resize transformation
|
||||||
- Improve upgrade instructions
|
- Improve upgrade instructions
|
||||||
|
- New image caching pipeline
|
||||||
|
|
||||||
Removals
|
Removals
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -82,8 +82,12 @@ body {
|
|||||||
overflow-x: scroll; height: 500px;
|
overflow-x: scroll; height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#carousel-container img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.carousel-item {
|
.carousel-item {
|
||||||
margin: 5px 10px 10px 10px
|
margin: 5px 10px 10px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-item-page-number {
|
.carousel-item-page-number {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ function set_image_noninteractive(image) {
|
|||||||
container.html(html);
|
container.html(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_document_image(image) {
|
function loadDocumentImage(image) {
|
||||||
$.get( image.attr('data-src'), function(result) {
|
$.get(image.attr('data-src'), function(result) {
|
||||||
image.attr('src', result.data);
|
image.attr('src', result.data);
|
||||||
image.addClass(image.attr('data-post-load-class'));
|
image.addClass(image.attr('data-post-load-class'));
|
||||||
})
|
})
|
||||||
@@ -76,20 +76,11 @@ jQuery(document).ready(function() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
})
|
})
|
||||||
|
|
||||||
$('img.lazy-load').lazyload({
|
$('img.lazy-load').lazyload();
|
||||||
appear: function(elements_left, settings) {
|
|
||||||
load_document_image($(this));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$('img.lazy-load-carousel').lazyload({
|
$('img.lazy-load-carousel').lazyload({
|
||||||
threshold : 400,
|
threshold : 400,
|
||||||
container: $("#carousel-container"),
|
container: $("#carousel-container"),
|
||||||
appear: function(elements_left, settings) {
|
|
||||||
var $this = $(this);
|
|
||||||
$this.removeClass('lazy-load-carousel');
|
|
||||||
load_document_image($this);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('th input:checkbox').click(function(e) {
|
$('th input:checkbox').click(function(e) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .classes import ( # NOQA
|
from .classes import (
|
||||||
TransformationResize, TransformationRotate, TransformationZoom # NOQA
|
BaseTransformation, TransformationResize, TransformationRotate,
|
||||||
)
|
TransformationZoom
|
||||||
|
) # NOQA
|
||||||
from .runtime import converter_class # NOQA
|
from .runtime import converter_class # NOQA
|
||||||
|
|
||||||
default_app_config = 'converter.apps.ConverterApp'
|
default_app_config = 'converter.apps.ConverterApp'
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from .links import (
|
|||||||
|
|
||||||
class ConverterApp(MayanAppConfig):
|
class ConverterApp(MayanAppConfig):
|
||||||
name = 'converter'
|
name = 'converter'
|
||||||
|
test = True
|
||||||
verbose_name = _('Converter')
|
verbose_name = _('Converter')
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
|
from operator import xor
|
||||||
import os
|
import os
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -221,6 +222,26 @@ class BaseTransformation(object):
|
|||||||
|
|
||||||
_registry = {}
|
_registry = {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_hash(decoded_value):
|
||||||
|
return hex(abs(decoded_value))[2:]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decode_hash(encoded_value):
|
||||||
|
return int(encoded_value, 16)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def combine(transformations):
|
||||||
|
result = None
|
||||||
|
|
||||||
|
for transformation in transformations:
|
||||||
|
if not result:
|
||||||
|
result = BaseTransformation.decode_hash(transformation.cache_hash())
|
||||||
|
else:
|
||||||
|
result ^= BaseTransformation.decode_hash(transformation.cache_hash())
|
||||||
|
|
||||||
|
return BaseTransformation.encode_hash(result)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register(cls, transformation):
|
def register(cls, transformation):
|
||||||
cls._registry[transformation.name] = transformation
|
cls._registry[transformation.name] = transformation
|
||||||
@@ -240,8 +261,17 @@ class BaseTransformation(object):
|
|||||||
return string_concat(cls.label, ': ', ', '.join(cls.arguments))
|
return string_concat(cls.label, ': ', ', '.join(cls.arguments))
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
self.kwargs = {}
|
||||||
for argument_name in self.arguments:
|
for argument_name in self.arguments:
|
||||||
setattr(self, argument_name, kwargs.get(argument_name))
|
setattr(self, argument_name, kwargs.get(argument_name))
|
||||||
|
self.kwargs[argument_name] = kwargs.get(argument_name)
|
||||||
|
|
||||||
|
def cache_hash(self):
|
||||||
|
result = unicode.__hash__(self.name)
|
||||||
|
for key, value in self.kwargs.items():
|
||||||
|
result ^= unicode.__hash__(key) ^ str.__hash__(str(value))
|
||||||
|
|
||||||
|
return BaseTransformation.encode_hash(result)
|
||||||
|
|
||||||
def execute_on(self, image):
|
def execute_on(self, image):
|
||||||
self.image = image
|
self.image = image
|
||||||
|
|||||||
0
mayan/apps/converter/tests/__init__.py
Normal file
0
mayan/apps/converter/tests/__init__.py
Normal file
92
mayan/apps/converter/tests/test_classes.py
Normal file
92
mayan/apps/converter/tests/test_classes.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from ..classes import (
|
||||||
|
BaseTransformation, TransformationResize, TransformationRotate,
|
||||||
|
TransformationZoom
|
||||||
|
)
|
||||||
|
|
||||||
|
TRANSFORMATION_RESIZE_WIDTH = 123
|
||||||
|
TRANSFORMATION_RESIZE_HEIGHT = 528
|
||||||
|
TRANSFORMATION_RESIZE_CACHE_HASH = '2cbabd3aaafdaf8f'
|
||||||
|
TRANSFORMATION_RESIZE_WIDTH_2 = 124
|
||||||
|
TRANSFORMATION_RESIZE_HEIGHT_2 = 529
|
||||||
|
TRANSFORMATION_RESIZE_CACHE_HASH_2 = '2cbabd3aaafdaf89'
|
||||||
|
TRANSFORMATION_ROTATE_DEGRESS = 34
|
||||||
|
TRANSFORMATION_ROTATE_CACHE_HASH = '2f9d036e13aacb48'
|
||||||
|
TRANSFORMATION_COMBINED_CACHE_HASH = '44a3b262e18b5d5d'
|
||||||
|
TRANSFORMATION_ZOOM_PERCENT = 49
|
||||||
|
TRANSFORMATION_ZOOM_CACHE_HASH = '47840c3658dc399a'
|
||||||
|
|
||||||
|
|
||||||
|
class TransformationTestCase(TestCase):
|
||||||
|
def test_resize_cache_hashing(self):
|
||||||
|
# Test if the hash is being generated correctly
|
||||||
|
transformation = TransformationResize(
|
||||||
|
width=TRANSFORMATION_RESIZE_WIDTH,
|
||||||
|
height=TRANSFORMATION_RESIZE_HEIGHT
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
transformation.cache_hash(), TRANSFORMATION_RESIZE_CACHE_HASH
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test if the hash is being alternated correctly
|
||||||
|
transformation = TransformationResize(
|
||||||
|
width=TRANSFORMATION_RESIZE_WIDTH_2,
|
||||||
|
height=TRANSFORMATION_RESIZE_HEIGHT_2
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
transformation.cache_hash(), TRANSFORMATION_RESIZE_CACHE_HASH_2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rotate_cache_hashing(self):
|
||||||
|
# Test if the hash is being generated correctly
|
||||||
|
transformation = TransformationRotate(
|
||||||
|
degrees=TRANSFORMATION_ROTATE_DEGRESS
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
transformation.cache_hash(), TRANSFORMATION_ROTATE_CACHE_HASH
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rotate_zoom_hashing(self):
|
||||||
|
# Test if the hash is being generated correctly
|
||||||
|
transformation = TransformationZoom(
|
||||||
|
percent=TRANSFORMATION_ZOOM_PERCENT
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
transformation.cache_hash(), TRANSFORMATION_ZOOM_CACHE_HASH
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cache_hash_combining(self):
|
||||||
|
# Test magic method and hash combining
|
||||||
|
|
||||||
|
transformation_resize = TransformationResize(
|
||||||
|
width=TRANSFORMATION_RESIZE_WIDTH,
|
||||||
|
height=TRANSFORMATION_RESIZE_HEIGHT
|
||||||
|
)
|
||||||
|
|
||||||
|
transformation_rotate = TransformationRotate(
|
||||||
|
degrees=TRANSFORMATION_ROTATE_DEGRESS
|
||||||
|
)
|
||||||
|
|
||||||
|
transformation_zoom = TransformationZoom(
|
||||||
|
percent=TRANSFORMATION_ZOOM_PERCENT
|
||||||
|
)
|
||||||
|
|
||||||
|
#self.assertEqual(
|
||||||
|
# #transformation_rotate ^ transformation_resize ^ transformation_zoom,
|
||||||
|
# transformation_rotate ^ transformation_resize ^ transformation_zoom,
|
||||||
|
# #transformation_resize ^ transformation_zoom,
|
||||||
|
# TRANSFORMATION_COMBINED_CACHE_HASH
|
||||||
|
#)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
BaseTransformation.combine(
|
||||||
|
(transformation_rotate, transformation_resize, transformation_zoom)
|
||||||
|
), TRANSFORMATION_COMBINED_CACHE_HASH
|
||||||
|
)
|
||||||
@@ -3,8 +3,10 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
|
from django_downloadview import DownloadMixin, VirtualFile
|
||||||
from rest_framework import generics, status
|
from rest_framework import generics, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ from permissions import Permission
|
|||||||
from rest_api.filters import MayanObjectPermissionsFilter
|
from rest_api.filters import MayanObjectPermissionsFilter
|
||||||
from rest_api.permissions import MayanPermission
|
from rest_api.permissions import MayanPermission
|
||||||
|
|
||||||
|
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
|
||||||
from .models import (
|
from .models import (
|
||||||
Document, DocumentPage, DocumentType, DocumentVersion, RecentDocument
|
Document, DocumentPage, DocumentType, DocumentVersion, RecentDocument
|
||||||
)
|
)
|
||||||
@@ -25,13 +28,14 @@ from .permissions import (
|
|||||||
permission_document_type_create, permission_document_type_delete,
|
permission_document_type_create, permission_document_type_delete,
|
||||||
permission_document_type_edit, permission_document_type_view
|
permission_document_type_edit, permission_document_type_view
|
||||||
)
|
)
|
||||||
|
from .runtime import cache_storage_backend
|
||||||
from .serializers import (
|
from .serializers import (
|
||||||
DeletedDocumentSerializer, DocumentPageImageSerializer,
|
DeletedDocumentSerializer, DocumentPageSerializer, DocumentSerializer,
|
||||||
DocumentPageSerializer, DocumentSerializer,
|
|
||||||
DocumentTypeSerializer, DocumentVersionSerializer,
|
DocumentTypeSerializer, DocumentVersionSerializer,
|
||||||
DocumentVersionRevertSerializer, NewDocumentSerializer,
|
DocumentVersionRevertSerializer, NewDocumentSerializer,
|
||||||
NewDocumentVersionSerializer, RecentDocumentSerializer
|
NewDocumentVersionSerializer, RecentDocumentSerializer
|
||||||
)
|
)
|
||||||
|
from .tasks import task_generate_document_page_image
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -86,15 +90,6 @@ class APIDeletedDocumentRestoreView(generics.GenericAPIView):
|
|||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
##############
|
|
||||||
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):
|
class APIDocumentDownloadView(DownloadMixin, generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
Download the latest version of a document.
|
Download the latest version of a document.
|
||||||
@@ -228,8 +223,18 @@ class APIDocumentView(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
class APIDocumentPageImageView(generics.RetrieveAPIView):
|
class APIDocumentPageImageView(generics.RetrieveAPIView):
|
||||||
"""
|
"""
|
||||||
Returns an image representation of the selected document.
|
Returns an image representation of the selected document.
|
||||||
size -- 'x' seprated width and height of the desired image representation.
|
---
|
||||||
zoom -- Zoom level of the image to be generated, numeric value only.
|
GET:
|
||||||
|
omit_serializer: true
|
||||||
|
parameters:
|
||||||
|
- name: size
|
||||||
|
description: 'x' seprated width and height of the desired image representation.
|
||||||
|
paramType: query
|
||||||
|
type: number
|
||||||
|
- name: zoom
|
||||||
|
description: Zoom level of the image to be generated, numeric value only.
|
||||||
|
paramType: query
|
||||||
|
type: number
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mayan_object_permissions = {
|
mayan_object_permissions = {
|
||||||
@@ -238,7 +243,25 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
|
|||||||
mayan_permission_attribute_check = 'document'
|
mayan_permission_attribute_check = 'document'
|
||||||
permission_classes = (MayanPermission,)
|
permission_classes = (MayanPermission,)
|
||||||
queryset = DocumentPage.objects.all()
|
queryset = DocumentPage.objects.all()
|
||||||
serializer_class = DocumentPageImageSerializer
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def retrieve(self, request, *args, **kwargs):
|
||||||
|
size = request.GET.get('size')
|
||||||
|
zoom = request.GET.get('zoom')
|
||||||
|
rotation = request.GET.get('rotation')
|
||||||
|
|
||||||
|
task = task_generate_document_page_image.apply_async(
|
||||||
|
kwargs=dict(
|
||||||
|
document_page_id=self.kwargs['pk'], size=size, zoom=zoom,
|
||||||
|
rotation=rotation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cache_filename = task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT)
|
||||||
|
with cache_storage_backend.open(cache_filename) as file_object:
|
||||||
|
return HttpResponse(file_object.read(), content_type='image')
|
||||||
|
|
||||||
|
|
||||||
class APIDocumentPageView(generics.RetrieveUpdateAPIView):
|
class APIDocumentPageView(generics.RetrieveUpdateAPIView):
|
||||||
|
|||||||
@@ -68,13 +68,13 @@ from .permissions import (
|
|||||||
)
|
)
|
||||||
# Just import to initialize the search models
|
# Just import to initialize the search models
|
||||||
from .search import document_search, document_page_search # NOQA
|
from .search import document_search, document_page_search # NOQA
|
||||||
from .settings import setting_thumbnail_size
|
from .settings import setting_display_size, setting_thumbnail_size
|
||||||
from .statistics import (
|
from .statistics import (
|
||||||
new_documents_per_month, new_document_pages_per_month,
|
new_documents_per_month, new_document_pages_per_month,
|
||||||
new_document_versions_per_month, total_document_per_month,
|
new_document_versions_per_month, total_document_per_month,
|
||||||
total_document_page_per_month, total_document_version_per_month
|
total_document_page_per_month, total_document_version_per_month
|
||||||
)
|
)
|
||||||
from .widgets import document_html_widget, document_thumbnail
|
from .widgets import document_html_widget
|
||||||
|
|
||||||
|
|
||||||
class DocumentsApp(MayanAppConfig):
|
class DocumentsApp(MayanAppConfig):
|
||||||
@@ -151,8 +151,12 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=Document, label=_('Thumbnail'),
|
source=Document, label=_('Thumbnail'),
|
||||||
func=lambda context: document_thumbnail(
|
func=lambda context: document_html_widget(
|
||||||
context['object'], gallery_name='documents:document_list',
|
document_page=context['object'].latest_version.pages.first(),
|
||||||
|
click_view='rest_api:documentpage-image',
|
||||||
|
click_view_arguments_lazy=lambda: (context['object'].latest_version.pages.first().pk,),
|
||||||
|
click_view_querydict={'size': setting_display_size.value},
|
||||||
|
gallery_name='documents:document_list',
|
||||||
size=setting_thumbnail_size.value,
|
size=setting_thumbnail_size.value,
|
||||||
title=getattr(context['object'], 'label', None),
|
title=getattr(context['object'], 'label', None),
|
||||||
)
|
)
|
||||||
@@ -165,8 +169,8 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
source=DocumentPage, label=_('Thumbnail'),
|
source=DocumentPage, label=_('Thumbnail'),
|
||||||
func=lambda context: document_html_widget(
|
func=lambda context: document_html_widget(
|
||||||
document_page=context['object'],
|
document_page=context['object'],
|
||||||
click_view='documents:document_display',
|
click_view='rest_api:documentpage-image',
|
||||||
click_view_arguments=(context['object'].document.pk,),
|
click_view_arguments=(context['object'].pk,),
|
||||||
gallery_name='documents:document_page_list',
|
gallery_name='documents:document_page_list',
|
||||||
preview_click_view='documents:document_page_view',
|
preview_click_view='documents:document_page_view',
|
||||||
size=setting_thumbnail_size.value,
|
size=setting_thumbnail_size.value,
|
||||||
@@ -178,8 +182,8 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
source=DocumentPageResult, label=_('Thumbnail'),
|
source=DocumentPageResult, label=_('Thumbnail'),
|
||||||
func=lambda context: document_html_widget(
|
func=lambda context: document_html_widget(
|
||||||
document_page=context['object'],
|
document_page=context['object'],
|
||||||
click_view='documents:document_display',
|
click_view='rest_api:documentpage-image',
|
||||||
click_view_arguments=(context['object'].document.pk,),
|
click_view_arguments=(context['object'].pk,),
|
||||||
gallery_name='documents:document_page_list',
|
gallery_name='documents:document_page_list',
|
||||||
preview_click_view='documents:document_page_view',
|
preview_click_view='documents:document_page_view',
|
||||||
size=setting_thumbnail_size.value,
|
size=setting_thumbnail_size.value,
|
||||||
@@ -205,8 +209,11 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
|
|
||||||
SourceColumn(
|
SourceColumn(
|
||||||
source=DeletedDocument, label=_('Thumbnail'),
|
source=DeletedDocument, label=_('Thumbnail'),
|
||||||
func=lambda context: document_thumbnail(
|
func=lambda context: document_html_widget(
|
||||||
context['object'],
|
document_page=context['object'].latest_version.pages.first(),
|
||||||
|
click_view='rest_api:documentpage-image',
|
||||||
|
click_view_arguments_lazy=lambda: (context['object'].latest_version.pages.first().pk,),
|
||||||
|
click_view_querydict={'size': setting_display_size.value},
|
||||||
gallery_name='documents:delete_document_list',
|
gallery_name='documents:delete_document_list',
|
||||||
size=setting_thumbnail_size.value,
|
size=setting_thumbnail_size.value,
|
||||||
title=getattr(context['object'], 'label', None),
|
title=getattr(context['object'], 'label', None),
|
||||||
@@ -285,7 +292,7 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
'documents.tasks.task_clear_image_cache': {
|
'documents.tasks.task_clear_image_cache': {
|
||||||
'queue': 'tools'
|
'queue': 'tools'
|
||||||
},
|
},
|
||||||
'documents.tasks.task_get_document_page_image': {
|
'documents.tasks.task_generate_document_page_image': {
|
||||||
'queue': 'converter'
|
'queue': 'converter'
|
||||||
},
|
},
|
||||||
'documents.tasks.task_update_page_count': {
|
'documents.tasks.task_update_page_count': {
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ class DocumentPageForm(DetailForm):
|
|||||||
model = DocumentPage
|
model = DocumentPage
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
zoom = kwargs.pop('zoom', 100)
|
zoom = kwargs.pop('zoom', None)
|
||||||
rotation = kwargs.pop('rotation', 0)
|
rotation = kwargs.pop('rotation', None)
|
||||||
super(DocumentPageForm, self).__init__(*args, **kwargs)
|
super(DocumentPageForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['page_image'].initial = self.instance
|
self.fields['page_image'].initial = self.instance
|
||||||
self.fields['page_image'].widget.attrs.update({
|
self.fields['page_image'].widget.attrs.update({
|
||||||
|
|||||||
42
mayan/apps/documents/migrations/0035_auto_20161102_0633.py
Normal file
42
mayan/apps/documents/migrations/0035_auto_20161102_0633.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('documents', '0034_auto_20160509_2321'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentPageCachedImage',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('filename', models.CharField(max_length=128, verbose_name='Filename')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Document page cached image',
|
||||||
|
'verbose_name_plural': 'Document page cached images',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentPageResult',
|
||||||
|
fields=[
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('document_version__document', 'page_number'),
|
||||||
|
'verbose_name': 'Document page',
|
||||||
|
'proxy': True,
|
||||||
|
'verbose_name_plural': 'Document pages',
|
||||||
|
},
|
||||||
|
bases=('documents.documentpage',),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='documentpagecachedimage',
|
||||||
|
name='document_page',
|
||||||
|
field=models.ForeignKey(related_name='cached_images', verbose_name='Document page', to='documents.DocumentPage'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -16,8 +16,8 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
|||||||
from acls.models import AccessControlList
|
from acls.models import AccessControlList
|
||||||
from common.literals import TIME_DELTA_UNIT_CHOICES
|
from common.literals import TIME_DELTA_UNIT_CHOICES
|
||||||
from converter import (
|
from converter import (
|
||||||
converter_class, TransformationResize, TransformationRotate,
|
converter_class, BaseTransformation, TransformationResize,
|
||||||
TransformationZoom
|
TransformationRotate, TransformationZoom
|
||||||
)
|
)
|
||||||
from converter.exceptions import InvalidOfficeFormat, PageCountError
|
from converter.exceptions import InvalidOfficeFormat, PageCountError
|
||||||
from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
|
||||||
@@ -683,15 +683,15 @@ class DocumentPage(models.Model):
|
|||||||
def document(self):
|
def document(self):
|
||||||
return self.document_version.document
|
return self.document_version.document
|
||||||
|
|
||||||
def get_image(self, *args, **kwargs):
|
def generate_image(self, *args, **kwargs):
|
||||||
as_base64 = kwargs.pop('as_base64', False)
|
# Convert arguments into transformations
|
||||||
transformations = kwargs.pop('transformations', [])
|
transformations = kwargs.get('transformations', [])
|
||||||
size = kwargs.pop('size', setting_display_size.value)
|
size = kwargs.get('size', setting_display_size.value)
|
||||||
rotation = int(
|
rotation = int(
|
||||||
kwargs.pop('rotation', DEFAULT_ROTATION) or DEFAULT_ROTATION
|
kwargs.get('rotation', DEFAULT_ROTATION) or DEFAULT_ROTATION
|
||||||
)
|
) % 360
|
||||||
zoom_level = int(
|
zoom_level = int(
|
||||||
kwargs.pop('zoom', DEFAULT_ZOOM_LEVEL) or DEFAULT_ZOOM_LEVEL
|
kwargs.get('zoom', DEFAULT_ZOOM_LEVEL) or DEFAULT_ZOOM_LEVEL
|
||||||
)
|
)
|
||||||
|
|
||||||
if zoom_level < setting_zoom_min_level.value:
|
if zoom_level < setting_zoom_min_level.value:
|
||||||
@@ -700,8 +700,54 @@ class DocumentPage(models.Model):
|
|||||||
if zoom_level > setting_zoom_max_level.value:
|
if zoom_level > setting_zoom_max_level.value:
|
||||||
zoom_level = setting_zoom_max_level.value
|
zoom_level = setting_zoom_max_level.value
|
||||||
|
|
||||||
rotation = rotation % 360
|
# Generate transformation hash
|
||||||
|
|
||||||
|
transformation_list = []
|
||||||
|
|
||||||
|
# Stored transformations first
|
||||||
|
for stored_transformation in Transformation.objects.get_for_model(self, as_classes=True):
|
||||||
|
transformation_list.append(stored_transformation)
|
||||||
|
|
||||||
|
# Interactive transformations second
|
||||||
|
for transformation in transformations:
|
||||||
|
transformation_list.append(transformation)
|
||||||
|
|
||||||
|
if rotation:
|
||||||
|
transformation_list.append(
|
||||||
|
TransformationRotate(degrees=rotation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if size:
|
||||||
|
transformation_list.append(
|
||||||
|
TransformationResize(
|
||||||
|
**dict(zip(('width', 'height'), (size.split('x'))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if zoom_level:
|
||||||
|
transformation_list.append(TransformationZoom(percent=zoom_level))
|
||||||
|
|
||||||
|
cache_filename = '{}-{}'.format(
|
||||||
|
self.cache_filename, BaseTransformation.combine(transformation_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check is transformed image is available
|
||||||
|
logger.debug('transformations cache filename: %s', cache_filename)
|
||||||
|
|
||||||
|
if cache_storage_backend.exists(cache_filename):
|
||||||
|
logger.debug(
|
||||||
|
'transformations cache file "%s" found', cache_filename
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
image = self.get_image(transformations=transformation_list)
|
||||||
|
with cache_storage_backend.open(cache_filename, 'wb+') as file_object:
|
||||||
|
file_object.write(image.getvalue())
|
||||||
|
|
||||||
|
self.cached_images.create(filename=cache_filename)
|
||||||
|
|
||||||
|
return cache_filename
|
||||||
|
|
||||||
|
def get_image(self, transformations=None):
|
||||||
cache_filename = self.cache_filename
|
cache_filename = self.cache_filename
|
||||||
logger.debug('Page cache filename: %s', cache_filename)
|
logger.debug('Page cache filename: %s', cache_filename)
|
||||||
|
|
||||||
@@ -734,33 +780,15 @@ class DocumentPage(models.Model):
|
|||||||
cache_storage_backend.delete(cache_filename)
|
cache_storage_backend.delete(cache_filename)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Stored transformations
|
|
||||||
for stored_transformation in Transformation.objects.get_for_model(self, as_classes=True):
|
|
||||||
converter.transform(transformation=stored_transformation)
|
|
||||||
|
|
||||||
# Interactive transformations
|
|
||||||
for transformation in transformations:
|
for transformation in transformations:
|
||||||
converter.transform(transformation=transformation)
|
converter.transform(transformation=transformation)
|
||||||
|
|
||||||
if rotation:
|
return converter.get_page()
|
||||||
converter.transform(transformation=TransformationRotate(
|
|
||||||
degrees=rotation)
|
|
||||||
)
|
|
||||||
|
|
||||||
if size:
|
|
||||||
converter.transform(transformation=TransformationResize(
|
|
||||||
**dict(zip(('width', 'height'), (size.split('x')))))
|
|
||||||
)
|
|
||||||
|
|
||||||
if zoom_level:
|
|
||||||
converter.transform(
|
|
||||||
transformation=TransformationZoom(percent=zoom_level)
|
|
||||||
)
|
|
||||||
|
|
||||||
return converter.get_page(as_base64=as_base64)
|
|
||||||
|
|
||||||
def invalidate_cache(self):
|
def invalidate_cache(self):
|
||||||
cache_storage_backend.delete(self.cache_filename)
|
cache_storage_backend.delete(self.cache_filename)
|
||||||
|
for cached_image in self.cached_images.all():
|
||||||
|
cached_image.delete()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def siblings(self):
|
def siblings(self):
|
||||||
@@ -777,6 +805,22 @@ class DocumentPage(models.Model):
|
|||||||
return '{}-{}'.format(self.document_version.uuid, self.pk)
|
return '{}-{}'.format(self.document_version.uuid, self.pk)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentPageCachedImage(models.Model):
|
||||||
|
document_page = models.ForeignKey(
|
||||||
|
DocumentPage, related_name='cached_images',
|
||||||
|
verbose_name=_('Document page')
|
||||||
|
)
|
||||||
|
filename = models.CharField(max_length=128, verbose_name=_('Filename'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Document page cached image')
|
||||||
|
verbose_name_plural = _('Document page cached images')
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
cache_storage_backend.delete(self.filename)
|
||||||
|
return super(DocumentPageCachedImage, self).delete(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageResult(DocumentPage):
|
class DocumentPageResult(DocumentPage):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('document_version__document', 'page_number')
|
ordering = ('document_version__document', 'page_number')
|
||||||
|
|||||||
@@ -4,30 +4,11 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
from common.models import SharedUploadedFile
|
from common.models import SharedUploadedFile
|
||||||
|
|
||||||
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Document, DocumentVersion, DocumentPage, DocumentType, RecentDocument
|
Document, DocumentVersion, DocumentPage, DocumentType, RecentDocument
|
||||||
)
|
)
|
||||||
from .settings import setting_language
|
from .settings import setting_language
|
||||||
from .tasks import task_get_document_page_image, task_upload_new_version
|
from .tasks import task_upload_new_version
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageImageSerializer(serializers.Serializer):
|
|
||||||
data = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
def get_data(self, instance):
|
|
||||||
request = self.context['request']
|
|
||||||
size = request.GET.get('size')
|
|
||||||
zoom = request.GET.get('zoom')
|
|
||||||
rotation = request.GET.get('rotation')
|
|
||||||
|
|
||||||
task = task_get_document_page_image.apply_async(
|
|
||||||
kwargs=dict(
|
|
||||||
document_page_id=instance.pk, size=size, zoom=zoom,
|
|
||||||
rotation=rotation, as_base64=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT)
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentPageSerializer(serializers.HyperlinkedModelSerializer):
|
class DocumentPageSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
|||||||
@@ -56,14 +56,15 @@ def task_delete_stubs():
|
|||||||
logger.info('Finshed')
|
logger.info('Finshed')
|
||||||
|
|
||||||
|
|
||||||
@app.task(compression='zlib')
|
@app.task()
|
||||||
def task_get_document_page_image(document_page_id, *args, **kwargs):
|
def task_generate_document_page_image(document_page_id, *args, **kwargs):
|
||||||
DocumentPage = apps.get_model(
|
DocumentPage = apps.get_model(
|
||||||
app_label='documents', model_name='DocumentPage'
|
app_label='documents', model_name='DocumentPage'
|
||||||
)
|
)
|
||||||
|
|
||||||
document_page = DocumentPage.objects.get(pk=document_page_id)
|
document_page = DocumentPage.objects.get(pk=document_page_id)
|
||||||
return document_page.get_image(*args, **kwargs)
|
|
||||||
|
return document_page.generate_image(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.task(bind=True, default_retry_delay=UPDATE_PAGE_COUNT_RETRY_DELAY, ignore_result=True)
|
@app.task(bind=True, default_retry_delay=UPDATE_PAGE_COUNT_RETRY_DELAY, ignore_result=True)
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
{% block content_plain %}
|
{% block content_plain %}
|
||||||
{% for page in pages %}
|
{% for page in pages %}
|
||||||
<img src="{% url 'documents:document_display_print' object.id %}?page={{ page.page_number }}" />
|
<img src="{% url 'rest_api:documentpage-image' page.id %}" />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
setting_print_size.value
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from json import loads
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.utils.six import BytesIO
|
|
||||||
|
|
||||||
from django_downloadview import assert_download_response
|
from django_downloadview import assert_download_response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -23,7 +22,7 @@ from .literals import (
|
|||||||
TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
|
TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
|
||||||
TEST_SMALL_DOCUMENT_FILENAME, TEST_SMALL_DOCUMENT_PATH,
|
TEST_SMALL_DOCUMENT_FILENAME, TEST_SMALL_DOCUMENT_PATH,
|
||||||
)
|
)
|
||||||
from ..models import Document, DocumentType, HASH_FUNCTION
|
from ..models import Document, DocumentType
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeAPITestCase(APITestCase):
|
class DocumentTypeAPITestCase(APITestCase):
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from .api_views import (
|
|||||||
APIDocumentVersionRevertView, APIDocumentVersionView,
|
APIDocumentVersionRevertView, APIDocumentVersionView,
|
||||||
APIRecentDocumentListView
|
APIRecentDocumentListView
|
||||||
)
|
)
|
||||||
from .settings import setting_print_size, setting_display_size
|
|
||||||
from .views import (
|
from .views import (
|
||||||
ClearImageCacheView, DeletedDocumentDeleteView,
|
ClearImageCacheView, DeletedDocumentDeleteView,
|
||||||
DeletedDocumentDeleteManyView, DeletedDocumentListView,
|
DeletedDocumentDeleteManyView, DeletedDocumentListView,
|
||||||
@@ -98,17 +97,6 @@ urlpatterns = patterns(
|
|||||||
'document_multiple_update_page_count',
|
'document_multiple_update_page_count',
|
||||||
name='document_multiple_update_page_count'
|
name='document_multiple_update_page_count'
|
||||||
),
|
),
|
||||||
|
|
||||||
url(
|
|
||||||
r'^(?P<document_id>\d+)/display/$', 'get_document_image', {
|
|
||||||
'size': setting_display_size.value
|
|
||||||
}, 'document_display'
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {
|
|
||||||
'size': setting_print_size.value
|
|
||||||
}, 'document_display_print'
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r'^(?P<pk>\d+)/download/form/$',
|
r'^(?P<pk>\d+)/download/form/$',
|
||||||
DocumentDownloadFormView.as_view(), name='document_download_form'
|
DocumentDownloadFormView.as_view(), name='document_download_form'
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import base64
|
|
||||||
import logging
|
import logging
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
@@ -8,7 +7,7 @@ from django.conf import settings
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.urlresolvers import resolve, reverse, reverse_lazy
|
from django.core.urlresolvers import resolve, reverse, reverse_lazy
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import render_to_response, get_object_or_404
|
from django.shortcuts import render_to_response, get_object_or_404
|
||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
@@ -23,9 +22,7 @@ from common.generics import (
|
|||||||
SingleObjectEditView, SingleObjectListView
|
SingleObjectEditView, SingleObjectListView
|
||||||
)
|
)
|
||||||
from common.mixins import MultipleInstanceActionMixin
|
from common.mixins import MultipleInstanceActionMixin
|
||||||
from converter.literals import (
|
from converter.literals import DEFAULT_ZOOM_LEVEL
|
||||||
DEFAULT_PAGE_NUMBER, DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
|
|
||||||
)
|
|
||||||
from converter.models import Transformation
|
from converter.models import Transformation
|
||||||
from converter.permissions import permission_transformation_delete
|
from converter.permissions import permission_transformation_delete
|
||||||
from permissions import Permission
|
from permissions import Permission
|
||||||
@@ -36,9 +33,7 @@ from .forms import (
|
|||||||
DocumentPropertiesForm, DocumentTypeSelectForm,
|
DocumentPropertiesForm, DocumentTypeSelectForm,
|
||||||
DocumentTypeFilenameForm_create, PrintForm
|
DocumentTypeFilenameForm_create, PrintForm
|
||||||
)
|
)
|
||||||
from .literals import (
|
from .literals import PAGE_RANGE_RANGE, DEFAULT_ZIP_FILENAME
|
||||||
DOCUMENT_IMAGE_TASK_TIMEOUT, PAGE_RANGE_RANGE, DEFAULT_ZIP_FILENAME
|
|
||||||
)
|
|
||||||
from .models import (
|
from .models import (
|
||||||
DeletedDocument, Document, DocumentType, DocumentPage,
|
DeletedDocument, Document, DocumentType, DocumentPage,
|
||||||
DocumentTypeFilename, DocumentVersion, RecentDocument
|
DocumentTypeFilename, DocumentVersion, RecentDocument
|
||||||
@@ -53,13 +48,10 @@ from .permissions import (
|
|||||||
permission_document_view, permission_empty_trash
|
permission_document_view, permission_empty_trash
|
||||||
)
|
)
|
||||||
from .settings import (
|
from .settings import (
|
||||||
setting_preview_size, setting_rotation_step, setting_zoom_percent_step,
|
setting_print_size, setting_rotation_step, setting_zoom_percent_step,
|
||||||
setting_zoom_max_level, setting_zoom_min_level
|
setting_zoom_max_level, setting_zoom_min_level
|
||||||
)
|
)
|
||||||
from .tasks import (
|
from .tasks import task_clear_image_cache, task_update_page_count
|
||||||
task_clear_image_cache, task_get_document_page_image,
|
|
||||||
task_update_page_count
|
|
||||||
)
|
|
||||||
from .utils import parse_range
|
from .utils import parse_range
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -277,8 +269,8 @@ class DocumentPageView(SimpleView):
|
|||||||
).dispatch(request, *args, **kwargs)
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
zoom = int(self.request.GET.get('zoom', DEFAULT_ZOOM_LEVEL))
|
zoom = self.request.GET.get('zoom')
|
||||||
rotation = int(self.request.GET.get('rotation', DEFAULT_ROTATION))
|
rotation = self.request.GET.get('rotation')
|
||||||
document_page_form = DocumentPageForm(
|
document_page_form = DocumentPageForm(
|
||||||
instance=self.get_object(), zoom=zoom, rotation=rotation
|
instance=self.get_object(), zoom=zoom, rotation=rotation
|
||||||
)
|
)
|
||||||
@@ -742,37 +734,6 @@ def document_multiple_document_type_edit(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Get rid of this view and convert widget to use API and base64 only images
|
|
||||||
def get_document_image(request, document_id, size=setting_preview_size.value):
|
|
||||||
document = get_object_or_404(Document.passthrough, pk=document_id)
|
|
||||||
try:
|
|
||||||
Permission.check_permissions(request.user, (permission_document_view,))
|
|
||||||
except PermissionDenied:
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
permission_document_view, request.user, document
|
|
||||||
)
|
|
||||||
|
|
||||||
page = int(request.GET.get('page', DEFAULT_PAGE_NUMBER))
|
|
||||||
|
|
||||||
zoom = int(request.GET.get('zoom', DEFAULT_ZOOM_LEVEL))
|
|
||||||
|
|
||||||
version = int(request.GET.get('version', document.latest_version.pk))
|
|
||||||
|
|
||||||
if zoom < setting_zoom_min_level.value:
|
|
||||||
zoom = setting_zoom_min_level.value
|
|
||||||
|
|
||||||
if zoom > setting_zoom_max_level.value:
|
|
||||||
zoom = setting_zoom_max_level.value
|
|
||||||
|
|
||||||
rotation = int(request.GET.get('rotation', DEFAULT_ROTATION)) % 360
|
|
||||||
|
|
||||||
document_page = document.pages.get(page_number=page)
|
|
||||||
|
|
||||||
task = task_get_document_page_image.apply_async(kwargs=dict(document_page_id=document_page.pk, size=size, zoom=zoom, rotation=rotation, as_base64=True, version=version))
|
|
||||||
data = task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT)
|
|
||||||
return HttpResponse(base64.b64decode(data.partition('base64,')[2]), content_type='image')
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentDownloadFormView(FormView):
|
class DocumentDownloadFormView(FormView):
|
||||||
form_class = DocumentDownloadForm
|
form_class = DocumentDownloadForm
|
||||||
model = Document
|
model = Document
|
||||||
@@ -1279,6 +1240,7 @@ def document_print(request, document_id):
|
|||||||
'appearance_type': 'plain',
|
'appearance_type': 'plain',
|
||||||
'object': document,
|
'object': document,
|
||||||
'pages': pages,
|
'pages': pages,
|
||||||
|
'size': setting_print_size.value,
|
||||||
'title': _('Print: %s') % document,
|
'title': _('Print: %s') % document,
|
||||||
}, context_instance=RequestContext(request))
|
}, context_instance=RequestContext(request))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ from django.core.urlresolvers import reverse
|
|||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from converter.literals import DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
|
from converter.literals import DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
|
||||||
|
|
||||||
@@ -17,8 +16,8 @@ from .settings import setting_display_size, setting_thumbnail_size
|
|||||||
class DocumentPageImageWidget(forms.widgets.Widget):
|
class DocumentPageImageWidget(forms.widgets.Widget):
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
final_attrs = self.build_attrs(attrs)
|
final_attrs = self.build_attrs(attrs)
|
||||||
zoom = final_attrs.get('zoom', 100)
|
zoom = final_attrs.get('zoom')
|
||||||
rotation = final_attrs.get('rotation', 0)
|
rotation = final_attrs.get('rotation')
|
||||||
if value:
|
if value:
|
||||||
output = []
|
output = []
|
||||||
output.append(
|
output.append(
|
||||||
@@ -46,20 +45,16 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
|
|||||||
'data-height-difference=200>'
|
'data-height-difference=200>'
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
document_pages = value.pages.all()
|
||||||
document_pages = value.pages.all()
|
total_pages = value.pages.count()
|
||||||
total_pages = value.pages.count()
|
|
||||||
except AttributeError:
|
|
||||||
document_pages = []
|
|
||||||
total_pages = 0
|
|
||||||
|
|
||||||
for page in document_pages:
|
for document_page in document_pages:
|
||||||
output.append('<div class="carousel-item">')
|
output.append('<div class="carousel-item">')
|
||||||
output.append(
|
output.append(
|
||||||
document_html_widget(
|
document_html_widget(
|
||||||
page,
|
document_page=document_page,
|
||||||
click_view='documents:document_page_view',
|
click_view='documents:document_page_view',
|
||||||
click_view_arguments=[page.pk],
|
click_view_arguments=(document_page.pk,),
|
||||||
fancybox_class='',
|
fancybox_class='',
|
||||||
image_class='lazy-load-carousel',
|
image_class='lazy-load-carousel',
|
||||||
size=setting_display_size.value,
|
size=setting_display_size.value,
|
||||||
@@ -70,31 +65,27 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
|
|||||||
'<div class="carousel-item-page-number">%s</div>' % ugettext(
|
'<div class="carousel-item-page-number">%s</div>' % ugettext(
|
||||||
'Page %(page_number)d of %(total_pages)d'
|
'Page %(page_number)d of %(total_pages)d'
|
||||||
) % {
|
) % {
|
||||||
'page_number': page.page_number,
|
'page_number': document_page.page_number,
|
||||||
'total_pages': total_pages
|
'total_pages': total_pages
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
output.append('</div>')
|
output.append('</div>')
|
||||||
|
|
||||||
|
if not total_pages:
|
||||||
|
output.append('<span class="fa-stack fa-lg"><i class="fa fa-file-o fa-stack-2x"></i><i class="fa fa-times fa-stack-1x text-danger"></i></span>')
|
||||||
|
|
||||||
output.append('</div>')
|
output.append('</div>')
|
||||||
|
|
||||||
return mark_safe(''.join(output))
|
return mark_safe(''.join(output))
|
||||||
|
|
||||||
|
|
||||||
def document_thumbnail(document, **kwargs):
|
|
||||||
return document_html_widget(
|
|
||||||
document_page=document.latest_version.pages.first(),
|
|
||||||
click_view='documents:document_display', **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def document_link(document):
|
def document_link(document):
|
||||||
return mark_safe('<a href="%s">%s</a>' % (
|
return mark_safe('<a href="%s">%s</a>' % (
|
||||||
document.get_absolute_url(), document)
|
document.get_absolute_url(), document)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def document_html_widget(document_page, click_view=None, click_view_arguments=None, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False, post_load_class=None, disable_title_link=False, preview_click_view=None):
|
def document_html_widget(document_page, click_view=None, click_view_arguments=None, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False, post_load_class=None, disable_title_link=False, preview_click_view=None, click_view_querydict=None, click_view_arguments_lazy=None):
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
alt_text = _('Document page image')
|
alt_text = _('Document page image')
|
||||||
@@ -151,6 +142,9 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
|
|||||||
title_template = ''
|
title_template = ''
|
||||||
|
|
||||||
if click_view:
|
if click_view:
|
||||||
|
if click_view_arguments_lazy:
|
||||||
|
click_view_arguments = click_view_arguments_lazy()
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
'<a {gallery_template} class="{fancybox_class}" '
|
'<a {gallery_template} class="{fancybox_class}" '
|
||||||
'href="{image_data}" {title_template}>'.format(
|
'href="{image_data}" {title_template}>'.format(
|
||||||
@@ -158,8 +152,8 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
|
|||||||
fancybox_class=fancybox_class,
|
fancybox_class=fancybox_class,
|
||||||
image_data='%s?%s' % (
|
image_data='%s?%s' % (
|
||||||
reverse(
|
reverse(
|
||||||
click_view, args=click_view_arguments or [document.pk]
|
click_view, args=click_view_arguments
|
||||||
), query_string
|
), urlencode(click_view_querydict or {})
|
||||||
),
|
),
|
||||||
title_template=title_template
|
title_template=title_template
|
||||||
)
|
)
|
||||||
@@ -173,8 +167,8 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result.append(
|
result.append(
|
||||||
'<img class="thin_border %s" data-src="%s" '
|
'<img class="thin_border {}" data-original="{}" '
|
||||||
'data-post-load-class="%s" src="%s" alt="%s" />' % (
|
'data-post-load-class="{}" src="{}" alt="{}" />'.format(
|
||||||
image_class, preview_view, post_load_class,
|
image_class, preview_view, post_load_class,
|
||||||
static('appearance/images/loading.png'), alt_text
|
static('appearance/images/loading.png'), alt_text
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ class DocumentTypeSubmitView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
count = 0
|
count = 0
|
||||||
print form.cleaned_data
|
|
||||||
for document in form.cleaned_data['document_type'].documents.all():
|
for document in form.cleaned_data['document_type'].documents.all():
|
||||||
document.submit_for_ocr()
|
document.submit_for_ocr()
|
||||||
count += 1
|
count += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user