diff --git a/mayan/apps/appearance/static/appearance/css/base.css b/mayan/apps/appearance/static/appearance/css/base.css
index e353760ec5..5504f4c49d 100644
--- a/mayan/apps/appearance/static/appearance/css/base.css
+++ b/mayan/apps/appearance/static/appearance/css/base.css
@@ -102,15 +102,6 @@ img.lazy-load-carousel {
height: 0px;
}
-.tc {
- margin: auto;
- text-align: center;
-}
-
-.tc a {
- text-align: center;
-}
-
.img-nolazyload {
border: 1px solid black;
}
@@ -127,6 +118,10 @@ img.lazy-load-carousel {
visibility: visible;
}
+.instance-image-widget {
+ text-align: center;
+}
+
hr {
margin-top: 5px;
margin-bottom: 11px;
@@ -148,10 +143,6 @@ hr {
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
}
-.a-caption {
- color: white;
-}
-
.radio ul li {
list-style-type:none;
}
diff --git a/mayan/apps/appearance/static/appearance/js/base.js b/mayan/apps/appearance/static/appearance/js/base.js
index 21b59d93d7..2a125b2ad8 100644
--- a/mayan/apps/appearance/static/appearance/js/base.js
+++ b/mayan/apps/appearance/static/appearance/js/base.js
@@ -65,29 +65,6 @@ jQuery(document).ready(function() {
autoResize : true,
});
- $('a.fancybox-staging').click(function(e) {
- var $this = $(this);
-
- $.get($this.attr('href'), function( result ) {
- if (result.status == 'success') {
- $.fancybox.open([
- {
- href : result.data,
- title : $this.attr('title'),
- openEffect : 'elastic',
- closeEffect : 'elastic',
- prevEffect : 'none',
- nextEffect : 'none',
- titleShow : true,
- type : 'image',
- autoResize : true,
- },
- ]);
- }
- })
- e.preventDefault();
- })
-
$('img.lazy-load').lazyload({
appear: function(elements_left, settings) {
loadImage($(this));
diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py
index c32668a2c7..b953ef3ba4 100644
--- a/mayan/apps/documents/apps.py
+++ b/mayan/apps/documents/apps.py
@@ -70,14 +70,13 @@ from .permissions import (
)
# Just import to initialize the search models
from .search import document_search, document_page_search # NOQA
-from .settings import setting_display_size, setting_thumbnail_size
from .statistics import (
new_documents_per_month, new_document_pages_per_month,
new_document_pages_this_month, new_documents_this_month,
new_document_versions_per_month, total_document_per_month,
total_document_page_per_month, total_document_version_per_month
)
-from .widgets import document_page_html_widget
+from .widgets import DocumentThumbnailWidget, DocumentPageThumbnailWidget
class DocumentsApp(MayanAppConfig):
@@ -187,17 +186,14 @@ class DocumentsApp(MayanAppConfig):
model=DocumentPage, related='document',
)
+ # Document and document page thumbnail widget
+ document_thumbnail_widget = DocumentThumbnailWidget()
+ document_page_thumbnail_widget = DocumentPageThumbnailWidget()
+
SourceColumn(
source=Document, label=_('Thumbnail'),
- func=lambda context: document_page_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),
+ func=lambda context: document_thumbnail_widget.render(
+ instance=context['object']
)
)
SourceColumn(
@@ -206,29 +202,18 @@ class DocumentsApp(MayanAppConfig):
SourceColumn(
source=DocumentPage, label=_('Thumbnail'),
- func=lambda context: document_page_html_widget(
- document_page=context['object'],
- 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,
- title=unicode(context['object']),
+ func=lambda context: document_page_thumbnail_widget.render(
+ instance=context['object']
)
)
SourceColumn(
source=DocumentPageResult, label=_('Thumbnail'),
- func=lambda context: document_page_html_widget(
- document_page=context['object'],
- 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,
- title=unicode(context['object']),
+ func=lambda context: document_page_thumbnail_widget.render(
+ instance=context['object']
)
)
+
SourceColumn(
source=DocumentPageResult, label=_('Type'),
attribute='document_version.document.document_type'
@@ -248,18 +233,11 @@ class DocumentsApp(MayanAppConfig):
SourceColumn(
source=DeletedDocument, label=_('Thumbnail'),
- func=lambda context: document_page_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),
- disable_title_link=True
+ func=lambda context: document_thumbnail_widget.render(
+ instance=context['object']
)
)
+
SourceColumn(
source=DeletedDocument, label=_('Type'), attribute='document_type'
)
diff --git a/mayan/apps/documents/settings.py b/mayan/apps/documents/settings.py
index 2c37e86bb2..e2e9fb4308 100644
--- a/mayan/apps/documents/settings.py
+++ b/mayan/apps/documents/settings.py
@@ -15,12 +15,11 @@ LANGUAGE_CHOICES = [
]
namespace = Namespace(name='documents', label=_('Documents'))
-setting_storage_backend = namespace.add_setting(
- global_name='DOCUMENTS_STORAGE_BACKEND',
- default='storage.backends.filebasedstorage.FileBasedStorage'
+setting_display_size = namespace.add_setting(
+ global_name='DOCUMENTS_DISPLAY_SIZE', default='3600'
)
setting_preview_size = namespace.add_setting(
- global_name='DOCUMENTS_PREVIEW_SIZE', default='640x480'
+ global_name='DOCUMENTS_PREVIEW_SIZE', default='800'
)
setting_print_size = namespace.add_setting(
global_name='DOCUMENTS_PRINT_SIZE', default='3600'
@@ -28,9 +27,6 @@ setting_print_size = namespace.add_setting(
setting_thumbnail_size = namespace.add_setting(
global_name='DOCUMENTS_THUMBNAIL_SIZE', default='50x50'
)
-setting_display_size = namespace.add_setting(
- global_name='DOCUMENTS_DISPLAY_SIZE', default='3600'
-)
setting_recent_count = namespace.add_setting(
global_name='DOCUMENTS_RECENT_COUNT', default=40,
help_text=_(
@@ -38,6 +34,10 @@ setting_recent_count = namespace.add_setting(
'remember per user.'
)
)
+setting_storage_backend = namespace.add_setting(
+ global_name='DOCUMENTS_STORAGE_BACKEND',
+ default='storage.backends.filebasedstorage.FileBasedStorage'
+)
setting_zoom_percent_step = namespace.add_setting(
global_name='DOCUMENTS_ZOOM_PERCENT_STEP', default=25,
help_text=_(
diff --git a/mayan/apps/documents/widgets.py b/mayan/apps/documents/widgets.py
index 49fccc9564..31a9dd0eef 100644
--- a/mayan/apps/documents/widgets.py
+++ b/mayan/apps/documents/widgets.py
@@ -7,9 +7,9 @@ from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext, ugettext_lazy as _
-from converter.literals import DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
-
-from .settings import setting_display_size, setting_thumbnail_size
+from .settings import (
+ setting_display_size, setting_preview_size, setting_thumbnail_size
+)
class DocumentPageImageWidget(forms.widgets.Widget):
@@ -17,15 +17,19 @@ class DocumentPageImageWidget(forms.widgets.Widget):
final_attrs = self.build_attrs(attrs)
zoom = final_attrs.get('zoom')
rotation = final_attrs.get('rotation')
+
+ html_widget = InteractiveDocumentPageWidget()
+
if value:
output = []
output.append(
'
'
)
- output.append(document_page_html_widget(
- value, zoom=zoom, rotation=rotation, image_class='lazy-load',
- nolazyload=False, size=setting_display_size.value)
+ output.append(
+ html_widget.render(
+ instance=value, zoom=zoom, rotation=rotation,
+ )
)
output.append('
')
return mark_safe(''.join(output))
@@ -38,6 +42,8 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
Display many small representations of a document pages
"""
def render(self, name, value, attrs=None):
+ html_widget = CarouselDocumentPageThumbnailWidget()
+
output = []
output.append(
'')
output.append(
- document_page_html_widget(
- document_page=document_page,
- click_view='documents:document_page_view',
- click_view_arguments=(document_page.pk,),
- fancybox_class='',
- image_class='lazy-load-carousel',
- size=setting_display_size.value,
- )
+ html_widget.render(instance=document_page)
)
+
output.append(
'
%s
' % ugettext(
'Page %(page_number)d of %(total_pages)d'
@@ -83,98 +83,212 @@ def document_link(document):
)
-def document_page_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, disable_title_link=False, preview_click_view=None, click_view_querydict=None, click_view_arguments_lazy=None):
- result = []
+class InstanceImageWidget(object):
+ alt_text = _('Clickable image')
+ click_view_name = None
+ click_view_query_dict = {}
+ destination_view_name = None
+ destination_view_query_dict = {}
+ disable_title_link = False
+ fancybox_class = 'fancybox'
+ gallery_name = None
+ # TODO: update this to load a disk template
+ invalid_image_template = '
Invalid image
'
+ preview_view_name = None
+ preview_query_dict = {}
+ image_class = 'lazy-load'
+ title = None
- alt_text = _('Document page image')
+ # Click view
+ def get_click_view_kwargs(self, instance):
+ return {
+ 'pk': instance.pk
+ }
- if not document_page:
- return mark_safe(
- '
'
+ def get_click_view_query_dict(self, instance):
+ return self.click_view_query_dict
+
+ def get_click_view_querystring(self, instance):
+ return urlencode(self.get_click_view_query_dict(instance=instance))
+
+ def get_click_view_url(self, instance):
+ return '{}?{}'.format(
+ reverse(
+ viewname=self.click_view_name,
+ kwargs=self.get_click_view_kwargs(instance=instance)
+ ),
+ self.get_click_view_querystring(instance=instance)
)
- document = document_page.document
+ # Destination view
+ def get_destination_view_querystring(self, instance):
+ return urlencode(self.get_destination_view_query_dict(instance=instance))
- query_dict = {
- 'zoom': zoom or DEFAULT_ZOOM_LEVEL,
- 'rotation': rotation or DEFAULT_ROTATION,
- 'size': size,
- 'page': document_page.page_number
- }
-
- if gallery_name:
- gallery_template = 'rel="%s"' % gallery_name
- else:
- gallery_template = ''
-
- query_string = urlencode(query_dict)
-
- preview_view = '%s?%s' % (
- reverse('rest_api:documentpage-image', args=(document_page.pk,)),
- query_string
- )
-
- result.append(
- '
' % (
- document.pk, document_page.page_number if document_page.page_number else 1
+ def get_destination_url(self, instance):
+ return '{}?{}'.format(
+ reverse(
+ viewname=self.destination_view_name,
+ kwargs=self.get_destination_view_kwargs(instance=instance)
+ ),
+ self.get_destination_view_querystring(instance=instance)
)
- )
- if title:
- if not disable_title_link:
- if not preview_click_view:
- preview_click_link = document.get_absolute_url()
+ def get_destination_view_kwargs(self, instance):
+ return {
+ 'pk': instance.pk
+ }
+
+ # Preview view
+ def get_preview_view_kwargs(self, instance):
+ return {
+ 'pk': instance.pk
+ }
+
+ def get_preview_view_query_dict(self, instance):
+ return self.preview_view_query_dict
+
+ def get_preview_view_querystring(self, instance):
+ return urlencode(self.get_preview_view_query_dict(instance=instance))
+
+ def get_preview_view_url(self, instance):
+ return '{}?{}'.format(
+ reverse(
+ viewname=self.preview_view_name,
+ kwargs=self.get_preview_view_kwargs(instance=instance)
+ ),
+ self.get_preview_view_querystring(instance=instance)
+ )
+
+ def get_title(self, instance):
+ return self.title
+
+ def is_valid(self, instance):
+ return instance
+
+ def render(self, instance):
+ result = []
+
+ result.append('
')
- if nolazyload:
- result.append(
- '

' % (
- preview_view, alt_text
- )
- )
- else:
- result.append(
- '
'
- '
![{}]()
'.format(
- image_class, preview_view,
- '', alt_text
- )
- )
+ return mark_safe(''.join(result))
- if click_view:
- result.append('')
- result.append('
')
- return mark_safe(''.join(result))
+class BaseDocumentThumbnailWidget(InstanceImageWidget):
+ alt_text = _('Document page image')
+ click_view_name = 'rest_api:documentpage-image'
+ click_view_query_dict = {
+ 'size': setting_preview_size.value
+ }
+ gallery_name = 'document_list'
+ invalid_image_template = """
+
+ """
+ preview_view_name = 'rest_api:documentpage-image'
+ preview_view_query_dict = {
+ 'size': setting_thumbnail_size.value
+ }
+
+ def get_destination_url(self, instance):
+ return instance.get_absolute_url()
+
+
+class CarouselDocumentPageThumbnailWidget(BaseDocumentThumbnailWidget):
+ click_view_name = 'documents:document_page_view'
+ fancybox_class = ''
+ image_class = 'lazy-load-carousel'
+ preview_view_query_dict = {
+ 'size': setting_display_size.value
+ }
+
+
+class DocumentThumbnailWidget(BaseDocumentThumbnailWidget):
+ def get_click_view_kwargs(self, instance):
+ return {
+ 'pk': instance.latest_version.pages.first().pk
+ }
+
+ def get_preview_view_kwargs(self, instance):
+ return {
+ 'pk': instance.latest_version.pages.first().pk
+ }
+
+ def get_title(self, instance):
+ return getattr(instance, 'label', None)
+
+ def is_valid(self, instance):
+ return instance.latest_version.pages.all()
+
+
+class DocumentPageThumbnailWidget(BaseDocumentThumbnailWidget):
+ def get_title(self, instance):
+ return unicode(instance)
+
+
+class InteractiveDocumentPageWidget(BaseDocumentThumbnailWidget):
+ click_view_name = None
+
+ def get_preview_view_query_dict(self, instance):
+ return {
+ 'zoom': self.zoom,
+ 'rotation': self.rotation,
+ 'size': setting_display_size.value,
+ }
+
+ def render(self, instance, *args, **kwargs):
+ self.zoom = kwargs.pop('zoom')
+ self.rotation = kwargs.pop('rotation')
+
+ return super(
+ InteractiveDocumentPageWidget, self
+ ).render(instance=instance, *args, **kwargs)
diff --git a/mayan/apps/sources/api_views.py b/mayan/apps/sources/api_views.py
index c825a4bae2..36ae11c3a3 100644
--- a/mayan/apps/sources/api_views.py
+++ b/mayan/apps/sources/api_views.py
@@ -1,19 +1,14 @@
from __future__ import unicode_literals
+from django.http import HttpResponse
from django.shortcuts import get_object_or_404
-from converter.exceptions import UnkownConvertError, UnknownFileFormat
from converter.models import Transformation
from rest_framework import generics
from rest_framework.response import Response
-from documents.settings import setting_display_size
-
from .models import StagingFolderSource
-from .serializers import (
- StagingFolderFileSerializer,
- StagingFolderSerializer, StagingSourceFileImageSerializer
-)
+from .serializers import StagingFolderFileSerializer, StagingFolderSerializer
class APIStagingSourceFileView(generics.GenericAPIView):
@@ -51,47 +46,37 @@ class APIStagingSourceView(generics.RetrieveAPIView):
queryset = StagingFolderSource.objects.all()
-class APIStagingSourceFileImageView(generics.GenericAPIView):
+class APIStagingSourceFileImageView(generics.RetrieveAPIView):
"""
- Image of the selected staging file.
- size -- 'x' seprated width and height of the desired image representation.
- page -- Page number of the staging file to be imaged.
- zoom -- Zoom level of the image to be generated, numeric value only.
+ Returns an image representation of the selected document.
+ ---
+ GET:
+ omit_serializer: true
+ parameters:
+ - name: size
+ description: 'x' seprated width and height of the desired image representation.
+ paramType: query
+ type: number
"""
- serializer_class = StagingSourceFileImageSerializer
+ def get_serializer_class(self):
+ return None
- def get(self, request, staging_folder_pk, encoded_filename):
+ def retrieve(self, request, *args, **kwargs):
staging_folder = get_object_or_404(
- StagingFolderSource, pk=staging_folder_pk
+ StagingFolderSource, pk=self.kwargs['staging_folder_pk']
)
staging_file = staging_folder.get_file(
- encoded_filename=encoded_filename
+ encoded_filename=self.kwargs['encoded_filename']
)
- size = request.GET.get('size', setting_display_size.value)
+ size = request.GET.get('size')
- try:
- return Response({
- 'status': 'success',
- 'data': staging_file.get_image(
- as_base64=True, size=size,
- transformations=Transformation.objects.get_for_model(
- staging_folder, as_classes=True
- )
+ return HttpResponse(
+ staging_file.get_image(
+ size=size,
+ transformations=Transformation.objects.get_for_model(
+ staging_folder, as_classes=True
)
- })
- except UnknownFileFormat as exception:
- return Response(
- {
- 'status': 'error', 'detail': 'unknown_file_format',
- 'message': unicode(exception)
- }
- )
- except UnkownConvertError as exception:
- return Response(
- {
- 'status': 'error', 'detail': 'converter_error',
- 'message': unicode(exception)
- }
- )
+ ), content_type='image'
+ )
diff --git a/mayan/apps/sources/apps.py b/mayan/apps/sources/apps.py
index 7b99b54a53..fd502718ae 100644
--- a/mayan/apps/sources/apps.py
+++ b/mayan/apps/sources/apps.py
@@ -29,7 +29,7 @@ from .links import (
link_setup_source_edit, link_setup_source_logs, link_staging_file_delete,
link_upload_version
)
-from .widgets import staging_file_thumbnail
+from .widgets import StagingFileThumbnailWidget
class SourcesApp(MayanAppConfig):
@@ -67,13 +67,12 @@ class SourcesApp(MayanAppConfig):
func=lambda context: context['object'].get_date_time_created()
)
+ html_widget = StagingFileThumbnailWidget()
SourceColumn(
source=StagingFile,
label=_('Thumbnail'),
- func=lambda context: staging_file_thumbnail(
- context['object'],
- gallery_name='sources:staging_list',
- title=context['object'].filename, size='100'
+ func=lambda context: html_widget.render(
+ instance=context['object'],
)
)
diff --git a/mayan/apps/sources/classes.py b/mayan/apps/sources/classes.py
index e53fdb6399..beea517edf 100644
--- a/mayan/apps/sources/classes.py
+++ b/mayan/apps/sources/classes.py
@@ -71,7 +71,7 @@ class StagingFile(object):
def get_full_path(self):
return os.path.join(self.staging_folder.folder_path, self.filename)
- def get_image(self, size=None, as_base64=True, transformations=None):
+ def get_image(self, size=None, as_base64=False, transformations=None):
converter = converter_class(file_object=open(self.get_full_path()))
if size:
diff --git a/mayan/apps/sources/serializers.py b/mayan/apps/sources/serializers.py
index 1b2e9d58f5..9d450bbaf1 100644
--- a/mayan/apps/sources/serializers.py
+++ b/mayan/apps/sources/serializers.py
@@ -46,11 +46,6 @@ class StagingFolderSerializer(serializers.HyperlinkedModelSerializer):
model = StagingFolderSource
-class StagingSourceFileImageSerializer(serializers.Serializer):
- status = serializers.CharField()
- data = serializers.CharField()
-
-
class WebFormSourceSerializer(serializers.Serializer):
class Meta:
model = WebFormSource
diff --git a/mayan/apps/sources/widgets.py b/mayan/apps/sources/widgets.py
index 283624010a..696b77de06 100644
--- a/mayan/apps/sources/widgets.py
+++ b/mayan/apps/sources/widgets.py
@@ -1,105 +1,32 @@
from __future__ import unicode_literals
-from django.contrib.staticfiles.templatetags.staticfiles import static
-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_lazy as _
-
-from converter.literals import (
- DEFAULT_PAGE_NUMBER, DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
-)
from documents.settings import setting_preview_size, setting_thumbnail_size
+from documents.widgets import BaseDocumentThumbnailWidget
-def staging_file_thumbnail(staging_file, **kwargs):
- return staging_file_html_widget(
- staging_file, click_view='rest_api:stagingfolderfile-image-view',
- **kwargs
- )
-
-
-def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox-staging', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False):
- result = []
-
- alt_text = _('Staging file page image')
-
- query_dict = {
- 'page': page,
- 'zoom': zoom,
- 'rotation': rotation,
- 'size': size,
+class StagingFileThumbnailWidget(BaseDocumentThumbnailWidget):
+ disable_title_link = True
+ gallery_name = 'sources:staging_list'
+ click_view_name = 'rest_api:stagingfolderfile-image-view'
+ click_view_query_dict = {
+ 'size': setting_preview_size.value
+ }
+ preview_view_name = 'rest_api:stagingfolderfile-image-view'
+ preview_view_query_dict = {
+ 'size': setting_thumbnail_size.value
}
- if gallery_name:
- gallery_template = 'rel="%s"' % gallery_name
- else:
- gallery_template = ''
+ def get_click_view_kwargs(self, instance):
+ return {
+ 'staging_folder_pk': instance.staging_folder.pk,
+ 'encoded_filename': instance.encoded_filename
+ }
- query_string = urlencode(query_dict)
+ def get_preview_view_kwargs(self, instance):
+ return {
+ 'staging_folder_pk': instance.staging_folder.pk,
+ 'encoded_filename': instance.encoded_filename
+ }
- preview_view = '%s?%s' % (
- reverse(
- 'rest_api:stagingfolderfile-image-view',
- args=(
- staging_file.staging_folder.pk, staging_file.encoded_filename,
- )
- ), query_string
- )
-
- plain_template = []
- plain_template.append(
- '

' % (preview_view, alt_text)
- )
-
- result.append(
- '
' % (
- staging_file.filename, page if page else DEFAULT_PAGE_NUMBER
- )
- )
-
- if title:
- title_template = 'title="%s"' % strip_tags(title)
- else:
- title_template = ''
-
- if click_view:
- # TODO: fix this hack
- query_dict['size'] = setting_preview_size.value
- query_string = urlencode(query_dict)
- result.append(
- '
' % (
- gallery_template, fancybox_class,
- '%s?%s' % (
- reverse(
- click_view,
- args=(
- staging_file.staging_folder.pk,
- staging_file.encoded_filename,
- )
- ),
- query_string
- ), title_template
- )
- )
-
- if nolazyload:
- result.append(
- '
' % (
- preview_view, alt_text
- )
- )
- else:
- result.append(
- '
' % (
- image_class, preview_view,
- static('appearance/images/loading.png'), alt_text
- )
- )
-
- if click_view:
- result.append('')
- result.append('
')
-
- return mark_safe(''.join(result))
+ def get_title(self, instance):
+ return instance.filename