diff --git a/.gitignore b/.gitignore index 8afdc75..bc3265e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ python *.egg-info *.pyc example/db.sqlite +example/media diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..8c38f3d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Andrey Podoplelov +Evgeny V. Generalov +Ilya Shalyapin \ No newline at end of file diff --git a/LICENSE b/LICENSE index ea36deb..646c313 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2011 Andrey Podoplelov and Evgeny V. Generalov. All rights reserved. +Copyright 2011. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -10,7 +10,7 @@ modification, are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY ANDREY PODOPLELOV AND EVGENY V. GENERALOV ``AS +THIS SOFTWARE IS PROVIDED BY DJANGO RESUBMIT TEAM ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANDREY PODOPLELOV AND EVGENY V. GENERALOV OR @@ -24,4 +24,4 @@ OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, -either expressed or implied, of Andrey Podoplelov and Evgeny V. Generalov. +either expressed or implied, of Django Resubmit Team. diff --git a/README.rst b/README.rst index f005a6b..1763f1c 100644 --- a/README.rst +++ b/README.rst @@ -5,21 +5,31 @@ Django. Status and License ================== -It is written by Andrey Podoplelov and Evgeny V. Generalov. It is licensed under -an Simplified BSD License. +It is licensed under an Simplified BSD License. + + +Configuration +============= +Add 'django_resubmit' to the INSTALLED_APPS section in your django settings file. + +Also you can setup cache backend for you project in settings.py, for example:: + + DJANGO_RESUBMIT_BACKEND = "file:///tmp/?timeout=600" + +For more information about cache see Django documentation. Usage ===== -Supply FileFiled with custom FileCacheWidget widget:: +Supply FileFiled with custom FileWidget widget:: from django import forms - from django_resubmit.forms.widgets import FileCacheWidget + from django_resubmit.forms.widgets import FileWidget class SampleForm(forms.Form): name = forms.CharField(max_length=25) - file = forms.FileField(widget=FileCacheWidget) + file = forms.FileField(widget=FileWidget(thumb_size=[50,50])) What It Does @@ -33,6 +43,12 @@ random key and injects this key as hidden field into the form then ValidationError is occured. When user resubmits the form It restores the file from the cache and put it into the ``request.FILES``. +It automatically generates and shows thumbnails for uploaded image files. You +can easily extend it to show video, flash, etc. + +It makes Javascript image preview for just selected(not uploaded) files. Works +in Chrome, Firefox and IE. + How To Run Tests ================ @@ -49,9 +65,6 @@ Use virtualenv:: Bugs and TODO ============= -* Separate FileCacheWidget from django.contrib.admin -* Handle large files (larger than FILE_UPLOAD_MAX_MEMORY_SIZE) -* Add support for ImageField * Write documentation * Commit into the Django diff --git a/django_resubmit/fixtures/test-image.png b/django_resubmit/fixtures/test-image.png new file mode 100644 index 0000000..f5dca32 Binary files /dev/null and b/django_resubmit/fixtures/test-image.png differ diff --git a/django_resubmit/forms/widgets.py b/django_resubmit/forms/widgets.py index c3cb8e0..23a2355 100644 --- a/django_resubmit/forms/widgets.py +++ b/django_resubmit/forms/widgets.py @@ -1,42 +1,48 @@ -# -*- coding: utf-8 -*- +import random import string import time -import random +from django.conf import settings from django.contrib.admin.widgets import AdminFileWidget -from django.forms.fields import FileField from django.forms.widgets import HiddenInput, CheckboxInput from django.forms.widgets import FILE_INPUT_CONTRADICTION from django.forms.util import flatatt from django.utils.safestring import mark_safe from django.utils.html import escape -from django.utils.encoding import StrAndUnicode, force_unicode +from django.utils.encoding import force_unicode -from django_resubmit.storage import TemporaryFileStorage +from django_resubmit.storage import get_default_storage +from django_resubmit.thumbnails import ThumbFabrica FILE_INPUT_CLEAR = False -class FileCacheWidget(AdminFileWidget): +class FileWidget(AdminFileWidget): + + class Media: + js = (settings.STATIC_URL + 'django_resubmit/jquery.input_image_preview.js',) def __init__(self, *args, **kwargs): - backend = kwargs.pop("backend", None) - self.storage = TemporaryFileStorage(backend=backend) + """ + thumb_size - preview image size, default [50,50]. + """ + self.thumb_size = kwargs.pop("thumb_size", [50, 50]) + self.set_storage(get_default_storage()) self.hidden_keyname = u"" self.hidden_key = u"" self.isSaved = False - super(FileCacheWidget, self).__init__(*args, **kwargs) + super(FileWidget, self).__init__(*args, **kwargs) def value_from_datadict(self, data, files, name): self.hidden_keyname = "%s-cachekey" % name self.hidden_key = data.get(self.hidden_keyname, "") - upload = super(FileCacheWidget, self).value_from_datadict(data, files, name) + upload = super(FileWidget, self).value_from_datadict(data, files, name) # user checks 'clear' or checks 'clear' and uploads a file if upload is FILE_INPUT_CLEAR or upload is FILE_INPUT_CONTRADICTION: if self.hidden_key: - self.storage.clear_file(self.hidden_key) + self._storage.clear_file(self.hidden_key) self.hidden_key = u"" return upload @@ -45,10 +51,13 @@ def value_from_datadict(self, data, files, name): # generate random key self.hidden_key = string.replace(unicode(random.random() * time.time()), ".", "") upload = files[name] + upload.file.seek(0) - self.storage.put_file(self.hidden_key, upload) + + self._storage.put_file(self.hidden_key, upload) + elif self.hidden_key: - restored = self.storage.get_file(self.hidden_key, name) + restored = self._storage.get_file(self.hidden_key, name) if restored: upload = restored files[name] = upload @@ -66,12 +75,7 @@ def render(self, name, value, attrs=None): } template = u'%(input)s' - if value is None: - value = '' final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) - #if value != '' and self.hidden_key: - # # Only add the 'value' attribute if a value is non-empty. - # final_attrs['value'] = force_unicode(self._format_value(value)) substitutions['input'] = mark_safe(u'' % flatatt(final_attrs)) if value and hasattr(value, "url"): @@ -92,8 +96,36 @@ def render(self, name, value, attrs=None): html = template % substitutions if self.hidden_key: - hi = HiddenInput() - html += hi.render(self.hidden_keyname, self.hidden_key, {}) + key_hidden_input = HiddenInput() + html += key_hidden_input.render(self.hidden_keyname, self.hidden_key, {}) + + if value: + thumb = ThumbFabrica(value, self).thumb() + if thumb: + try: + html += thumb.render() + except: + html += u"Can't create preview" + + html += """ + + """ % (name, self.thumb_size[0]) + return mark_safe(html) + def set_storage(self, value): + self._storage = value + + + diff --git a/django_resubmit/static/django_resubmit/jquery.input_image_preview.js b/django_resubmit/static/django_resubmit/jquery.input_image_preview.js new file mode 100644 index 0000000..19e600b --- /dev/null +++ b/django_resubmit/static/django_resubmit/jquery.input_image_preview.js @@ -0,0 +1,142 @@ +/** + * Show image preview for form field. + * + * $(":file").inputImagePreview({ + * place: function(frame) { $(this).before(frame); }, + * // image.jpg + * // + * // display any url when input is empty + * imageUrl: function(){ return $(this).prevAll("a:first").attr("href");} + * }); + * + * @author Evgeny V. Generalov + */ + +(function($){ + var pluginName = "inputImagePreview"; + var defaultOptions = { + maxWidth: 100, + imageUrl: null, + containerClass: 'image-preview', + imageClass: 'image-preview-picture', + atServerClass: 'atserver', + widthClass: 'width', + heightClass: 'height', + dom: null, + image: null, + url: 'javascript:void(0)' + }; + var base64ImgUriPattern = /^data:image\/((png)|(gif)|(jpg)|(jpeg)|(bmp));base64/i; + + + $.extend($.fn, { + inputImagePreview: function(options) { + return this.each(function(){ + var $this = $(this); + if ($this.data(pluginName)) { + var inputImagePreview = $this.data(pluginName); + $.extend(true, inputImagePreview, options); + inputImagePreview.render(); + } else { + $this.data(pluginName, new InputImagePreview(this, options)); + } + }) + } + }); + + var InputImagePreview = function(elem, options) { + var self = this; + + this.inputFile = $(elem); + this.options = $.extend(true, defaultOptions, options); + + if (!this.options.dom) { + this.dom = $('', {'href':options.url, 'target': '_blank', + 'class':this.options.domClass}).append( + this.image = $('', {'class':this.options.imageClass})).append( + this.width = $('', {'class':this.options.widthClass}).append('×').append( + this.height = $('', {'class':this.options.heightClass}))); + this.dom.insertAfter(this.inputFile); + } else { + this.dom = this.options.dom; + this.image = this.options.image || this.dom.find('img'); + this.width = this.options.width || this.dom.parent().find('.'+this.options.widthClass); + this.height = this.options.height || this.dom.parent().find('.'+this.options.heightClass); + this.options.url = this.dom.attr('href'); + } + + this.inputFile.bind({ + change: function(){self.render()} + }); + + this.dom.addClass(this.options.domClass); + this.image.addClass(this.options.imageClass); + + this.render(); + } + + $.extend(InputImagePreview.prototype, { + updateImage: function() { + var inputFileNode = this.inputFile.get(0); + var image = this.image; + var file = inputFileNode.files && inputFileNode.files[0]; + var imageUrl= null; + var hasUrl = false; + // Check if we can access the serialized file via getAsDataURL(). + if (file) { + /* firefox or chrome */ + if (file.getAsDataURL) { + /* firefox */ + var src = file.getAsDataURL(); + if (src && base64ImgUriPattern.test(src)) { + imageUrl = src; + } + } + } else if (inputFileNode.value) { + /* maybe ie */ + imageUrl = this.value; + } else { + /* empty input */ + imageUrl = this.options.thumbnailUrl || image.attr('src'); + hasUrl = !!imageUrl; + } + + if (!imageUrl) { + image.hide() + .removeAttr('src') + ; + } else { + image.attr({"src": imageUrl}) + .css({'max-width': this.options.maxWidth, + 'max-height': this.options.maxHeight}) + ; + if (hasUrl) { + image.addClass(this.options.atServerClass); + } else { + image.removeClass(this.options.atServerClass); + // adjust width and height + var img = new Image(); + img.src=imageUrl; + // we must wait for image loading, therefore we + // take img.width and img.height values assyncroniusly + setTimeout(function(c, img) { + c.width.text(img.width); + c.height.text(img.height)}, 1, this, img); + } + image.show(); + } + }, + updateContainer: function() { + if (this.image.hasClass(this.options.atServerClass)) { + this.dom.attr({'href': this.options.url}); + } else { + this.dom.removeAttr('href'); + } + }, + render: function() { + this.updateImage(); + this.updateContainer(); + } + }); + +})(jQuery || django.jQuery); diff --git a/django_resubmit/storage.py b/django_resubmit/storage.py index f39a40f..01d3edf 100644 --- a/django_resubmit/storage.py +++ b/django_resubmit/storage.py @@ -1,5 +1,7 @@ # coding: utf-8 from cStringIO import StringIO +from django.conf import settings +from django.core.cache import get_cache from django.core.files.uploadedfile import InMemoryUploadedFile @@ -9,17 +11,17 @@ FIELD_CHARSET = "charset" FIELD_CONTENT = "content" +def get_default_storage(): + backend = getattr(settings, 'DJANGO_RESUBMIT_BACKEND', 'file:///tmp') + return TemporaryFileStorage(backend=get_cache(backend)) + class TemporaryFileStorage(object): - def __init__(self, backend=None, prefix=None, max_in_memory_size=None): + def __init__(self, backend=None, prefix=None): if backend is None: from django.core.cache import cache as backend if prefix is None: prefix = 'cachefile-' - if max_in_memory_size is None: - from django.conf import settings - max_in_memory_size = settings.FILE_UPLOAD_MAX_MEMORY_SIZE - self.max_in_memory_size = max_in_memory_size self.backend = backend self.prefix = prefix diff --git a/django_resubmit/test.py b/django_resubmit/test.py index d3017b5..0a5b0f6 100644 --- a/django_resubmit/test.py +++ b/django_resubmit/test.py @@ -1,8 +1,11 @@ +from shutil import rmtree +from tempfile import mkdtemp from StringIO import StringIO -from django.test import Client +from django.conf.urls.defaults import url from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.base import BaseHandler +from django.test import Client class CacheMock(object): @@ -74,3 +77,60 @@ class ContentFile(StringIO): def __init__(self, *args, **kwargs): self.name = kwargs.pop('name') StringIO.__init__(self, *args, **kwargs) + + + +class MediaStub(object): + """Stub for settings.MEDIA_ROOT and settings.MEDIA_URL.""" + + def __init__(self, media_url, media_root=None, settings=None): + if not settings: + from django.conf import settings + self._use_termporary_root = None + self.old_root = None + self.old_url = None + self.show_indexes = False + self.settings = settings + self._root = media_root or self._make_temporary_root() + self._url = media_url or '/media/' + self._stub_settings() + self._recreate_default_storage() + + def dispose(self): + self.settings.MEDIA_ROOT = self.old_root + self.settings.MEDIA_URL = self.old_url + if self._use_temporary_root: + rmtree(self._root) + self._recreate_default_storage() + + def url_patterns(self): + """Return list of url patterns for serve static. + + This is situable to append into urls.py""" + return [ + url(r'^media/(?P.*)$', 'django.views.static.serve', { + 'document_root': self.settings.MEDIA_ROOT, + 'show_indexes': self.show_indexes}), + ] + + def _stub_settings(self): + self.old_root = self.settings.MEDIA_ROOT + self.old_url = self.settings.MEDIA_URL + self.settings.MEDIA_ROOT = self._root + self.settings.MEDIA_URL = self._url + + def _make_temporary_root(self): + """Make temporary directory for media root""" + self._use_temporary_root = True + return mkdtemp() + + def _recreate_default_storage(self): + """Install default storage. + + It is nessesary to reinstall default_storage because of it + memorizes settings.MEDIA_ROOT and settings.MEDIA_URL + """ + from django.core.files import storage + storage.default_storage = storage.DefaultStorage() + + diff --git a/django_resubmit/tests.py b/django_resubmit/tests.py index 224a0d9..fd726bd 100644 --- a/django_resubmit/tests.py +++ b/django_resubmit/tests.py @@ -1,15 +1,22 @@ +# coding: utf-8 +import re +import os import unittest from django_webtest import WebTest from django import forms from django import template from django.http import HttpResponse -from django.conf.urls.defaults import patterns, include, url +from django.conf.urls.defaults import patterns, url, include +from django.test import TestCase + from django_resubmit.test import CacheMock +from django_resubmit.test import MediaStub from django_resubmit.test import RequestFactory -from django_resubmit.forms.widgets import FileCacheWidget +from django_resubmit.forms.widgets import FileWidget from django_resubmit.forms.widgets import FILE_INPUT_CONTRADICTION +from django_resubmit.storage import TemporaryFileStorage class HttpResponseOk(HttpResponse): @@ -35,7 +42,7 @@ class HttpResponseCreated(HttpResponse): class SampleForm(forms.Form): name = forms.CharField(max_length=25) - file = forms.FileField(widget=FileCacheWidget()) + file = forms.FileField(widget=FileWidget()) def view_upload_file(request): @@ -57,39 +64,103 @@ def view_upload_file(request): urlpatterns = patterns('', url(r'^$', view_upload_file), + url(r'^thumbnail/$', include('django_resubmit.urls')), ) +class MediaStubTest(TestCase): + urls = __name__ + + def setUp(self): + global urlpatterns + self.media = MediaStub(media_url="/media/") + urlpatterns = patterns('', *self.media.url_patterns()) + + def tearDown(self): + self.media.dispose() + + def test_should_get_uploaded_file(self): + from django.conf import settings + + uploaded_path = os.path.join(settings.MEDIA_ROOT, "upload.txt") + f = open(uploaded_path, 'wb') + f.write("some data") + f.close() + response = self.client.get(settings.MEDIA_URL + "upload.txt") + self.assertEquals(response.status_code, 200) + self.assertEquals(response.content, "some data") + + def test_should_get_file_using_media_url(self): + from django.core.files.storage import default_storage + from django.core.files.base import ContentFile + + default_storage.save("upload.txt", ContentFile("some data")) + response = self.client.get("/media/upload.txt") + self.assertEquals(response.status_code, 200) + self.assertEquals(response.content, "some data") + + class FormTest(WebTest): urls = 'django_resubmit.tests' + def setUp(self): + self.app.relative_to = os.path.dirname(__file__) + self.media = MediaStub(media_url='/media/') + + def tearDown(self): + self.media.dispose() + + def test_should_preserve_file_on_form_errors(self): response = self.app.get('/') form = response.forms[0] form['file'] = ['test.txt', u'test content'] - self.assertEquals(sorted(form.fields.keys()), - [u'csrfmiddlewaretoken', u'file', u'name', u'submit']) response = form.submit() self.assertEquals(response.status_int, HttpResponseOk.status_code) self.assertEquals(len(response.context['form']['file'].errors), 0) self.assertEquals(len(response.context['form']['name'].errors), 1) form = response.forms[0] - self.assertEquals(sorted(form.fields.keys()), - [u'csrfmiddlewaretoken', u'file', u'file-cachekey', u'name', u'submit']) form['name'] = u'value for required field' response = form.submit() self.assertEquals(response.status_int, HttpResponseCreated.status_code, response.body) self.assertEquals(response.unicode_body, u'test content') + def test_should_show_cached_file_without_link(self): + response = self.app.get('/') + form = response.forms[0] + form['file'] = ['test.txt', u'test content'] + response = form.submit() + self.assertEquals(response.status_int, HttpResponseOk.status_code) + + self.assertTrue(u'Currently: test.txt' in response.unicode_body, + u"Should show cached file without link") + + def test_if_thumb_is_rendered_on_submit_errors(self): + """ Check thumb generation for image files on submit errors""" + + response = self.app.get('/') + form = response.forms[0] + form['file'] = ['fixtures/test-image.png'] + response = form.submit() + self.assertEquals(response.status_int, HttpResponseOk.status_code) + + preview_match = re.search(ur'preview', + response.unicode_body) + self.assertTrue(preview_match, + u"page contains an tag with preview") + preview_url = preview_match.group(1) + preview_response = self.app.get(preview_url) + self.assertEquals(200, preview_response.status_int, + u"preview available for download") + class ClearTest(unittest.TestCase): def setUp(self): self.factory = RequestFactory() - self.backend = CacheMock() - self.widget = FileCacheWidget(backend=self.backend) + self.widget = FileWidget() def test_should_not_display_clear_checkbox_then_is_required(self): widget = self.widget @@ -110,6 +181,9 @@ def test_should_display_clear_checkbox_then_does_not_required(self): self.assertTrue(' """ % src + return html + diff --git a/django_resubmit/urls.py b/django_resubmit/urls.py new file mode 100644 index 0000000..c30c5d1 --- /dev/null +++ b/django_resubmit/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import patterns, url + + +urlpatterns = patterns('django_resubmit.views', + url(r'^$', 'thumbnail', name='thumbnail'), +) diff --git a/django_resubmit/views.py b/django_resubmit/views.py new file mode 100644 index 0000000..393a818 --- /dev/null +++ b/django_resubmit/views.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +from django.http import HttpResponse +from django.core.servers.basehttp import FileWrapper +from django_resubmit.storage import TemporaryFileStorage +from django.core.cache import get_cache +from django.conf import settings +import Image +from cStringIO import StringIO +from django.core.files.uploadedfile import SimpleUploadedFile + +from django_resubmit.storage import get_default_storage + + +def thumbnail(request): + try: + key = request.GET['key'] + name = request.GET['name'] + width = int(request.GET['width']) + height = int(request.GET['height']) + storage = get_default_storage() + + restored = storage.get_file(key, name) + if not restored: + raise + restored.seek(0) + img = Image.open(restored.file) + img.thumbnail([width, height], Image.ANTIALIAS) + buf = StringIO() + img.save(buf, img.format) + buf.seek(0) + thumb = SimpleUploadedFile(restored.name, buf.read(), restored.content_type) + buf.close() + response = HttpResponse(FileWrapper(thumb), content_type=restored.content_type) + except: + response = HttpResponse('', content_type='image/jpg') + return response + + + diff --git a/example/manage.py b/example/manage.py old mode 100644 new mode 100755 diff --git a/example/settings.py b/example/settings.py index db98020..1a477c7 100644 --- a/example/settings.py +++ b/example/settings.py @@ -64,6 +64,7 @@ 'django.contrib.admin', 'django_resubmit', + 'testapp', ) LOGGING = { 'version': 1, diff --git a/example/testapp/__init__.py b/example/testapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/testapp/admin.py b/example/testapp/admin.py new file mode 100644 index 0000000..fc5ad4d --- /dev/null +++ b/example/testapp/admin.py @@ -0,0 +1,17 @@ +# coding: utf-8 +from django.contrib import admin +from django.db import models +from django_resubmit.forms.widgets import FileWidget + +from models import Topic + + +class TopicAdmin(admin.ModelAdmin): + list_display = ('title',) + formfield_overrides = { + models.FileField: {'widget': FileWidget(thumb_size=[50,50])}, + } + + +admin.site.register(Topic, TopicAdmin) + diff --git a/example/testapp/fixtures/penguin.png b/example/testapp/fixtures/penguin.png new file mode 100644 index 0000000..bb48e3e Binary files /dev/null and b/example/testapp/fixtures/penguin.png differ diff --git a/example/testapp/models.py b/example/testapp/models.py new file mode 100644 index 0000000..3fc6a33 --- /dev/null +++ b/example/testapp/models.py @@ -0,0 +1,7 @@ +from django.db import models + + +# Create your models here. +class Topic(models.Model): + title = models.CharField(max_length=255) + icon = models.FileField(upload_to="uploads") diff --git a/example/testapp/tests.py b/example/testapp/tests.py new file mode 100644 index 0000000..615d001 --- /dev/null +++ b/example/testapp/tests.py @@ -0,0 +1,51 @@ +import os +import re + +from django_webtest import WebTest +from django.conf.urls.defaults import patterns, include, url +from django.contrib.auth.models import User +from django_resubmit.test import MediaStub + + +class AdminEditTopicTest(WebTest): + urls = __name__ + + def setUp(self): + global urlpatterns + self.app.relative_to = os.path.join(os.path.dirname(__file__)) + self.media = MediaStub(media_url='/media/') + self.superuser = User.objects.create_superuser( + username=u'admin', + email=u'admin@admin.com', + password=u'letmein') + urlpatterns = patterns('', + url(r'^admin/', include(admin.site.urls)), + *self.media.url_patterns()) + + response = self.app.get('/admin/testapp/topic/add/', + user=self.superuser) + form = response.forms['topic_form'] + form.set('title', u"Penguin") + form.set('icon', ["fixtures/penguin.png"]) + post = form.submit(u"_continue") + self.topic = post.follow().context["original"] + + def tearDown(self): + self.media.dispose() + + def test_should_see_icon_preview(self): + response = self.app.get('/admin/testapp/topic/%d/' % self.topic.pk, + user=self.superuser) + + preview_match = re.search(ur'preview', + response.unicode_body) + self.assertTrue(preview_match, + u"page contains an tag with preview") + preview_url = preview_match.group(1) + preview_response = self.app.get(preview_url) + self.assertEquals(200, preview_response.status_int, + u"preview available for download") + + +from django.contrib import admin +admin.autodiscover() diff --git a/example/testapp/views.py b/example/testapp/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/example/testapp/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/example/urls.py b/example/urls.py index 16eba16..9fd01c4 100644 --- a/example/urls.py +++ b/example/urls.py @@ -1,3 +1,5 @@ +import settings +from django.conf.urls.static import static from django.conf.urls.defaults import patterns, include, url # Uncomment the next two lines to enable the admin: @@ -5,8 +7,9 @@ admin.autodiscover() urlpatterns = patterns('example.views', - # Examples: - # url(r'^$', 'example.views.home', name='home'), - # Uncomment the next line to enable the admin: + url(r'^thumbnail/$', include('django_resubmit.urls')), url(r'^admin/', include(admin.site.urls)), ) + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file