Skip to content

Commit

Permalink
Merge f0f6d7b into 796377e
Browse files Browse the repository at this point in the history
  • Loading branch information
jvamvas committed Sep 18, 2015
2 parents 796377e + f0f6d7b commit 0746e91
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 6 deletions.
19 changes: 19 additions & 0 deletions docs/installation.rst
Expand Up @@ -110,6 +110,25 @@ secure downloads

See :ref:`secure_downloads` section.

Canonical URLs
..............

You can configure your project to generate canonical URLs for your public files. Just include django-filer's
URLConf in your project's ``urls.py``::

urlpatterns = [
...
url(r'^filer/', include('filer.urls')),
...
]

Contrary to the file's actual URL, the canonical URL does not change if you upload a new version of the file.
Thus, you can safely share the canonical URL. As long as the file exists, people will be redirected to its
latest version.

The canonical URL is displayed in the "advanced" panel on the file's admin page.


debugging and logging
.....................

Expand Down
13 changes: 11 additions & 2 deletions filer/admin/fileadmin.py
Expand Up @@ -23,7 +23,7 @@ class FileAdmin(PrimitivePermissionAwareModelAdmin):
list_per_page = 10
search_fields = ['name', 'original_filename', 'sha1', 'description']
raw_id_fields = ('owner',)
readonly_fields = ('sha1',)
readonly_fields = ('sha1', 'display_canonical')

# save_as hack, because without save_as it is impossible to hide the
# save_and_add_another if save_as is False. To show only save_and_continue
Expand All @@ -45,7 +45,7 @@ def build_fieldsets(cls, extra_main_fields=(), extra_advanced_fields=(), extra_f
'fields': ('name', 'owner', 'description',) + extra_main_fields,
}),
(_('Advanced'), {
'fields': ('file', 'sha1',) + extra_advanced_fields,
'fields': ('file', 'sha1', 'display_canonical') + extra_advanced_fields,
'classes': ('collapse',),
}),
) + extra_fieldsets
Expand Down Expand Up @@ -141,4 +141,13 @@ def get_model_perms(self, request):
'delete': False,
}

def display_canonical(self, instance):
canonical = instance.canonical_url
if canonical:
return '<a href="%s">%s</a>' % (canonical, canonical)
else:
return '-'
display_canonical.allow_tags = True
display_canonical.short_description = _('canonical URL')

FileAdmin.fieldsets = FileAdmin.build_fieldsets()
13 changes: 13 additions & 0 deletions filer/models/filemodels.py
Expand Up @@ -240,6 +240,19 @@ def url(self):
r = ''
return r

@property
def canonical_url(self):
url = ''
if self.file and self.is_public:
try:
url = urlresolvers.reverse('canonical', kwargs={
'uploaded_at': self.uploaded_at.strftime('%s'),
'file_id': self.id
})
except urlresolvers.NoReverseMatch:
pass # No canonical url, return empty string
return url

@property
def path(self):
try:
Expand Down
1 change: 1 addition & 0 deletions filer/test_utils/templates/404.html
@@ -0,0 +1 @@
404
1 change: 1 addition & 0 deletions filer/test_utils/urls.py
Expand Up @@ -10,4 +10,5 @@
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('filer.server.urls')),
url(r'^filer/', include('filer.urls')),
)
38 changes: 38 additions & 0 deletions filer/tests/models.py
Expand Up @@ -223,3 +223,41 @@ def test_custom_model(self):

reloaded = Image.objects.get(pk=image.pk)
self.assertEqual(reloaded.author, image.author)

def test_canonical_url(self):
"""
Check that a public file's canonical url redirects to the file's current version
"""
image = self.create_filer_image()
image.save()
# Private file
image.is_public = False
image.save()
canonical = image.canonical_url
self.assertEqual(self.client.get(canonical).status_code, 404)
# First public version
image.is_public = True
image.save()
canonical = image.canonical_url
file_url_1 = image.file.url
self.assertRedirects(self.client.get(canonical), file_url_1)
# Second public version
img_2 = create_image()
image_name_2 = 'test_file_2.jpg'
filename_2 = os.path.join(settings.FILE_UPLOAD_TEMP_DIR, image_name_2)
img_2.save(filename_2, 'JPEG')
file_2 = DjangoFile(open(filename_2, 'rb'), name=image_name_2)
image.file = file_2
image.save()
file_url_2 = image.file.url
self.assertNotEqual(file_url_1, file_url_2)
self.assertRedirects(self.client.get(canonical), file_url_2)
# No file
image.file = None
image.save()
self.assertEqual(self.client.get(canonical).status_code, 404)
# Teardown
image.file = file_2
image.save()
os.remove(filename_2)

8 changes: 8 additions & 0 deletions filer/urls.py
@@ -0,0 +1,8 @@
#-*- coding: utf-8 -*-
from django.conf.urls import url

from filer import views

urlpatterns = [
url(r'canonical/(?P<uploaded_at>[0-9]+)/(?P<file_id>[0-9]+)/$', views.canonical, name='canonical'),
]
16 changes: 13 additions & 3 deletions filer/views.py
Expand Up @@ -5,11 +5,11 @@
from django.contrib.admin import widgets
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as _

from .models import Folder, Image, Clipboard, tools, FolderRoot
from .models import Folder, File, Image, Clipboard, tools, FolderRoot
from . import settings as filer_settings


Expand Down Expand Up @@ -57,6 +57,16 @@ def _userperms(item, request):
return r


def canonical(request, uploaded_at, file_id):
"""
Redirect to the current url of a public file
"""
filer_file = get_object_or_404(File, pk=file_id, is_public=True)
if uploaded_at != filer_file.uploaded_at.strftime('%s') or not filer_file.file:
raise Http404('No %s matches the given query.' % File._meta.object_name)
return redirect(filer_file.url)


@login_required
def edit_folder(request, folder_id):
# TODO: implement edit_folder view
Expand Down
5 changes: 4 additions & 1 deletion test_settings.py
Expand Up @@ -4,6 +4,8 @@

gettext = lambda s: s

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

HELPER_SETTINGS = {
'NOSE_ARGS': [
'-s',
Expand Down Expand Up @@ -50,7 +52,8 @@
'easy_thumbnails.processors.filters',
),
'FILE_UPLOAD_TEMP_DIR': mkdtemp(),
'FILER_IMAGE_MODEL': False
'FILER_IMAGE_MODEL': False,
'TEMPLATE_DIRS': (os.path.join(BASE_DIR, 'django-filer', 'filer', 'test_utils', 'templates'),),

}
if os.environ.get('CUSTOM_IMAGE', False):
Expand Down

0 comments on commit 0746e91

Please sign in to comment.