diff --git a/.gitignore b/.gitignore index 06f5f1d..8fef53c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,15 @@ +db /media -/db /bin /include /lib /local /share +/env +/venv +/django-jquery-file-upload/media +*.log +*.pot +*.pyc +local_settings.py +.idea diff --git a/README.md b/README.md index b503a8a..3498b31 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This is a small example on how to setup Sebastian Tschan's jQuery File Upload in Here, you'll find a minimal Django project with a minimal app. You can run the example standalone by cloning the repository, running the migrations and starting the server. -I want to give a thank to Sebastian Tschan, the original author, and Jørgen Bergquist for helping me over the first hurdles. +I want to give a thank to [Sebastian Tschan](https://github.com/blueimp), the original author, [Etay Cohen-Solal](https://github.com/et-cs), for the latest major update, and [Jørgen Bergquist](https://github.com/bergquis) for helping me over the first hurdles. Features ======== @@ -19,12 +19,30 @@ Features * No flash (or other browser plugins) needed * … more at the [upstream's features page](http://aquantum-demo.appspot.com/file-upload#features) +Requirements +============ + +* Django +* Python Imaging Library + +If you do not get PIL to work (_pillow_ is a replacement package that works +with virtulalenvs), use FileField instead of ImageField in +fileupload/models.py as commented in the file. + Installation ============ -* run ./manage.py syncdb -* go to localhost:8000/upload/new/ -* upload some files +I recommend to install this within a virtualenv. + +```sh +virtualenv -p python3 venv +source venv/bin/activate +pip install -r requirements.txt +./manage.py migrate +./manage.py runserver +``` + +And then go to localhost:8000 and try to upload some files. License ======= diff --git a/__init__.py b/django-jquery-file-upload/__init__.py similarity index 100% rename from __init__.py rename to django-jquery-file-upload/__init__.py diff --git a/django-jquery-file-upload/settings.py b/django-jquery-file-upload/settings.py new file mode 100644 index 0000000..d27d9db --- /dev/null +++ b/django-jquery-file-upload/settings.py @@ -0,0 +1,95 @@ +""" +Django settings. +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '9%$in^gpdaig@v3or_to&_z(=n)3)$f1mr3hf9e#kespy2ajlo' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = ( + 'fileupload.apps.FileuploadConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', +) + +ROOT_URLCONF = 'django-jquery-file-upload.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + #'DIRS': ['django-jquery-file-upload/templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'django-jquery-file-upload.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db'), + } +} + + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'Europe/Oslo' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +STATIC_URL = '/static/' + +STATICFILES_DIRS = [ + 'django-jquery-file-upload/static', +] + +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'django-jquery-file-upload', 'media') diff --git a/django-jquery-file-upload/urls.py b/django-jquery-file-upload/urls.py new file mode 100644 index 0000000..8f26ec4 --- /dev/null +++ b/django-jquery-file-upload/urls.py @@ -0,0 +1,17 @@ +from django.urls import include, path +from django.http import HttpResponseRedirect +from django.conf import settings + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = [ + path('', lambda x: HttpResponseRedirect('/upload/new/')), + path('upload/', include('fileupload.urls')), + path('admin/', admin.site.urls), +] + +if settings.DEBUG: + from django.conf.urls.static import static + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/django-jquery-file-upload/wsgi.py b/django-jquery-file-upload/wsgi.py new file mode 100644 index 0000000..8e47fba --- /dev/null +++ b/django-jquery-file-upload/wsgi.py @@ -0,0 +1,11 @@ +""" +WSGI config for django-jquery-file-upload project. +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django-jquery-file-upload.settings") + +application = get_wsgi_application() diff --git a/fileupload/admin.py b/fileupload/admin.py new file mode 100644 index 0000000..faa5cdc --- /dev/null +++ b/fileupload/admin.py @@ -0,0 +1,4 @@ +from fileupload.models import Picture +from django.contrib import admin + +admin.site.register(Picture) \ No newline at end of file diff --git a/fileupload/apps.py b/fileupload/apps.py new file mode 100644 index 0000000..ff22d3e --- /dev/null +++ b/fileupload/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FileuploadConfig(AppConfig): + name = 'fileupload' + diff --git a/fileupload/migrations/0001_initial.py b/fileupload/migrations/0001_initial.py new file mode 100644 index 0000000..0c157ef --- /dev/null +++ b/fileupload/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2016-06-07 19:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Picture', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.ImageField(upload_to='pictures')), + ('slug', models.SlugField(blank=True)), + ], + ), + ] diff --git a/fileupload/migrations/__init__.py b/fileupload/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fileupload/models.py b/fileupload/models.py index eed5f86..81a20a3 100644 --- a/fileupload/models.py +++ b/fileupload/models.py @@ -1,14 +1,19 @@ +# encoding: utf-8 from django.db import models + class Picture(models.Model): + """This is a small demo using just two fields. The slug field is really not + necessary, but makes the code simpler. ImageField depends on PIL or + pillow (where Pillow is easily installable in a virtualenv. If you have + problems installing pillow, use a more generic FileField instead. - # This is a small demo using FileField instead of ImageField, not - # depending on PIL. You will probably want ImageField in your app. - file = models.FileField(upload_to="pictures") + """ + file = models.ImageField(upload_to="pictures") slug = models.SlugField(max_length=50, blank=True) - def __unicode__(self): - return self.file + def __str__(self): + return self.file.name @models.permalink def get_absolute_url(self): @@ -17,3 +22,8 @@ def get_absolute_url(self): def save(self, *args, **kwargs): self.slug = self.file.name super(Picture, self).save(*args, **kwargs) + + def delete(self, *args, **kwargs): + """delete -- Remove to leave file.""" + self.file.delete(False) + super(Picture, self).delete(*args, **kwargs) diff --git a/fileupload/response.py b/fileupload/response.py new file mode 100644 index 0000000..334eb29 --- /dev/null +++ b/fileupload/response.py @@ -0,0 +1,38 @@ +# encoding: utf-8 +from django.http import HttpResponse +import json + +MIMEANY = '*/*' +MIMEJSON = 'application/json' +MIMETEXT = 'text/plain' + + +def response_mimetype(request): + """response_mimetype -- Return a proper response mimetype, accordingly to + what the client accepts, as available in the `HTTP_ACCEPT` header. + + request -- a HttpRequest instance. + + """ + can_json = MIMEJSON in request.META['HTTP_ACCEPT'] + can_json |= MIMEANY in request.META['HTTP_ACCEPT'] + return MIMEJSON if can_json else MIMETEXT + + +class JSONResponse(HttpResponse): + """JSONResponse -- Extends HTTPResponse to handle JSON format response. + + This response can be used in any view that should return a json stream of + data. + + Usage: + + def a_iew(request): + content = {'key': 'value'} + return JSONResponse(content, mimetype=response_mimetype(request)) + + """ + def __init__(self, obj='', json_opts=None, mimetype=MIMEJSON, *args, **kwargs): + json_opts = json_opts if isinstance(json_opts, dict) else {} + content = json.dumps(obj, **json_opts) + super(JSONResponse, self).__init__(content, mimetype, *args, **kwargs) diff --git a/fileupload/serialize.py b/fileupload/serialize.py new file mode 100644 index 0000000..96749af --- /dev/null +++ b/fileupload/serialize.py @@ -0,0 +1,38 @@ +# encoding: utf-8 +import mimetypes +import re +from django.urls import reverse + + +def order_name(name): + """order_name -- Limit a text to 20 chars length, if necessary strips the + middle of the text and substitute it for an ellipsis. + + name -- text to be limited. + + """ + name = re.sub(r'^.*/', '', name) + if len(name) <= 20: + return name + return name[:10] + "..." + name[-7:] + + +def serialize(instance, file_attr='file'): + """serialize -- Serialize a Picture instance into a dict. + + instance -- Picture instance + file_attr -- attribute name that contains the FileField or ImageField + + """ + obj = getattr(instance, file_attr) + return { + 'url': obj.url, + 'name': order_name(obj.name), + 'type': mimetypes.guess_type(obj.path)[0] or 'image/png', + 'thumbnailUrl': obj.url, + 'size': obj.size, + 'deleteUrl': reverse('upload-delete', args=[instance.pk]), + 'deleteType': 'DELETE', + } + + diff --git a/fileupload/static/application.js b/fileupload/static/application.js deleted file mode 100644 index bf87c9c..0000000 --- a/fileupload/static/application.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * jQuery File Upload Plugin JS Example 5.0.2 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://creativecommons.org/licenses/MIT/ - */ - -/*jslint nomen: true */ -/*global $ */ - -$(function () { - 'use strict'; - - // Initialize the jQuery File Upload widget: - $('#fileupload').fileupload(); - - // Load existing files: - $.getJSON($('#fileupload form').prop('action'), function (files) { - var fu = $('#fileupload').data('fileupload'); - fu._adjustMaxNumberOfFiles(-files.length); - fu._renderDownload(files) - .appendTo($('#fileupload .files')) - .fadeIn(function () { - // Fix for IE7 and lower: - $(this).show(); - }); - }); - - // Open download dialogs via iframes, - // to prevent aborting current uploads: - $('#fileupload .files a:not([target^=_blank])').live('click', function (e) { - e.preventDefault(); - $('