Add multiple photo upload support
This commit is contained in:
parent
dc87792545
commit
a682ea17c1
|
@ -293,16 +293,32 @@ def upload_from_pc(request):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
form = request.form
|
form = request.form
|
||||||
upload = request.files["file"]
|
count = 0
|
||||||
|
status = None
|
||||||
|
for upload in request.files.getlist("file"):
|
||||||
|
count += 1
|
||||||
|
|
||||||
# Make a temp filename for it.
|
# Make a temp filename for it.
|
||||||
filetype = upload.filename.rsplit(".", 1)[1]
|
filetype = upload.filename.rsplit(".", 1)[-1]
|
||||||
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
|
if not allowed_filetype(upload.filename):
|
||||||
logger.debug("Save incoming photo to: {}".format(tempfile))
|
return dict(success=False, error="Unsupported file extension.")
|
||||||
upload.save(tempfile)
|
|
||||||
|
|
||||||
# All good so far. Process the photo.
|
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
|
||||||
return process_photo(form, tempfile)
|
logger.debug("Save incoming photo to: {}".format(tempfile))
|
||||||
|
upload.save(tempfile)
|
||||||
|
|
||||||
|
# All good so far. Process the photo.
|
||||||
|
status = process_photo(form, tempfile)
|
||||||
|
if not status["success"]:
|
||||||
|
return status
|
||||||
|
|
||||||
|
# Multi upload?
|
||||||
|
if count > 1:
|
||||||
|
status["multi"] = True
|
||||||
|
else:
|
||||||
|
status["multi"] = False
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
def upload_from_www(form):
|
def upload_from_www(form):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash
|
||||||
|
|
||||||
import rophako.model.user as User
|
import rophako.model.user as User
|
||||||
import rophako.model.photo as Photo
|
import rophako.model.photo as Photo
|
||||||
from rophako.utils import template, pretty_time, login_required
|
from rophako.utils import template, pretty_time, login_required, ajax_response
|
||||||
from rophako.log import logger
|
from rophako.log import logger
|
||||||
from config import *
|
from config import *
|
||||||
|
|
||||||
|
@ -68,7 +68,15 @@ def upload():
|
||||||
"""Upload a photo."""
|
"""Upload a photo."""
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
# We're posting the upload. What source is the pic from?
|
# We're posting the upload.
|
||||||
|
|
||||||
|
# Is this an ajax post or a direct post?
|
||||||
|
is_ajax = request.form.get("__ajax", "false") == "true"
|
||||||
|
|
||||||
|
# Album name.
|
||||||
|
album = request.form.get("album") or request.form.get("new-album")
|
||||||
|
|
||||||
|
# What source is the pic from?
|
||||||
result = None
|
result = None
|
||||||
location = request.form.get("location")
|
location = request.form.get("location")
|
||||||
if location == "pc":
|
if location == "pc":
|
||||||
|
@ -83,9 +91,24 @@ def upload():
|
||||||
|
|
||||||
# How'd it go?
|
# How'd it go?
|
||||||
if result["success"] is not True:
|
if result["success"] is not True:
|
||||||
flash("The upload has failed: {}".format(result["error"]))
|
if is_ajax:
|
||||||
return redirect(url_for(".upload"))
|
return ajax_response(False, result["error"])
|
||||||
return redirect(url_for(".crop", photo=result["photo"]))
|
else:
|
||||||
|
flash("The upload has failed: {}".format(result["error"]))
|
||||||
|
return redirect(url_for(".upload"))
|
||||||
|
|
||||||
|
# Good!
|
||||||
|
if is_ajax:
|
||||||
|
# Was it a multiple upload?
|
||||||
|
if result["multi"]:
|
||||||
|
return ajax_response(True, url_for(".album_index", name=album))
|
||||||
|
else:
|
||||||
|
return ajax_response(True, url_for(".crop", photo=result["photo"]))
|
||||||
|
else:
|
||||||
|
if result["multi"]:
|
||||||
|
return redirect(url_for(".album_index", name=album))
|
||||||
|
else:
|
||||||
|
return redirect(url_for(".crop", photo=result["photo"]))
|
||||||
|
|
||||||
# Get the list of available albums.
|
# Get the list of available albums.
|
||||||
g.info["album_list"] = [
|
g.info["album_list"] = [
|
||||||
|
|
|
@ -10,6 +10,7 @@ import re
|
||||||
import importlib
|
import importlib
|
||||||
import smtplib
|
import smtplib
|
||||||
import markdown
|
import markdown
|
||||||
|
import json
|
||||||
|
|
||||||
from rophako.log import logger
|
from rophako.log import logger
|
||||||
from config import *
|
from config import *
|
||||||
|
@ -45,6 +46,15 @@ def admin_required(f):
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
|
def ajax_response(status, msg):
|
||||||
|
"""Return a standard JSON response."""
|
||||||
|
status = "ok" if status else "error"
|
||||||
|
return json.dumps(dict(
|
||||||
|
status=status,
|
||||||
|
msg=msg,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
def template(name, **kwargs):
|
def template(name, **kwargs):
|
||||||
"""Render a template to the browser."""
|
"""Render a template to the browser."""
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
You can upload a photo from your computer or by pasting in the URL to a photo
|
You can upload a photo from your computer or by pasting in the URL to a photo
|
||||||
somewhere else on the Internet.
|
somewhere else on the Internet.
|
||||||
|
|
||||||
<form name="upload" action="{{ url_for('photo.upload') }}" method="POST" enctype="multipart/form-data">
|
<form id="upload-form" action="{{ url_for('photo.upload') }}" method="POST" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
<input type="hidden" name="token" value="{{ csrf_token() }}">
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
@ -22,7 +22,10 @@ somewhere else on the Internet.
|
||||||
|
|
||||||
<div id="pic-pc" class="location-div">
|
<div id="pic-pc" class="location-div">
|
||||||
<strong>Upload a picture from my computer</strong><br>
|
<strong>Upload a picture from my computer</strong><br>
|
||||||
<input type="file" size="30" name="file">
|
<input type="file" size="30" name="file" id="file-picker" accept="image/*" multiple><p>
|
||||||
|
|
||||||
|
<strong>Or, drag images here:</strong><br>
|
||||||
|
<div id="dropbox" class="photo-upload-dropbox">Drag and drop images into this box</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="pic-www" class="location-div">
|
<div id="pic-www" class="location-div">
|
||||||
|
@ -34,6 +37,7 @@ somewhere else on the Internet.
|
||||||
Only jpeg, gif and png images are supported. There is no maximum file size
|
Only jpeg, gif and png images are supported. There is no maximum file size
|
||||||
limit, but be reasonable.
|
limit, but be reasonable.
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<p>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Photo Options</legend>
|
<legend>Photo Options</legend>
|
||||||
|
@ -58,13 +62,23 @@ somewhere else on the Internet.
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
<button type="submit">Upload Picture</button>
|
<fieldset id="upload-progress" style="display: none">
|
||||||
|
<legend>Upload Progress</legend>
|
||||||
|
|
||||||
|
<div class="upload-trough">
|
||||||
|
<div id="upload-progress-bar" class="upload-progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<button type="submit" id="upload-button">Upload Picture</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script type="text/javascript" src="/rophako/multiupload.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$("#pic-www").hide();
|
$("#pic-www").hide();
|
||||||
|
|
|
@ -42,6 +42,7 @@ Uploaded by {{ author["name"] }} on {{ photo["pretty_time"] }}.
|
||||||
<li><a href="{{ url_for('photo.set_cover', album=photo['album'], key=photo['key']) }}">Set Album Cover</a></li>
|
<li><a href="{{ url_for('photo.set_cover', album=photo['album'], key=photo['key']) }}">Set Album Cover</a></li>
|
||||||
<li><a href="{{ url_for('photo.set_profile', key=photo['key']) }}">Set as my Profile Picture</a></li>
|
<li><a href="{{ url_for('photo.set_profile', key=photo['key']) }}">Set as my Profile Picture</a></li>
|
||||||
<li><a href="{{ url_for('photo.edit', key=photo['key']) }}">Edit this photo</a></li>
|
<li><a href="{{ url_for('photo.edit', key=photo['key']) }}">Edit this photo</a></li>
|
||||||
|
<li><a href="{{ url_for('photo.crop', photo=photo['key']) }}">Change Thumbnail</a></li>
|
||||||
<li><a href="{{ url_for('photo.delete', key=photo['key']) }}">Delete this photo</a></li>
|
<li><a href="{{ url_for('photo.delete', key=photo['key']) }}">Delete this photo</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
191
rophako/www/rophako/multiupload.js
Normal file
191
rophako/www/rophako/multiupload.js
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/* rophako cms
|
||||||
|
-----------
|
||||||
|
HTML5 multi-upload script for the photo albums.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function RophakoUpload() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
this.MAX_UPLOAD_FILE_SIZE = 1024*1024; // 1 MB
|
||||||
|
this.UPLOAD_URL = "/photos/upload";
|
||||||
|
this.NEXT_URL = "/files/";
|
||||||
|
|
||||||
|
// List of pending files to handle when the Upload button is finally clicked.
|
||||||
|
this.PENDING_FILES = [];
|
||||||
|
|
||||||
|
this.ready = function() {
|
||||||
|
// Set up the drag/drop zone.
|
||||||
|
self.initDropbox();
|
||||||
|
|
||||||
|
// Set up the handler for the file input box.
|
||||||
|
$("#file-picker").on("change", function() {
|
||||||
|
self.handleFiles(this.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle the submit button.
|
||||||
|
$("#upload-button").on("click", function(e) {
|
||||||
|
// If the user has JS disabled, none of this code is running but the
|
||||||
|
// file multi-upload input box should still work. In this case they'll
|
||||||
|
// just POST to the upload endpoint directly. However, with JS we'll do
|
||||||
|
// the POST using ajax and then redirect them ourself when done.
|
||||||
|
e.preventDefault();
|
||||||
|
self.doUpload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.doUpload = function() {
|
||||||
|
$("#upload-progress").show();
|
||||||
|
var $progressBar = $("#upload-progress-bar");
|
||||||
|
|
||||||
|
// Gray out the form.
|
||||||
|
$("#upload-button").attr("disabled", "disabled");
|
||||||
|
|
||||||
|
// Initialize the progress bar.
|
||||||
|
$progressBar.css({"width": "0%"});
|
||||||
|
|
||||||
|
// Collect the form data.
|
||||||
|
fd = self.collectFormData();
|
||||||
|
|
||||||
|
// Attach the files.
|
||||||
|
for (var i = 0, ie = self.PENDING_FILES.length; i < ie; i++) {
|
||||||
|
// Collect the other form data.
|
||||||
|
fd.append("file", self.PENDING_FILES[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform the back-end that we're doing this over ajax.
|
||||||
|
fd.append("__ajax", "true");
|
||||||
|
|
||||||
|
var xhr = $.ajax({
|
||||||
|
xhr: function() {
|
||||||
|
var xhrobj = $.ajaxSettings.xhr();
|
||||||
|
if (xhrobj.upload) {
|
||||||
|
xhrobj.upload.addEventListener("progress", function(event) {
|
||||||
|
var percent = 0;
|
||||||
|
var position = event.loaded || event.position;
|
||||||
|
var total = event.total;
|
||||||
|
if (event.lengthComputable) {
|
||||||
|
percent = Math.ceil(position / total * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the progress bar.
|
||||||
|
$progressBar.css({"width": percent + "%"});
|
||||||
|
$progressBar.text(percent + "%");
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
return xhrobj;
|
||||||
|
},
|
||||||
|
url: self.UPLOAD_URL,
|
||||||
|
method: "POST",
|
||||||
|
contentType: false, //"multipart/form-data",
|
||||||
|
processData: false,
|
||||||
|
dataType: "json",
|
||||||
|
cache: false,
|
||||||
|
data: fd,
|
||||||
|
success: function(data) {
|
||||||
|
console.log(data);
|
||||||
|
$progressBar.css({"width": "100%"});
|
||||||
|
|
||||||
|
// How'd it go?
|
||||||
|
if (data.status === "error") {
|
||||||
|
// Uh-oh.
|
||||||
|
window.alert(data.msg);
|
||||||
|
$("#upload-button").removeAttr("disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Ok!
|
||||||
|
window.location = data.msg;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.collectFormData = function() {
|
||||||
|
// Go through all the form fields and collect their names/values.
|
||||||
|
var fd = new FormData();
|
||||||
|
|
||||||
|
$("#upload-form :input").each(function() {
|
||||||
|
var $this = $(this);
|
||||||
|
var name = $this.attr("name");
|
||||||
|
var type = $this.attr("type") || "";
|
||||||
|
var value = $this.val();
|
||||||
|
|
||||||
|
// No name = no care.
|
||||||
|
if (name === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the file upload box for now.
|
||||||
|
if (type === "file") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkboxes? Only add their value if they're checked.
|
||||||
|
if (type === "checkbox" || type === "radio") {
|
||||||
|
if (!$this.is(":checked")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fd.append(name, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.handleFiles = function(files) {
|
||||||
|
// Add them to the pending files list.
|
||||||
|
for (var i = 0, ie = files.length; i < ie; i++) {
|
||||||
|
self.PENDING_FILES.push(files[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
this.initDropbox = function() {
|
||||||
|
var $dropbox = $("#dropbox");
|
||||||
|
|
||||||
|
// On drag enter...
|
||||||
|
$dropbox.on("dragenter", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).addClass("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
// On drag over...
|
||||||
|
$dropbox.on("dragover", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// On drop...
|
||||||
|
$dropbox.on("drop", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).removeClass("active");
|
||||||
|
|
||||||
|
// Get the files.
|
||||||
|
var files = e.originalEvent.dataTransfer.files;
|
||||||
|
self.handleFiles(files);
|
||||||
|
|
||||||
|
// Update the display to acknowledge the number of pending files.
|
||||||
|
$dropbox.text(self.PENDING_FILES.length + " files ready for upload!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the files are dropped outside of the drop zone, the browser will
|
||||||
|
// redirect to show the files in the window. To avoid that we can prevent
|
||||||
|
// the 'drop' event on the document.
|
||||||
|
function stopDefault(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
$(document).on("dragenter", stopDefault);
|
||||||
|
$(document).on("dragover", stopDefault);
|
||||||
|
$(document).on("drop", stopDefault);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
new RophakoUpload().ready();
|
||||||
|
});
|
|
@ -130,6 +130,27 @@ table.table td {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Photo Upload page */
|
||||||
|
.photo-upload-dropbox {
|
||||||
|
border: 2px dashed #000000;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
.photo-upload-dropbox.active {
|
||||||
|
border: 4px dashed #FF0000;
|
||||||
|
}
|
||||||
|
.upload-trough {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #000000;
|
||||||
|
width: 100%;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
.upload-progress-bar {
|
||||||
|
position: relative;
|
||||||
|
width: 0%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #FF9900;
|
||||||
|
}
|
||||||
|
|
||||||
/* Photo Grids: see http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
|
/* Photo Grids: see http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
|
||||||
ul.photo-grid {
|
ul.photo-grid {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user