Django and Dropzone.js - javascript

When I upload files with dropzone it adds them to the database, but they don't have a file, just an ID and creation date. I think the view is the problem but I've tried tons of stuff and I can't figure it out. See my edit below for a more detailed account.
Here is the view
#login_required(login_url='/dashboard-login/')
def dashboard(request):
current_user = request.user
current_client = request.user.client
files = ClientUpload.objects.filter(client=current_client)
form = UploadFileForm()
if request.method == 'POST':
if request.FILES is None:
logger = logging.getLogger(__name__)
logger.warning("No files were attached to the upload.")
return HttpResponseBadRequest('No Files Attached.')
if form.is_valid():
upload = form.save()
form = UploadFileForm(request.POST, request.FILES)
else:
uploaded_files = [request.FILES.get('file_upload[%d]' % i)
for i in range(0, len(request.FILES))]
for f in uploaded_files:
client_upload = ClientUpload.objects.create(client=current_client, file_upload=f)
#for key in request.FILES:
# cupload = ClientUpload.objects.create(client=current_client, file_upload=request.FILES[key])
logger = logging.getLogger(__name__)
logger.debug(request.FILES)
logger.info("File(s) uploaded from " + current_client.company)
return HttpResponseRedirect(reverse('dashboard'))
data = {'form': form, 'client': current_client, 'files': files}
return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
Here are my dz options:
url: '127.0.0.1:8003/dashboard/',
method: "post",
withCredentials: false,
parallelUploads: 12,
uploadMultiple: true,
maxFilesize: 256*4*2,
paramName: "file_upload",
createImageThumbnails: true,
maxThumbnailFilesize: 20,
thumbnailWidth: 100,
thumbnailHeight: 100,
maxFiles: 12,
params: {},
clickable: true,
ignoreHiddenFiles: true,
acceptedFiles: null,
acceptedMimeTypes: null,
autoProcessQueue: false,
addRemoveLinks: true,
previewsContainer: null,
dictDefaultMessage: "Drop files here to upload",
dictFallbackMessage: "Your browser does not support drag and drop file uploads.",
dictFallbackText: "Please use the fallback form below to upload your files.",
dictFileTooBig: "File is too big ({{filesize}}MB). Max filesize: {{maxFilesize}}MB.",
dictInvalidFileType: "You can't upload files of this type.",
dictResponseError: "Server responded with {{statusCode}} code.",
dictCancelUpload: "Cancel upload",
dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
dictRemoveFile: "Remove",
dictRemoveFileConfirmation: null,
dictMaxFilesExceeded: "You can only upload {{maxFiles}} files.",
And here is the template:
{% load i18n %}
{% load staticfiles %}
{% load crispy_forms_tags %}
<link href="{% static 'css/dropzone2.css' %}" type="text/css" rel="stylesheet"/>
<form class="dropzone" id="myDropzone" method="post" action="{% url 'dashboard' %}" enctype="multipart/form-data">
{% csrf_token %}
<div class="fallback">
<input name="file" type="file" multiple />
</div>
</form>
<button class="upload-control btn-success btn" type="submit" id='submit-all' onclick="document.getElementById('myDropzone').submit()">
<i class="glyphicon glyphicon-upload"></i>
<span>{% trans 'Submit' %}</span>
</button>
<style>
.upload-control {
margin-top: 10px;
margin-bottom: 0px;
}
</style>
<script src="{% static 'js/dropzone.js' %}"></script>
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script type="text/javascript">
Dropzone.autoDiscover = false
$(document).ready(function() {
Dropzone.options.myDropzone = {
init : function() {
var submitButton = document.querySelector("#submit-all")
myDropzone = this;
submitButton.addEventListener("click", function(e) {
e.stopPropagation();
e.preventDefault();
myDropzone.processQueue();
});
this.on("sendingmultiple", function() {
// Figure out what I want here or if I want at all
});
this.on("successmultiple", function(files, response) {
window.location.reload();
});
this.on("errormultiple", function(files, response) {
// Figure out what I want here or if I want at all
});
}
// Do I need this?
//myDropzone.on('success', myDropzone.processQueue.bind(myDropzone));
};
});
</script>
EDIT:
It works now after adding http:// to the url setting. But when I upload a file it is added to the database, but the file field is blank. The multivaluedict shows the file when I print it out, but when it is saved to the database the file field has nothing in it.
When I upload one file I get this in request.FILES:
<MultiValueDict: {u'file_upload[]': [<InMemoryUploadedFile: normal.PNG (image/png)>]}>
When I upload two I get this in request.FILES:
<MultiValueDict: {u'file_upload[]': [<TemporaryUploadedFile: normal.PNG (image/png)>]}>
Despite being two files it only shows the one, but adds them both to the database (both without files and just ID and creation date). Also what is TemporaryUploadedFile and InMemoryUploadedFile?
It should have indexes in the u'file_upload[]' when I upload more than one but it doesn't. I have the settings correct for uploading multiples.
But I can't seem to get them out of the MultiValueDict. And when I try something like:
for upload in request.FILES:
client_upload = ClientUpload.objects.create(client=current_client, file_upload=upload)
I run into that problem where the admin panel shows an ID and time but no file. It happens when uploading one or more. I'm not sure what the difference is between InMemoryUploadedfile and TemporaryUploadedFile either. How can I extract the files from the MultiValueDict? get() is not working, with the list comp I just get an empty list.
The other odd thing, is when I upload certain files the MultiValueDict is empty, and with others it is not. Also it seems that my view gets called more than once (according to the log outputs) and that is normal, except it should be a post then redirect to a get, but it seems to have more than one post request. I checked the dev tools in chrome and I only see one, but oddly it outputs my log statement twice for every time I submit. I know the issue is probably in my view but I've tried a ton of stuff and can't figure out what is wrong.
Anybody have any ideas?

I'm working with Dropzone and Django myself for creating Image objects for each file uploaded, which seems to be akin to what you want to do. I'd like to point out some things that I've experienced and show you how I'm doing it to see if that helps.
What you need
The things that you need in order to create a record in the Database for files uploaded with Dropzone is:
The Dropzone HTML form
The Javascript initialization of Dropzone.
A Django View to handle the uploaded files.
I don't understand what you're doing with the Form (is it just validating?) but it seems to be unnecessary. You don't need it (and don't use it) to actually save the file.
Accessing the uploaded files
First lets talk about how to access the files in request.FILES. By setting uploadMultiple: true on your Dropzone configuration you condition Dropzone not to send dzfile but to send each file represented as dzfile[%d] (i.e. dzfile[0], dzfile[1], etc).
Even if that was not the case you're using request.FILES like if it was a list (for f in request.FILES), but like you point out it's actually a dict.
Here's what Python shows when I print request.FILES:
<MultiValueDict: {u'dzfile[1]': [<InMemoryUploadedFile: image2.jpg (image/jpeg)>], u'dzfile[2]': [<InMemoryUploadedFile: image3.jpg (image/jpeg)>], u'dzfile[0]': [<InMemoryUploadedFile: image1.jpg (image/jpeg)>]}>
To access the actual files you need to get each key by it's name.
files = [request.FILES.get('dzfile[%d]' % i)
for i in range(0, len(request.FILES))]
NOW you have the file list you wanted. Simply iterate through it and create your objects however you want. I'm not sure on how your Models work so I'm going to approximate.
for f in files:
# Create a ClientUpload object by setting its FK to client and
# FileField to the file. Correct me if I deduced the models incorrectly
client_upload = ClientUpload.objects.create(
client=current_client,
file_upload=f,
)
That should be enough to create the objects that you want.
Dropzone Javascript
It seems that in the Click event listener you add to the submit button you have to add
e.preventDefault();
e.stopPropagation();
before calling processQueue() to avoid a double form submission.
As to the sendingmultiple, successmultiple and errormultiple, what do you want to happen there? The comments are just there to indicate when those events are trigered.
I personally use:
this.on('sendingmultiple', function () {
// `sendingmultiple` to hide the submit button
$('#my-dropzone').find('button[type=submit]').hide();
});
this.on('successmultiple', function (files, response) {
// `successmultiple` to reload the page (and show the updated info)
window.location.reload();
});
this.on('errormultiple', function (files, response) {
// `errormultiple` to un-hide the button
$('#my-dropzone').find('button[type=submit]').show();
});
But of course you can do what you want.
And finally, what do you intend to happen with that last line in the <script> tag? I don't quite understand it, it looks like if you wanted to re-process the queue on success. It seems not to belong there.
Comment if anything's off, but this setup works fine for me.

In your Javascript, I think you want to use this for the paramName:
paramName: "file_upload",
in order for a Django Form or ModelForm to recognize the uploaded files.
Also make sure that the upload request is using a multipart/form-data content type.
Also, try this instead of "get_list":
dz_files = request.FILES.getlist("file_upload")

I don't know if what i show now will help you specifically but maybe it will help others,Working with dropzone made me do a workaround because ajax, files and django combined are always a little complicated.
so in my html i have this code:
<div class="logos">
<i class="fa fa-upload" id="dropzone_icon" data-name="icon" title="{% trans "Drag and drop or click" %}" alt="{% trans "Drag and drop or click" %}" ></i>
<input type="hidden" name="icon" value="" >
<input type="hidden" name="icon_name" value="" >
<div class="img-holder">
<img title='{% trans "Upload a Company Icon" %}' id="img_icon" alt='{% trans "Company icon" %}' src="{* icon *}"/>
</div>
<label>{% trans "Company Icon" %}</label>
</div>
in my js i got this:
dropz = new Dropzone(value, {
url: "branding/dropzone",
maxFiles: 1,
acceptedFiles: "image/*",
thumbnail: function(file, dataUrl) {
/* change existing image */
var file_type = file.name.split('.');
file_type = file_type[file_type.length - 1];
if(!(file_type=="png" || file_type=="jpg" || file_type=="jpeg")){
createAlert('file type must be .png, .jpg or .jpeg', '#pp_content', 'alert');
return false;
}
$("input[name='icon']").val(dataUrl.split(",")[1]);
$("input[name='icon_name']").val(file.name);
$("#img_" + type).prop("src", dataUrl);
this.removeFile(file);
},
previewTemplate: "<span></span>",
autoProcessQueue: false
});
this tells dropzone to insert into the values into the inputs(the base64 presentation of the image, and the file name) so basically i'm sending the image as a string.
after sending the inputs as a form in the ajax, this is how i handle them in my views.py:
import datetime
from django.core.files.base import ContentFile
def base64_to_image(img_b64,img_name):
"""
Creates image file from bas64 encoded data
:param img_b64: base64 data
:param img_name: filename for created image
:return: file or false if there's no data
"""
if img_b64:
image_data = b64decode(img_b64)
img_file = ContentFile(image_data,datetime.datetime.now().strftime("%y%d%m_%H%M%S") + img_name)
return img_file
else:
return False
company.icon = base64_to_image(request.POST["icon"], request.POST["icon_name"])
company.save()
this is my work around working with dropzone, maybe it will help others here as well

Related

Django: Writing JavaScript inside template vs loading it

I am using JavaScript to add comment without refreshing the page. When I am using JavaScript inside the template it is working perfectly fine but if I am writing the JavaScript to a file and loading the file inside the template then it is not displaying the name of the person who wrote the comment (It is displaying the comment fine). Below is the JavaScript I used inside the template:
function create_comment(event,div_id,form_id,commentinsert_id) {
event.preventDefault();
var text_id = '#'.concat(div_id,' ','#comment-text')
var slug_id = '#'.concat(div_id,' ','#post_slug')
var appenddiv_id = '#'.concat(div_id)
comment_count ++;
var divcommentinsert_id = 'inserted_comment-'.concat(comment_count.toString())
$.ajax({
url : "create_comment/", // the endpoint
type : "POST", // http method
data : { text : $(text_id).val(), post_slug: $(slug_id).val(),},
// handle a successful response
success : function(json) {
div.innerHTML = `
<div id = `+divcommentinsert_id+` class="card-footer">
<div class="row">
<div>
<img src="{{user.profile.profile_picture.url}}" alt="" width="40" height="40">
</div>
<div class="col-md-8">
<b style="color:rgb(240, 0, 0);"> {{user.first_name}} {{user.last_name}}</b>
<br>
`+json.text+`
<a onClick="edit_comment('`+json.slug+`','`+divcommentinsert_id+`','`+json.text+`')"><i class="fa fa-edit"></i></a>
<a onClick="delete_comment('`+json.slug+`','`+divcommentinsert_id+`')"><i class="fa fa-trash-o"></i></a>
</div>
</div>
</div>`;
document.getElementById(commentinsert_id).appendChild(div);
document.getElementById(form_id).reset();
},
// handle a non-successful response
error : function(xhr,errmsg,err) {
$('#results').html("<div class='alert-box alert radius' data-alert>Oops! We have encountered an error: "+errmsg+
" <a href='#' class='close'>×</a></div>"); // add the error to the dom
console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
}
});
};
In the above code <b style="color:rgb(240, 0, 0);"> {{user.first_name}} {{user.last_name}}</b> is used to display the name. It is displaying name of the person who added the comment but when I copy the same function to comments.js file and load the file like <script src="{% static 'scripts/comments.js' %}"></script> then its not displaying the name instead it is displaying {{user.first_name}} {{user.last_name}}. I am using this javascript in several pages, I thought it is best to write to file and load the file instead of writing the same script in every page. Can someone help me with this?
{{ dictionary.key }}, etc in a template is how you use variables with the django template engine.
A static javascript file is not a template. It isn't processed by the django template engine.
Put the values you need in your html (through the template engine), access them from JS.
Basic ideas:
Create a JS variable (eg: <script>var user_full_name = "{{ user.user_name }} {{ user.last_name }}"</script>), use user_full_name inside the ajax sucess callback.
Put the values inside some html element (eg: <input type="hidden" id="user_name" value="{{ user.user_name }}">) and get it with JS/jQuery $("#user_name").val().
edit: added missing closing double quote to the user_full_name declaration.

Build a list of filenames in Dropzone.js to pass in form submit

I have been working to build a list of file names that are queued for upload in the Dropzone.js. I have been searching the forums for weeks looking for an answer. https://github.com/enyo/dropzone/issues/1652
I started here:
https://github.com/enyo/dropzone/wiki/Upload-all-files-with-a-button.
In this section of the code is where I am working:
this.on("addedfile", function() {
console.log(this.getAcceptedFiles(file));
});
This function returns an array of information about the queued files to be uploaded on click. The array looks like this:
[File(75099)]
0: File(75099)
accepted: true
lastModified: 1508061061300
lastModifiedDate: Sun Oct 15 2017 05:51:01 GMT-0400 (Eastern Daylight Time) {}
name: "ITI-TV-REPAIR-FORM.pdf"
previewElement: div.dz-preview.dz-file-preview
previewTemplate: div.dz-preview.dz-file-preview
size: 75099
status: "queued"
type: "application/pdf"
upload: {progress: 0, total: 75099, bytesSent: 0}
webkitRelativePath: ""
_removeLink: a.dz-remove
__proto__: File
length: 1
__proto__: Array(0)
I know that if I use this.getAcceptedFiles().length. It will return 1. As it should since length is in the "root" of the array. But trying to access the name is where I am stumped. The hard part is that the [File(75099)] at the head of the array will always change but the key name will always be in the same place.
I'm thinking out loud while I type this up. The thing I just thought of is if I can do a key value search of the array since I know the key that I am looking for is name:.
I have tried accessing the name value with
this.getAcceptedFiles().name
Of course, that did not work. So my ending question would be what is the best way to access the key value in this array to get the name of the file?
UPDATE: Figure it out!!!
this.on("addedfile", function(file) {
//returns file names that are in the queue
console.log(file.name);
});
Full code here: https://github.com/enyo/dropzone/issues/1652
The addedfile event is called whenever a file is added to the list of uploaded files, also you can get the added file as parameter in the event callback:
this.on("addedfile", function(file) {
console.log(file.name)
});
Here is a working example:
Dropzone.options.myDropzone = {
// Prevents Dropzone from uploading dropped files immediately
autoProcessQueue: false,
acceptedFiles: 'image/*',
init: function() {
var submitButton = document.querySelector("#submit-all");
myDropzone = this;
submitButton.addEventListener("click", function() {
myDropzone.processQueue(); // Tell Dropzone to process all queued files.
});
// You might want to show the submit button only when
// files are dropped here:
this.on("addedfile", function(file) {
console.log(file.name)
});
}
};
<!DOCTYPE html>
<script src="https://rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script>
<link rel="stylesheet" href="https://rawgit.com/enyo/dropzone/master/dist/dropzone.css">
<!-- Change /upload-target to your upload address -->
<form action="/upload-target" class="dropzone" id="my-dropzone"></form>
<br>
<button id="submit-all">Submit all files</button>
Hope this helps

RESTLET - Angular.JS File Upload on Client Side

We are using Restlet for our API on the client side, everything server side is written in Laravel 5. We are having trouble in one portion. We have a couple endpoints that require you to upload a file. In Angular, I have only gotten that to work using the following method thus far:
var fd = new FormData();
fd.append('image', $scope.file);
$http.post(apiURL + "/upload", fd, {
transformRequest: angular.identity,
headers: {
'Content-Type': undefined
}
}).then(function(response) {
//yay it worked
}, function(response) {
//try again
});
I don't know why that is the only way I have been successful, but I would like to change it over to the Restlet endpoint I have created. For most of my calls, it is as simple as this:
$rootScope.restlet.getCompanyAll().then(function(response) {
$scope.companies = response.data;
});
and
var config = {
params: {
start: "2016-01-01",
end: "2016-01-31"
}
};
var id = 1;
$rootScope.restlet.getCompanyStatsCompany_id(id, config).then(function(response) {
$scope.companies = response.data;
});
Pretty simple, but when I try to implement the post of an image, it doesn't recognize it, and leaves the image out completely. Here is the code I am attempting to use, it works with the non-Restlet way, but doesn't work with Restlet.
var config = {
params: {
name: $scope.newCompany.name,
rep_id: $scope.newCompany.rep_id,
image: $scope.image_input
}
};
var id = 1;
$rootScope.restlet.postCompanyCreate(config).then(function(response) {
$scope.companies = response.data;
});
Has anyone gotten something like this to work? And if so, how does it work? Thanks! :)
EDIT 1:
Here is the HTML of the page I have set up. It does have a file input, but for some reason it Restlet doesn't like the file. I have tried a plain file input, along with an input with a directive on it. The current version I am using is an image, that when clicked is linked to an file input that is hidden. I am also using a custom directive on it currently.
HTML:
<div class="modal-body">
<form ng-submit="createCompany()">
<!-- OTHER FORM STUFF GOES HERE -->
<div class="col-sm-12">
<img src="" id="imagePreview" onClick="$('#imageUpload').trigger('click');" style="max-width: 100%;" />
<input type="file" style="display: none;" id="imageUpload" file="file" />
</div>
<!-- FORM SUBMIT AND RESET BUTTONS HERE -->
</form>
</div>
Custom Directive:
app.directive('file', function() {
return {
scope: {
file: '='
},
link: function(scope, el, attrs) {
el.bind('change', function(event) {
var file = event.target.files[0];
scope.file = file ? file : undefined;
scope.$apply();
});
}
};
});
You didn't post your HTML but I assume that you are using an input with a type of file to specify the file to upload and binding to it with ng-model or some native Angular binding mechanism. I have no idea why, but Angular doesn't support this. There are 2 common ways to implement this.
The 1st is to use a directive that works around this issue. There's one here: https://github.com/danialfarid/ng-file-upload.
The 2nd, and at least where I would start, is you can simply use document.getElementById to retrieve the file name from the input in your controller. IOW, $scope.file = document.getElementById("myFileThingy").value.

Additional data passed via Jquery BlueImp File Upload Plugin not getting correctly at Server side

I am using Blueimp jQuery File Upload Plugin to upload single and multiple images.
Here I want to pass some additional data along with images to server like caption,share option etc. I searched site and found how to sent multiple files in a single ajax request and also passed additional data to server. But the problem is, when I added a textarea in template for capturing caption of images in template the data is getting like [object Object] in server. But when I pass only caption I am getting it as array.
Here is the code:
$('#fileupload').fileupload({
disableImageResize: false,
autoUpload: false,
singleFileUploads:false,
url: jQuery('#site').val()+'UserPhotos/upload?Token='+jQuery('#token').val()
}).bind('fileuploadsubmit', function(e, data) {
//binding
var photo_share = jQuery('#photo_share').val();
var public = 1;
//serializing the captions for photos to be send to server
var inputs = data.context.find(':input');
if (inputs.filter('[value=""]').first().focus().length) {
data.context.find('button').prop('disabled', false);
return false;
}
data.formData = {
'caption': inputs.serializeArray(),
'photo_share':photo_share,
'public':public
};
});
HTML:
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-upload fade">
<td>
<textarea name="caption[]" placeholder="Enter caption" class="form-control" rows="3"></textarea>
</td>
// ...
Any help to get this passed to server properly and parse the same?
SOLUTION: simply use serializeArray() of form id in "fileuploadsubmit" bind of fileupload function.Data will be passed to server correctly.
var inputs = data.context.find(':input');
if (inputs.filter('[value=""]').first().focus().length) {
data.context.find('button').prop('disabled', false);
return false;
}
data.formData = jQuery('#fileupload').serializeArray();

backbone and javascript: upload and store the image data in GAE datastore?

I have been searching through wherever possible but got no help at all for this question.
I am working on a small program using python GAE with backbone javascript, I want the user can create a new book with a html form given below and store the new book in the GAE datastore, the form contains strings and an image.
<form>
<div><label>Title:</label> <input type="text" id="title" name="book_title" ></div>
<div><label>Author:</label> <input type="text" id="author" name="book_author" ></div>
<div><label>Picture:</label> <input id="image" type="file" class="upload" name="img" /></div>
<div><img id="img_preview" src="#" alt="image" ></img></div>
<div><button class="create_book">Create Book</button></div>
</form>
selecting a book picture from the local file system is handled by displayPicture method in order to load a thumbnail image on the page for preview.
clicking "Create Book" button event is handled by the createBook method in javascript file:
event:{
"click .create_book": "createBook" ,
"change .upload": "displayPicture",
......
},
createBook:function(){
this.model.set({
title: $('#title').val(),
author: $('#author').val(),
image: this.pictureFile.name
});
......
app.bookList.create(this.model,{wait:true,
success:function(){
alert("A new book has been created!");
}
});
return false;
},
......
displayPicture: function(evt){
var files = evt.target.files;
if(files){
this.pictureFile = files[0];
var reader = new FileReader();
reader.onloadend = function(){
$('#img_preview').attr('src',reader.result).width(200).height(200);
};
reader.readAsDataURL(this.pictureFile);
},
..........
at the python server end:
class Book(db.Model):
title = db.StringProperty()
author = db.StringProperty()
image = db.BlobProperty()
class createBook(webapp.RequestHandler):
def post(self):
book = Book()
book.title = self.request.get("title")
book.author = self.request.get("author")
book.image = db.Blob(self.request.get("image"))
book.put()
......
application = webapp.WSGIApplication(
[
.....
('/books/?',createBook)
],
debug=Ture
)
I can only display the thumbnail picture on the page for preview, but fail to create the new book, the line "app.bookList.create(...)" does send a POST request to the python server end, and the request is handled by the "createBook" method, but Firebug shows the "self.request.get("title"/"author"/"image")" lines are just an empty string, which means the content of the form is not retrieved from the Http request properly.
can you tell what are the problems in my code snippet ? thanks for your help.
I think that your problem is likely that you are passing a model in to create. From the Backbone docs:
create collection.create(attributes, [options])
Convenience to create a new instance of a model within a collection.
create isn't expecting a model, because it creates a model; what it's expecting is a raw JSON object (eg. {foo: bar}).
*EDIT* Actually, that's incorrect, create can accept an (un-saved) model; leaving the next few lines here any way so this doesn't get too confusing.
You could fix this by doing:
app.bookList.create(this.model.attributes, {wait:true,
or:
app.bookList.create(this.model.toJSON(), {wait:true,
but really you don't need to use create at all because you don't need to create a model (you already have one). What you (probably) want to do is add the model you have to the collection:
app.bookList.add(this.model)
and then sync it with the server separately:
this.model.save(null, {wait:true,
success:function(){
alert("A new book has been created!");
}
});
In the python side, you should get the data from
self.request.body
Not
self.request.get('xxx')

Categories

Resources