Merge branch 'feature/optimize_document_widget' into development

This commit is contained in:
Roberto Rosario
2011-11-22 05:51:48 -04:00
19 changed files with 274 additions and 51 deletions

View File

@@ -3,6 +3,8 @@ import os
import re
import types
import tempfile
import string
import random
from django.utils.http import urlquote as django_urlquote
from django.utils.http import urlencode as django_urlencode
@@ -358,8 +360,13 @@ def validate_path(path):
return True
def encapsulate(function):
# Workaround Django ticket 15791
# Changeset 16045
# http://stackoverflow.com/questions/6861601/cannot-resolve-callable-context-variable/6955045#6955045
return lambda: function
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))

View File

@@ -14,8 +14,8 @@ from converter.literals import TRANSFORMATION_RESIZE, \
from converter.literals import DIMENSION_SEPARATOR
from converter.literals import FILE_FORMATS
from converter.utils import cleanup
from converter.office_converter import OfficeConverter
from converter.runtime import office_converter
from converter.exceptions import OfficeConversionError
HASH_FUNCTION = lambda x: hashlib.sha256(x).hexdigest()
@@ -51,18 +51,19 @@ def convert(input_filepath, output_filepath=None, cleanup_files=False, mimetype=
if os.path.exists(output_filepath):
return output_filepath
office_converter = OfficeConverter()
office_converter.convert(input_filepath, mimetype=mimetype)
if office_converter:
try:
input_filepath = office_converter.output_filepath
mimetype = 'application/pdf'
except OfficeConverter:
raise UnknownFileFormat('office converter exception')
else:
# Recycle the already detected mimetype
mimetype = office_converter.mimetype
office_converter.convert(input_filepath, mimetype=mimetype)
if office_converter.exists:
input_filepath = office_converter.output_filepath
mimetype = 'application/pdf'
else:
# Recycle the already detected mimetype
mimetype = office_converter.mimetype
except OfficeConversionError:
raise UnknownFileFormat('office converter exception')
if size:
transformations.append(
@@ -98,14 +99,15 @@ def convert(input_filepath, output_filepath=None, cleanup_files=False, mimetype=
def get_page_count(input_filepath):
office_converter = OfficeConverter()
office_converter.convert(input_filepath)
if office_converter:
try:
input_filepath = office_converter.output_filepath
except OfficeConverter:
raise UnknownFileFormat('office converter exception')
office_converter.convert(input_filepath)
if office_converter.exists:
input_filepath = office_converter.output_filepath
except OfficeConversionError:
raise UnknownFileFormat('office converter exception')
return backend.get_page_count(input_filepath)

View File

@@ -5,6 +5,7 @@ DEFAULT_ZOOM_LEVEL = 100
DEFAULT_ROTATION = 0
DEFAULT_PAGE_NUMBER = 1
DEFAULT_FILE_FORMAT = u'jpeg'
DEFAULT_FILE_FORMAT_MIMETYPE = u'image/jpeg'
DIMENSION_SEPARATOR = u'x'

View File

@@ -1,8 +1,10 @@
import os
import subprocess
import logging
from mimetype.api import get_mimetype
from common.conf.settings import TEMPORARY_DIRECTORY
from common.utils import id_generator
from converter.conf.settings import UNOCONV_PATH, UNOCONV_USE_PIPE
from converter.exceptions import (OfficeConversionError,
@@ -26,10 +28,13 @@ CONVERTER_OFFICE_FILE_MIMETYPES = [
'application/vnd.oasis.opendocument.graphics',
]
logger = logging.getLogger(__name__)
class OfficeConverter(object):
def __init__(self):
self.backend_class = OfficeConverterBackendUnoconv
self.backend = self.backend_class()
self.exists = False
self.mimetype = None
self.encoding = None
@@ -38,6 +43,10 @@ class OfficeConverter(object):
return CONVERTER_OFFICE_FILE_MIMETYPES
def convert(self, input_filepath, mimetype=None):
self.exists = False
self.mimetype = None
self.encoding = None
self.input_filepath = input_filepath
# Make sure file is of a known office format
@@ -52,7 +61,6 @@ class OfficeConverter(object):
self.exists = os.path.exists(self.output_filepath)
if not self.exists:
try:
self.backend = self.backend_class()
self.backend.convert(self.input_filepath, self.output_filepath)
self.exists = True
except OfficeBackendError, msg:
@@ -64,11 +72,6 @@ class OfficeConverter(object):
def __str__(self):
return str(self.__unicode__())
def __nonzero__(self):
return self.exists
__bool__ = __nonzero__
class OfficeConverterBackendUnoconv(object):
@@ -78,9 +81,9 @@ class OfficeConverterBackendUnoconv(object):
raise OfficeBackendError('cannot find unoconv executable')
def convert(self, input_filepath, output_filepath):
"""
'''
Executes the program unoconv using subprocess's Popen
"""
'''
self.input_filepath = input_filepath
self.output_filepath = output_filepath
@@ -89,17 +92,22 @@ class OfficeConverterBackendUnoconv(object):
if UNOCONV_USE_PIPE:
command.append(u'--pipe')
command.append(u'mayan')
command.append(u'mayan-%s' % id_generator())
command.append(u'--format=pdf')
command.append(u'--output=%s' % self.output_filepath)
command.append(self.input_filepath)
try:
logger.debug('prev environment: %s' % os.environ)
proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
return_code = proc.wait()
logger.debug('post environment: %s' % os.environ)
readline = proc.stderr.readline()
if return_code != 0:
raise OfficeBackendError(proc.stderr.readline())
except OSError, msg:
raise OfficeBackendError(msg)
except Exception, msg:
logger.error('Unhandled exception: %s' % msg)

View File

@@ -0,0 +1,8 @@
from converter.office_converter import OfficeConverter
from converter.exceptions import OfficeBackendError
try:
office_converter = OfficeConverter()
except OfficeBackendError:
office_converter = None

View File

@@ -18,7 +18,7 @@ from common.literals import PAGE_SIZE_DIMENSIONS, \
PAGE_ORIENTATION_PORTRAIT, PAGE_ORIENTATION_LANDSCAPE
from common.conf.settings import DEFAULT_PAPER_SIZE
from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \
DEFAULT_PAGE_NUMBER
DEFAULT_PAGE_NUMBER, DEFAULT_FILE_FORMAT_MIMETYPE
from converter.office_converter import OfficeConverter
from filetransfers.api import serve_file
from metadata.forms import MetadataFormSet, MetadataSelectionForm
@@ -288,7 +288,7 @@ 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))
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

@@ -37,21 +37,79 @@ def document_html_widget(document, size='document_thumbnail', click_view=None, p
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>')
"""
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))
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 {
$('#document-%(pk)d-%(page)d').html('%(plain_template)s');
}
//$('.fancybox-noscaling').live('click', function(e) {alert("CLICK");});
})
.error(function(data) { alert("error"); })
;
});
</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)),
}
)
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>')
try:
document.get_valid_image()
result.append('<div class="tc">')
if click_view:
result.append('<a %s class="%s" href="%s">' % (gallery_template, fancybox_class, u'%s?%s' % (reverse(click_view, args=[document.pk]), query_string)))
result.append('<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))
result.append('<noscript><img style="border: 1px solid black;" src="%s" alt="%s" /></noscript>' % (preview_view, alt_text))
if click_view:
result.append('</a>')
result.append('</div>')
except (UnknownFileFormat, UnkownConvertError):
result.append('<div class="tc">')
result.append('<img class="lazy-load" data-href="%s" src="%s/images/ajax-loader.gif" alt="%s" />' % (preview_view, settings.STATIC_URL, alt_text))
result.append('<noscript><img src="%s" alt="%s" /></noscript>' % (preview_view, alt_text))
result.append('</div>')
return mark_safe(u''.join(result))

View File

3
apps/rest_api/models.py Normal file
View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,38 @@
from django.core.urlresolvers import reverse
from djangorestframework.resources import ModelResource
from documents.models import Document
from converter.exceptions import UnknownFileFormat, UnkownConvertError
class DocumentResourceSimple(ModelResource):
model = Document
fields = ('url', 'pk', 'document_type', 'uuid', 'date_added', 'description', 'tags', 'comments', 'expensive_methods', 'files')
def files(self, instance):
return [
{
'version': 1,
'mimetype': instance.file_mimetype,
'encoding': instance.file_mime_encoding,
'filename': instance.get_fullname(),
'date_updated': instance.date_updated,
'checksum': instance.checksum,
'size': instance.size,
'exists': instance.exists(),
'pages': [
{
'page_numer': page.page_number,
'page_label': page.page_label,
#'content':
}
for page in instance.documentpage_set.all()
]
}
]
def expensive_methods(self, instance):
return [
{'is_zoomable': reverse('documents-expensive-is_zoomable', args=[instance.pk])},
]

1
apps/rest_api/tests.py Normal file
View File

@@ -0,0 +1 @@

18
apps/rest_api/urls.py Normal file
View File

@@ -0,0 +1,18 @@
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.resources import DocumentResourceSimple
urlpatterns = patterns('',
url(r'^$', APIBase.as_view(), name='api-root'),
url(r'^v0/$', Version_0.as_view(), name='api-version-0'),
# 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'),
)

69
apps/rest_api/views.py Normal file
View File

@@ -0,0 +1,69 @@
'''Views file for the rest_api app'''
import logging
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.core.urlresolvers import reverse
from documents.models import Document
from converter.exceptions import UnknownFileFormat, UnkownConvertError
from djangorestframework.views import View, ModelView, ListModelView, InstanceModelView
from djangorestframework.mixins import InstanceMixin, ReadModelMixin
from djangorestframework.response import Response
from djangorestframework import status
logger = logging.getLogger(__name__)
class ReadOnlyInstanceModelView(InstanceModelView):
allowed_methods = ['GET']
class APIBase(View):
"""This is the REST API for Mayan EDMS (https://github.com/rosarior/mayan/).
All the API calls can be navigated either through the browser or from the command line...
bash: curl -X GET http://127.0.0.1:8000/api/ # (Use default renderer)
bash: curl -X GET http://127.0.0.1:8000/api/ -H 'Accept: text/plain' # (Use plaintext documentation renderer)
"""
def get(self, request):
return [
{'name': 'Version 0 Alpha', 'url': reverse('api-version-0')}
]
class Version_0(View):
def get(self, request):
return [
{'name': 'Resources', 'resources': ['documents/<pk>']}
]
class IsZoomable(View):
def get(self, request, pk):
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
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}

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,7 @@
{% endcompress %}
{% compress js %}
<script type="text/javascript" charset="utf-8" src="{{ STATIC_URL }}web_theme_media/javascripts/jquery-1.5.2.min.js"></script>
<script type="text/javascript" charset="utf-8" src="{{ STATIC_URL }}web_theme_media/javascripts/jquery-1.7.min.js"></script>
{% if enable_scroll_js %}
<script type="text/javascript" charset="utf-8" src="{{ STATIC_URL }}web_theme_media/javascripts/jquery.scrollTo.js"></script>#}

View File

@@ -19,3 +19,4 @@ Pillow==1.7.4
cssmin==0.1.4
django-compressor==1.1
-e git://github.com/rosarior/django-sendfile.git#egg=django-sendfile
djangorestframework==0.2.3

View File

@@ -16,3 +16,4 @@ Pillow==1.7.4
cssmin==0.1.4
django-compressor==1.1
-e git://github.com/rosarior/django-sendfile.git#egg=django-sendfile
djangorestframework==0.2.3

View File

@@ -81,7 +81,8 @@ STATIC_URL = '/%s-static/' % PROJECT_NAME
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = STATIC_URL + 'grappelli/'
#ADMIN_MEDIA_PREFIX = STATIC_URL + 'grappelli/'
ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/'
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'om^a(i8^6&h+umbd2%pt91cj!qu_@oztw117rgxmn(n2lp^*c!'
@@ -117,7 +118,7 @@ TEMPLATE_DIRS = (
)
INSTALLED_APPS = (
'grappelli',
#'grappelli',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -164,6 +165,8 @@ INSTALLED_APPS = (
'history',
'main',
'compressor',
'djangorestframework',
'rest_api',
)
TEMPLATE_CONTEXT_PROCESSORS = (
@@ -286,7 +289,7 @@ CELERY_DISABLE_RATE_LIMITS = True
#--------- Web theme ---------------
WEB_THEME_ENABLE_SCROLL_JS = False
#--------- Grappelli ----------------
GRAPPELLI_ADMIN_TITLE = PROJECT_TITLE
#GRAPPELLI_ADMIN_TITLE = PROJECT_TITLE
#--------- Django -------------------
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'

View File

@@ -15,7 +15,7 @@ urlpatterns = patterns('',
(r'^tags/', include('tags.urls')),
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/', include(admin.site.urls)),
(r'^grappelli/', include('grappelli.urls')),
#(r'^grappelli/', include('grappelli.urls')),
(r'^sentry/', include('sentry.urls')),
(r'^comments/', include('document_comments.urls')),
(r'^user_management/', include('user_management.urls')),
@@ -28,6 +28,7 @@ urlpatterns = patterns('',
(r'^sources/', include('sources.urls')),
(r'^project_setup/', include('project_setup.urls')),
(r'^project_tools/', include('project_tools.urls')),
(r'^api/', include('rest_api.urls')),
)