diff --git a/apps/documents/forms.py b/apps/documents/forms.py index a6f71c0a12..320b60bc71 100644 --- a/apps/documents/forms.py +++ b/apps/documents/forms.py @@ -15,7 +15,6 @@ from documents.models import Document, DocumentType, \ DocumentPage, DocumentPageTransformation, DocumentTypeFilename from documents.widgets import document_html_widget - # Document page forms class DocumentPageTransformationForm(forms.ModelForm): class Meta: @@ -35,7 +34,7 @@ class DocumentPageImageWidget(forms.widgets.Widget): output = [] output.append('
') - output.append(document_html_widget(value.document, size='document_display', page=value.page_number, zoom=zoom, rotation=rotation)) + output.append(document_html_widget(value.document, view='document_display', page=value.page_number, zoom=zoom, rotation=rotation)) output.append('
') return mark_safe(u''.join(output)) else: @@ -106,7 +105,7 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget): output.append( document_html_widget( page.document, - size='document_preview_multipage', + view='document_preview_multipage', click_view='document_display', page=page.page_number, gallery_name='document_pages', diff --git a/apps/documents/models.py b/apps/documents/models.py index 97d5c848d8..914426a568 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -2,7 +2,9 @@ import os import tempfile import hashlib from ast import literal_eval - +import base64 +from StringIO import StringIO + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext @@ -26,6 +28,8 @@ from documents.conf.settings import STORAGE_BACKEND from documents.conf.settings import PREVIEW_SIZE from documents.conf.settings import DISPLAY_SIZE from documents.conf.settings import CACHE_PATH +from documents.conf.settings import ZOOM_MAX_LEVEL +from documents.conf.settings import ZOOM_MIN_LEVEL from documents.managers import RecentDocumentManager, \ DocumentPageTransformationManager @@ -256,15 +260,31 @@ class Document(models.Model): image_cache_name = self.get_image_cache_name(page=page) return convert(image_cache_name, cleanup_files=False, size=size, zoom=zoom, rotation=rotation) - def get_image(self, size=DISPLAY_SIZE, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION): + def get_image(self, size=DISPLAY_SIZE, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, as_base64=False): + if zoom < ZOOM_MIN_LEVEL: + zoom = ZOOM_MIN_LEVEL + + if zoom > ZOOM_MAX_LEVEL: + zoom = ZOOM_MAX_LEVEL + + rotation = rotation % 360 + try: - return self.get_valid_image(size=size, page=page, zoom=zoom, rotation=rotation) + file_path = self.get_valid_image(size=size, page=page, zoom=zoom, rotation=rotation) except UnknownFileFormat: - return get_icon_file_path(self.file_mimetype) + file_path = get_icon_file_path(self.file_mimetype) except UnkownConvertError: - return get_error_icon_file_path() + file_path = get_error_icon_file_path() except: - return get_error_icon_file_path() + file_path = get_error_icon_file_path() + + if as_base64: + image = open(file_path, 'r') + out = StringIO() + base64.encode(image, out) + return u'data:%s;base64,%s' % (get_mimetype(open(file_path, 'r'), file_path, mimetype_only=True)[0], out.getvalue().replace('\n', '')) + else: + return file_path def invalidate_cached_image(self, page): try: diff --git a/apps/documents/urls.py b/apps/documents/urls.py index c20d04c47a..0d16fb3880 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -25,6 +25,10 @@ urlpatterns = patterns('documents.views', url(r'^(?P\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE}, 'document_display'), url(r'^(?P\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE}, 'document_display_print'), + url(r'^(?P\d+)/display/preview/base64/$', 'get_document_image', {'size': PREVIEW_SIZE, 'base64_version': True}, 'document_preview_base64'), + url(r'^(?P\d+)/display/preview/multipage/base64/$', 'get_document_image', {'size': MULTIPAGE_PREVIEW_SIZE, 'base64_version': True}, 'document_preview_multipage_base64'), + url(r'^(?P\d+)/display/thumbnail/base64/$', 'get_document_image', {'size': THUMBNAIL_SIZE, 'base64_version': True}, 'document_thumbnail_base64'), + url(r'^(?P\d+)/download/$', 'document_download', (), 'document_download'), url(r'^(?P\d+)/create/siblings/$', 'document_create_siblings', (), 'document_create_siblings'), url(r'^(?P\d+)/find_duplicates/$', 'document_find_duplicates', (), 'document_find_duplicates'), diff --git a/apps/documents/views.py b/apps/documents/views.py index 8c76416d7f..60a03ee74c 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -2,7 +2,7 @@ import urlparse import copy from django.utils.translation import ugettext_lazy as _ -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponse from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.contrib import messages @@ -271,7 +271,7 @@ def document_edit(request, document_id): }, context_instance=RequestContext(request)) -def get_document_image(request, document_id, size=PREVIEW_SIZE): +def get_document_image(request, document_id, size=PREVIEW_SIZE, base64_version=False): check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW]) document = get_object_or_404(Document, pk=document_id) @@ -288,7 +288,12 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE): rotation = int(request.GET.get('rotation', DEFAULT_ROTATION)) % 360 - return sendfile.sendfile(request, document.get_image(size=size, page=page, zoom=zoom, rotation=rotation), mimetype=DEFAULT_FILE_FORMAT_MIMETYPE) + if base64_version: + return HttpResponse(u'' % document.get_image(size=size, page=page, zoom=zoom, rotation=rotation, as_base64=True)) + else: + # TODO: hardcoded MIMETYPE + return sendfile.sendfile(request, document.get_image(size=size, page=page, zoom=zoom, rotation=rotation), mimetype=DEFAULT_FILE_FORMAT_MIMETYPE) + def document_download(request, document_id): diff --git a/apps/documents/widgets.py b/apps/documents/widgets.py index 6de68c8df7..e449ffc335 100644 --- a/apps/documents/widgets.py +++ b/apps/documents/widgets.py @@ -4,7 +4,12 @@ from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse from django.utils.http import urlencode +from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \ + DEFAULT_PAGE_NUMBER from converter.exceptions import UnknownFileFormat, UnkownConvertError +from mimetype.api import get_error_icon_url + +from documents.conf.settings import DISPLAY_SIZE def document_thumbnail(document): @@ -15,20 +20,15 @@ def document_link(document): return mark_safe(u'%s' % (reverse('document_view_simple', args=[document.pk]), document)) -def document_html_widget(document, size='document_thumbnail', click_view=None, page=None, zoom=None, rotation=None, gallery_name=None, fancybox_class='fancybox'): +def document_html_widget(document, view='document_thumbnail', click_view=None, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox'): result = [] alt_text = _(u'document page image') - query_dict = {} - - if page: - query_dict['page'] = page - - if zoom: - query_dict['zoom'] = zoom - - if rotation: - query_dict['rotation'] = rotation + query_dict = { + 'page': page, + 'zoom': zoom, + 'rotation': rotation, + } if gallery_name: gallery_template = u'rel="%s"' % gallery_name @@ -36,80 +36,43 @@ def document_html_widget(document, size='document_thumbnail', click_view=None, p gallery_template = u'' query_string = urlencode(query_dict) - preview_view = u'%s?%s' % (reverse(size, args=[document.pk]), query_string) - print 'preview_view', preview_view - - zoomable_template = [] - if click_view: - zoomable_template.append(u'' % (gallery_template, fancybox_class, u'%s?%s' % (reverse(click_view, args=[document.pk]), query_string))) - zoomable_template.append(u'%s' % (preview_view, settings.STATIC_URL, alt_text)) - zoomable_template.append(u'' % (preview_view, alt_text)) - if click_view: - zoomable_template.append(u'') - - """ + preview_view = u'%s?%s' % (reverse(view, args=[document.pk]), query_string) + plain_template = [] - plain_template.append(u'%s' % (preview_view, settings.STATIC_URL, alt_text)) - plain_template.append(u'' % (preview_view, alt_text)) + plain_template.append(u'%s' % (preview_view, alt_text)) + + result.append(u'') result.append(u''' ''' % { - u'url': reverse('documents-expensive-is_zoomable', args=[document.pk]), - u'pk': document.pk, - u'page': page if page else 1, - u'zoomable_template': mark_safe(u''.join(zoomable_template)), - u'plain_template': mark_safe(u''.join(plain_template)), + 'url': reverse('documents-expensive-is_zoomable', args=[document.pk, page]), + 'pk': document.pk, + 'page': page if page else 1, + 'plain_template': mark_safe(u''.join(plain_template)), + 'error_image': u''.join([settings.STATIC_URL, get_error_icon_url()]), } ) - - result.append(u'') - """ - - #Fancybox w/ jQuery live - """ - jQuery("a.fancybox-noscaling").live('click', function(){ - jQuery.fancybox({ - 'autoDimensions' : false, - 'width' : 'auto', - 'height' : 'auto', - 'href' : $(this).attr('href'), - 'titleShow' : false, - 'transitionIn' : 'elastic', - 'transitionOut' : 'elastic', - 'easingIn' : 'easeOutBack', - 'easingOut' : 'easeInBack', - 'type' : 'image', - 'autoScale' : false - - }); - return false; - }); - """ - result.append(u'
' % (document.pk, page if page else 1)) - result.extend(zoomable_template) - result.append(u'
') return mark_safe(u''.join(result)) diff --git a/apps/linking/forms.py b/apps/linking/forms.py index 864b532dcc..9568994b89 100644 --- a/apps/linking/forms.py +++ b/apps/linking/forms.py @@ -48,7 +48,7 @@ class SmartLinkImageWidget(forms.widgets.Widget): output.append(u'
%s: %d
' % (ugettext(u'Pages'), document.documentpage_set.count())) output.append(get_tags_inline_widget(document)) output.append(u'
' % document) - output.append(document_html_widget(document, click_view='document_display', size='document_preview_multipage', fancybox_class='fancybox-noscaling', gallery_name=u'smart_link_%d_documents_gallery' % value['smart_link_instance'].pk)) + output.append(document_html_widget(document, click_view='document_display', view='document_preview_multipage', fancybox_class='fancybox-noscaling', gallery_name=u'smart_link_%d_documents_gallery' % value['smart_link_instance'].pk)) output.append(u'
') output.append(u'
') output.append(u'%s' % (reverse('document_view_simple', args=[document.pk]), ugettext(u'Select'))) diff --git a/apps/main/static/packages/JqueryAsynchImageLoader-0.9.7.min.js b/apps/main/static/packages/JqueryAsynchImageLoader-0.9.7.min.js new file mode 100644 index 0000000000..248aa4b264 --- /dev/null +++ b/apps/main/static/packages/JqueryAsynchImageLoader-0.9.7.min.js @@ -0,0 +1,9 @@ +/* +* JqueryAsynchImageLoader (JAIL) : plugin for jQuery +* +* Developed by +* Sebastiano Armeli-Battana (@sebarmeli) - http://www.sebastianoarmelibattana.com +* Dual licensed under the MIT or GPL Version 3 licenses. +* @version 0.9.7 +*/ +;(function(a){var b=a(window);a.fn.asynchImageLoader=a.fn.jail=function(d){d=a.extend({timeout:10,effect:false,speed:400,selector:null,offset:0,event:"load+scroll",callback:jQuery.noop,callbackAfterEachImage:jQuery.noop,placeholder:false,ignoreHiddenImages:false},d);var c=this;a.jail.initialStack=this;this.data("triggerEl",(d.selector)?a(d.selector):b);if(d.placeholder!==false){c.each(function(){a(this).attr("src",d.placeholder);});}if(/^load/.test(d.event)){a.asynchImageLoader.later.call(this,d);}else{a.asynchImageLoader.onEvent.call(this,d,c);}return this;};a.asynchImageLoader=a.jail={_purgeStack:function(c){var d=0;while(true){if(d===c.length){break;}else{if(c[d].getAttribute("data-href")){d++;}else{c.splice(d,1);}}}},_loadOnEvent:function(g){var f=a(this),d=g.data.options,c=g.data.images;a.asynchImageLoader._loadImageIfVisible(d,f);f.unbind(d.event,a.asynchImageLoader._loadOnEvent);a.asynchImageLoader._purgeStack(c);if(!!d.callback){a.asynchImageLoader._purgeStack(a.jail.initialStack);a.asynchImageLoader._launchCallback(a.jail.initialStack,d);}},_bufferedEventListener:function(g){var c=g.data.images,d=g.data.options,f=c.data("triggerEl");clearTimeout(c.data("poller"));c.data("poller",setTimeout(function(){c.each(function e(){a.asynchImageLoader._loadImageIfVisible(d,this,f);});a.asynchImageLoader._purgeStack(c);if(!!d.callback){a.asynchImageLoader._purgeStack(a.jail.initialStack);a.asynchImageLoader._launchCallback(a.jail.initialStack,d);}},d.timeout));},onEvent:function(d,c){c=c||this;if(d.event==="scroll"||d.selector){var e=c.data("triggerEl");if(c.length>0){e.bind(d.event,{images:c,options:d},a.asynchImageLoader._bufferedEventListener);if(d.event==="scroll"||!d.selector){b.resize({images:c,options:d},a.asynchImageLoader._bufferedEventListener);}return;}else{if(!!e){e.unbind(d.event,a.asynchImageLoader._bufferedEventListener);}}}else{c.bind(d.event,{options:d,images:c},a.asynchImageLoader._loadOnEvent);}},later:function(d){var c=this;if(d.event==="load"){c.each(function(){a.asynchImageLoader._loadImageIfVisible(d,this,c.data("triggerEl"));});}a.asynchImageLoader._purgeStack(c);a.asynchImageLoader._launchCallback(c,d);setTimeout(function(){if(d.event==="load"){c.each(function(){a.asynchImageLoader._loadImage(d,a(this));});}else{c.each(function(){a.asynchImageLoader._loadImageIfVisible(d,this,c.data("triggerEl"));});}a.asynchImageLoader._purgeStack(c);a.asynchImageLoader._launchCallback(c,d);if(d.event==="load+scroll"){d.event="scroll";a.asynchImageLoader.onEvent(d,c);}},d.timeout);},_launchCallback:function(c,d){if(c.length===0&&!a.jail.isCallback){d.callback.call(this,d);a.jail.isCallback=true;}},_loadImageIfVisible:function(e,h,g){var f=a(h),d=(/scroll/i.test(e.event))?g:b,c=true;if(e.ignoreHiddenImages){c=a.jail._isVisibleInOverflownContainer(f,e)&&f.is(":visible");}if(c&&a.asynchImageLoader._isInTheScreen(d,f,e.offset)){a.asynchImageLoader._loadImage(e,f);}},_isInTheScreen:function(j,c,h){var f=j[0]===window,n=(f?{top:0,left:0}:j.offset()),g=n.top+(f?j.scrollTop():0),i=n.left+(f?j.scrollLeft():0),e=i+j.width(),k=g+j.height(),m=c.offset(),l=c.width(),d=c.height();return(g-h)<=(m.top+d)&&(k+h)>=m.top&&(i-h)<=(m.left+l)&&(e+h)>=m.left;},_loadImage:function(c,d){d.hide();d.attr("src",d.attr("data-href"));d.removeAttr("data-href");if(c.effect){if(c.speed){d[c.effect](c.speed);}else{d[c.effect]();}}else{d.show();}c.callbackAfterEachImage.call(this,d,c);},_isVisibleInOverflownContainer:function(e,d){var f=e.parent(),c=true;while(f.get(0).tagName!=="BODY"){if(f.css("overflow")==="hidden"){if(!a.jail._isInTheScreen(f,e,d.offset)){c=false;break;}}if(f.css("visibility")==="hidden"||e.css("visibility")==="hidden"){c=false;break;}f=f.parent();}return c;}};}(jQuery)); \ No newline at end of file diff --git a/apps/main/templates/base.html b/apps/main/templates/base.html index 1a656df0d7..c1fcedd0cb 100644 --- a/apps/main/templates/base.html +++ b/apps/main/templates/base.html @@ -102,13 +102,17 @@ -webkit-box-shadow: -1px -1px 2px #004977; box-shadow: -1px -1px 2px #004977; } + + .thin_border { + border: 1px solid black; + } {% block stylesheets %}{% endblock %} {% endblock %} {% block web_theme_javascript %} - + @@ -135,6 +139,7 @@ 'type' : 'image', 'autoScale' : true }); + $("a.fancybox-noscaling").fancybox({ 'titleShow' : false, 'transitionIn' : 'elastic', @@ -144,6 +149,7 @@ 'type' : 'image', 'autoScale' : false }); + $("a.fancybox-iframe").fancybox({ 'titleShow' : false, 'transitionIn' : 'elastic', @@ -166,7 +172,7 @@ $('img.lazy-load').jail({ event: 'load', timeout: 10, - placeholder: '{{ STATIC_URL }}/images/ajax-loader.gif' + placeholder: '{{ STATIC_URL }}images/ajax-loader.gif' }); }); diff --git a/apps/mimetype/api.py b/apps/mimetype/api.py index 4888aa5b8d..5790bce8a2 100644 --- a/apps/mimetype/api.py +++ b/apps/mimetype/api.py @@ -83,6 +83,9 @@ def get_error_icon_file_path(): else: return os.path.join(settings.STATIC_ROOT, MIMETYPE_ICONS_DIRECTORY_NAME, ERROR_FILE_NAME) +def get_error_icon_url(): + return os.path.join(MIMETYPE_ICONS_DIRECTORY_NAME, ERROR_FILE_NAME) + def get_mimetype(file_description, filepath, mimetype_only=False): """ diff --git a/apps/rest_api/resources.py b/apps/rest_api/resources.py index bbb8f52ebf..99a5c26613 100644 --- a/apps/rest_api/resources.py +++ b/apps/rest_api/resources.py @@ -25,6 +25,8 @@ class DocumentResourceSimple(ModelResource): { 'page_numer': page.page_number, 'page_label': page.page_label, + 'is_zoomable': reverse('documents-expensive-is_zoomable', args=[instance.pk, page.page_number]), + #'content': } for page in instance.documentpage_set.all() @@ -33,6 +35,4 @@ class DocumentResourceSimple(ModelResource): ] def expensive_methods(self, instance): - return [ - {'is_zoomable': reverse('documents-expensive-is_zoomable', args=[instance.pk])}, - ] + return [] diff --git a/apps/rest_api/urls.py b/apps/rest_api/urls.py index b9eb0eea66..3265dd7574 100644 --- a/apps/rest_api/urls.py +++ b/apps/rest_api/urls.py @@ -3,7 +3,7 @@ from django.conf.urls.defaults import patterns, url from djangorestframework.views import ListModelView from djangorestframework.views import ListOrCreateModelView, InstanceModelView -from rest_api.views import APIBase, Version_0, ReadOnlyInstanceModelView, IsZoomable, Exists, Size +from rest_api.views import APIBase, Version_0, ReadOnlyInstanceModelView, IsZoomable from rest_api.resources import DocumentResourceSimple urlpatterns = patterns('', @@ -12,7 +12,5 @@ urlpatterns = patterns('', # Version 0 alpha API calls url(r'^v0/document/(?P[0-9]+)/$', ReadOnlyInstanceModelView.as_view(resource=DocumentResourceSimple), name='documents-simple'), - url(r'^v0/document/(?P[0-9]+)/expensive/is_zoomable/$', IsZoomable.as_view(), name='documents-expensive-is_zoomable'), - url(r'^v0/document/(?P[0-9]+)/expensive/exists/$', IsZoomable.as_view(), name='documents-expensive-exists'), - url(r'^v0/document/(?P[0-9]+)/expensive/size/$', Size.as_view(), name='documents-expensive-size'), + url(r'^v0/document/(?P[0-9]+)/page/(?P[0-9]+)/expensive/is_zoomable/$', IsZoomable.as_view(), name='documents-expensive-is_zoomable'), ) diff --git a/apps/rest_api/views.py b/apps/rest_api/views.py index 06b5a46ce5..6a1b5dfc14 100644 --- a/apps/rest_api/views.py +++ b/apps/rest_api/views.py @@ -40,30 +40,16 @@ class APIBase(View): class Version_0(View): def get(self, request): return [ - {'name': 'Resources', 'resources': ['documents/']} + {'name': 'Resources', 'resources': ['document/']} ] class IsZoomable(View): - def get(self, request, pk): + def get(self, request, pk, page_number): logger.info('received is_zoomable call from: %s' % (request.META['REMOTE_ADDR'])) document = get_object_or_404(Document, pk=pk) try: - document.get_image_cache_name(1) # TODO: page + document.get_image_cache_name(int(page_number)) return {'result': True} except (UnknownFileFormat, UnkownConvertError): return {'result': False} - - -class Exists(View): - def get(self, request, pk): - logger.info('received exists call from: %s' % (request.META['REMOTE_ADDR'])) - document = get_object_or_404(Document, pk=pk) - return {'result': document.exists()} - - -class Size(View): - def get(self, request, pk): - logger.info('received size call from: %s' % (request.META['REMOTE_ADDR'])) - document = get_object_or_404(Document, pk=pk) - return {'result': document.size}