Merge branch 'feature/base64_thumbnails' into development

This commit is contained in:
Roberto Rosario
2011-11-22 09:24:02 -04:00
12 changed files with 104 additions and 111 deletions

View File

@@ -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('<div class="full-height scrollable" style="overflow: auto;">')
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('</div>')
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',

View File

@@ -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:

View File

@@ -25,6 +25,10 @@ urlpatterns = patterns('documents.views',
url(r'^(?P<document_id>\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE}, 'document_display'),
url(r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE}, 'document_display_print'),
url(r'^(?P<document_id>\d+)/display/preview/base64/$', 'get_document_image', {'size': PREVIEW_SIZE, 'base64_version': True}, 'document_preview_base64'),
url(r'^(?P<document_id>\d+)/display/preview/multipage/base64/$', 'get_document_image', {'size': MULTIPAGE_PREVIEW_SIZE, 'base64_version': True}, 'document_preview_multipage_base64'),
url(r'^(?P<document_id>\d+)/display/thumbnail/base64/$', 'get_document_image', {'size': THUMBNAIL_SIZE, 'base64_version': True}, 'document_thumbnail_base64'),
url(r'^(?P<document_id>\d+)/download/$', 'document_download', (), 'document_download'),
url(r'^(?P<document_id>\d+)/create/siblings/$', 'document_create_siblings', (), 'document_create_siblings'),
url(r'^(?P<document_id>\d+)/find_duplicates/$', 'document_find_duplicates', (), 'document_find_duplicates'),

View File

@@ -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'<html><body><img src="%s" /></body></html>' % 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):

View File

@@ -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'<a href="%s">%s</a>' % (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'<a %s class="%s" href="%s">' % (gallery_template, fancybox_class, u'%s?%s' % (reverse(click_view, args=[document.pk]), query_string)))
zoomable_template.append(u'<img style="border: 1px solid black;" class="lazy-load" data-href="%s" src="%s/images/ajax-loader.gif" alt="%s" />' % (preview_view, settings.STATIC_URL, alt_text))
zoomable_template.append(u'<noscript><img style="border: 1px solid black;" src="%s" alt="%s" /></noscript>' % (preview_view, alt_text))
if click_view:
zoomable_template.append(u'</a>')
"""
preview_view = u'%s?%s' % (reverse(view, args=[document.pk]), query_string)
plain_template = []
plain_template.append(u'<img class="lazy-load" data-href="%s" src="%simages/ajax-loader.gif" alt="%s" />' % (preview_view, settings.STATIC_URL, alt_text))
plain_template.append(u'<noscript><img src="%s" alt="%s" /></noscript>' % (preview_view, alt_text))
plain_template.append(u'<img src="%s" alt="%s" />' % (preview_view, alt_text))
result.append(u'<div class="tc" id="document-%d-%d">' % (document.pk, page if page else 1))
if click_view:
result.append(u'<a %s class="%s" href="%s">' % (gallery_template, fancybox_class, u'%s?%s' % (reverse(click_view, args=[document.pk]), query_string)))
result.append(u'<img class="thin_border lazy-load" data-href="%s" src="%simages/ajax-loader.gif" alt="%s" />' % (preview_view, settings.STATIC_URL, alt_text))
result.append(u'<noscript><img style="border: 1px solid black;" src="%s" alt="%s" /></noscript>' % (preview_view, alt_text))
if click_view:
result.append(u'</a>')
result.append(u'</div>')
result.append(u'''
<script type="text/javascript">
$(document).ready(function() {
$.get('%(url)s', function(data) {})
.success(function(data) {
if (data.result) {
$('#document-%(pk)d-%(page)d').html('%(zoomable_template)s');
} else {
if (!data.result) {
$('#document-%(pk)d-%(page)d').html('%(plain_template)s');
}
//$('.fancybox-noscaling').live('click', function(e) {alert("CLICK");});
})
.error(function(data) { alert("error"); })
;
});
.error(function(data) {
$('#document-%(pk)d-%(page)d').html('<img src="%(error_image)s" />');
});
});
</script>
''' % {
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'<div class="tc" id="document-%d-%d">' % (document.pk, page if page else 1))
result.append(u'<a href="%s">' % (u'%s?%s' % (reverse(click_view, args=[document.pk]), query_string)))
result.append(u'<img src="%s/images/ajax-loader.gif" alt="%s" />' % (settings.STATIC_URL, alt_text))
result.append(u'<noscript><img style="border: 1px solid black;" src="%s" alt="%s" /></noscript>' % (preview_view, alt_text))
result.append(u'</a>')
result.append(u'</div>')
"""
#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'<div class="tc" id="document-%d-%d">' % (document.pk, page if page else 1))
result.extend(zoomable_template)
result.append(u'</div>')
return mark_safe(u''.join(result))

View File

@@ -48,7 +48,7 @@ class SmartLinkImageWidget(forms.widgets.Widget):
output.append(u'<div class="tc">%s: %d</div>' % (ugettext(u'Pages'), document.documentpage_set.count()))
output.append(get_tags_inline_widget(document))
output.append(u'<div style="padding: 5px;">' % 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'</div>')
output.append(u'<div class="tc">')
output.append(u'<a href="%s"><span class="famfam active famfam-page_go"></span>%s</a>' % (reverse('document_view_simple', args=[document.pk]), ugettext(u'Select')))

View File

@@ -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));

View File

@@ -102,13 +102,17 @@
-webkit-box-shadow: -1px -1px 2px #004977;
box-shadow: -1px -1px 2px #004977;
}
.thin_border {
border: 1px solid black;
}
</style>
{% block stylesheets %}{% endblock %}
{% endblock %}
{% block web_theme_javascript %}
<script type="text/javascript" src="{{ STATIC_URL }}packages/jquery.scrollview.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}packages/JqueryAsynchImageLoader-0.8.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}packages/JqueryAsynchImageLoader-0.9.7.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}packages/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.pack.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}packages/jquery.fancybox-1.3.4/fancybox/jquery.easing-1.3.pack.js"></script>
@@ -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'
});
});
</script>

View File

@@ -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):
"""

View File

@@ -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 []

View File

@@ -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<pk>[0-9]+)/$', ReadOnlyInstanceModelView.as_view(resource=DocumentResourceSimple), name='documents-simple'),
url(r'^v0/document/(?P<pk>[0-9]+)/expensive/is_zoomable/$', IsZoomable.as_view(), name='documents-expensive-is_zoomable'),
url(r'^v0/document/(?P<pk>[0-9]+)/expensive/exists/$', IsZoomable.as_view(), name='documents-expensive-exists'),
url(r'^v0/document/(?P<pk>[0-9]+)/expensive/size/$', Size.as_view(), name='documents-expensive-size'),
url(r'^v0/document/(?P<pk>[0-9]+)/page/(?P<page_number>[0-9]+)/expensive/is_zoomable/$', IsZoomable.as_view(), name='documents-expensive-is_zoomable'),
)

View File

@@ -40,30 +40,16 @@ class APIBase(View):
class Version_0(View):
def get(self, request):
return [
{'name': 'Resources', 'resources': ['documents/<pk>']}
{'name': 'Resources', 'resources': ['document/<pk>']}
]
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}