├── demo ├── demo │ ├── __init__.py │ ├── urls.py │ ├── wsgi.py │ └── settings.py ├── filepicker_demo │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0001_initial.py │ │ └── 0002_auto_20150323_1549.py │ ├── urls.py │ ├── admin.py │ ├── tests.py │ ├── models.py │ ├── templates │ │ └── home.html │ └── views.py ├── build │ └── pip-delete-this-directory.txt ├── manage.py └── templates │ └── home.html ├── MANIFEST.in ├── django_filepicker ├── .DS_Store ├── __init__.py ├── context_processors.py ├── widgets.py ├── middleware.py ├── utils.py ├── models.py └── forms.py ├── .gitignore ├── MANIFEST ├── setup.py ├── CHANGELOG ├── LICENSE.txt └── README.md /demo/demo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG 2 | -------------------------------------------------------------------------------- /demo/filepicker_demo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/filepicker_demo/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /django_filepicker/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filepicker/filepicker-django/HEAD/django_filepicker/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | demo/media/uploads/ 4 | demo/db.sql 5 | dist/ 6 | db.sqlite3 7 | .DS_STORE 8 | build 9 | django_filepicker.egg-info 10 | .idea 11 | -------------------------------------------------------------------------------- /demo/filepicker_demo/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.contrib import admin 3 | from filepicker_demo.views import pick 4 | 5 | 6 | urlpatterns = patterns('', 7 | url(r'^$', pick, name='pick'), 8 | ) -------------------------------------------------------------------------------- /demo/build/pip-delete-this-directory.txt: -------------------------------------------------------------------------------- 1 | This file is placed here by pip to indicate the source was put 2 | here by pip. 3 | 4 | Once this package is successfully installed this source code will be 5 | deleted (unless you remove this file). 6 | -------------------------------------------------------------------------------- /demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /django_filepicker/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from . import models 3 | from . import forms 4 | from . import middleware 5 | from . import widgets 6 | from . import context_processors 7 | except ImportError: 8 | import models 9 | import forms 10 | import middleware 11 | import widgets 12 | import context_processors -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | CHANGELOG 3 | setup.py 4 | django_filepicker/__init__.py 5 | django_filepicker/context_processors.py 6 | django_filepicker/forms.py 7 | django_filepicker/middleware.py 8 | django_filepicker/models.py 9 | django_filepicker/utils.py 10 | django_filepicker/widgets.py 11 | -------------------------------------------------------------------------------- /django_filepicker/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.utils.safestring import mark_safe 2 | from .widgets import JS_URL 3 | 4 | def js(request): 5 | #Defines a {{FILEPICKER_JS}} tag that inserts the filepicker javascript library 6 | return {"FILEPICKER_JS": 7 | mark_safe('' % JS_URL)} 8 | -------------------------------------------------------------------------------- /demo/demo/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url 2 | from django.contrib import admin 3 | 4 | urlpatterns = patterns('', 5 | # Examples: 6 | # url(r'^$', 'demo.views.home', name='home'), 7 | # url(r'^blog/', include('blog.urls')), 8 | url(r'^$', include('filepicker_demo.urls', namespace='demo')), 9 | url(r'^admin/', include(admin.site.urls)), 10 | ) 11 | -------------------------------------------------------------------------------- /demo/demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /demo/filepicker_demo/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from models import FileModel, BasicFilesModel 3 | 4 | 5 | class BasicFilesModelAdmin(admin.ModelAdmin): 6 | pass 7 | 8 | 9 | class FileModelAdmin(admin.ModelAdmin): 10 | readonly_fields = ('image_tag',) 11 | list_display = ('__unicode__', 'image_tag') 12 | 13 | 14 | admin.site.register(FileModel, FileModelAdmin) 15 | admin.site.register(BasicFilesModel, BasicFilesModelAdmin) -------------------------------------------------------------------------------- /demo/filepicker_demo/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | setup( 7 | name='django-filepicker', 8 | version='0.2.3', 9 | description='Official Filepicker Django Library', 10 | author='Filepicker.io', 11 | author_email='contact@filepicker.io', 12 | url='http://developers.filepicker.io/', 13 | packages=['django_filepicker'], 14 | install_requires=['django >= 1.3','requests'], 15 | license="BSD", 16 | zip_safe=False, 17 | ) 18 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Version 0.2.3 - Jul 15 2015 2 | - Fixed problem of missing request headers 3 | 4 | Version 0.2.1 - Sep 26 2014 5 | - Updated the demo app 6 | - Updated .gitignore 7 | 8 | Version 0.2.0 - Sep 26 2014 9 | - Updated the library to work with Python 3 10 | - Rewritten the demo app to Django 1.7 11 | 12 | Version 0.1.5 - Aug 27 2013 13 | - Added South introspection rule 14 | 15 | Version 0.1.2 - Oct 2 2012 16 | - Patched middleware, FPUrlField 17 | - Added middleware support for multiple files 18 | - Added FPUrlField 19 | 20 | Version 0.1.1 - Sept 30 2012 21 | - Updated widgets for services field 22 | - Added a "services" attribute to the FPFileField 23 | 24 | Version 0.1 25 | - Basic hooks via FPFileField in models and forms 26 | - Filepicker widget 27 | - Filepicker middleware 28 | -------------------------------------------------------------------------------- /demo/filepicker_demo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_filepicker.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='TestModel', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('text', models.CharField(max_length=64)), 19 | ('fpfile', django_filepicker.models.FPFileField(upload_to=b'uploads')), 20 | ], 21 | options={ 22 | }, 23 | bases=(models.Model,), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /django_filepicker/widgets.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.forms import widgets 3 | 4 | #JS_URL is the url to the filepicker.io javascript library 5 | JS_VERSION = getattr(settings, "FILEPICKER_JS_VERSION", 1) 6 | JS_URL = "//api.filepicker.io/v%d/filepicker.js" % (JS_VERSION) 7 | 8 | INPUT_TYPE = getattr(settings, "FILEPICKER_INPUT_TYPE", "filepicker-dragdrop") 9 | 10 | class FPFileWidget(widgets.Input): 11 | input_type = INPUT_TYPE 12 | needs_multipart_form = False 13 | 14 | def value_from_datadict_old(self, data, files, name): 15 | #If we are using the middleware, then the data will already be 16 | #in FILES, if not it will be in POST 17 | if name not in data: 18 | return super(FPFileWidget, self).value_from_datadict( 19 | data, files, name) 20 | 21 | return data 22 | 23 | class Media: 24 | js = (JS_URL,) 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 Filepicker.io 2 | https://filepicker.io/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/filepicker_demo/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django import forms 3 | import django_filepicker 4 | 5 | 6 | class BasicFilesModel(models.Model): 7 | text = models.CharField(max_length=64) 8 | 9 | def __unicode__(self): 10 | return 'Files chain {}. ID:{}'.format(self.text, self.pk) 11 | 12 | 13 | class FileModel(models.Model): 14 | mid = models.ForeignKey(BasicFilesModel) 15 | fpfile = django_filepicker.models.FPFileField( 16 | upload_to='uploads', additional_params={'data-fp-multiple': 'true'}) 17 | fpurl = models.URLField(max_length=255, null=True, blank=True) 18 | 19 | def __unicode__(self): 20 | return 'File from chain {}:id-{}. ID:{}'.format( 21 | self.mid.text, self.mid.pk, self.pk) 22 | 23 | def image_tag(self): 24 | return u'' % self.fpurl 25 | image_tag.short_description = 'Image' 26 | image_tag.allow_tags = True 27 | 28 | 29 | class BasicFilesForm(forms.ModelForm): 30 | class Meta: 31 | model = BasicFilesModel 32 | fields = ['text'] 33 | 34 | 35 | class FileForm(forms.ModelForm): 36 | class Meta: 37 | model = FileModel 38 | fields = ['fpfile'] -------------------------------------------------------------------------------- /demo/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Filepicker.io Django Library | Demo 4 | {{ form.media }} 5 | 6 | 7 |

Filepicker.io Django Library - Demo page

8 | {%if message%}

{{message}}

{%endif%} 9 |
10 | {%csrf_token%} 11 | {{ form.as_p }} 12 |

13 |
14 |

Form code:

15 |
import django_filepicker
16 | 
17 | class TestModel(models.Model):
18 |     text = models.CharField(max_length=64)
19 |     fpfile = django_filepicker.models.FPFileField(upload_to='uploads')
20 | 
21 |

Processing the form

22 |
form = models.TestModelForm(request.POST, request.FILES)
23 | if form.is_valid():
24 |     #Save will read the data and upload it to the location defined in TestModel
25 |     form.save()
26 | 27 |
28 |

Code available on GitHub at https://github.com/Filepicker/django-filepicker.

29 |

For more info and to get an api key, check out Filepicker.io.

30 | 31 | 32 | -------------------------------------------------------------------------------- /demo/filepicker_demo/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Filepicker.io Django Library | Demo 4 | {{ form.media }} 5 | 6 | 7 |

Filepicker.io Django Library - Demo page

8 | {%if message%}

{{message}}

{%endif%} 9 |
10 | {%csrf_token%} 11 | {{ basic_form.as_p }} 12 | {{ form.as_p }} 13 |

14 |
15 |

Form code:

16 |
import django_filepicker
17 | 
18 | class TestModel(models.Model):
19 |     text = models.CharField(max_length=64)
20 |     fpfile = django_filepicker.models.FPFileField(upload_to='uploads')
21 | 
22 |

Processing the form

23 |
form = models.TestModelForm(request.POST, request.FILES)
24 | if form.is_valid():
25 |     #Save will read the data and upload it to the location defined in TestModel
26 |     form.save()
27 | 28 |
29 |

Code available on GitHub at https://github.com/Filepicker/django-filepicker.

30 |

For more info and to get an api key, check out Filepicker.io.

31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/filepicker_demo/migrations/0002_auto_20150323_1549.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django_filepicker.models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('filepicker_demo', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='BasicFilesModel', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('text', models.CharField(max_length=64)), 20 | ], 21 | options={ 22 | }, 23 | bases=(models.Model,), 24 | ), 25 | migrations.CreateModel( 26 | name='FileModel', 27 | fields=[ 28 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 29 | ('fpfile', django_filepicker.models.FPFileField(upload_to=b'uploads')), 30 | ('fpurl', models.URLField(max_length=255, null=True, blank=True)), 31 | ('mid', models.ForeignKey(to='filepicker_demo.BasicFilesModel')), 32 | ], 33 | options={ 34 | }, 35 | bases=(models.Model,), 36 | ), 37 | migrations.DeleteModel( 38 | name='TestModel', 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /django_filepicker/middleware.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .utils import FilepickerFile 3 | except ImportError: 4 | from utils import FilepickerFile 5 | 6 | class URLFileMapperMiddleware(object): 7 | """ 8 | This middleware will take any Filepicker.io urls that are posted to the server via a POST 9 | and put a matching File object into request.FILES. This way, if you're used to grabbing files out of 10 | request.FILES, you don't have to change your backend code when using the filepicker.io widgets. 11 | 12 | This middleware is rather agressive in that it will automatically fetch any and all filepicker 13 | urls passed to the server, so if you are already processing the files via FPFileField or similar 14 | this functionality is redundant 15 | 16 | Note that the original filepicker.io url will still be available in POST if you need it. 17 | """ 18 | def process_request(self, request): 19 | #Iterate over GET or POST data, search for filepicker.io urls 20 | for key, val in list(request.POST.items()): 21 | try: 22 | fp = FilepickerFile(val) 23 | except ValueError: 24 | pass 25 | else: 26 | splits = val.split(",") 27 | if key in request.FILES: 28 | request.FILES.setlist(key, 29 | request.FILES.getlist(key) + fp.get_file()) 30 | else: 31 | request.FILES.setlist(key, fp.get_file()) 32 | -------------------------------------------------------------------------------- /demo/filepicker_demo/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | try: 4 | from .models import BasicFilesForm, FileForm 5 | except ImportError: 6 | from models import BasicFilesForm, FileForm 7 | 8 | 9 | def pick(request): 10 | message = None 11 | basic_form = BasicFilesForm() 12 | form = FileForm() 13 | 14 | if request.method == "POST": 15 | post = request.POST.dict() 16 | basic_form = BasicFilesForm(post) 17 | if basic_form.is_valid(): 18 | f = basic_form.save() 19 | post['mid_id'] = f.id 20 | else: 21 | message = 'Invalid form' 22 | 23 | files_links = request.POST['fpfile'].split(',') 24 | if post.get('mid_id', None): 25 | for i, f in enumerate(request.FILES.getlist("fpfile")): 26 | form = FileForm(post) 27 | if form.is_valid(): 28 | fp = form.save(commit=False) 29 | fp.fpfile = f 30 | fp.fpurl = files_links[i] 31 | fp.mid_id = post.get('mid_id') 32 | fp.save() 33 | else: 34 | message = "Invalid form" 35 | files = ", ".join([str(f) for f in request.FILES.getlist("fpfile")]) 36 | message = "Save successful. URL for {0}: {1}".format( 37 | files, request.POST["fpfile"]) if not message else message 38 | 39 | return render(request, "home.html", {'form': form, 'message': message, 'basic_form': basic_form}) -------------------------------------------------------------------------------- /demo/demo/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for demo project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | CWD = os.getcwd() 16 | 17 | # Quick-start development settings - unsuitable for production 18 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 19 | 20 | # SECURITY WARNING: keep the secret key used in production secret! 21 | SECRET_KEY = '&v@4o*w1ocho4anvc-z5u-zkgk33k7a5xi)f%2jh4!z*vdz2*a' 22 | 23 | # SECURITY WARNING: don't run with debug turned on in production! 24 | DEBUG = True 25 | 26 | TEMPLATE_DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | FILEPICKER_API_KEY = 'REPLACE_ME' 31 | 32 | MEDIA_ROOT = os.path.join(CWD, 'media') 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = ( 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'filepicker_demo' 44 | ) 45 | 46 | MIDDLEWARE_CLASSES = ( 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | #New in Django 1.7 52 | #'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | #This optional middleware takes all filepicker urls and puts the data into request.FILES 56 | 'django_filepicker.middleware.URLFileMapperMiddleware', 57 | ) 58 | 59 | ROOT_URLCONF = 'demo.urls' 60 | 61 | WSGI_APPLICATION = 'demo.wsgi.application' 62 | 63 | 64 | # Database 65 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 66 | 67 | DATABASES = { 68 | 'default': { 69 | 'ENGINE': 'django.db.backends.sqlite3', 70 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 71 | } 72 | } 73 | 74 | # Internationalization 75 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 76 | 77 | LANGUAGE_CODE = 'en-us' 78 | 79 | TIME_ZONE = 'UTC' 80 | 81 | USE_I18N = True 82 | 83 | USE_L10N = True 84 | 85 | USE_TZ = True 86 | 87 | 88 | # Static files (CSS, JavaScript, Images) 89 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 90 | 91 | STATIC_URL = '/static/' 92 | -------------------------------------------------------------------------------- /django_filepicker/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import requests 4 | import tempfile 5 | from django.core.files import File 6 | 7 | 8 | class FilepickerFile(File): 9 | filepicker_url_regex = re.compile( 10 | r'https?:\/\/www.filepicker.io\/api\/file\/.*') 11 | 12 | def __init__(self, url): 13 | if not self.filepicker_url_regex.match(url): 14 | raise ValueError('Not a filepicker.io URL: %s' % url) 15 | self.url = url 16 | 17 | def get_file(self, additional_params=None): 18 | ''' 19 | Downloads the file from filepicker.io and returns a 20 | Django File wrapper object. 21 | additional_params should include key/values such as: 22 | { 23 | 'data-fp-signature': HEXDIGEST, 24 | 'data-fp-policy': HEXDIGEST, 25 | } 26 | (in other words, parameters should look like additional_params 27 | of the models) 28 | ''' 29 | # clean up any old downloads that are still hanging around 30 | self.cleanup() 31 | 32 | # Fetch any fields possibly required for fetching files for reading. 33 | query_params = {} 34 | 35 | if additional_params: 36 | for field in ('policy','signature'): 37 | longfield = 'data-fp-{0}'.format(field) 38 | if longfield in additional_params: 39 | query_params[field] = additional_params[longfield] 40 | 41 | # iterate through one or more file urls 42 | result = list() 43 | for url in self.url.split(","): 44 | # Append the fields as GET query parameters to the URL in data. 45 | r = requests.get(url, params=query_params, stream=True) 46 | header = r.headers 47 | name = '' 48 | disposition = header.get('Content-Disposition') 49 | if disposition: 50 | name = disposition.rpartition("filename=")[2].strip('" ') 51 | filename = header.get('X-File-Name') 52 | if filename: 53 | name = filename 54 | 55 | # Create a temporary file to save to it later 56 | tmp = tempfile.NamedTemporaryFile(mode='w+b') 57 | for chunk in r.iter_content(chunk_size=1024): 58 | if chunk: 59 | tmp.write(chunk) # Write the chunk 60 | tmp.flush() 61 | 62 | # initialize File components of this object 63 | file = File(tmp, name=name) 64 | result.append(file) 65 | return result 66 | 67 | def cleanup(self): 68 | ''' 69 | Removes any downloaded objects and closes open files. 70 | ''' 71 | # self.file comes from Django File 72 | if hasattr(self, 'file'): 73 | if not self.file.closed: 74 | self.file.close() 75 | delattr(self, 'file') 76 | 77 | if hasattr(self, 'filename'): 78 | # the file might have been moved in the meantime so 79 | # check first 80 | if os.path.exists(self.filename): 81 | os.remove(self.filename) 82 | delattr(self, 'filename') 83 | 84 | def __enter__(self): 85 | ''' 86 | Allow FilepickerFile to be used as a context manager as such: 87 | 88 | with FilepickerFile(url) as f: 89 | model.field.save(f.name, f.) 90 | ''' 91 | self.get_file() 92 | # call Django's File context manager 93 | return super(FilepickerFile, self).__enter__() 94 | 95 | def __exit__(self, *args): 96 | # call Django's File context manager 97 | super(FilepickerFile, self).__exit__(*args) 98 | self.cleanup() 99 | 100 | def __del__(self): 101 | self.cleanup() 102 | -------------------------------------------------------------------------------- /django_filepicker/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import ugettext_lazy 3 | 4 | try: 5 | from . import forms 6 | except ImportError: 7 | import forms 8 | 9 | class FPUrlField(models.URLField): 10 | description = ugettext_lazy("A URL field for storing FilePicker.IO urls that works with the FilePicker widget") 11 | 12 | def __init__(self, *args, **kwargs): 13 | """ 14 | Initializes the Filepicker url field. 15 | Valid arguments: 16 | * apikey. This string is required if it isn't set as settings.FILEPICKER_API_KEY 17 | * mimetypes. Optional, the allowed mimetypes for files. Defaults to "*/*" (all files) 18 | * services. Optional, the allowed services to pull from. 19 | * additional_params. Optional, additional parameters to be applied. 20 | """ 21 | self.apikey = kwargs.pop("apikey", None) 22 | self.mimetypes = kwargs.pop("mimetypes", None) 23 | self.services = kwargs.pop("services", None) 24 | self.additional_params=kwargs.pop("additional_params", None) 25 | 26 | super(FPUrlField, self).__init__(*args, **kwargs) 27 | 28 | def formfield(self, **kwargs): 29 | defaults = {'form_class': forms.FPUrlField, 30 | 'max_length': self.max_length} 31 | 32 | if 'initial' in kwargs: 33 | defaults['required'] = False 34 | 35 | if self.apikey: 36 | defaults['apikey'] = self.apikey 37 | if self.mimetypes: 38 | defaults['mimetypes'] = self.mimetypes 39 | if self.services: 40 | defaults['services'] = self.services 41 | if self.additional_params: 42 | defaults['additional_params'] = self.additional_params 43 | 44 | defaults.update(kwargs) 45 | return super(FPUrlField, self).formfield(**defaults) 46 | 47 | try: 48 | # For South. See: http://south.readthedocs.org/en/latest/customfields.html#extending-introspection 49 | from south.modelsinspector import add_introspection_rules 50 | add_introspection_rules([], ["django_filepicker\.models\.FPUrlField"]) 51 | except ImportError: 52 | pass 53 | 54 | 55 | class FPFileField(models.FileField): 56 | description = ugettext_lazy("A File selected using Filepicker.io") 57 | 58 | def __init__(self, *args, **kwargs): 59 | """ 60 | Initializes the Filepicker file field. 61 | Valid arguments: 62 | * apikey. This string is required if it isn't set as settings.FILEPICKER_API_KEY 63 | * mimetypes. Optional, the allowed mimetypes for files. Defaults to "*/*" (all files) 64 | * services. Optional, the allowed services to pull from. 65 | * additional_params. Optional, additional parameters to be applied. 66 | """ 67 | self.apikey = kwargs.pop("apikey", None) 68 | self.mimetypes = kwargs.pop("mimetypes", None) 69 | self.services = kwargs.pop("services", None) 70 | self.additional_params=kwargs.pop("additional_params", None) 71 | 72 | super(FPFileField, self).__init__(*args, **kwargs) 73 | 74 | def formfield(self, **kwargs): 75 | defaults = {'form_class': forms.FPFileField, 76 | 'max_length': self.max_length} 77 | 78 | if 'initial' in kwargs: 79 | defaults['required'] = False 80 | 81 | if self.apikey: 82 | defaults['apikey'] = self.apikey 83 | if self.mimetypes: 84 | defaults['mimetypes'] = self.mimetypes 85 | if self.services: 86 | defaults['services'] = self.services 87 | if self.additional_params: 88 | defaults['additional_params'] = self.additional_params 89 | 90 | defaults.update(kwargs) 91 | return super(FPFileField, self).formfield(**defaults) 92 | 93 | try: 94 | # For South. See: http://south.readthedocs.org/en/latest/customfields.html#extending-introspection 95 | from south.modelsinspector import add_introspection_rules 96 | add_introspection_rules([], ["django_filepicker\.models\.FPFileField"]) 97 | except ImportError: 98 | pass 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-filepicker 2 | ================= 3 | ![pypi-release](https://pypip.in/version/django-filepicker/badge.svg) 4 | ![pypi-downloads](https://pypip.in/download/django-filepicker/badge.svg) 5 | 6 | A django plugin to make integrating with Filepicker.io even easier 7 | 8 | ##Installation 9 | 10 | 1. Install the python package: 11 | 12 | pip install django-filepicker 13 | 14 | 2. Add your file picker api key to your settings.py file. You api key can be 15 | found in the [developer portal](https://developers.inkfilepicker.com/apps/). 16 | 17 | FILEPICKER_API_KEY = 18 | 19 | 3. Configure your media root. 20 | 21 | CWD = os.getcwd() 22 | MEDIA_ROOT = os.path.join(CWD, 'media') 23 | 24 | 3. Add a filepicker field to your model and set the upload_to value. 25 | 26 | # *Please note, that FPFileField handle only one file* 27 | # In demo you can see how to handle multiple files upload. 28 | fpfile = django_filepicker.models.FPFileField(upload_to='uploads') 29 | 30 | 4. Modify your view to accept the uploaded files along with the post data. 31 | 32 | form = models.TestModelForm(request.POST, request.FILES) 33 | if form.is_valid(): 34 | #Save will read the data and upload it to the location 35 | # defined in TestModel 36 | form.save() 37 | 38 | 5. Add the form.media variable above your other JavaScript calls. 39 | 40 | 41 | Form Template Example 42 | 44 | {{ form.media }} 45 | 46 | 47 | 48 |
49 | {{ form.as_p }} 50 | 51 |
52 | 53 | 54 | ##Demo 55 | To see how all the pieces come together, see the example code in demo/, which you can run with the standard 56 | `python manage.py runserver` command 57 | 58 | ###models.py 59 | import django_filepicker 60 | class TestModel(models.Model): 61 | #FPFileField is a field that will render as a filepicker dragdrop widget, but 62 | #When accessed will provide a File-like interface (so you can do fpfile.read(), for instance) 63 | fpfile = django_filepicker.models.FPFileField(upload_to='uploads') 64 | 65 | ###views.py 66 | #building the form - automagically turns the uploaded fpurl into a File object 67 | form = models.TestModelForm(request.POST, request.FILES) 68 | if form.is_valid(): 69 | #Save will read the data and upload it to the location defined in TestModel 70 | form.save() 71 | 72 | Be sure to also provide your Filepicker.io api key, either as a parameter to the FPFileField or in settings.py as `FILEPICKER_API_KEY` 73 | 74 | ##Components 75 | ###Models 76 | The filepicker django library defines the `FPFileField` model field so you can get all the benefits of using Filepicker.io as a drop-in replacement for the standard django `FileField`. No need to change any of your view logic. 77 | 78 | ###Forms 79 | Similarly with the `FPFileField` for models, the filepicker django library defines a `FPFileField` for forms as well, that likewise serves as a drop-in replacement for the standard django `FileField`. There is also the `FPUrlField` if you want to store the Filepicker.io URL instead 80 | 81 | ###Middleware 82 | Also included is a middleware library that will take any Filepicker.io urls passed to the server, download the contents, and place the result in request.FILES. This way, you can keep your backend code for handling file uploads the same as before while adding all the front-end magic that Filepicker.io provides 83 | 84 | If you have any questions, don't hesitate to reach out at [contact@filepicker.io](mailto:contact@filepicker.io). For more information, see [https://filepicker.io](https://www.filepicker.io) 85 | 86 | Open-sourced under the MIT License. Pull requests encouraged! 87 | -------------------------------------------------------------------------------- /django_filepicker/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.core.files import File 3 | from django.conf import settings 4 | from io import StringIO 5 | 6 | try: 7 | from .utils import FilepickerFile 8 | from .widgets import FPFileWidget 9 | except ImportError: 10 | from utils import FilepickerFile 11 | from widgets import FPFileWidget 12 | 13 | class FPFieldMixin(): 14 | widget = FPFileWidget 15 | default_mimetypes = "*/*" 16 | 17 | def initialize(self, apikey=None, mimetypes=None, services=None, additional_params=None): 18 | """ 19 | Initializes the Filepicker field. 20 | Valid arguments: 21 | * apikey. This string is required if it isn't set as settings.FILEPICKER_API_KEY 22 | * mimetypes. Optional, the allowed mimetypes for files. Defaults to "*/*" (all files) 23 | * services. Optional, the allowed services to pull from. 24 | * additional_params. Optional, additional parameters to be applied. 25 | """ 26 | 27 | self.apikey = apikey or getattr(settings, 'FILEPICKER_API_KEY', None) 28 | if not self.apikey: 29 | raise Exception("Cannot find filepicker.io api key." + 30 | " Be sure to either pass as the apikey argument when creating the FPFileField," + 31 | " or set it as settings.FILEPICKER_API_KEY. To get a key, go to https://filepicker.io") 32 | 33 | self.mimetypes = mimetypes or self.default_mimetypes 34 | if not isinstance(self.mimetypes, str): 35 | #If mimetypes is an array, form a csv string 36 | try: 37 | self.mimetypes = ",".join(iter(self.mimetypes)) 38 | except TypeError: 39 | self.mimetypes = str(self.mimetypes) 40 | 41 | self.services = services or getattr(settings, 'FILEPICKER_SERVICES', None) 42 | self.additional_params = additional_params or getattr(settings, 'FILEPICKER_ADDITIONAL_PARAMS', None) 43 | 44 | def widget_attrs(self, widget): 45 | attrs = { 46 | 'data-fp-apikey': self.apikey, 47 | 'data-fp-mimetypes': self.mimetypes, 48 | } 49 | 50 | if self.services: 51 | attrs['data-fp-option-services'] = self.services 52 | 53 | if self.additional_params: 54 | attrs = dict(list(attrs.items()) + list(self.additional_params.items())) 55 | 56 | return attrs 57 | 58 | 59 | class FPUrlField(FPFieldMixin, forms.URLField): 60 | widget = FPFileWidget 61 | default_mimetypes = "*/*" 62 | 63 | def __init__(self, *args, **kwargs): 64 | """ 65 | Initializes the Filepicker url field. 66 | Valid arguments: 67 | * apikey. This string is required if it isn't set as settings.FILEPICKER_API_KEY 68 | * mimetypes. Optional, the allowed mimetypes for files. Defaults to "*/*" (all files) 69 | * services. Optional, the allowed services to pull from. 70 | * additional_params. Optional, additional parameters to be applied. 71 | """ 72 | self.initialize( 73 | apikey=kwargs.pop('apikey', None), 74 | mimetypes=kwargs.pop('mimetypes', None), 75 | services=kwargs.pop('services', None), 76 | additional_params=kwargs.pop('additional_params', None), 77 | ) 78 | super(FPUrlField, self).__init__(*args, **kwargs) 79 | 80 | 81 | class FPFileField(FPFieldMixin, forms.FileField): 82 | def __init__(self, *args, **kwargs): 83 | """ 84 | Initializes the Filepicker url field. 85 | Valid arguments: 86 | * apikey. This string is required if it isn't set as settings.FILEPICKER_API_KEY 87 | * mimetypes. Optional, the allowed mimetypes for files. Defaults to "*/*" (all files) 88 | * services. Optional, the allowed services to pull from. 89 | * additional_params. Optional, additional parameters to be applied. 90 | """ 91 | self.initialize( 92 | apikey=kwargs.pop('apikey', None), 93 | mimetypes=kwargs.pop('mimetypes', None), 94 | services=kwargs.pop('services', None), 95 | additional_params=kwargs.pop('additional_params', None), 96 | ) 97 | super(FPFileField, self).__init__(*args, **kwargs) 98 | 99 | def to_python(self, data): 100 | """Takes the url in data and creates a File object""" 101 | try: 102 | fpf = FilepickerFile(data) 103 | except ValueError as e: 104 | if 'Not a filepicker.io URL' in str(e): 105 | # Return None for invalid URLs 106 | return None 107 | else: 108 | # Pass the buck 109 | raise e 110 | else: 111 | return fpf.get_file(self.additional_params) 112 | --------------------------------------------------------------------------------