Folder / Directory with large files upload in Django - django-views

I am trying to upload a folder with multiple files that will be zipped before storing it into the database. I am following ajax file upload technique and Django/python to accomplish it. The following code is working fine when the file size is small. When file size is larger, it created multiple small files of that single file. I think it's because before uploading the whole file, my program is starting to write the already received chunk of that file.
I believe the problem is in view.py where I am writing the uploaded files. Can anyone please help me with some advice so I can solve this problem?
I have posted an image of my directory before and after uploading it.
enter image description here
View.py:
def handle_uploaded_file(upload_file,file_path,zipfile):
full_path = "%s/%s" % (file_path, upload_file.name)
for chunk in upload_file.chunks():
zipfile.writestr(full_path,chunk)
def fileUploadView(request):
file_obj = file_store()
if request.method == "POST":
filename = request.session.get('file_name')
directory_name = request.POST.get('directories')
#write zip file with uploaded folder
zipfile = ZipFile(filename,'a',ZIP_DEFLATED)
json_to_dictionar = json.loads(directory_name)
for upload_file in request.FILES.getlist('file'):
file_path = os.path.dirname(json_to_dictionar[upload_file.name])
if os.path.exists(file_path):
handle_uploaded_file(upload_file,file_path,zipfile)
else:
os.makedirs(file_path)
handle_uploaded_file(upload_file,file_path,zipfile)
zipfile.close()
file_obj.file = File(open(zipfile.filename,'rb'))
file_obj.fileName = os.path.basename(zipfile.filename)
file_obj.save()
data = {'name':file_obj.fileName, 'url':file_obj.fileName}
return render(request, 'fileupload_app/transfer_list/index.html',data)
else:
return render(request,'fileupload_app/basic_upload/test.html',{})
HTML Template:
<form id="my-form" method="POST" action="{% url 'fileupload_app:basic_upload' %}" enctype="multipart/form-data">
{% csrf_token %}
<div id="file-wrap">
<p>Drag and drop file here</p>
<input id="my-file" type="file" name="file" multiple webkitdirectory directory draggable="true">
<input type="text" id="directories" name="directories" hidden />
</div>
<br>
<input type="submit" value="upload">
</form>
Ajax Function:
$( function() {
$("#my-file").on('change', function (e) { // if file input value
$("#file-wrap p").html('Now click on Upload button'); // change wrap message
});
$("#my-form").on('submit', function (e) {
files = document.querySelector("#my-file").files;
var directories = {}
for (var file of files) {
file.webkitRelativePath
directories[file.name] = file.webkitRelativePath
}
directories = JSON.stringify(directories);
document.querySelector("#directories").value = directories
var eventType = $(this).attr("method");
var eventLink = $(this).attr("action");
$.ajax({
type: eventType,
url: eventLink,
data: new FormData( this ),
cache: false,
contentType: false,
processData: false,
success: function(getResult) {
$('#my-form')[0].reset();
$("#result").html(getResult);
$("#file-wrap p").html('Drag and drop file here');
}
});
e.preventDefault();
});
});

Related

How to accept a django.http.HttpResponse object containing a csv file in Ajax?

When I click a button on the html page, I want to trigger a query in backend database, creating a csv file specific to the request and then download it.
I managed to pass data to django, however, I cannot find a way to trigger download as I click the button and I don't know how to make ajax accept django.http.HttpResponse containing csv.
Meanwhile I find everything is ok if I visit the url directly, but when I use ajax it's not the case.
For illustration,
My urls.py is:
url('export/',views.export)
My views.py is:
def export(request):
if request.method == 'GET':
export_list = json.loads(request.GET['id_list'])
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
writer = csv.writer(response)
writer.writerow(['column_1','column_2'])
for id in export_list:
item = Item.objects.get(id=id)
witer.writerow([item.attr_1,item.attr_2])
return response
And my html is
<a class="btn btn-primary" id="export" rel="external nofollow" role="button" style="margin-left: 30px;">export to csv</a>
$("#export").click( function(){
data.id_list = JSON.stringify(paperids);
data.csrfmiddlewaretoken = '{{ csrf_token }}';
$.ajax({
url: '/export/',
type: 'GET',
data: data,
dataType: "text/csv",
headers:{ 'Accept': 'text/csv','Content-Type': 'text/csv' };
success: function(result){
console.log(result);
},
error: fucntion(){
console.log("why");
}
})
How can I manage to download the csv when clicking the button?
I find Ajax cannot handle download request. I now post data through the hidden input in a form and return a download response instead.

Django render is not refreshing page after a form submission

After almost a full day trying to make things work, I resign... I need some help because I don't understand where I'm doing things the wrong way.
I searched SO and stumbled on many answers about redirection and so on...
Let me give you some context :
I have a simple form where one can upload a file, so far, it's working well. I want the user to be redirected to another page after the upload is successful and this is where it fails :'(
My views.py :
# Create your views here.
def thank_you(request):
data = {'text': 'Thank you for your file'}
print('thank you blablabla')
print(data)
return render(request, 'app/thank_you.html', data)
def home(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES)
if form.is_valid():
uploaded_file = request.FILES['file']
new_file = UploadFile(file=uploaded_file)
new_file.save()
return redirect(reverse(thank_you))
else:
form = UploadFileForm()
data = {'form': form}
return render(request, 'app/drop_file.html', data)
I do see the 2 prints in the 'thank_you' function, meaning that the redirect is working as expected. But the view doesn't refresh and I'm stuck.
And if I try to access the url directly (going to http://.../thank_you/ ) it does show correctly.
The thank_you.html :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Upload done</title>
</head>
<body>
<h1>Hello !</h1>
<h1>{{ text }} </h1>
</body>
</html>
The form looks like that :
<form id="my-dropzone" class="dropzone" action="{% url 'home' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
</form>
<button id="submit-all">
Submit all files
</button>
<script type="text/javascript">
Dropzone.options.myDropzone = {
// Prevents Dropzone from uploading dropped files immediately
autoProcessQueue : false,
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() {
// Show submit button here and/or inform user to click it.
});
this.on("queuecomplete", function() {
console.log('should we redirect ?');
});
}
};
</script>
And my urls.py file :
url(r'^$', views.home, name='home'),
url(r'thank_you/$', views.thank_you, name='thank_you'),
Nothing out of the ordinary I guess.
Even if I change the 'home' to :
if form.is_valid():
uploaded_file = request.FILES['file']
new_file = UploadFile(file=uploaded_file)
new_file.save()
data = {'text': 'Thank you for your file'}
print('thank you blablabla')
print(data)
return render(request, 'app/thank_you.html', data)
The view doesn't update, I do see the prints in the django console but the template does not render...
I would like the return render(request, 'app/thank_you.html', data) to actually refresh the page after the form is submitted but I can't achieve to do so :( Any help ?
Firebug show me this in the console :
console output

Django + dropzone.js: form validation fails for multiple files

I'm trying to upload multiple files in one request using dropzone.js. When I set the uploadMultiple option to true in dropzone, one request containing both files is sent to my view, but form validation fails.
Here is my Django form:
class UploadForm(forms.Form):
data = forms.IntegerField(widget=forms.HiddenInput())
file = forms.FileField()
My view:
def upload(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
print request.FILES
if form.is_valid():
for file in request.FILES.getlist('file'):
print str(file)
else:
print form.errors
else:
form = UploadForm(initial={'data': 5})
return render(request, 'upload.html', {
'form': form
})
and my template:
<script type="text/javascript">
Dropzone.options.myAwesomeDropzone = {
autoProcessQueue : false,
uploadMultiple: true,
init : function() {
myDropzone = this;
this.element.querySelector("input[type='submit']").addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
myDropzone.processQueue();
});
}
}
</script>
<form id='my-awesome-dropzone' class="dropzone"
action="{% url 'upload.views.upload' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.data }}
<input type="submit" value="Upload" />
</form>
I see that request.FILES has two files:
<MultiValueDict: {u'file[]': [<InMemoryUploadedFile: Forest Flowers.jpg (image/jpeg)>,
<InMemoryUploadedFile: Forest.jpg (image/jpeg)>]}>
I guess the issue is Django doesn't recognize file[]. It expects file instead. How can I get Django to recognize the two uploads?
You are right assuming that the validation error is originated because of the input name dropzone.js sends to the server. The "file[n]" pattern when your Django form is expecting a field named "file" throws a validation error (required field).
In Dropzone.js you can specify the parameter "paramName" and this object property also accepts a function instead of a simple string, so if you set your paramName to something like:
...
paramName: function(){
return "file";
}
...
the name of the field sent to server doesn't change and you get a "file" field in request.FILES that is a dict with one element (file) that is an array of files as it is expected.
You just need to do:
file1 = request.FILES.get(file[][0], None) # For the first file
file2 = request.FILES.get(file[][1], None) # For the second file
... and so on...
Hope that helps.

How to create Ajax refresh for django-simple-captcha

I'm using the django-simple-captcha app for my django based website, I am able to integrate the captcha form field into my form, but the problem is, how do I create a button which calls Ajax refresh to refresh the captcha image on click? The documentation for the app is not very clear, and I tried to follow the example given in the documentation but it doesn't work. Please help me on this issue?
EDIT: Here's the link to the django package:
django-simple-captcha
Here's a working implementation in javascript:
$(function() {
// Add refresh button after field (this can be done in the template as well)
$('img.captcha').after(
$('Refresh')
);
// Click-handler for the refresh-link
$('.captcha-refresh').click(function(){
var $form = $(this).parents('form');
var url = location.protocol + "//" + window.location.hostname + ":"
+ location.port + "/captcha/refresh/";
// Make the AJAX-call
$.getJSON(url, {}, function(json) {
$form.find('input[name="captcha_0"]').val(json.key);
$form.find('img.captcha').attr('src', json.image_url);
});
return false;
});
});
Then you just need to add some CSS for the class captcha-refresh, perhaps place an image in the <a> and you're good to go!
The chosen answer is with jQuery not JavaScript.
If using purely JavaScript you should do this instead. This will also refresh the audio not just the image django-simple-captcha uses.
https://django-simple-captcha.readthedocs.io/en/latest/advanced.html#rendering
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
custom_field.html:
​
{% load i18n %}
{% spaceless %}
<label class="control-label">{{ label }}</label>
<img src="{{ image }}" alt="captcha" class="captcha" />
<br/>
<audio id="audio" controls>
<source id="audioSource" src="{{ audio }}" />
</audio>
{% include "django/forms/widgets/multiwidget.html" %}
{% endspaceless %}
Forms.py:
​
class CustomCaptchaTextInput(CaptchaTextInput):
template_name = 'custom_field.html'
​
class Form(forms.Form):
captcha = CaptchaField(widget=CustomCaptchaTextInput)
​
def __init__(self, *args, **kwargs):
super(Form, self).__init__(*args, **kwargs)
self.fields['captcha'].widget.attrs['placeholder'] = 'Solve the captcha'
self.fields['captcha'].label = "Captcha"
Add this at the end of the body tag:
<script>
const captchas = document.querySelectorAll('img.captcha')
function headers(options) {
options = options || {}
options.headers = options.headers || {}
options.headers['X-Requested-With'] = 'XMLHttpRequest'
return options
}
for (const captcha of captchas) {
const anchor = document.createElement('a')
anchor.href = '#void'
anchor.classList.add('captcha-refresh')
anchor.textContent = 'Refresh'
anchor.addEventListener('click', ({ target }) => {
const url = `${window.location.origin}/captcha/refresh/`
let formEl = target.parentElement
while (formEl && formEl.tagName.toLowerCase() !== 'form') {
formEl = formEl.parentElement
}
fetch(url, headers())
.then(res => res.json())
.then(json => {
formEl.querySelector('input[name="captcha_0"]').value = json.key
captcha.setAttribute('src', json.image_url)
document.getElementById('audioSource').setAttribute('src', json.audio_url)
document.getElementById('audio').load()
})
.catch(console.error)
return false
})
captcha.after(anchor)
}
</script>

Django: How to upload a file using ajax

I am using django 1.5, python 2.7 and jquery 1.9. I have a form which has precisely 2 fields i.e. title and document. When I press submit I want the users chosen document to be present in the request.FILES as shown in the view.
When I submit the regular form (without ajax), this works fine, but with ajax I do not get the file field in my request. Any suggestions on how to upload a file using ajax.
HTML:
<form enctype="multipart/form-data" action="{% url 'upload_document' %}" method="post" id="uploadForm">
{% csrf_token %}
<ul>
<li>
<div>Title</div>
<input id="title" type="text" maxlength="200"/>
<div class="error"></div>
</li>
<li>
<div>Upload File</div>
<input id="document" type="file" size="15" />
<div class="error"></div>
</li>
</ul>
<input type="submit" value="submit"/></p>
</form>
FORMS.PY:
class UploadForm( forms.Form ):
document = forms.FileField()
title = forms.CharField(max_length = 200)
def clean(self):
cleaned_data = super(UploadForm, self).clean()
return cleaned_data
def save(self, *args, **kwargs):
title = self.cleaned_data['title']
doc = self.cleaned_data['document']
document = Document(title = title, document = doc)
document.save()
return document
SCRIPT:
<script type="text/javascript">
$("#uploadForm").submit(function(event){
event.preventDefault();
$.ajax({
url : "{% url 'upload_document' %}",
type: "POST",
data : {csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
title: document.getElementById('title').value,
//document: document: document.getElementById('document'),
},
dataType : "json",
success: function( response ){
if(response == "True"){
// success
}
else {
//append errors
}
}
});
});
</script>
VIEWs.PY
def upload_document(request):
print request.POST
print request.FILES
if request.is_ajax():
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES, user = request.user)
if form.is_valid():
form.save()
return HttpResponse(simplejson.dumps('True'), mimetype = 'application/json' )
else:
errors = form.errors
return HttpResponse(simplejson.dumps(errors), mimetype = 'application/json' )
The answer to that question is not that simple. First of all if you intend to support old browsers then indeed it gets nasty. You have to deal with hidden iframes and some JavaScript tricks. I do advice using some well-known scripts for that like jQuery-File-Upload.
But the world is evolving and new technologies arise including HTML5. There's a new File API which is available in most modern browsers ( IE10+, FireFox3.6+, Chrome13+, see: http://caniuse.com/fileapi ) which can be used for that. First you need some HTML:
<input type="file" id="file-select" />
Then you can bind to (for example) change event:
$('#file-select').change( handleFileSelect );
and finally the handler itself:
var data = {};
function createReaderHandler(name) {
return function(ev) {
data[name] = ev.target.result;
};
}
function handleFileSelect(ev) {
var files = ev.target.files; // FileList object
// Loop through the FileList
for (var i = 0; i < files.length; i++) {
var file = files[i],
name = file.name || file.fileName,
reader = new FileReader();
reader.onload = createReaderHandler(name);
reader.readAsText(file);
}
}
Once the data is loaded into JavaScript memory (note that the operation is asynchronous) you can send it via AJAX like any other data. There are more options: depending on your file you can read it as a binary data using .readAsBinaryString and so on. Google is your friend. :)
Also I think there already are good scripts for uploading files with a fallback to old methods. This one can be interesting (haven't tried it):
http://www.plupload.com/
I think the issue is in the submit button, change it into normal button
ie, <button type='button' id='submit'>submit</button>(by default all buttons in form are submit)
and the ajax as
$('#submit').on('click',function(){
frm = $(this).parents('form')
$.ajax({
type: frm.attr('method'),
dataType:'json',
url: frm.attr('action'),
data: frm.serialize(),
async: false,
success: function (data) {
console.log('success')
},
error: function(data) {
console.log("Something went wrong!");
}
})
All others will be same
Just try it will work