Skip to content

Commit 36b9d49

Browse files
committed
added initial codebase
1 parent 38d11d4 commit 36b9d49

19 files changed

+868
-27
lines changed

.gitignore

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1 @@
11
*.py[co]
2-
3-
# Packages
4-
*.egg
5-
*.egg-info
6-
dist
7-
build
8-
eggs
9-
parts
10-
bin
11-
var
12-
sdist
13-
develop-eggs
14-
.installed.cfg
15-
16-
# Installer logs
17-
pip-log.txt
18-
19-
# Unit test / coverage reports
20-
.coverage
21-
.tox
22-
23-
#Translations
24-
*.mo
25-
26-
#Mr Developer
27-
.mr.developer.cfg

README.md

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,107 @@
11
django-ajax-upload-widget
22
=========================
33

4-
Provides AJAX file upload functionality for FileFields and ImageFields with a simple widget replacement in the form.
4+
Provides AJAX file upload functionality for FileFields and ImageFields with a simple widget replacement in the form.
5+
6+
7+
Features
8+
--------
9+
10+
* Drop-in replacement for Django's built-in `ClearableFileInput` widget (no change required to your model).
11+
* Works in Internet Explorer 7 and higher (not tested in IE 6, but may work).
12+
* Random hash string added to file names to ensure uploaded file paths are not guessable by others.
13+
14+
15+
Usage
16+
-----
17+
18+
### Server Side
19+
20+
In your form, use the `AjaxClearableFileInput` on your `FileField` or `ImageField`.
21+
22+
from django import forms
23+
from ajax_upload.widgets import AjaxClearableFileInput
24+
25+
class MyForm(forms.Form):
26+
my_image_field = forms.ImageField(widget=AjaxClearableFileInput())
27+
28+
29+
Or, if using a `ModelForm` you can just override the widget.
30+
31+
from django import forms
32+
from ajax_upload.widgets import AjaxClearableFileInput
33+
34+
class MyForm(forms.ModelForm):
35+
class Meta:
36+
model = MyModel
37+
widgets = {
38+
'my_image_field': AjaxClearableFileInput
39+
}
40+
41+
42+
### Client Side
43+
44+
Include the Javascript (and optionally CSS) files in your page and call the `autoDiscover` function.
45+
This will search the page for all the AJAX file input fields and apply the necessary Javascript.
46+
47+
<link href="{{ STATIC_URL }}ajax_upload/css/ajax-upload-widget.css" rel="stylesheet" type="text/css"/>
48+
<script src="{{ STATIC_URL }}ajax_upload/js/jquery.iframe-transport.js"></script>
49+
<script src="{{ STATIC_URL }}ajax_upload/js/ajax-upload-widget.js"></script>
50+
51+
<script>
52+
$(function() {
53+
AjaxUploadWidget.autoDiscover();
54+
});
55+
</script>
56+
57+
58+
You can also pass options to `autoDiscover()`:
59+
60+
61+
<script>
62+
$(function() {
63+
AjaxUploadWidget.autoDiscover({
64+
changeButtonText: 'Click to change',
65+
onError: function(data) { alert('Error!'); }
66+
// see source for full list of options
67+
});
68+
});
69+
</script>
70+
71+
72+
OR ... you can explicitly instantiate an AjaxUploadWidget on an AJAX file input field:
73+
74+
<input id="Foo" name="foo" type="file" data-upload-url="/ajax-upload/" data-filename="" data-required=""/>
75+
<!-- The input field needs to be outputed by Django to contain the appropriate data attributes -->
76+
77+
<script>
78+
new AjaxUploadWidget($('#Foo'), {
79+
// options
80+
});
81+
</script>
82+
83+
84+
85+
Dependencies
86+
------------
87+
* jQuery
88+
* jQuery Iframe Transport plugin (included in this package)
89+
90+
91+
Installation
92+
------------
93+
94+
1. Add `ajax_upload` to your `INSTALLED_APPS` setting.
95+
96+
1. Hook in the urls.
97+
98+
# urls.py
99+
urlpatterns += patterns('',
100+
(r'^ajax-upload/', include('ajax_upload.urls')),
101+
)
102+
103+
1. Run the tests to verify it is working.
104+
105+
./manage.py test ajax_upload
106+
107+
1. That's it (don't forget include the Javascript as mentioned above).

ajax_upload/__init__.py

Whitespace-only changes.

ajax_upload/admin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from django.contrib import admin
2+
3+
from .models import UploadedFile
4+
5+
6+
class UploadedFileAdmin(admin.ModelAdmin):
7+
list_display = ('__unicode__',)
8+
date_hierarchy = 'creation_date'
9+
search_fields = ('file',)
10+
11+
12+
admin.site.register(UploadedFile, UploadedFileAdmin)

ajax_upload/forms.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import uuid
2+
3+
from django import forms
4+
5+
from .models import UploadedFile
6+
7+
8+
class UploadedFileForm(forms.ModelForm):
9+
10+
class Meta:
11+
model = UploadedFile
12+
fields = ('file',)
13+
14+
def clean_file(self):
15+
data = self.cleaned_data['file']
16+
# Change the name of the file to something unguessable
17+
# Construct the new name as <unique-hex>-<original>.<ext>
18+
data.name = u'%s-%s' % (uuid.uuid4().hex, data.name)
19+
return data

ajax_upload/models.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.db import models
2+
from django.utils.translation import ugettext_lazy as _
3+
4+
5+
class UploadedFile(models.Model):
6+
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
7+
file = models.FileField(_('file'), upload_to='ajax_uploads/')
8+
9+
class Meta:
10+
ordering = ('id',)
11+
verbose_name = _('uploaded file')
12+
verbose_name_plural = _('uploaded files')
13+
14+
def __unicode__(self):
15+
return unicode(self.file)
16+
17+
def delete(self, *args, **kwargs):
18+
super(UploadedFile, self).delete(*args, **kwargs)
19+
if self.file:
20+
self.file.delete()
21+
delete.alters_data = True

ajax_upload/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.conf import settings
2+
3+
4+
# Number of seconds to keep uploaded files. The clean_uploaded command will
5+
# delete them after this has expired.
6+
UPLOADER_DELETE_AFTER = getattr(settings, 'UPLOADER_DELETE_AFTER', 60 * 60)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.ajax-upload-preview-area {
2+
display: inline-block;
3+
background: #ddd;
4+
border-radius: 6px;
5+
border: 2px dashed #999;
6+
padding: 5px;
7+
margin-right: 5px;
8+
font-size: 12px;
9+
font-family: Arial, sans-serif;
10+
text-align: center;
11+
}
12+
.ajax-upload-preview-area img {
13+
display: block;
14+
max-width: 130px;
15+
max-height: 130px;
16+
border: 1px solid #666;
17+
margin: 0 auto;
18+
margin-top: 5px;
19+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
(function() {
2+
var global = this;
3+
var $ = global.$;
4+
var console = global.console || {log: function() {}};
5+
6+
var AjaxUploadWidget = global.AjaxUploadWidget = function(element, options) {
7+
this.options = {
8+
changeButtonText: 'Change',
9+
removeButtonText: 'Remove',
10+
previewAreaClass: 'ajax-upload-preview-area',
11+
previewFilenameLength: 30,
12+
onUpload: null, // right before uploading to the server
13+
onComplete: null,
14+
onError: null,
15+
onRemove: null
16+
};
17+
$.extend(this.options, options);
18+
this.$element = $(element);
19+
this.initialize();
20+
};
21+
22+
AjaxUploadWidget.prototype.DjangoAjaxUploadError = function(message) {
23+
this.name = 'DjangoAjaxUploadError';
24+
this.message = message;
25+
};
26+
AjaxUploadWidget.prototype.DjangoAjaxUploadError.prototype = new Error();
27+
AjaxUploadWidget.prototype.DjangoAjaxUploadError.prototype.constructor = AjaxUploadWidget.prototype.DjangoAjaxUploadError;
28+
29+
AjaxUploadWidget.prototype.initialize = function() {
30+
var self = this;
31+
this.name = this.$element.attr('name');
32+
33+
// Create a hidden field to contain our uploaded file name
34+
this.$hiddenElement = $('<input type="hidden"/>')
35+
.attr('name', this.name)
36+
.val(this.$element.data('filename'));
37+
this.$element.attr('name', ''); // because we don't want to conflict with our hidden field
38+
this.$element.after(this.$hiddenElement);
39+
40+
// Initialize preview area and action buttons
41+
this.$previewArea = $('<div class="'+this.options.previewAreaClass+'"></div>');
42+
this.$element.before(this.$previewArea);
43+
44+
// Listen for when a file is selected, and perform upload
45+
this.$element.on('change', function(evt) {
46+
self.upload();
47+
});
48+
this.$changeButton = $('<input type="button"/>')
49+
.val(this.options.changeButtonText)
50+
.on('click', function(evt) {
51+
self.$element.show();
52+
$(this).hide();
53+
});
54+
this.$element.after(this.$changeButton);
55+
56+
this.$removeButton = $('<input type="button"/>')
57+
.val(this.options.removeButtonText)
58+
.on('click', function(evt) {
59+
self.$hiddenElement.val('');
60+
self.displaySelection();
61+
if(self.options.onRemove) self.options.onRemove.call(self);
62+
});
63+
this.$changeButton.after(this.$removeButton);
64+
65+
this.displaySelection();
66+
};
67+
68+
AjaxUploadWidget.prototype.upload = function() {
69+
var self = this;
70+
if(!this.$element.val()) return;
71+
if(this.options.onUpload) this.options.onUpload.call(this);
72+
this.$element.attr('name', 'file');
73+
$.ajax(this.$element.data('upload-url'), {
74+
iframe: true,
75+
files: this.$element,
76+
processData: false,
77+
type: 'POST',
78+
dataType: 'json',
79+
success: function(data) { self.uploadDone(data); },
80+
error: function(data) { self.uploadFail(data); }
81+
});
82+
};
83+
84+
AjaxUploadWidget.prototype.uploadDone = function(data) {
85+
// This handles errors as well because iframe transport does not
86+
// distinguish between 200 response and other errors
87+
if(data.errors) {
88+
if(this.options.onError) {
89+
this.options.onError.call(this, data);
90+
} else {
91+
console.log('Upload failed:');
92+
console.log(data);
93+
}
94+
} else {
95+
this.$hiddenElement.val(data.path);
96+
var tmp = this.$element;
97+
this.$element = this.$element.clone(true).val('');
98+
tmp.replaceWith(this.$element);
99+
this.displaySelection();
100+
if(this.options.onComplete) this.options.onComplete.call(this, data.path);
101+
}
102+
};
103+
104+
AjaxUploadWidget.prototype.uploadFail = function(xhr) {
105+
if(this.options.onError) {
106+
this.options.onError.call(this);
107+
} else {
108+
console.log('Upload failed:');
109+
console.log(xhr);
110+
}
111+
};
112+
113+
AjaxUploadWidget.prototype.displaySelection = function() {
114+
var filename = this.$hiddenElement.val();
115+
116+
if(filename !== '') {
117+
this.$previewArea.empty();
118+
this.$previewArea.append(this.generateFilePreview(filename));
119+
120+
this.$previewArea.show();
121+
this.$changeButton.show();
122+
if(this.$element.data('required') === 'True') {
123+
this.$removeButton.hide();
124+
} else {
125+
this.$removeButton.show();
126+
}
127+
this.$element.hide();
128+
} else {
129+
this.$previewArea.hide();
130+
this.$changeButton.hide();
131+
this.$removeButton.hide();
132+
this.$element.show();
133+
}
134+
};
135+
136+
AjaxUploadWidget.prototype.generateFilePreview = function(filename) {
137+
// Returns the html output for displaying the given uploaded filename to the user.
138+
var prettyFilename = this.prettifyFilename(filename);
139+
var output = '<a href="'+filename+'" target="_blank">'+prettyFilename+'';
140+
$.each(['jpg', 'jpeg', 'png', 'gif'], function(i, ext) {
141+
if(filename.slice(-3) == ext) {
142+
output += '<img src="'+filename+'"/>';
143+
return false;
144+
}
145+
});
146+
output += '</a>';
147+
return output;
148+
};
149+
150+
AjaxUploadWidget.prototype.prettifyFilename = function(filename) {
151+
// Get rid of the folder names
152+
var cleaned = filename.slice(filename.lastIndexOf('/')+1);
153+
154+
// Strip the random hex in the filename inserted by the backend (if present)
155+
var re = /^[a-f0-9]{32}\-/i;
156+
cleaned = cleaned.replace(re, '');
157+
158+
// Truncate the filename
159+
var maxChars = this.options.previewFilenameLength;
160+
var elipsis = '...';
161+
if(cleaned.length > maxChars) {
162+
cleaned = elipsis + cleaned.slice((-1 * maxChars) + elipsis.length);
163+
}
164+
return cleaned;
165+
};
166+
167+
AjaxUploadWidget.autoDiscover = function(options) {
168+
$('input[type="file"].ajax-upload').each(function(index, element) {
169+
new AjaxUploadWidget(element, options);
170+
});
171+
};
172+
}).call(this);

0 commit comments

Comments
 (0)