mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-11 08:43:10 +00:00
first pass of multi-uploader
This commit is contained in:
parent
16ca198377
commit
138b3ba285
10 changed files with 1843 additions and 1 deletions
|
|
@ -295,6 +295,7 @@ button.icon{
|
|||
|
||||
> li{
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
background-color:white;
|
||||
padding:1em 1.5em;
|
||||
margin-bottom:1em;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,15 @@ def get_image_form():
|
|||
widgets={'file': forms.FileInput()})
|
||||
|
||||
|
||||
def get_image_form_for_multi():
|
||||
return modelform_factory(
|
||||
get_image_model(),
|
||||
# set the 'file' widget to a FileInput rather than the default ClearableFileInput
|
||||
# so that when editing, we don't get the 'currently: ...' banner which is
|
||||
# a bit pointless here
|
||||
exclude=('file',))
|
||||
|
||||
|
||||
class ImageInsertionForm(forms.Form):
|
||||
"""
|
||||
Form for selecting parameters of the image (e.g. format) prior to insertion
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
$(function () {
|
||||
|
||||
function process_result(data) {
|
||||
var result = $.parseJSON(data);
|
||||
if (result.success) {
|
||||
$('li#image-'+result.image_id).slideUp(function() { $(this).remove(); });
|
||||
}
|
||||
}
|
||||
|
||||
$('#fileupload').fileupload({
|
||||
dataType: 'html',
|
||||
sequentialUploads: true,
|
||||
done: function (e, data) {
|
||||
var im_li = $(data.result);
|
||||
|
||||
im_li.find('form').each(function() {
|
||||
|
||||
var jform = $(this);
|
||||
|
||||
jform.submit(function(event) { //convert save to an ajax call
|
||||
event.preventDefault();
|
||||
$.post(this.action, $(this).serialize(), process_result);
|
||||
});
|
||||
|
||||
jform.find('a').each(function(){ //convert delete to an ajax call
|
||||
$(this).click(function(event) {
|
||||
event.preventDefault();
|
||||
$.post(this.href, jform.serialize(), process_result);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
jform.find('#id_'+ im_li.attr('id') +'-tags').tagit(window.tagit_opts);
|
||||
});
|
||||
|
||||
im_li
|
||||
$("#image-forms").append(im_li);
|
||||
}
|
||||
});
|
||||
});
|
||||
1426
wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.fileupload.js
vendored
Normal file
1426
wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.fileupload.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
214
wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.iframe-transport.js
vendored
Normal file
214
wagtail/wagtailimages/static/wagtailimages/js/vendor/jquery.iframe-transport.js
vendored
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* jQuery Iframe Transport Plugin 1.8.2
|
||||
* https://github.com/blueimp/jQuery-File-Upload
|
||||
*
|
||||
* Copyright 2011, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* global define, window, document */
|
||||
|
||||
(function (factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// Register as an anonymous AMD module:
|
||||
define(['jquery'], factory);
|
||||
} else {
|
||||
// Browser globals:
|
||||
factory(window.jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
'use strict';
|
||||
|
||||
// Helper variable to create unique names for the transport iframes:
|
||||
var counter = 0;
|
||||
|
||||
// The iframe transport accepts four additional options:
|
||||
// options.fileInput: a jQuery collection of file input fields
|
||||
// options.paramName: the parameter name for the file form data,
|
||||
// overrides the name property of the file input field(s),
|
||||
// can be a string or an array of strings.
|
||||
// options.formData: an array of objects with name and value properties,
|
||||
// equivalent to the return data of .serializeArray(), e.g.:
|
||||
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
|
||||
// options.initialIframeSrc: the URL of the initial iframe src,
|
||||
// by default set to "javascript:false;"
|
||||
$.ajaxTransport('iframe', function (options) {
|
||||
if (options.async) {
|
||||
// javascript:false as initial iframe src
|
||||
// prevents warning popups on HTTPS in IE6:
|
||||
/*jshint scripturl: true */
|
||||
var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
|
||||
/*jshint scripturl: false */
|
||||
form,
|
||||
iframe,
|
||||
addParamChar;
|
||||
return {
|
||||
send: function (_, completeCallback) {
|
||||
form = $('<form style="display:none;"></form>');
|
||||
form.attr('accept-charset', options.formAcceptCharset);
|
||||
addParamChar = /\?/.test(options.url) ? '&' : '?';
|
||||
// XDomainRequest only supports GET and POST:
|
||||
if (options.type === 'DELETE') {
|
||||
options.url = options.url + addParamChar + '_method=DELETE';
|
||||
options.type = 'POST';
|
||||
} else if (options.type === 'PUT') {
|
||||
options.url = options.url + addParamChar + '_method=PUT';
|
||||
options.type = 'POST';
|
||||
} else if (options.type === 'PATCH') {
|
||||
options.url = options.url + addParamChar + '_method=PATCH';
|
||||
options.type = 'POST';
|
||||
}
|
||||
// IE versions below IE8 cannot set the name property of
|
||||
// elements that have already been added to the DOM,
|
||||
// so we set the name along with the iframe HTML markup:
|
||||
counter += 1;
|
||||
iframe = $(
|
||||
'<iframe src="' + initialIframeSrc +
|
||||
'" name="iframe-transport-' + counter + '"></iframe>'
|
||||
).bind('load', function () {
|
||||
var fileInputClones,
|
||||
paramNames = $.isArray(options.paramName) ?
|
||||
options.paramName : [options.paramName];
|
||||
iframe
|
||||
.unbind('load')
|
||||
.bind('load', function () {
|
||||
var response;
|
||||
// Wrap in a try/catch block to catch exceptions thrown
|
||||
// when trying to access cross-domain iframe contents:
|
||||
try {
|
||||
response = iframe.contents();
|
||||
// Google Chrome and Firefox do not throw an
|
||||
// exception when calling iframe.contents() on
|
||||
// cross-domain requests, so we unify the response:
|
||||
if (!response.length || !response[0].firstChild) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (e) {
|
||||
response = undefined;
|
||||
}
|
||||
// The complete callback returns the
|
||||
// iframe content document as response object:
|
||||
completeCallback(
|
||||
200,
|
||||
'success',
|
||||
{'iframe': response}
|
||||
);
|
||||
// Fix for IE endless progress bar activity bug
|
||||
// (happens on form submits to iframe targets):
|
||||
$('<iframe src="' + initialIframeSrc + '"></iframe>')
|
||||
.appendTo(form);
|
||||
window.setTimeout(function () {
|
||||
// Removing the form in a setTimeout call
|
||||
// allows Chrome's developer tools to display
|
||||
// the response result
|
||||
form.remove();
|
||||
}, 0);
|
||||
});
|
||||
form
|
||||
.prop('target', iframe.prop('name'))
|
||||
.prop('action', options.url)
|
||||
.prop('method', options.type);
|
||||
if (options.formData) {
|
||||
$.each(options.formData, function (index, field) {
|
||||
$('<input type="hidden"/>')
|
||||
.prop('name', field.name)
|
||||
.val(field.value)
|
||||
.appendTo(form);
|
||||
});
|
||||
}
|
||||
if (options.fileInput && options.fileInput.length &&
|
||||
options.type === 'POST') {
|
||||
fileInputClones = options.fileInput.clone();
|
||||
// Insert a clone for each file input field:
|
||||
options.fileInput.after(function (index) {
|
||||
return fileInputClones[index];
|
||||
});
|
||||
if (options.paramName) {
|
||||
options.fileInput.each(function (index) {
|
||||
$(this).prop(
|
||||
'name',
|
||||
paramNames[index] || options.paramName
|
||||
);
|
||||
});
|
||||
}
|
||||
// Appending the file input fields to the hidden form
|
||||
// removes them from their original location:
|
||||
form
|
||||
.append(options.fileInput)
|
||||
.prop('enctype', 'multipart/form-data')
|
||||
// enctype must be set as encoding for IE:
|
||||
.prop('encoding', 'multipart/form-data');
|
||||
// Remove the HTML5 form attribute from the input(s):
|
||||
options.fileInput.removeAttr('form');
|
||||
}
|
||||
form.submit();
|
||||
// Insert the file input fields at their original location
|
||||
// by replacing the clones with the originals:
|
||||
if (fileInputClones && fileInputClones.length) {
|
||||
options.fileInput.each(function (index, input) {
|
||||
var clone = $(fileInputClones[index]);
|
||||
// Restore the original name and form properties:
|
||||
$(input)
|
||||
.prop('name', clone.prop('name'))
|
||||
.attr('form', clone.attr('form'));
|
||||
clone.replaceWith(input);
|
||||
});
|
||||
}
|
||||
});
|
||||
form.append(iframe).appendTo(document.body);
|
||||
},
|
||||
abort: function () {
|
||||
if (iframe) {
|
||||
// javascript:false as iframe src aborts the request
|
||||
// and prevents warning popups on HTTPS in IE6.
|
||||
// concat is used to avoid the "Script URL" JSLint error:
|
||||
iframe
|
||||
.unbind('load')
|
||||
.prop('src', initialIframeSrc);
|
||||
}
|
||||
if (form) {
|
||||
form.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// The iframe transport returns the iframe content document as response.
|
||||
// The following adds converters from iframe to text, json, html, xml
|
||||
// and script.
|
||||
// Please note that the Content-Type for JSON responses has to be text/plain
|
||||
// or text/html, if the browser doesn't include application/json in the
|
||||
// Accept header, else IE will show a download dialog.
|
||||
// The Content-Type for XML responses on the other hand has to be always
|
||||
// application/xml or text/xml, so IE properly parses the XML response.
|
||||
// See also
|
||||
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
|
||||
$.ajaxSetup({
|
||||
converters: {
|
||||
'iframe text': function (iframe) {
|
||||
return iframe && $(iframe[0].body).text();
|
||||
},
|
||||
'iframe json': function (iframe) {
|
||||
return iframe && $.parseJSON($(iframe[0].body).text());
|
||||
},
|
||||
'iframe html': function (iframe) {
|
||||
return iframe && $(iframe[0].body).html();
|
||||
},
|
||||
'iframe xml': function (iframe) {
|
||||
var xmlDoc = iframe && iframe[0];
|
||||
return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
|
||||
$.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
|
||||
$(xmlDoc.body).html());
|
||||
},
|
||||
'iframe script': function (iframe) {
|
||||
return iframe && $.globalEval($(iframe[0].body).text());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}));
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load image_tags %}
|
||||
{% load i18n %}
|
||||
{% block titletag %}{% trans "Add multiple images" %}{% endblock %}
|
||||
{% block bodyclass %}menu-images{% endblock %}
|
||||
{% block extra_css %}
|
||||
{% include "wagtailadmin/shared/tag_field_css.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script src="{{ STATIC_URL }}wagtailimages/js/vendor/jquery.iframe-transport.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailimages/js/vendor/jquery.fileupload.js"></script>
|
||||
<script src="{{ STATIC_URL }}wagtailimages/js/add-multiple.js"></script>
|
||||
|
||||
<script src="{{ STATIC_URL }}wagtailadmin/js/vendor/tag-it.js"></script>
|
||||
{% url 'wagtailadmin_tag_autocomplete' as autocomplete_url %}
|
||||
<script>
|
||||
window.tagit_opts = {
|
||||
autocomplete: {source: "{{ autocomplete_url|addslashes }}"}
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% trans "Add image" as add_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=add_str icon="image" %}
|
||||
|
||||
|
||||
<div class="nice-padding">
|
||||
<form action="{% url 'wagtailimages_add_multiple' %}" method="POST" enctype="multipart/form-data">
|
||||
<input id="fileupload" type="file" name="files[]" data-url="{% url 'wagtailimages_add_multiple' %}" multiple>
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
<ul class="multiple" id="image-forms">
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"image_id": {{ image_id }},
|
||||
"success": {% if success %}true{% else %}false{% endif %}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{% load i18n image_tags %}
|
||||
<li id="image-{{ image.id }}">
|
||||
<form action="{% url 'wagtailimages_edit_multiple' image.id %}" method="POST" enctype="multipart/form-data">
|
||||
<ul class="fields col7">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% include "wagtailadmin/shared/field_as_li.html" %}
|
||||
{% endfor %}
|
||||
<li>
|
||||
<input type="submit" value="{% trans 'Save' %}" />
|
||||
<a href="{% url 'wagtailimages_delete_multiple' image.id %}" class="button button-secondary no">{% trans "Cancel upload" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
<div class="col5">
|
||||
{% image image max-800x600 %}
|
||||
</div>
|
||||
</li>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
from django.conf.urls import url
|
||||
from wagtail.wagtailimages.views import images, chooser
|
||||
from wagtail.wagtailimages.views import images, chooser, multiple
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', images.index, name='wagtailimages_index'),
|
||||
|
|
@ -7,6 +7,10 @@ urlpatterns = [
|
|||
url(r'^(\d+)/delete/$', images.delete, name='wagtailimages_delete_image'),
|
||||
url(r'^add/$', images.add, name='wagtailimages_add_image'),
|
||||
|
||||
url(r'^multiple/add/$', multiple.add, name='wagtailimages_add_multiple'),
|
||||
url(r'^multiple/(\d+)/$', multiple.edit, name='wagtailimages_edit_multiple'),
|
||||
url(r'^multiple/(\d+)/delete$', multiple.delete, name='wagtailimages_delete_multiple'),
|
||||
|
||||
url(r'^chooser/$', chooser.chooser, name='wagtailimages_chooser'),
|
||||
url(r'^chooser/(\d+)/$', chooser.image_chosen, name='wagtailimages_image_chosen'),
|
||||
url(r'^chooser/upload/$', chooser.chooser_upload, name='wagtailimages_chooser_upload'),
|
||||
|
|
|
|||
87
wagtail/wagtailimages/views/multiple.py
Normal file
87
wagtail/wagtailimages/views/multiple.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib import messages
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
|
||||
|
||||
from django.forms.models import modelformset_factory
|
||||
from django.template.loader import render_to_string
|
||||
from django.http import HttpResponse
|
||||
|
||||
from wagtail.wagtailadmin.forms import SearchForm
|
||||
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
from wagtail.wagtailimages.forms import get_image_form_for_multi
|
||||
|
||||
import json
|
||||
|
||||
@permission_required('wagtailimages.add_image')
|
||||
@vary_on_headers('X-Requested-With')
|
||||
def add(request):
|
||||
ImageForm = get_image_form_for_multi()
|
||||
ImageModel = get_image_model()
|
||||
|
||||
if request.POST and request.is_ajax():
|
||||
if not request.FILES:
|
||||
return HttpResponseBadRequest('Must upload a file')
|
||||
else:
|
||||
image = ImageModel(uploaded_by_user=request.user, title=request.FILES['files[]'].name, file=request.FILES['files[]'])
|
||||
image.save()
|
||||
form = ImageForm(instance=image, prefix='image-%d'%image.id)
|
||||
|
||||
return render(request, 'wagtailimages/multiple/edit.html', {
|
||||
'image': image,
|
||||
'form': form
|
||||
})
|
||||
else:
|
||||
pass
|
||||
|
||||
return render(request, "wagtailimages/multiple/add.html", {})
|
||||
|
||||
@permission_required('wagtailadmin.access_admin') # more specific permission tests are applied within the view
|
||||
def edit(request, image_id, callback=None):
|
||||
Image = get_image_model()
|
||||
ImageForm = get_image_form_for_multi()
|
||||
|
||||
image = get_object_or_404(Image, id=image_id)
|
||||
|
||||
if not image.is_editable_by_user(request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
if request.POST:
|
||||
form = ImageForm(request.POST, request.FILES, instance=image, prefix='image-'+image_id)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return HttpResponse(render_to_string("wagtailimages/multiple/confirmation.json", {
|
||||
'success': True,
|
||||
'image_id': image_id
|
||||
}))
|
||||
else:
|
||||
pass
|
||||
|
||||
return HttpResponse(render_to_string("wagtailimages/multiple/confirmation.json", {
|
||||
'success': False,
|
||||
'image_id': image_id
|
||||
}))
|
||||
|
||||
@permission_required('wagtailadmin.access_admin') # more specific permission tests are applied within the view
|
||||
def delete(request, image_id):
|
||||
image = get_object_or_404(get_image_model(), id=image_id)
|
||||
|
||||
if not image.is_editable_by_user(request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
if request.POST:
|
||||
image.delete()
|
||||
return HttpResponse(render_to_string("wagtailimages/multiple/confirmation.json", {
|
||||
'success': True,
|
||||
'image_id': image_id
|
||||
}))
|
||||
else:
|
||||
return HttpResponse(render_to_string("wagtailimages/multiple/confirmation.json", {
|
||||
'success': False,
|
||||
'image_id': image_id
|
||||
}))
|
||||
Loading…
Reference in a new issue