Retrieving a Django form field value using AJAX - ajax

For each fact in facts, there is a form where you can upvote or downvote the fact.
Further explanation is found below.
Template and Form code are listed below respectively:
template
<ul>
{% for fact in facts %}
<form method='POST' action="{% url 'facts:list' fact.pk %}" id="list_vote">
{% csrf_token %}
{{ form }}
<input type="submit" value="vote" />
</form>
{% endfor %}
</ul>
forms.py code:
VOTE_CHOICES = [
(1, 'upvote'),
(0, 'downvote')
]
class Vote(forms.Form):
vote = forms.ChoiceField(choices=VOTE_CHOICES,
widget=forms.RadioSelect(attrs={'class': 'vote'}))
For each fact in models.Fact.objects.all(), there is a form, which consists of radio buttons of 2 inputs (upvote, downvote), created for this specific fact. What I'm now doing is basically Django 101: getting the value of the fact that is being voted and update its model accordingly in the views.
What I want to do is retrieve the value of this specific fact using AJAX and update the model accordingly without leaving/refreshing the page

I think I can help you. I just went through a lot of learning on AJAX and how to connect to DJANGO templates. You'll need some javascript on your template to make the AJAX connection.
Below is a generic AJAX javascript connector function I abstracted from my recent work. It needs both prototype.js http://prototypejs.org/ and jquery.js https://jquery.com/download/ imported into your template to run. Also requires a jQuery noconflict statement to allow both to run at the same time.
Basically all you need to do is pass the AJAXconnector function I wrote your data in a {'myvariable' : 'myvalue', 'myvariable2' : 'myvalue2'} format and destination is the url (in string format) which points to your views.py processing function to handle the AJAX data and return a reply. Also its important to use a local (relative) link as your destination.
I did a little tutorial on my blog if you want to check it out too - it walks through the AJAX connection on the template (javascript side) and the server (python side) http://www.americantechnocracy.com/getArticle/4/
the code I'm posting below also has some more description at:
http://www.americantechnocracy.com/getArticle/9
Let me know if you have questions. Happy to answer.
// Requires prototype.js
// Requires jquery.js
// enable use of both prototype and jquery
var $j = jQuery.noConflict();
// get the CSRF Token
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
// function for AJAX communication of data from Template to View.py and back
function AJAXConnector(parameters, destination) {
// myParameters = {'targetPK': pk, };
myParameters = parameters;
csrfHeader = {'X-CSRFToken' : csrftoken};
// This is the start of my AJAX Function
new Ajax.Request(destination, {
method: 'post', parameters: myParameters, requestHeaders: csrfHeader,
onSuccess: function (transport) {
var response = transport.responseText || "no response text";
data = response.evalJSON();
},
onFailure: function () {
alert('Something went wrong...');
}
});
}

Related

How to submit data to Flask from an Ajax call, and return its response in Flask from another Ajax call?

Sorry if the title is a little confusing. A kind user here on StackOverflow helped me make my Flask app display some scraped data, only now I have added a parameter in the function so that I can scrape the data I want to search for. I have an input box, and I want to be able to get the data from it, and pass it as a string in my python function in Flask
Current HTML Side
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "utf-8">
<title>NBA Data Web App</title>
</head>
<body>
<script src = "http://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js" crossorigin = "anonymous"></script>
<form id = "nameForm" method = "POST" role = "form">
<input name = "text">
<button id = "searchBtn"> Search </button>
</form>
<div id = "container"></div>
<script type = "text/javascript">
//Function to take place when our search button is clicked
$('button#searchBtn').click(function() {
$.ajax({
url: '/_get_data',
data: $('form').serialize(),
type: 'POST',
success: function(response) {
console.log = response;
},
error: function() {
alert('Failure in first Ajax call');
}
});
/*Everything below this was working before, as I only made one ajax call when a button was pressed. Now, when I press the button, I want to pass its contents as a string to my scrape_data() function in Flask, and return, and display, its contents as shown below. */
//Declare our list so we can print something, and loop through it later
var data_list;
//Variable for our HTML table
var rowMax = 29, html = "<table><tr>";
//Post request
$.post('/_get_data', {
//If done, do this
}).done(function(response) {
/* Assign our scraped data to our variable that was declared earlier,
which is turned into an array here in JS */
data_list = response['data'];
//Declare some variables for making our table
var perRow = 1, count = 0, table = document.createElement("table"),
row = table.insertRow();
//Loop through the data and add it to the cells
for (var i of data_list) {
//Insert a cell for each piece of data
var cell = row.insertCell();
//Add the data to the cell
cell.innerHTML = i;
//Increment our count variable
count++;
//If we have met our set number of items in the row
if (count % perRow == 0) {
//Start a new row
row = table.insertRow();
}
}
//Add the table to our container in our HTML
document.getElementById("container").appendChild(table);
//If request fails
}).fail(function() {
alert("request failed");
});
});
</script>
</body>
</html>
Python (Flask) Side
rom flask import Flask, render_template, jsonify, request, escape, url_for
#Get our lists to post
headers = data_headers()
#Start the flask app
app = Flask(__name__)
#Start page
#app.route('/')
def index():
return render_template('index.html')
#What happens when our button is clicked
#app.route('/_get_data', methods = ['POST'])
def _get_data():
text = request.form['text']
#Here, I am trying to assign the contents of the input box of the form to a variable, so I can pass that variable as a parameter for my function.
data = scrape_data(text)
#Return the json format of the data we scraped
return jsonify({'data' : data})
#Run the app
if __name__ == "__main__":
app.run(debug = True)
I am currently getting error 405 method not allowed. I'm not sure if my syntax in the first Ajax call is incorrect, or if I need to break this up into two different #app.route(urls) since each call is going a different way.
If you use the method attribute of form element and do not specify the action, request will be sent /. What is happening here is when you click on search button it will send two post requests one to '/' and '/_get_data' from ajax. In Flask routing if you do not explicitly provides methods=[] that route will allow GET only. Remove the method attribute from you form, you should not get method not allowed error.

Add a custom user-facing form to Django app (uses selectize and taggit)

I'm fairly new to django, and I'm trying to figure out how to create a form using the taggit-selectize widget (or django-taggit). Everything I've found online refers to its use the admin page, but I want the tags to be user-facing and editable - much like the tags I create below this post. So far, I've determined that I need to create a form using a widget:
# models.py
from taggit_selectize.managers import TaggableManager
tags = TaggableManager()
# forms.py
from taggit_selectize.widgets import TagSelectize
from .models import MyModel
class TagForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('tags',)
widgets = {'tags': TagSelectize(),}
but I can't figure out how to include this form in my template so that it appears beside my MyModel objects. Ideally, I guess I'd was expecting it to behave like django-fluent-comments, where I can just call {% render_comment_form for obj %} and call it a day.
Update
I've edited views (see below) and can now access the form in the template, but I can't seem to submit my tags (ideally this wouldn't trigger a redirect, either).
# views.py
from .forms import TagForm
def show_tags(request):
return render(request, 'tags.html', {'tagform' : TagForm})
# tags.html
<div>
{{ tagform.media }}
{{ tagform.as_p }}
</div>
So, I finally figured this out. It involves wrapping the tagform in <form> tags and catching the POST request. For the record, this is part of a project that involves using Haystack to return a list of results that I then want to tag. My views.py subclasses a SearchView rather than defining a function as I do here (show_tags()), and rather than one object per page I have multiple.
For an object obj on the page, you have the following
# views.py
from .forms import TagForm
from .models import MyModel
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
def show_tags(request):
# Perhaps the request specifies the object, but
# for simplicity's sake we just pick a specific model instance
object = MyModel.objects.filter(pk = 123)
return render(request, 'tags.html', {'tagform' : TagForm,
'obj' : MyModel})
#require_POST
#csrf_exempt
def create_tag(request):
# See javascript below for creation of POST request
data = request.POST
tag_text_raw = data.get('tag_data')
# clean_tag() not shown, but it splits the serialized
# tag_text_raw and returns a list of strings
tag_text_clean = clean_tag(tag_text_raw)
obj_pk = data.get('obj_pk')
#save tags to document
doc = DocInfo.objects.get(pk = obj_pk)
doc.tags.add(*tag_text_clean)
# not strictly necessary; mainly for logging
response_data = {'tag_text': tag_text_clean,
'obj_pk': obj_pk
}
return JsonResponse(response_data)
So show_tags sends the information to the template with render, then the template has access to those objects. This is what didn't make sense to me initially.
# tags.html (extends base.html)
{% block scripts %}{{ block.super }}
<script type="text/javascript" src="{{ STATIC_URL }}js/ajaxtag.js"></script>
{{ tagform.media }}
{% endblock %}
{{ obj.text }}
<form method="post" action="create_tag/" id="tag-form-{{ obj.pk }}" name="tag-form-obj" data-object-id={{ obj.pk }}>
{{ tagform.as_p }}
<input type="submit" name ="tag-form-input" value="Add Tags" />
</form>
We can catch the POST request with javascript:
#ajaxtag.js
(function($)
{
// A stripped-down version of ajaxcomments.js from fluent_comments
// See that file for further expansions
$.fn.ready(function () {
var tagform = $('form[name="tag-form-obj"]');
if (tagform.length > 0) {
// Detect last active input.
// Submit if return is hit
tagform.find(':input').focus(setActiveInput).mousedown(setActiveInput);
tagform.submit(onTagFormSubmit);
}
});
function onTagFormSubmit(event)
{
event.preventDefault(); // prevents redirect
var form = event.target;
create_tag(form);
return false;
}
function create_tag(form)
{
console.log("create_tag is working!") // sanity check
var $form = $(form);
var tag_text = $form.serialize();
var url = $form.attr('action');
var obj_id = $form.attr('data-object-id')
$.ajax({
url : url,
type: "POST",
data: { tag_data: tag_text, obj_pk: obj_id},
success: function (data) {
data;
console.log(data);
console.log('success');
},
error: function (xhr, errmsg, err) {
// Return error to console
console.log(xhr.status + ": " + xhr.responseText)
}
});
}
function setActiveInput() {
active_input = this.name;
}
})(window.jQuery);
Finally, urls.py sends the request back to create_tag()
# urls.py
from .views import create_tag
...
url(r'^create_tag', create_tag, name = 'tag-form')
...

For-in Loop through Tumblr posts with ajax

The expectation is that $(".post-title").append(postTitle); will return the title of that post, as will postBody. Yet when I console.log these variables > undefined is returned.
$.ajax({
url: "http://api.tumblr.com/v2/blog/ohc-gallery.tumblr.com/posts?api_key=***",
dataType: 'jsonp',
success: function(res){
var postings = res.response.posts;
var postTitle = "";
var postBody = "";
$(".post-title").append(postTitle);
$(".post-body").append(postBody);
for (var i in postings){
postTitle = postings[i].title;
postBody = postings[i].body;
}
console.log("postBody: " + postBody);
}
});
Am I missing something basic regarding Javascript closures... I really don't know right now. I simply want to loop through created post data for later display.
Github JS code- https://github.com/mrcn/ohc/blob/master/js/tumblr.js
Github HTML code- https://github.com/mrcn/ohc/blob/master/index-posting.html#L82-L89
I got it. The problem was with how I intended to display this information on the website, and I had to alter the code accordingly. The idea was to display paired blog post titles and entries. The problem was all titles were appearing together, and all bodies were appearing together- not paired off respectively.
The updated code is more along the lines of --
Javascript --
//use $.each() or Array.forEach
$.each(postings, function (i, post) {
$(".post ").append("<h3>" + post.title + "</h3>" + post.body + "<br><br>");
});
}
});
HTML --
<div class="post-wrap"><!--post-wrap-->
<div class="post">
</div>
</div><!--post-wrap-->
The for..in is used to iterate over an object... posts is an array for you can use the normal for (var i=0;i<x;i++) loop or any other iteration methods like $.each() or Array.forEach()
$.ajax({
url: 'http://api.tumblr.com/v2/blog/ohc-gallery.tumblr.com/posts?api_key=***',
dataType: 'jsonp',
success: function (res) {
var postings = res.response.posts;
var postTitle = '';
var postBody = '';
//use $.each() or Array.forEach
$.each(postings, function (i, post) {
$(".post-title ").append(post.title);
$(".post-body ").append(post.body);
})
}
});
Not every post type supports title or body. You currently have three posts, two text and one photo. The photo post type only support photos and caption, which is causing the undefined.
Check the API for more details: https://www.tumblr.com/docs/en/api/v2

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!

How do I repopulate form fields after validation errors with express-form?

Using node.js and express (2.5.9) with express-form.
How should I repopulate form fields with the submitted values?
I have a get and a post route. If there are validation errors when the form is posted, I redirect the user back to the get, the problem is that the repopulated locals don't show up (I do have autoLocals: true, so I assume it's because I am redirecting and res is reset.)
So how do you guys repopulate and what's your application flow, do you res.send instead of res.redirect and set up the whole thing again? That seems repetitive.
Here's an example of my post route:
app.post(
'/projects/:id'
, form(field("title").required("title", "Title is required)
, function (req, res){
if (!req.form.isValid){
res.redirect('/project/'+req.params.id+'/edit');
}
else{
// save to db
}
});
I am working with expressjs4.0 to repopulate the forms fields after validation you do:
router.route('/posts/new')
.get(function(req, res) {
res.render('posts/new', new Post({}));
});
The second argument in res.render below will set some variables in the view.
res.render('posts/new', new Post({}));
In my view I then set my form fields as follows:
...
<input type="text" name="title" value="<%- post.title %>">
<textarea name="article"><%- post.article %></textarea>
...
When you submit this form, it should be caught by your router like so:
router.route('/posts')
.post(function(req, res) {
var post = new Post(req.body)
post.save(function(err) {
if (err) {
res.locals.errors = err.errors;
res.locals.post = post;
return res.render('posts/new');
}
return res.redirect('/posts');
});
...
})
This line of code, resets the form fields in your view
res.locals.post = post;
I hope someone finds this useful ;)
Not sure if it's best practice, but when I have validation failure, I don't redirect I just re-render the view (often by passing control to the 'get' callback). Somethign like this:
function loadProject(req,res, id){ /* fetch or create logic, storing as req.model or req.project */}
function editProject(req,res){ /* render logic */ }
function saveProject(req,res){
if(!req.form.isValid){
editProject(req,res);
}else{
saveToDb(req.project);
res.redirect('/project'+req.project.id+'/edit');
}
}
app.param('id', loadProject);
app.get('/projects/:id/edit', editProject);
app.post('/projects/:id', saveProject);
I had to work on similar problem recently and used two node modules: validator and flashify.
In the form view I configured my form fields as follows:
div.control-group
label.control-label Description
div.controls
textarea(name='eventForm[desc]', id='desc', rows='3').input-xxlarge= eventForm.desc
div.control-group
label.control-label Tag
div.controls
select(id='tag', name='eventForm[tag]')
tags = ['Medjugorje', 'Kibeho', 'Lourdes', 'Fatima']
for tag in tags
option(selected=eventForm.tag == tag)= tag
Notice the naming convention of the form fields. Then in my config file I set one global variable, which is really just a placeholder for when the form first loads:
//locals
app.locals.eventForm = []; // placeholder for event form repopulation
The validation logic is in my router file and looks like this:
app.post('/posts', function(req, res){
var formData = req.body.eventForm;
var Post = models.events;
var post = new Post();
post.text = formData.desc;
post.tag = formData.tag;
// run validations before saving
var v = new Validator();
var isPostValid = true;
// custom error catcher for validator, which uses flashify
v.error = function(msg) {
res.flash('error', msg);
isPostValid = false;
}
v.check(post.text, "Description field cannot be empty").notEmpty();
v.check(post.tag, "Tag field cannot be empty").notEmpty();
Then I check to see there are errors, and if so, pass the form data back to the view:
// reject it
res.render('Event.jade', {page: req.session.page, eventForm: formData});
Notice this evenForm data gets passed back to the view, which repopulates the default values.
The final step is to include the flashify component in your form view.
div(style='margin-top: 60px').container-fluid
include flashify
The code for the flashify view looks like this:
if (flash.error != undefined)
div.container
div.alert.alert-error
b Oops!
button(type='button', data-dismiss='alert').close ×
ul
each error in flash.error
li= error
if (flash.success != undefined)
div.container
div.alert.alert-success
b Success!
button(type='button', data-dismiss='alert').close ×
ul
each success in flash.success
li= success

Resources