Refactor document page image generation and transformation classes

to cache all transformed versions of a document page.
This commit is contained in:
Roberto Rosario
2016-11-02 02:34:57 -04:00
parent ff67b11110
commit c0194c63dc
21 changed files with 348 additions and 185 deletions

View File

@@ -16,6 +16,7 @@ Other changes
- Remove dependency on the django-filetransfer library
- Fix height calculation in resize transformation
- Improve upgrade instructions
- New image caching pipeline
Removals
--------

View File

@@ -82,8 +82,12 @@ body {
overflow-x: scroll; height: 500px;
}
#carousel-container img {
width: 100%;
}
.carousel-item {
margin: 5px 10px 10px 10px
margin: 5px 10px 10px 10px;
}
.carousel-item-page-number {

View File

@@ -16,7 +16,7 @@ function set_image_noninteractive(image) {
container.html(html);
}
function load_document_image(image) {
function loadDocumentImage(image) {
$.get(image.attr('data-src'), function(result) {
image.attr('src', result.data);
image.addClass(image.attr('data-post-load-class'));
@@ -76,20 +76,11 @@ jQuery(document).ready(function() {
e.preventDefault();
})
$('img.lazy-load').lazyload({
appear: function(elements_left, settings) {
load_document_image($(this));
},
});
$('img.lazy-load').lazyload();
$('img.lazy-load-carousel').lazyload({
threshold : 400,
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) {

View File

@@ -1,8 +1,9 @@
from __future__ import unicode_literals
from .classes import ( # NOQA
TransformationResize, TransformationRotate, TransformationZoom # NOQA
)
from .classes import (
BaseTransformation, TransformationResize, TransformationRotate,
TransformationZoom
) # NOQA
from .runtime import converter_class # NOQA
default_app_config = 'converter.apps.ConverterApp'

View File

@@ -15,6 +15,7 @@ from .links import (
class ConverterApp(MayanAppConfig):
name = 'converter'
test = True
verbose_name = _('Converter')
def ready(self):

View File

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import base64
import logging
from operator import xor
import os
try:
@@ -221,6 +222,26 @@ class BaseTransformation(object):
_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
def register(cls, transformation):
cls._registry[transformation.name] = transformation
@@ -240,8 +261,17 @@ class BaseTransformation(object):
return string_concat(cls.label, ': ', ', '.join(cls.arguments))
def __init__(self, **kwargs):
self.kwargs = {}
for argument_name in self.arguments:
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):
self.image = image

View File

View 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
)

View File

@@ -3,8 +3,10 @@ from __future__ import absolute_import, unicode_literals
import logging
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django_downloadview import DownloadMixin, VirtualFile
from rest_framework import generics, status
from rest_framework.response import Response
@@ -13,6 +15,7 @@ from permissions import Permission
from rest_api.filters import MayanObjectPermissionsFilter
from rest_api.permissions import MayanPermission
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from .models import (
Document, DocumentPage, DocumentType, DocumentVersion, RecentDocument
)
@@ -25,13 +28,14 @@ from .permissions import (
permission_document_type_create, permission_document_type_delete,
permission_document_type_edit, permission_document_type_view
)
from .runtime import cache_storage_backend
from .serializers import (
DeletedDocumentSerializer, DocumentPageImageSerializer,
DocumentPageSerializer, DocumentSerializer,
DeletedDocumentSerializer, DocumentPageSerializer, DocumentSerializer,
DocumentTypeSerializer, DocumentVersionSerializer,
DocumentVersionRevertSerializer, NewDocumentSerializer,
NewDocumentVersionSerializer, RecentDocumentSerializer
)
from .tasks import task_generate_document_page_image
logger = logging.getLogger(__name__)
@@ -86,15 +90,6 @@ class APIDeletedDocumentRestoreView(generics.GenericAPIView):
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):
"""
Download the latest version of a document.
@@ -228,8 +223,18 @@ class APIDocumentView(generics.RetrieveUpdateDestroyAPIView):
class APIDocumentPageImageView(generics.RetrieveAPIView):
"""
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 = {
@@ -238,7 +243,25 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
mayan_permission_attribute_check = 'document'
permission_classes = (MayanPermission,)
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):

View File

@@ -68,13 +68,13 @@ from .permissions import (
)
# Just import to initialize the search models
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 (
new_documents_per_month, new_document_pages_per_month,
new_document_versions_per_month, total_document_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):
@@ -151,8 +151,12 @@ class DocumentsApp(MayanAppConfig):
SourceColumn(
source=Document, label=_('Thumbnail'),
func=lambda context: document_thumbnail(
context['object'], gallery_name='documents:document_list',
func=lambda context: document_html_widget(
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,
title=getattr(context['object'], 'label', None),
)
@@ -165,8 +169,8 @@ class DocumentsApp(MayanAppConfig):
source=DocumentPage, label=_('Thumbnail'),
func=lambda context: document_html_widget(
document_page=context['object'],
click_view='documents:document_display',
click_view_arguments=(context['object'].document.pk,),
click_view='rest_api:documentpage-image',
click_view_arguments=(context['object'].pk,),
gallery_name='documents:document_page_list',
preview_click_view='documents:document_page_view',
size=setting_thumbnail_size.value,
@@ -178,8 +182,8 @@ class DocumentsApp(MayanAppConfig):
source=DocumentPageResult, label=_('Thumbnail'),
func=lambda context: document_html_widget(
document_page=context['object'],
click_view='documents:document_display',
click_view_arguments=(context['object'].document.pk,),
click_view='rest_api:documentpage-image',
click_view_arguments=(context['object'].pk,),
gallery_name='documents:document_page_list',
preview_click_view='documents:document_page_view',
size=setting_thumbnail_size.value,
@@ -205,8 +209,11 @@ class DocumentsApp(MayanAppConfig):
SourceColumn(
source=DeletedDocument, label=_('Thumbnail'),
func=lambda context: document_thumbnail(
context['object'],
func=lambda context: document_html_widget(
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',
size=setting_thumbnail_size.value,
title=getattr(context['object'], 'label', None),
@@ -285,7 +292,7 @@ class DocumentsApp(MayanAppConfig):
'documents.tasks.task_clear_image_cache': {
'queue': 'tools'
},
'documents.tasks.task_get_document_page_image': {
'documents.tasks.task_generate_document_page_image': {
'queue': 'converter'
},
'documents.tasks.task_update_page_count': {

View File

@@ -29,8 +29,8 @@ class DocumentPageForm(DetailForm):
model = DocumentPage
def __init__(self, *args, **kwargs):
zoom = kwargs.pop('zoom', 100)
rotation = kwargs.pop('rotation', 0)
zoom = kwargs.pop('zoom', None)
rotation = kwargs.pop('rotation', None)
super(DocumentPageForm, self).__init__(*args, **kwargs)
self.fields['page_image'].initial = self.instance
self.fields['page_image'].widget.attrs.update({

View 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'),
),
]

View File

@@ -16,8 +16,8 @@ from django.utils.translation import ugettext, ugettext_lazy as _
from acls.models import AccessControlList
from common.literals import TIME_DELTA_UNIT_CHOICES
from converter import (
converter_class, TransformationResize, TransformationRotate,
TransformationZoom
converter_class, BaseTransformation, TransformationResize,
TransformationRotate, TransformationZoom
)
from converter.exceptions import InvalidOfficeFormat, PageCountError
from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
@@ -683,15 +683,15 @@ class DocumentPage(models.Model):
def document(self):
return self.document_version.document
def get_image(self, *args, **kwargs):
as_base64 = kwargs.pop('as_base64', False)
transformations = kwargs.pop('transformations', [])
size = kwargs.pop('size', setting_display_size.value)
def generate_image(self, *args, **kwargs):
# Convert arguments into transformations
transformations = kwargs.get('transformations', [])
size = kwargs.get('size', setting_display_size.value)
rotation = int(
kwargs.pop('rotation', DEFAULT_ROTATION) or DEFAULT_ROTATION
)
kwargs.get('rotation', DEFAULT_ROTATION) or DEFAULT_ROTATION
) % 360
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:
@@ -700,8 +700,54 @@ class DocumentPage(models.Model):
if 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
logger.debug('Page cache filename: %s', cache_filename)
@@ -734,33 +780,15 @@ class DocumentPage(models.Model):
cache_storage_backend.delete(cache_filename)
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:
converter.transform(transformation=transformation)
if rotation:
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)
return converter.get_page()
def invalidate_cache(self):
cache_storage_backend.delete(self.cache_filename)
for cached_image in self.cached_images.all():
cached_image.delete()
@property
def siblings(self):
@@ -777,6 +805,22 @@ class DocumentPage(models.Model):
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 Meta:
ordering = ('document_version__document', 'page_number')

View File

@@ -4,30 +4,11 @@ from rest_framework import serializers
from common.models import SharedUploadedFile
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from .models import (
Document, DocumentVersion, DocumentPage, DocumentType, RecentDocument
)
from .settings import setting_language
from .tasks import task_get_document_page_image, 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)
from .tasks import task_upload_new_version
class DocumentPageSerializer(serializers.HyperlinkedModelSerializer):

View File

@@ -56,14 +56,15 @@ def task_delete_stubs():
logger.info('Finshed')
@app.task(compression='zlib')
def task_get_document_page_image(document_page_id, *args, **kwargs):
@app.task()
def task_generate_document_page_image(document_page_id, *args, **kwargs):
DocumentPage = apps.get_model(
app_label='documents', model_name='DocumentPage'
)
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)

View File

@@ -4,6 +4,9 @@
{% block content_plain %}
{% 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 %}
{% endblock %}
setting_print_size.value

View File

@@ -9,7 +9,6 @@ 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
@@ -23,7 +22,7 @@ from .literals import (
TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
TEST_SMALL_DOCUMENT_FILENAME, TEST_SMALL_DOCUMENT_PATH,
)
from ..models import Document, DocumentType, HASH_FUNCTION
from ..models import Document, DocumentType
class DocumentTypeAPITestCase(APITestCase):

View File

@@ -12,7 +12,6 @@ from .api_views import (
APIDocumentVersionRevertView, APIDocumentVersionView,
APIRecentDocumentListView
)
from .settings import setting_print_size, setting_display_size
from .views import (
ClearImageCacheView, DeletedDocumentDeleteView,
DeletedDocumentDeleteManyView, DeletedDocumentListView,
@@ -98,17 +97,6 @@ urlpatterns = patterns(
'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(
r'^(?P<pk>\d+)/download/form/$',
DocumentDownloadFormView.as_view(), name='document_download_form'

View File

@@ -1,6 +1,5 @@
from __future__ import absolute_import, unicode_literals
import base64
import logging
import urlparse
@@ -8,7 +7,7 @@ from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
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.template import RequestContext
from django.utils.http import urlencode
@@ -23,9 +22,7 @@ from common.generics import (
SingleObjectEditView, SingleObjectListView
)
from common.mixins import MultipleInstanceActionMixin
from converter.literals import (
DEFAULT_PAGE_NUMBER, DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
)
from converter.literals import DEFAULT_ZOOM_LEVEL
from converter.models import Transformation
from converter.permissions import permission_transformation_delete
from permissions import Permission
@@ -36,9 +33,7 @@ from .forms import (
DocumentPropertiesForm, DocumentTypeSelectForm,
DocumentTypeFilenameForm_create, PrintForm
)
from .literals import (
DOCUMENT_IMAGE_TASK_TIMEOUT, PAGE_RANGE_RANGE, DEFAULT_ZIP_FILENAME
)
from .literals import PAGE_RANGE_RANGE, DEFAULT_ZIP_FILENAME
from .models import (
DeletedDocument, Document, DocumentType, DocumentPage,
DocumentTypeFilename, DocumentVersion, RecentDocument
@@ -53,13 +48,10 @@ from .permissions import (
permission_document_view, permission_empty_trash
)
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
)
from .tasks import (
task_clear_image_cache, task_get_document_page_image,
task_update_page_count
)
from .tasks import task_clear_image_cache, task_update_page_count
from .utils import parse_range
logger = logging.getLogger(__name__)
@@ -277,8 +269,8 @@ class DocumentPageView(SimpleView):
).dispatch(request, *args, **kwargs)
def get_extra_context(self):
zoom = int(self.request.GET.get('zoom', DEFAULT_ZOOM_LEVEL))
rotation = int(self.request.GET.get('rotation', DEFAULT_ROTATION))
zoom = self.request.GET.get('zoom')
rotation = self.request.GET.get('rotation')
document_page_form = DocumentPageForm(
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):
form_class = DocumentDownloadForm
model = Document
@@ -1279,6 +1240,7 @@ def document_print(request, document_id):
'appearance_type': 'plain',
'object': document,
'pages': pages,
'size': setting_print_size.value,
'title': _('Print: %s') % document,
}, context_instance=RequestContext(request))
else:

View File

@@ -6,8 +6,7 @@ from django.core.urlresolvers import reverse
from django.utils.html import strip_tags
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext, ugettext_lazy as _
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):
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs)
zoom = final_attrs.get('zoom', 100)
rotation = final_attrs.get('rotation', 0)
zoom = final_attrs.get('zoom')
rotation = final_attrs.get('rotation')
if value:
output = []
output.append(
@@ -46,20 +45,16 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
'data-height-difference=200>'
)
try:
document_pages = value.pages.all()
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(
document_html_widget(
page,
document_page=document_page,
click_view='documents:document_page_view',
click_view_arguments=[page.pk],
click_view_arguments=(document_page.pk,),
fancybox_class='',
image_class='lazy-load-carousel',
size=setting_display_size.value,
@@ -70,31 +65,27 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
'<div class="carousel-item-page-number">%s</div>' % ugettext(
'Page %(page_number)d of %(total_pages)d'
) % {
'page_number': page.page_number,
'page_number': document_page.page_number,
'total_pages': total_pages
}
)
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>')
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):
return mark_safe('<a href="%s">%s</a>' % (
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 = []
alt_text = _('Document page image')
@@ -151,6 +142,9 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
title_template = ''
if click_view:
if click_view_arguments_lazy:
click_view_arguments = click_view_arguments_lazy()
result.append(
'<a {gallery_template} class="{fancybox_class}" '
'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,
image_data='%s?%s' % (
reverse(
click_view, args=click_view_arguments or [document.pk]
), query_string
click_view, args=click_view_arguments
), urlencode(click_view_querydict or {})
),
title_template=title_template
)
@@ -173,8 +167,8 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
)
else:
result.append(
'<img class="thin_border %s" data-src="%s" '
'data-post-load-class="%s" src="%s" alt="%s" />' % (
'<img class="thin_border {}" data-original="{}" '
'data-post-load-class="{}" src="{}" alt="{}" />'.format(
image_class, preview_view, post_load_class,
static('appearance/images/loading.png'), alt_text
)

View File

@@ -99,7 +99,6 @@ class DocumentTypeSubmitView(FormView):
def form_valid(self, form):
count = 0
print form.cleaned_data
for document in form.cleaned_data['document_type'].documents.all():
document.submit_for_ocr()
count += 1