Problems with Django / AJAX status message when uploading / processing data file - ajax

I have a Django app, with a page in Admin where I can upload a zip file of product data, which then goes to get processed. I have the list status_message set up in Python so that it is constantly updated with information about how the processing is going.
While on Firefox, making this on my dev environment, this worked pretty well. When I tested it on Chrome the report was no longer displayed at all. More importantly, but perhaps related, is that when I push to my staging server and test it, as soon as the upload happens and the file begins to be processed, I get redirected to a 404 error page, and I don't know why. The biggest difference I can think of is SSL on the staging server, but not my dev environment. Finally, probably due to my inexperience with AJAX + Django, there is a final page refresh after the status message is loaded. That's annoying, but doing no real harm (unless it's also causing one of the other problems). My head's spinning. What's going on? Is one line the problem, my view, or the whole thing?
Problem 1. The report in the div #status-message updates as it comes in Firefox, but not Chrome.
EDIT 1: Turns out this partially comes from Chrome 28 caching things. See Jquery ajax random error in Chrome only. Now with this, Chrome displays the message after all processing, but still not as the status-message changes until then:
function retrieve_status()
{
$.ajax({
url: "print_status",
success: function(data){
$('#status-message').html("");
for (var i=0; i<data['message'].length; i++)
$('#status-message').append(data['message'][i] + "<br>");
$('#status-message').scrollTop($('#status-message')[0].scrollHeight);
},
cache: false
});
}
Problem 2. 404 error after zip upload only on staging server.
Edit 2: In the browser console from the staging server, I get the warning "The page at https://myapp.com/admin/product-load/ displayed insecure content from http://myapp.com/admin/product-load/print_status/?_=1380231818430." I haven't figured out how to fix this yet. The whole site is supposed to use SSL, and if you copy in that second path, it even forwards to the https version. The url: in the AJAX doesn't seem to matter. I even completely hardcoded the url with an https prefix and it said the same thing: url: https://myapp.com/admin/product-load/print_status",
Problem 3. Annoying page refresh after processing is complete.
Here's my code. I didn't trim much off because I'm not sure what is or isn't relevant.
views.py:
from django.http import HttpResponse
from django.shortcuts import render
from django.contrib.admin.views.decorators import staff_member_required
from myappload.forms import ProductLoadForm
from myappload.load import ProductLoader
import json
status_message = []
#staff_member_required
def index(request):
global status_message
status_message = []
if request.method == 'POST':
form = ProductLoadForm( request.POST, request.FILES)
if form.is_valid():
product_data = request.FILES.get('file')
loader = ProductLoader( zip_filename=product_data, index=False, status=record_status)
load_report = loader.load()
return render ( request, 'myappload/index.html', { 'form': form})
else:
form = ProductLoadForm()
return render ( request, 'myappload/index.html', { 'form': form})
def record_status(status):
global status_message
status_message.append(status)
print status
def print_status(request):
return HttpResponse(json.dumps({ 'message': status_message }), content_type="application/json")
urls.py
from django.conf.urls.defaults import patterns, url
# URLs
urlpatterns = patterns('myappload.views',
url(r'^$', 'index', name='product_load_page'),
url(r'^print_status/$', 'print_status', name='product_upload_status'),
)
forms.py
from django import forms
class ProductLoadForm(forms.Form):
file = forms.FileField()
index.html:
{% extends "admin/base_site.html" %}
{% block content %}
<form name="product_load_form" class="form form-horizontal"
enctype="multipart/form-data" method="post">
{% csrf_token %}
{% include 'forms/basic.html' %}
<input id="upload-button" type="submit" value="Upload"/>
</form>
<div id="status-message">
The status message will show up here <br>
</div>
<script>
function retrieve_status()
{
$.get("print_status", function( data ) {
$('#status-message').html("");
for (var i=0; i<data['message'].length; i++)
$('#status-message').append(data['message'][i] + "<br>");
$('#status-message').scrollTop($('#status-message')[0].scrollHeight);
});
}
$(document).ready(function() {
retrieve_status(); //the page will refresh when done, calling this on load
$('#upload-button').click(function() {
setInterval(function(){retrieve_status()},500);
});
$('#status-message').css('height', $(window).height() - 170);
});
</script>
{% endblock %}

Related

Refresh form in Django without reloading page

Hi I'm new in Ajax and django and I want to refresh my form. I try some code but it didn't work. I'm sure what I want to do is very basic.
Here my html:
<div class="row" style="padding-top:20px;">
<div class="col-md-12" id="testAjax">
{% load crispy_forms_tags %}
{% crispy form %}
</div>
</div>
I want to refresh my form in the div testAjax.
Here my view:
def createPin(request):
error = False
if request.method == "POST":
form = CreatePinForm(request.POST)
if form.is_valid():
pin = form.save(commit=False)
pin.customer = request.user.customer
pin.save()
msg = "pin saved"
return redirect('/pin/CreatePin', {'form': form, 'msg': msg})
else:
error = True
else:
form = CreatePinForm()
return render(request, 'createPin.html', {'form': form, 'error': error,})
My Ajax:
function refresh()
{
$form=$('#createPin');
var datastring = $form.serialize();
$.ajax({
type: "POST",
url: '/pin/CreatePin/',
dataType: 'html',
data: datastring,
success: function(result)
{
/* The div contains now the updated form */
$('#testAjax').html(result);
}
});
}
Thanks alot for your help.
When I need to do some operations and I don't want to reload the page I use a JQuery call to Ajax, I make the pertinent operations in AJAX and then receive the AJAX response in the JQuery function without leaving or reloading the page. I'll make an easy example here for you to understand the basics of this:
JQuery function, placed in the template you need
function form_post(){
//You have to get in this code the values you need to work with, for example:
var datastring = $form.serialize();
$.ajax({ //Call ajax function sending the option loaded
url: "/ajax_url/", //This is the url of the ajax view where you make the search
type: 'POST',
data: datastring,
success: function(response) {
result = JSON.parse(response); // Get the results sended from ajax to here
if (result.error) { // If the function fails
// Error
alert(result.error_text);
} else { // Success
//Here do whatever you need with the result;
}
}
}
});
}
You have to realize that I cannot finish the code without knowing what kind of results you're getting or how do you want to display them, so you need to retouch this code on your needs.
AJAX function called by JQuery
Remember you need to add an url for this Ajax function in your urls.py something like:
url(r'^/ajax_url/?$', 'your_project.ajax.ajax_view', name='ajax_view'),
Then your AJAX function, it's like a normal Django View, but add this function into ajax.py from django.core.context_processors import csrf from django.views.decorators.csrf import csrf_exempt from django.utils import simplejson
#csrf_exempt
def ajax_view(request):
response = []
#Here you have to enter code here
#to receive the data (datastring) you send here by POST
#Do the operations you need with the form information
#Add the data you need to send back to a list/dictionary like response
#And return it to the JQuery function `enter code here`(simplejson.dumps is to convert to JSON)
return HttpResponse(simplejson.dumps(response))
So, without leaving the page you receive via javascript a list of items that you sended from ajax view.
So you can update the form, or any tag you need using JQuery
I know that this can be so confusing at the beginning but once you are used to AJAX this kind of operations without leaving or reloading the page are easy to do.
The basics for understanding is something like:
JQuery function called on click or any event you need
JQuery get some values on the template and send them to AJAX via
POST
Receive that information in AJAX via POST
Do whatever you need in AJAX like a normal DJango view
Convert your result to JSON and send back to the JQuery function
JQuery function receive the results from AJAX and you can do
whatever you need

How to load Django new dynamic content in a Jquery Dialog?

Im trying to do what is suggested here: How to reopen a Django form in a jQuery dialog when the form validation fails in the backend?
But I don't have enough points to add a comment there..
In my base html page i have a link which opens a dialog with a Django-form. I use the jquery load() to fill the Dialog with this child-html-template. In this child template i have a submit button. I'm trying to bind this button to an ajax function that will:
Post the form to the right URL
Fetch the response from Django view (the form as HTML to be able to show valdidation errors)
Replace the content in the dialog box with the data i get back from the submit-POST.
Is this possbible? Been working on this for days now and i just cant make it happen. Can somone post an example with code to end my suffering.. It's the ajax that is my biggest problem.
Where should i put the script? In the base or the child template? Do you have any alternative solutions?
Thank you!
I did this not long ago in. I found it easier to send the errors in json, and then handle them client-side and attach them to the relevent fields. Like so:
Use ajax to load the form from a view into the jQuery dialog box
When the user sends the data send the information to same view
If validation fails, send errors as a json array. Use js on client-side to attach them to the relevant fields
If succeeds send a positive response of some kind
Check out this excellent example for reference
edit
Here's a working example. Not sure I'm using the best methods to do this, but I think it's pretty understandable. Also, I'm not accounting for the possibility of non-ajax form submit (it's easy enough to do, some logical conditioning using form.is_ajax() and see example linked above for further reference).
So first the views (ContactForm is the same as the one linked):
import json
from django.http import HttpResponse
from django.shortcuts import render_to_response
def home(request):
return render_to_response('index.html') #nothing special here
from django.views.decorators.csrf import csrf_exempt
from cStringIO import StringIO
#csrf_exempt #you should use csrf, I'm just skipping this for the example
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
response = {}
if form.is_valid():
response["status"] = "OK"
# save the data, or do whatever.
else:
response["status"] = "bad"
response.update(form.errors)
# now just to serialize and respond
s = StringIO()
json.dump(response, s)
s.seek(0)
return HttpResponse(s.read())
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
As you can see, if there's nothing here you get an html with the form, if not, you get a json response with an object called response which contains 'status' and might also contain errors. I'm using StringIO with json.dump(data, file) as it has always proved the least buggy and most fluent way I ever used to serialize to json (seriously. You won't believe how easily it can break).
Now let's go over the client side:
base.html:
<html>
<head>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
<link rel="stylesheet" href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css">
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
contact.html:
{% extends 'base.html' %}
{% block content %}
<form method="post" id='myform'>
{{ form.as_p }}
</form>
{% endblock %}
and finally, the main.html with the relevant js. This is where the magic happens:
{% extends 'base.html' %}
{% block content %}
<button class='btn'>Contact!</button>
<div id="dialog-modal">
</div>
<script>
$(function() {
$('button').on('click', function() {
// first things firts, fetch the form into the dialog
$('#dialog-modal').load('contact/ #myform');
// initiate dialog
$('#dialog-modal').dialog({
height: 450,
modal: true,
// I'm using the built-in buttons, but you can use your own
buttons: {
Send: function() {
var dialog = $(this),
form = $('#myform'),
data = form.serialize();
$('.off').remove(); // this is to avoid duplicates
// run ajax post call
$.ajax({
url: 'contact/',
data: data,
type: 'post',
// if successful print response
success: function(response) {
res = $.parseJSON(response);
// if form was successful:
if (res['status'] == 'OK') {
// form was successful
alert('Thank you! Form has been submitted'); // say thank you
dialog.dialog('close'); // close dialog
}
// if not...
else if (res['status'] == 'bad') {
delete res['status'] // we don't need this anymore...
var errors = res; // just for simplicity
$.each(errors, function(key, value) {
// i.e. key='subject' and value is the error message itself
var err = $('<span></span>', {
'class': 'off',
'text': value
}),
br = $('<br></br>', {
'class': 'off',
}),
input = $('#id_'+key).parent(); //find the parent div for the relevant input by key
// add a small break
br.appendTo(input);
// add the error
err.appendTo(input);
// add some styling
err.css('color', 'red').css('font-size', '10px');
});
}
}
});
}
}
});
});
});
</script>
{% endblock %}
Hope that's not too much. Here's an image how it looks after attempting to send:
Pick it up from here. There's a lot of room to play around and extend this.
Good luck!

AJAX file upload in Play Framework 2.1 RC1 delivers an empty file

Scala/Play gurus out there.
I'm trying to upload a file using AJAX, in Play 2.1 (RC1). For the client part I'm using eldarion/bootstrap-ajax and everything seems to be fine, except that the uploaded file is empty.
The front-end snippet:
...
<form action="#routes.Campaigns.upload" method="post" class="form ajax replaceable" data-replace=".replaceable">
<input type="file" name="picture">
<p><input class="btn" type="submit"></p>
</form>
...
Note that I had to use the explicit <form> tag instead of the #form helper, due to the fact that the required css class (data-replace) contains a dash, and therefore can not be used as a Symbol. But anyway. The called action in the controller looks like this:
def upload = Action(parse.temporaryFile) {
request =>
Logger.info("Trying to upload a file")
val resultString = try {
val file = new File("/tmp/picture")
request.body.moveTo(file, true)
"file has been uploaded"
} catch {
case e: Exception => "an error has occurred while uploading the file"
}
val jsonResponse = Json.toJson(
Map("html" -> Json.toJson("<p>" + resultString + "</p>")
)
)
Ok(jsonResponse)
}
I'm aware that as my development goes forward the file name should be more intelligently set, but for the moment being, /tmp/picture is for me as good a name as any other one.
The JSON response gets generated (with the "file has been uploaded" message within), and is sent back to the browser as the payload of the 200 response. The JSON is received and correctly used to modify the page (in this case, merely removing the very uploading form).
But the file, although appearing in the right moment and in the right place, is always empty:
larsson:tmp bruno$ ls -l /tmp/picture
-rw-r--r-- 1 bruno staff 0 7 Jan 03:07 /tmp/picture
That's specially strange, in my opinion, because the uploading code which uses a traditional multipart/form-data form, with no AJAX whatsoever, and an Action with parse.multipartFormData as a parameter, instead of parse.temporaryFile, works finely.
Any help will be very appreciated. Thanks in advance.
I don't know bootstrap-ajax, anyway if it hasn't dedicated support for uploading files via AJAX (and I didn't find any info about that possibility in its readme file) it will NOT send files with AJAX.
Reason: In standard JavaScript uploading files with AJAX is not possible due the security limits and there are some techniques to workaround this, mainly using iFrames, however I can't see nothing similar in the code of bootstrap-ajax so probably you need to modify it or use other solution.
Solution: There are some AJAX file uploaders, which works good with HTML5 ie. jQuery File Upload, which offers ajax upload, multi-file uploads, drag file to the drop zone etc.
In general HTML5 supports file uploads better than earlier versions of HTML, so you can build uploader easily without need of using additional plugins, take a look to this topic. As you can see it delivers possibilities to validate some data BEFORE the upload and also offers progress bars.
I'm currently trying to implement something like this and I got a first version working. This is how I do it:
In my Controller I define a method for uploading files. In my case I use Action.async since I save stuff to my MongoDB with reactivemongo. I have removed that code so that it do not complicate this example.
What I do in this example is that I upload a csv file, save it to disk and then produce the first row back as a string to the user. In real life the method produces a list back so that user is able to choose which column represent what an so on.
I use mighty csv for csv parsing. GREAT LIB!
Application:
def upload = Action.async(parse.multipartFormData) {
implicit request =>
val result = uploadForm.bindFromRequest().fold(
errorForm => Future(BadRequest(views.html.index(errorForm))),
form => {
import java.io.File
request.body.file("csvFile").map {
csv =>
val path = current.configuration.getString("csv.job.new.file.path").getOrElse("")
val name = DateTime.now().getMillis + ".csv"
csv.ref.moveTo(new File(path + name))
val settings = CSVReaderSettings.Standard(linesToSkip = form.linesToSkip)
val rows: Iterator[Array[String]] = CSVReader(path + name)(settings)
val firstRow = rows.next()
val test = firstRow match {
case xs if xs.size == 0 || xs.size == 1 => xs.mkString
case xs if xs.size > 1 => xs.mkString(", ")
}
Future(Ok(test))
}.getOrElse(Future(BadRequest("ahadasda")))
}
)
result
}
routes:
POST /upload #controllers.Application.upload
I use # before the controllers because I use DI with guice for my service classes.
Since we will use javascript for uploading we need to define our jsRoutes:
jsRoutes:
def javascriptRoutes = Action {
implicit request =>
import routes.javascript._
Ok(
Routes.javascriptRouter("jsRoutes")(
Application.upload
)
).as("text/javascript")
}
Remember to import in your template where you want to use the routes:
<script type="text/javascript" src="#routes.Application.javascriptRoutes"></script>
<script src="#routes.Assets.at("javascripts/app.js")#Messages("js.version")" type="text/javascript" ></script>
In my view template I have a regular helper form. There is some css style stuff I do to
change the looks and feel of the upload button and file chooser. But the input fields
are there.
index.scala.html:
<div class="csvContainer">
#helper.form(action = routes.Application.upload, 'enctype -> "multipart/form-data", 'id -> "csvUpload") {
#Messages("upload.row.skip")
#inputText(uploadForm("linesToSkip"), 'class -> "hidden")
<div style="position:relative;">
<div id="csvFile" style="position:absolute;">
#Messages("upload.choose")
</div>
<input id="uploadFile" type="file" name="csvFile" style="opacity:0; z-index:1;" onchange="document.getElementById('csvFile').innerHTML = this.value;" />
</div>
<p>
<input type="submit" value="#Messages("upload.submit")">
</p>
}
</div>
In app.js is where the ajax magic happens, remember I have not implemented any validation or cool html5 stuff yet as the progressbar and other handlers, described in besiors link.
I use regular JQuery.
app.js:
$('#uploadFile').change(function(){
var name = $(this).val().split("\\");
console.log(name[2]);
$('#csvFile').text(name[2]);
});
$('#csvFile').click(function(){
$('#uploadFile').click();
});
$("#csvUpload").submit(function(e) {
e.preventDefault();
var formData = new FormData();
formData.append('csvFile', $( '#uploadFile' )[0].files[0]);
formData.append('linesToSkip', $( "#linesToSkip").val());
jsRoutes.controllers.Application.upload().ajax({
data: formData,
processData: false,
contentType: false,
cache: false,
type: 'POST',
success: function(data){
alert(data);
}
});
});
I have removed a lot of code to simplify this example and I hope that I have not forgotten anything. Hope this helps!

How do I render a view after POSTing data via AJAX?

I've built an app that works, and uses forms to submit data. Once submitted, the view then redirects back to display the change. Cool. Django 101. Now, instead of using forms, I'm using Ajax to submit the data via a POST call. This successfully saves the data to the database.
Now, the difficult (or maybe not, just hard to find) part is whether or not it's possible to tell Django to add the new item that has been submitted (via Ajax) to the current page, without a page refresh. At the moment, my app saves the data, and the item shows up on the page after a refresh, but this obviously isn't the required result.
If possible, I'd like to use exactly the same view and templates I'm using at the moment - essentially I'd like to know if there's a way to replace a normal HTTP request (which causes page refresh) with an Ajax call, and get the same result (using jQuery). I've hacked away at this for most of today, so any help would be appreciated, before I pull all of my hair out.
I had a very similar issue and this is how I got it working...
in views.py
from django.utils import simplejson
...
ctx = {some data to be returned to the page}
if ajax == True:
return HttpResponse(simplejson.dumps(ctx), mimetype='json')
then in the javascript
jQuery.ajax({
target: '#id_to_be_updated',
type: "POST",
url: "/",
dataType: 'json',
contentType: "text/javascript; charset=\"utf-8\"",
data: {
'foo':foo,
'bar':bar,
},
success: function(data){
$("#id_to_be_updated").append(data.foo);
}
});
Here's how I did it:
The page that has the form includes the form like so
contact.html
{% include "contact_form.html" %}
This way it's reusable.
Next I setup my view code (this view code assumes the contact form needs to be save to the db, hence the CreateView):
class ContactView(CreateView):
http_method_names = ['post']
template_name = "contact_form.html"
form_class = ContactForm
success_url = "contact_form_succes.html"
There are a few things to note here,
This view only accepts pots methods, because the form will be received through the contact.html page. For this view I've setup another template which is what we included in contact.html, the bare form.
contact_form.html
<form method="POST" action="/contact">{% crsf_token %}
{{ form.as_p }}
</form>
Now add the javascript to the contact.html page:
$("body").on("submit", 'form', function(event) {
event.preventDefault();
$("#contact").load($(this).attr("action"),
$(this).serializeArray(),
function(responseText, responseStatus) {
// response callback
});
});
This POSTS the form to the ContactView and replaces whatever is in between #contact, which is our form. You could not use jquery's .load function to achieve some what more fancy replacement of the html.
This code is based on an existing working project, but slightly modified to make explaining what happens easier.

How to create a waiting page in Django

I am constructing an application that need a lengthy calculation. After a user submitted the information, it need about 30 minutes to calculate and then return the result. So I am considering to add a “please wait" page.
I followed instructions mentioned in the following link,
http://groups.google.com/group/django-users/browse_thread/thread/c1b0d916bbf86868
However, when I submit something, it stays in http://127.0.0.1:8000/please_wait
and will not redirect to the result page like http://127.0.0.1:8000/display_DHM
does anybody know what is going on?
Here are all related files, I tried various ways, but when I
submit a form, it only return the please_wait page and then stay there
forever. There is no redirect happened.
Since I want to check if it works first, there is no actual
calculation in the code.
url.py
urlpatterns = patterns('',
(r'^test$',views.test_form),
(r'^please_wait', views.please_wait),
url(r'^run_DHM$', views.run_DHM, name="run_DHM") ,
url(r'^displayDHM', views.display_DHM, name="displayDHM")
)
view.py
def test_form(request):
return render_to_response('test.html')
def please_wait(request):
return render_to_response('please_wait.html')
def run_DHM(request):
### lengthy calculations... ...
return HttpResponse("OK")
def display_DHM(request):
return render_to_response('display_DHM.html')
test.html
{% extends "baseFrame.html" %}
{% block maincontent %}
<form method="POST" action="please_wait">
<p>Test:</p>
<div id="address"></div>
<p>Type your value in here:</p>
<p><textarea name="order" rows="6" cols="50" id="order"></
textarea></p>
<p><input type="submit" value="submit" id="submit" /></p>
</form>
{% endblock %}
please_wait.html
<html>Please wait
<script type="text/javascript" src="http://code.jquery.com/
jquery-1.7.1.min.js">
$.getJSON('{% url run_DHM %}', function(data) {
if (data == 'OK') {
window.location.href = '{% url displayDHM %}';
} else {
alert(data);
}
});
</script>
</html>
display_DHM.html
<HTML>
<BODY>END FINALLY!</BODY>
</HTML>
I write here because I can't use the comment space. My question is a bit similar to yours and maybe the answer can help you.
Briefly:
the question:
I have an external python program, named c.py, which "counts" up to 20 seconds. I call it from my Django app views.py and in the html page I have a button to start it. It's ok (= in Eclipse I can see that c.py prints 0,1,2,3,...20 when I press the button on the webpage) but I would like that the button changes from "GO" to "WAIT" during c.py process (or I would like to perform a waiting page during the counting or also a pop-up).
the answer:
You would need to be able to report back the status of c to the client
via ajax long polling or WebSockets, or, if you don't care about the
incremental status of c and just want to change the text of the link,
you'll need to use JavaScript to set the value when the click event of
the link fires:
views.py
from django.core.urlresolvers import reverse
from django.http import JsonResponse
def conta(request):
c.prova(0)
redirect = reverse('name_of_home_user_view')
return JsonResponse({'redirect': redirect})
and js:
// assuming jQuery for brevity...
$(document).ready(function() {
// avoid hard-coding urls...
var yourApp = {
contaUrl: "{% url 'conta' %}"
};
$('#btnGo').click(function(e) {
e.preventDefault(); // prevent the link from navigating
// set css classes and text of button
$(this)
.removeClass('btn-primary')
.addClass('btn-danger')
.text('WAIT');
$.get(yourApp.contaUrl, function(json) {
window.top = json.redirect;
});
});
});
If you need lengthy calculations I think you could be interested in celery. Nice waiting pages (progress indicators?) would be a byproduct.

Resources