Browse Source

Add multiple photo upload support

pull/2/head
Noah Petherbridge 6 years ago
parent
commit
a682ea17c1
7 changed files with 293 additions and 17 deletions
  1. +25
    -9
      rophako/model/photo.py
  2. +28
    -5
      rophako/modules/photo.py
  3. +10
    -0
      rophako/utils.py
  4. +17
    -3
      rophako/www/photos/upload.html
  5. +1
    -0
      rophako/www/photos/view.html
  6. +191
    -0
      rophako/www/rophako/multiupload.js
  7. +21
    -0
      rophako/www/smoke/style.css

+ 25
- 9
rophako/model/photo.py View File

@@ -293,16 +293,32 @@ def upload_from_pc(request):
"""

form = request.form
upload = request.files["file"]

# Make a temp filename for it.
filetype = upload.filename.rsplit(".", 1)[1]
tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
logger.debug("Save incoming photo to: {}".format(tempfile))
upload.save(tempfile)
count = 0
status = None
for upload in request.files.getlist("file"):
count += 1

# Make a temp filename for it.
filetype = upload.filename.rsplit(".", 1)[-1]
if not allowed_filetype(upload.filename):
return dict(success=False, error="Unsupported file extension.")

tempfile = "{}/rophako-photo-{}.{}".format(config.TEMPDIR, int(time.time()), filetype)
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

# All good so far. Process the photo.
return process_photo(form, tempfile)
return status


def upload_from_www(form):


+ 28
- 5
rophako/modules/photo.py View File

@@ -6,7 +6,7 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash

import rophako.model.user as User
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 config import *

@@ -68,7 +68,15 @@ def upload():
"""Upload a photo."""

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
location = request.form.get("location")
if location == "pc":
@@ -83,9 +91,24 @@ def upload():

# How'd it go?
if result["success"] is not True:
flash("The upload has failed: {}".format(result["error"]))
return redirect(url_for(".upload"))
return redirect(url_for(".crop", photo=result["photo"]))
if is_ajax:
return ajax_response(False, result["error"])
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.
g.info["album_list"] = [


+ 10
- 0
rophako/utils.py View File

@@ -10,6 +10,7 @@ import re
import importlib
import smtplib
import markdown
import json

from rophako.log import logger
from config import *
@@ -45,6 +46,15 @@ def admin_required(f):
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):
"""Render a template to the browser."""



+ 17
- 3
rophako/www/photos/upload.html View File

@@ -7,7 +7,7 @@
You can upload a photo from your computer or by pasting in the URL to a photo
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() }}">

<fieldset>
@@ -22,7 +22,10 @@ somewhere else on the Internet.

<div id="pic-pc" class="location-div">
<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 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
limit, but be reasonable.
</fieldset>
<p>

<fieldset>
<legend>Photo Options</legend>
@@ -58,13 +62,23 @@ somewhere else on the Internet.
</fieldset>
<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>

{% endblock %}

{% block scripts %}
<script type="text/javascript" src="/rophako/multiupload.js"></script>
<script>
$(document).ready(function() {
$("#pic-www").hide();


+ 1
- 0
rophako/www/photos/view.html View File

@@ -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_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.crop', photo=photo['key']) }}">Change Thumbnail</a></li>
<li><a href="{{ url_for('photo.delete', key=photo['key']) }}">Delete this photo</a></li>
</ul>
{% endif %}


+ 191
- 0
rophako/www/rophako/multiupload.js View 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();
});

+ 21
- 0
rophako/www/smoke/style.css View File

@@ -130,6 +130,27 @@ table.table td {
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 */
ul.photo-grid {
list-style: none;


Loading…
Cancel
Save