Skip to content

Commit

Permalink
initial commit of django-uploadify
Browse files Browse the repository at this point in the history
  • Loading branch information
zbyte64 committed Feb 28, 2012
1 parent a9e0fbd commit 386a1c0
Show file tree
Hide file tree
Showing 28 changed files with 1,475 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
*.py[cdo]
*~

24 changes: 24 additions & 0 deletions LICENSE
@@ -0,0 +1,24 @@
Copyright (c) 2012, Cuker Interactive
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of other contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1 change: 1 addition & 0 deletions MANIFEST.in
@@ -0,0 +1 @@
recursive-include directupload/templates *
Empty file added directupload/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions directupload/admin.py
@@ -0,0 +1,17 @@
from django.db import models
from django.contrib.admin import ModelAdmin

from widgets import UploadifyClearableFileInput

class UploadifyAdminMixin(object):
def formfield_for_dbfield(self, db_field, **kwargs):
if isinstance(db_field, models.FileField):
return self.formfield_for_file_field(db_field, kwargs.pop('request', None), **kwargs)
return ModelAdmin.formfield_for_dbfield(self, db_field, **kwargs)

def formfield_for_file_field(self, db_field, request=None, **kwargs):
"""
Get a form Field that is prepared for uploadify
"""
kwargs['widget'] = UploadifyClearableFileInput(db_field=db_field)
return db_field.formfield(**kwargs)
5 changes: 5 additions & 0 deletions directupload/app_settings.py
@@ -0,0 +1,5 @@
from django.conf import settings

UPLOADIFY_BACKEND = getattr(settings, 'UPLOADIFY_BACKEND', 'directupload.backends.djangoview.DjangoViewBackend')
#UPLOADIFY_BACKEND = getattr(settings, 'UPLOADIFY_BACKEND', 'directupload.backends.s3.S3UploadifyBackend')

11 changes: 11 additions & 0 deletions directupload/backends/__init__.py
@@ -0,0 +1,11 @@
try:
import importlib
except ImportError:
from django.utils import importlib

def get_uploadify_backend():
from directupload.app_settings import UPLOADIFY_BACKEND
module_name, class_name = UPLOADIFY_BACKEND.rsplit('.', 1)
module = importlib.import_module(module_name)
return getattr(module, class_name)

61 changes: 61 additions & 0 deletions directupload/backends/base.py
@@ -0,0 +1,61 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse

from django.utils import simplejson as json

from common import UPLOADIFY_OPTIONS, UPLOADIFY_METHODS, DEFAULT_CANCELIMG, DEFAULT_UPLOADER, BUTTON_TEXT

class BaseUploadifyBackend(object):
def __init__(self, request, uploadify_options={}, post_data={}):
self.request = request
self.options = getattr(settings, 'UPLOADIFY_DEFAULT_OPTIONS', {})
self.options.update(uploadify_options)

if any(True for key in self.options if key not in UPLOADIFY_OPTIONS + UPLOADIFY_METHODS):
raise ImproperlyConfigured("Attempted to initialize with unrecognized option '%s'." % key)

_set_default_if_none(self.options, 'cancelImage', DEFAULT_CANCELIMG)
_set_default_if_none(self.options, 'swf', DEFAULT_UPLOADER)
_set_default_if_none(self.options, 'uploader', self.get_uploader())
_set_default_if_none(self.options, 'buttonText', BUTTON_TEXT)
_set_default_if_none(self.options, 'checkExisting', self.get_check_existing())
_set_default_if_none(self.options, 'determineName', self.get_determine_name())

self.post_data = post_data
self.build_post_data()

def get_check_existing(self):
return False

def get_determine_name(self):
return reverse('uploadify-determine-name')

def get_uploader(self):
pass

def build_post_data(self):
pass

def update_post_params(self, params):
pass

def get_options_json(self):
self.options['postData'] = self.post_data
subs = []
for key in self.options:
if key in UPLOADIFY_METHODS:
subs.append(('"%%%s%%"' % key, self.options[key]))
self.options[key] = "%%%s%%" % key

out = json.dumps(self.options)

for search, replace in subs:
out = out.replace(search, replace)

return out

def _set_default_if_none(dict, key, default=None):
if key not in dict:
dict[key] = default

11 changes: 11 additions & 0 deletions directupload/backends/common.py
@@ -0,0 +1,11 @@
from django.conf import settings

UPLOADIFY_OPTIONS = ('auto', 'buttonImg', 'buttonText', 'cancelImg', 'checkScript', 'displayData', 'expressInstall', 'fileDataName', 'fileDesc', 'fileExt', 'folder', 'height', 'hideButton', 'method', 'multi', 'queueID', 'queueSizeLimit', 'removeCompleted', 'rollover', 'script','scriptAccess', 'scriptData', 'simUploadLimit', 'sizeLimit', 'uploader', 'width', 'wmode')

UPLOADIFY_METHODS = ('onAllComplete', 'onCancel', 'onCheck', 'onClearQueue', 'onComplete', 'onError', 'onInit', 'onOpen', 'onProgress', 'onQueueFull', 'onSelect', 'onSelectOnce', 'onSWFReady')

BUTTON_TEXT = 'Select File'

# Defaults for required Uploadify options
DEFAULT_CANCELIMG = settings.STATIC_URL + "uploadify/uploadify-cancel.png"
DEFAULT_UPLOADER = settings.STATIC_URL + "uploadify/uploadify.swf"
31 changes: 31 additions & 0 deletions directupload/backends/djangoview.py
@@ -0,0 +1,31 @@
from base import BaseUploadifyBackend

import datetime
from urllib import urlencode

from django.middleware.csrf import get_token
from django.core.urlresolvers import reverse

from utils import Signer
signer = Signer()

def sign(value):
return signer.sign(value)

def unsign(value):
return signer.unsign(value)

class DjangoViewBackend(BaseUploadifyBackend):
"""Uploadify for using the builtin django view"""

def get_uploader(self):
return reverse('uploadify-upload-file')

def build_post_data(self):
data = {'upload_to': self.options['folder'],
'request_time': datetime.datetime.now().isoformat(),
'_request_id': 'foo',} #TODO salt}
signed_data = sign(urlencode(data))
self.post_data['payload'] = signed_data
self.post_data['csrfmiddlewaretoken'] = get_token(self.request)

99 changes: 99 additions & 0 deletions directupload/backends/s3.py
@@ -0,0 +1,99 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from urllib import quote_plus
from datetime import datetime
from datetime import timedelta
import base64
import hmac
import hashlib
import os

from base import BaseUploadifyBackend, _set_default_if_none, json

# AWS Options
ACCESS_KEY_ID = getattr(settings, 'AWS_ACCESS_KEY_ID', None)
SECRET_ACCESS_KEY = getattr(settings, 'AWS_SECRET_ACCESS_KEY', None)
BUCKET_NAME = getattr(settings, 'AWS_BUCKET_NAME', None)
SECURE_URLS = getattr(settings, 'AWS_S3_SECURE_URLS', False)
BUCKET_URL = getattr(settings, 'AWS_BUCKET_URL', ('https://' if SECURE_URLS else 'http://') + BUCKET_NAME + '.s3.amazonaws.com')
DEFAULT_ACL = getattr(settings, 'AWS_DEFAULT_ACL', 'public-read')
DEFAULT_KEY_PATTERN = getattr(settings, 'AWS_DEFAULT_KEY_PATTERN', '${targetname}')
DEFAULT_FORM_TIME = getattr(settings, 'AWS_DEFAULT_FORM_LIFETIME', 36000) # 10 HOURS


class S3UploadifyBackend(BaseUploadifyBackend):
"""Uploadify for Amazon S3"""

def __init__(self, request, uploadify_options={}, post_data={}, conditions={}):
self.conditions = conditions
super(S3UploadifyBackend, self).__init__(request, uploadify_options, post_data)

def get_uploader(self):
return BUCKET_URL

def build_post_data(self):
self.options['fileObjName'] = 'file' #S3 requires this be the field name

if 'folder' in self.options:
key = os.path.join(self.options['folder'], DEFAULT_KEY_PATTERN)
else:
key = DEFAULT_KEY_PATTERN
#_set_default_if_none(self.post_data, 'key', key) #this is set by update_post_params
_set_default_if_none(self.post_data, 'acl', DEFAULT_ACL)

try:
_set_default_if_none(self.post_data, 'bucket', BUCKET_NAME)
except ValueError:
raise ImproperlyConfigured("Bucket name is a required property.")

try:
_set_default_if_none(self.post_data, 'AWSAccessKeyId', ACCESS_KEY_ID)
except ValueError:
raise ImproperlyConfigured("AWS Access Key ID is a required property.")

self.conditions = self.build_conditions()

if not SECRET_ACCESS_KEY:
raise ImproperlyConfigured("AWS Secret Access Key is a required property.")

expiration_time = datetime.utcnow() + timedelta(seconds=DEFAULT_FORM_TIME)
self.policy_string = self.build_post_policy(expiration_time)
self.policy = base64.b64encode(self.policy_string)

self.signature = base64.encodestring(hmac.new(SECRET_ACCESS_KEY, self.policy, hashlib.sha1).digest()).strip()

self.post_data['policy'] = self.policy
self.post_data['signature'] = self.signature

def build_conditions(self):
conditions = list()

#make s3 happy with uploadify
#conditions.append(['starts-with', '$folder', '']) #no longer passed by uploadify
conditions.append(['starts-with', '$filename', ''])
conditions.append(['starts-with', '$targetname', '']) #variable introduced by this package
conditions.append(['starts-with', '$targetpath', self.options['folder']])
#conditions.append({'success_action_status': '200'})

#real conditions
conditions.append(['starts-with', '$key', self.options['folder']])
conditions.append({'bucket': self.post_data['bucket']})
conditions.append({'acl': self.post_data['acl']})
return conditions

def build_post_policy(self, expiration_time):
policy = {'expiration': expiration_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
'conditions': self.conditions,}
return json.dumps(policy)

def update_post_params(self, params):
#instruct s3 that our key is the targetpath
params['key'] = params['targetpath']

def _uri_encode(str):
try:
# The Uploadify flash component apparently decodes the scriptData once, so we need to encode twice here.
return quote_plus(quote_plus(str, safe='~'), safe='~')
except:
raise ValueError

44 changes: 44 additions & 0 deletions directupload/backends/utils.py
@@ -0,0 +1,44 @@
from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac

import base64

def b64_encode(s):
return base64.urlsafe_b64encode(s).strip('=')

def b64_decode(s):
pad = '=' * (-len(s) % 4)
return base64.urlsafe_b64decode(s + pad)

def base64_hmac(salt, value, key):
return b64_encode(salted_hmac(salt, value, key).digest())

class LegacySigner(object):
'''
Limited functionality signer
'''
def __init__(self, key=None, sep=':', salt=None):
self.sep = sep
self.key = key or settings.SECRET_KEY
self.salt = salt or ('%s.%s' %
(self.__class__.__module__, self.__class__.__name__))

def signature(self, value):
return base64_hmac(self.salt + 'signer', value, self.key)

def sign(self, value):
return '%s%s%s' % (value, self.sep, self.signature(value))

def unsign(self, signed_value):
if not self.sep in signed_value:
raise ValueError('No "%s" found in value' % self.sep)
value, sig = signed_value.rsplit(self.sep, 1)
if constant_time_compare(sig, self.signature(value)):
return value
raise ValueError('Signature "%s" does not match' % sig)

try:
from django.core.signing import Signer
except ImportError:
Signer = LegacySigner

11 changes: 11 additions & 0 deletions directupload/models.py
@@ -0,0 +1,11 @@
from django.db import models

from widgets import UploadifyClearableFileInput

def patch_admin():
from django.contrib.admin.options import FORMFIELD_FOR_DBFIELD_DEFAULTS
FORMFIELD_FOR_DBFIELD_DEFAULTS[models.ImageField] = {'widget': UploadifyClearableFileInput}
FORMFIELD_FOR_DBFIELD_DEFAULTS[models.FileField] = {'widget': UploadifyClearableFileInput}

patch_admin()

677 changes: 677 additions & 0 deletions directupload/static/uploadify/jquery.uploadify.js

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions directupload/static/uploadify/jquery.uploadify.min.js

Large diffs are not rendered by default.

Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 386a1c0

Please sign in to comment.