Flask, WTForms - send the whole form using ajax - ajax

I am wondering how to send the whole wtform using ajax request (this one does not work):
function addReport(e){
e.preventDefault();
var serializeData = $('form').serialize();
$.ajax({
type:'GET',
url:'/reports_list',
data: serializeData,
success:function(result){
},
async: false
});
}
I have one main form, which includes another:
Reports = FieldList(FormField(ReportsForm))
The idea is to render the form under form, in case user wants to add another one. In short, it should just copy this underform and clone it, then renders to the main one.
How can I pass the entire form in ajax request?

It seems to me that you are trying to nest form elements. So you only send the innermost form. The validation errors are also not displayed when the form is sent via AJAX.
I think you can follow your approach more easily if you add the necessary form fields by cloning with JavaScript without submit.
The following example breaks the nested forms into fieldsets.
If a fieldset is to be added, the previous one is cloned and the necessary attributes are adapted to the requirements.
When removing, the last one is removed.
from flask import (
Flask,
render_template,
request
)
from flask_wtf import FlaskForm
from wtforms import FieldList, FormField, StringField, SelectField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.secret_key = 'your secret here'
class ReportForm(FlaskForm):
class Meta:
csrf = False
title = StringField('Name', [DataRequired()])
lang = SelectField('Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
class ReportsForm(FlaskForm):
reports = FieldList(FormField(ReportForm), min_entries=1)
#app.route('/reports', methods=['GET', 'POST'])
def reports():
form = ReportsForm(request.form)
if form.validate_on_submit():
for report in form.reports.data:
print(report)
return render_template('reports.html', **locals())
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reports</title>
</head>
<body>
<form name="my-form" method="post">
{{ form.csrf_token }}
{% for subform in form.reports -%}
<fieldset name="subform">
{% for field in subform -%}
<div>
{{ field.label() }}
{{ field() }}
{% if field.errors -%}
<ul class="errors">
{% for error in field.errors -%}
<li>{{ error }}</li>
{% endfor -%}
</ul>
{% endif -%}
</div>
{% endfor -%}
</fieldset>
{% endfor -%}
<button id="btn-less" type="button">Less</button>
<button id="btn-more" type="button">More</button>
<button type="submit">Submit</button>
</form>
<script type="text/javascript">
(function() {
const btnMore = document.querySelector('#btn-more');
btnMore.addEventListener('click', function() {
// Clone the previous fieldset element.
const subform = document.querySelector('fieldset[name="subform"]:last-of-type');
const newform = subform.cloneNode(true);
// Remove possible validation error messages.
const errors = newform.querySelector('.errors');
errors && errors.remove();
// Update the necessary attributes.
const fields = newform.querySelectorAll('input, select');
fields.forEach(field => {
const attrName = field.name;
field.name = field.id = attrName.replace(/^(.*)-([0-9]+)-(\w+)$/g, (...args) => {
return [args[1], Number(args[2]) + 1, args[3]].join('-');
});
field.value = '';
const labels = newForm.querySelectorAll(`label[for="${attrName}"]`);
labels.forEach(label => label.setAttribute('for', field.id));
});
// Add the new fieldset.
subform.after(newform);
});
const btnLess = document.querySelector('#btn-less');
btnLess.addEventListener('click', function() {
const subforms = document.querySelectorAll('fieldset[name="subform"]');
if (subforms.length > 1) {
// Remove the last of the fieldset elements.
subforms[subforms.length - 1].remove();
}
});
})();
</script>
</body>
</html>

Related

Updating a SQL Alchemy list without reloading the page?

I am working on a small Flask app and the user is able to update a list of items via using a form to submit some data.
I am currently using SQL Alchemy as the ORM for my database and using the list in my template to display the items. I want to update it so that when the list is updated. The page is updated without the user having to reload the page.
I have tried this with AJAX using the below script but the update is not occurring.
$(document).ready(function(){
$('form').on('submit',function (e) {
$.ajax({
type: 'post',
url: '/todo',
data: $('#todoInput').serialize(),
success: function (q) {
console.log(q);
}
});
e.preventDefault();
});
}
My template:
{% extends "base.html" %}
{% block script %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename='js/custom.js') }}"></script>
{% endblock %}
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-success" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row justify-content-md-center">
<div class="col-md-auto">
<h1>What needs to be done today?</h1>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-auto">
<form action="" method="post">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.todo (id="todoInput") }}
{{ form.submit (class_="btn btn-primary btn-sm") }}
</div>
</form>
<div id="todoList">
<ul class="list-group">
{% for todo in todos %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ todo.body }}
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}
Model:
from datetime import datetime
from app import db, login
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
todos = db.relationship('Todo', backref='owner', lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Todo {}>'.format(self.body)
Route:
#app.route('/todo', methods=['GET', 'POST'])
def todo():
if current_user.is_authenticated:
form = TodoForm()
todos = current_user.todos.order_by(Todo.timestamp.desc()).all()
if form.validate_on_submit():
new_todo = Todo(body=form.todo.data, owner=current_user)
db.session.add(new_todo)
db.session.commit()
flash('Todo added')
todos = current_user.todos.order_by(Todo.timestamp.desc()).all()
return render_template('todo.html', form=form, todos=todos)
return render_template('todo.html', form=form, todos=todos)
flash('You first need to login or register')
return redirect(url_for('index'))
The todos list is what im trying to update. But it just reloads the page :/
You need to do two things. Firstly you need to be sure that your database is updated. That means that your form was submitted successfully. This can be done by submitting the form manually via an ajax call. This is more or less what you did. Did you check, whether your backend receives the form-submission and updates the database?
Secondly you need to update the html in the browser. Normally the page is refreshed automatically if your form is submitted. If you dont want a page reload, you need to add some client-side logic, to ensure that the page is updated. This can easily be done with a little bit of javascript.
Edit:
Your custom js might be missing }); at the end. Or did you just forget to post it here?

Can't get all comments to reload without page refresh after comment submitted thourgh ajax

I have been trying to come up with a scheme where the comments sections refreshes in a template when a user posts comments with the comment that was just posted being included. The page must not be refreshed.
Sorry for indentation.
template -
{% extends "home/header.html" %}
{% block content %}
{% if request.user.is_authenticated %}
<form>
{% csrf_token %}
<p>Comment: </p><input type="text" name="fname" id="posted_comment">
<input type="hidden" class='meme_char_id' meme_char_id={{meme.meme_char_id}}>
<input type="submit" id ="btnSubmit" name="submit" value="Post">
</form>
<!-- displaying comments here -->
<div class="box" id="comments_div">
<h3 class="h4">{{comment_count}} comments </h4>
<ul class="list-group">
{% for comment in all_comments %}
<h5>{{ comment.commentby.username }} : </h5>
<li class="list-group-item list-group-item-success">{{ comment.comment_text }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endblock %}
Now, my JS file is -
$(document).ready(function() {
$('#btnSubmit').click(function(e) {
e.preventDefault();
var data = new FormData();
var comment = $('#posted_comment').val()
var meme_char_id = $(".meme_char_id").attr('meme_char_id')
data.append('comment', comment);
data.append('meme_char_id', meme_char_id);
$.ajax({
type: 'POST',
url: '<mydomain>/comment/',
data: data,
processData: false,
contentType: false,
success: function(reply) {
$(".comments_div").html(reply)
}
})
});
});
And finally my view is -
def comment(request):
if request.is_ajax():
comment = request.POST['comment']
meme_char_id = request.POST['meme_char_id']
this_meme = Memes.objects.get(meme_char_id=meme_char_id)
print "comment - ", comment, " meme_char_id - ", meme_char_id
new_comment = Comments(comment_text=comment, meme_id=this_meme.meme_id, commentby_id=request.user.id)
new_comment.save()
all_comments = Comments.objects.filter(meme_id=this_meme.meme_id).order_by('-created_at').values()
return HttpResponse(all_comments, content_type='application/json')
else:
raise Http404
Couple questions -
I want to return an Comments query set through ajax(don't see a point in jsonifying it, if I can just send the query set to the template.)
The queryset I am returning to template isn't picked up by the comments_div in the view.
I am a rookie so please explain step by step what I am doing wrong. Thanks.
You are sending back a JSON in a response and doing$(".comments_div").html(reply). You must be sending back a template rendered with all_comments as response on successful comment. Also, a queryset is not JSON serializable as far as I understand.

Django, submiting a form via AJAX

I have seen answers (here and here) for similar questions, but none of them work in my case. I have a simple form in a template, I am using bootstrap for rendering.
Once I submit the form, the response is rendered directly in the browser. When I return to the previous page (with the browser's button) then the success part of the AJAX call is executed.
forms.py
class QueryForm(forms.Form):
query = forms.CharField(label='Discover something', max_length=256)
views.py
def query_view(request, id):
if request.method == 'POST':
# Just for testing, send True
response_data = {
'result': True
}
return HttpResponse(json.dumps(response_data), content_type="application/json")
else:
try:
# Create a form and send it to the template
query_form = QueryForm()
return render(request, 'query_template.html', {'query_form': query_form})
except ObjectDoesNotExist:
return render(request, 'error.html')
urls.py
urlpatterns = [
url(r'^query', views.query_view, name='query_view'),
url(r'^', views.home, name='home'),
]
query_template.html
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% block content %}
{# Display a form #}
<form method="post" class="form">
{% csrf_token %}
{% bootstrap_form query_form %}
{% buttons %}
<button class="btn btn-primary" id="query-button">
{% bootstrap_icon "star" %} Submit
</button>
{% endbuttons %}
</form>
<ul id="result"></ul>
<script src="{% static 'scripts/main.js' %}"></script>
{% endblock %}
main.js
$('#query-button').click(function (event) {
$.ajax({
url: "/query/",
type: "POST",
data: {},
cache: false,
// handle a successful response
success: function (json) {
console.log(json); // log the returned json to the console
$("#result").html("<li>" + json.result + "</li>");
console.log("success"); // another sanity check
},
// handle a non-successful response
error: function (xhr, errmsg, err) {
console.log(xhr.status + ": " + xhr.responseText);
}
});
});
// It also includes functions to manage the CRFS token
I have been playing with the code. If instead a form I use <input> and <button id='query-button'> it renders the response without reloading the page.
You need to prevent the default submit action of the HTML form, via event.preventDefault().
You can using FormData instead of custom send data, open below links:
How to send FormData objects with Ajax-requests in jQuery?
https://developer.mozilla.org/en-US/docs/Web/API/FormData

Render template into Symfony2 with ajax

I have a action in my controller for the index route
routing.yml
index:
pattern: /index
defaults: { _controller:AcmeDemoBundle:Default:index }
Controller for this path
public function indexAction()
{
return $this->render('AcmeDemoBundle:Plugin:index.html.twig');
}
And the index.html.twig template
{% extends'::base.html.twig' %}
{% block stylesheets %}
{% stylesheets filter='cssrewrite' output='css/*.css'
'bundles/acmedemo/css/*' %}
<link href="{{ asset_url }}" type="text/css" rel="stylesheet" />
{% endstylesheets %}
{% endblock stylesheets %}
{% block body %}
<br>
<div class="container">
<div class="wp_attachment_holder">
<div class="imgedit-response" id="imgedit-response-8"></div>
<div class="wp_attachment_image" id="media-head-8">
<p id="thumbnail-head-8"><img class="thumbnail" src="http://localhost/wordpress/wp-content/uploads/2014/06/121-1024x583.jpeg" style="max-width:100%" alt=""></p>
<p><a class="btn btn-sm btn-default" id="edik-wp-extended-edit">Редактировать</a> <span class="spinner"></span></p>
</div>
<div style="display:none" class="image-editor" id="image-editor-8">
</div>
</div>
<div id="output"></div>
<img class="thumbnail" data-attach-id="8" data-src="http://localhost/wordpress/wp-content/uploads/2014/06/121-1024x583.jpeg" style="max-width:100%" alt="">
<script>
$('#edik-wp-extended-edit').click(function() {
window.location= Routing.generate('ajax');
// $('#output').load('/ajax/index');
});
</script>
</div>
{% endblock %}`
When the button Редактировать is clicked i want to load another template with ajax.
another.html.twig
<div>Hello</div>
routing.yml
ajax:
pattern: /ajax/index
defaults: { _controller :AcmeDemoBundle:Default:ajax }
options:
expose: true
Controller for this path
public function ajaxAction()
{
$template = $this->renderView('AcmeDemoBundle:Plugin:another.html.twig');
return new Response($template);
}
But when i click the button my uri will be /ajax/index. What i want is that it stays by /index and the template will be rendered into my index template
What am i doing wrong?
Thanks.
First, your ajaxAction() should be a bit different as far as I know.
For me this works:
$template = $this->forward('AcmeDemoBundle:Plugin:another.html.twig')->getContent();
$json = json_encode($template);
$response = new Response($json, 200);
$response->headers->set('Content-Type', 'application/json');
return $response;
The forward() function renders the template and returns the rendered HTML code.
Your JavaScript file should look like this:
$.ajax({
type: "POST",
dataType: 'json',
url: Routing.generate('ajax'),
async: false //you won't need that if nothing in your following code is dependend of the result
})
.done(function(response){
template = response;
$('#your_div').html(template.html); //Change the html of the div with the id = "your_div"
})
.fail(function(jqXHR, textStatus, errorThrown){
alert('Error : ' + errorThrown);
});
You make an AJAX call to the your ajaxAction, which will return the HTML of the template you want to be rendered.
After that you just need to add a <div id="your_div"></div> at the position you want the template to be rendered. This workes perfectly for me.
To mention is that you need to break down the ajax template to just the code that should be shown.
Please try generate ajax route like this
window.location= '{{ path("ajax") }}';
Added:
For make ajax request change windows.location to ajax request
$( "#output" ).load( '{{ path("ajax") }}', function() {
alert('Load done');
});
Added explanation of use:
js code will work only if you put it on Twig template. If you put it to js file it will not work.
In case of original questions.
<div id="output"></div>
<img class="thumbnail" data-attach-id="8" data-src="http://localhost/wordpress/wp-content/uploads/2014/06/121-1024x583.jpeg" style="max-width:100%" alt="">
<script>
$('#edik-wp-extended-edit').click(function() {
$( "#output" ).load('{{ path("ajax") }}');
});
</script>
You can use something like FOSJsRoutingBundle to proper use SF2 routing in js

Django with Ajax and jQuery

I would like after clicking on one of the many Item shown a window with his description (single item description).
How to create this using Ajax and jQuery with Django?
model:
class Item(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField()
price = models.DecimalField(max_digits=5, decimal_places=2)
desc = models.TextField()
views:
def item_list(request):
items = Item.objects.all()[:6]
return render_to_response('items.html', {'items':items}, context_instance=RequestContext(request))
def single_item(request, slug):
item = Item.objects.get(slug=slug)
return render_to_response('single.html', {'item':item}, context_instance=RequestContext(request))
template:
<!-- Single item description: -->
<div id="description">
<img src="/site_media/images/photo.png">
<div id="item_description">
<input name="add" type="button" id="add" value="Add to Cart">
<p class="title">Single Item name</p>
<p class="description"><span>Description:</span>
This is single item description
</p>
</div>
</div>
<!-- All item: -->
<div id="item">
{% for i in items %}
<div class="item">
<img src="/{{ i.image.url }}" />
<p>
<span> {{ i.name }} </span>
<span> {{i.price}} </span>
</p>
</div>
{% endfor %}
</div>
</div>
</div>
If you want to use ajax to refresh your page, you'll need to do three things:
Add an entry to urls.py for the ajax call (or add a condition to your view function to process the request if it's ajax)
Add the javascript block to make the ajax call and update the html/text with the new data
Add the code in your views.py to handle the ajax call and respond with json data
urls.py
...
url(r'/ajax-view-single/)/$', 'ajax_single_item', name='app_name_ajax_single_item'),
html/js
<script type="text/javascript" src="/js/json2.js"></script>
$("#view-single-item").click(function () {
try {
// get slug from html
var slug = "";
var data = {
slug: slug
};
$.get('{% url app_name_ajax_single_item %}', data, function(data){
// your data returned from django is in data
alert(data.item_name);
}, 'json');
//$('#error').hide();
}
catch(err) {
$('#error').html(err);
$('#error').show();
}
return false;
});
views.py
from django.http import HttpResponse
from django.utils import simplejson
from django.shortcuts import get_object_or_404
def ajax_single_item(request):
'''gets single item'''
if not request.is_ajax():
return HttpResponse(simplejson.dumps({'result': False}))
# get slug from data
slug = request.GET.get('slug', None)
# get item from slug
item = get_object_or_404(Item, slug=slug)
return HttpResponse(simplejson.dumps({
'result': True,
'item_name': item.name,
'item_price': item.price,
'item_desc': item.desc,
'item_slug': item.slug
}))

Resources