Django/Js: how to post a form without reloading whole page - javascript

My application currently flows through 3 pages:
User selects question in index page
User submits answer in answer page
User is presented with result in results page.
I want to compress that down to a single page where the user submits an answer to the question and result is shown on the same page.
The following django-template code separates questions with Bootstrap accordion. How do I post the form without refreshing the whole page? I want to be able to display the result on the page, update CSS styling with Javascript etc.
<h2>{{ category.title }}</h2>
<div class="accordion" id="accordion{{category.title}}">
{% for challenge in category.challenge_set.all %}
<div class="card">
<div class="card-header" id="heading{{challenge.id}}">
<h2 class="mb-0">
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapse{{challenge.id}}" aria-expanded="true" aria-controls="collapse{{challenge.id}}">
{{ challenge.question_text }} - {{ challenge.point_value }} points
</button>
</h2>
</div>
<div id="collapse{{challenge.id}}" class="collapse in" aria-labelledby="heading{{challenge.id}}" data-parent="#accordion{{category.title}}">
<div class="card-body">
<p>{{ challenge.description }}</p>
<form action="{% url 'challenges:answer' challenge.id %}" method="post">
{% if challenge|is_answered:request %}
<label for="answered">Answer</label>
<input type="text" name="answered" id="answered" value="{{ challenge.answer_text }}" readonly>
{% else %}
{% csrf_token %}
<label for="answer">Answer</label>
<input type="text" name="answer" id="answer">
<input type="submit" value="Submit">
{% endif %}
</form>
</div>
</div>
{% endfor %}
</div>
Here is the view:
def index(request):
context = {'challenges_by_category_list': Category.objects.all()}
return render(request, 'challenges/index.html', context)
def detail(request, challenge_id):
challenge = get_object_or_404(Challenge, pk=challenge_id)
return render(request, 'challenges/detail.html', {'challenge': challenge})
def results(request, challenge_id, result):
challenge = get_object_or_404(Challenge, pk=challenge_id)
return render(request, 'challenges/results.html', {'challenge':challenge, 'result':result})
def answer(request, challenge_id):
challenge = get_object_or_404(Challenge, pk=challenge_id)
result = "Incorrect, try again!"
if challenge.answer_text.lower() == request.POST['answer'].lower():
current_user = request.user
session = User_Challenge(user=current_user, challenge=challenge, answered=True)
session.save()
points = Profile(user=current_user, points=challenge.point_value)
points.save()
result = "Correct!"
return HttpResponseRedirect(reverse('challenges:results', args=(challenge.id, result)))

You can try this:
Add the below script in your template:
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
write a script and a function inside it to submit the form data.
<script type="text/javascript">
function submitData( challenge_id ){
// Get answer from the input element
var answer = document.getElementById("answer").value;
// add the url over here where you want to submit form & challenge_id is also taken as a parameter.
var url = "<your_url>";
$.ajax({
url: url,
data: {
'answer': answer,
},
dataType: 'JSON',
success: function(data){
// show an alert message when form is submitted and it gets a response from the view where result is provided and if url is provided then redirect the user to that url.
alert(data.result);
if (data.url){
window.open(data.url, '_self');
}
}
});
}
</script>
Change type of the submit button and add an onclick event to call the submitData() function and pass the challenge id to it. And remove the action attr from the form.
see below:
<form method="post">
{% csrf_token %}
{% if challenge|is_answered:request %}
<label for="answered">Answer</label>
<input type="text" name="answered" id="answered" value="{{ challenge.answer_text }}" readonly>
{% else %}
<label for="answer">Answer</label>
<input type="text" name="answer" id="answer">
// over here
<button type="button" onclick="submitData({{ challenge.id }})">
Submit
</button>
{% endif %}
</form>
Return a JsonReponse to the ajax call from the views.
views.py
def answer(request, challenge_id):
answer = request.GET.get('answer', False)
url = False
if challenge.objects.filter(id=challenge_id).exists() and answer:
challenge = Challenge.objects.get(id=challenge_id)
if challenge.answer_text.lower() == answer.lower():
current_user = request.user
session = User_Challenge(user=current_user, challenge=challenge, answered=True)
session.save()
points = Profile(user=current_user, points=challenge.point_value)
points.save()
result = "Correct!"
# specify the url where you want to redirect the user after correct answer
url = ""
else:
result = "Incorrect, try again!"
data = {
'result': result,
'url': url
}
return JsonResponse(data)

Related

How to get additional Django Inline Formsets to POST to database

I'm currently building out a Django ModelForm with an adjoining inline_formset. I'm also using a button with javascript to allow the user to add in more instances of the formset in the front-end. However, the issue im running into is that the extra instances of the formset that are added by the user, don't get sent to the database when the form is submitted.
I'm unsure as to whether it is an issue with the javascript itself. Or something more fundamental to do with the Django inline formsets. Any help would be much appreciated. Everything else is working fine; heres my HTML template for reference
{% extends 'main.html' %}
{% load static %}
{% block content %}
<section id="form">
<form class="form" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="input">
<h1>{{field.label}}:</h1>
<p>{{field}}</p>
</div>
{% endfor %}
<div id="form-container">
{{formset.management_form}}
{% for field in formset %}
<div class="input">
<h1>Parts Required:</h1>
<p>{{field}}</p>
</div>
<div id="empty-form" class="input" style="display: none;">
<p>{{formset}}</p>
</div>
{% endfor %}
</div>
<button class="btn btn-primary" id="add-more" type="button">Add More</button>
<input class="submit btn btn-primary" type="submit"></input>
</form>
</section>
<script>
document.getElementById("add-more").onclick = function () {
var emptyForm = document.getElementById("empty-form");
var newForm = emptyForm.cloneNode(true);
newForm.style.display = "block";
document.getElementById("form-container").appendChild(newForm);
}
</script>
{% endblock content %}
Here's my view for the warranty form:
# Create Warranty Form:
#login_required(login_url='login')
def createWarranty(request):
warranty = WarrantyClaim.objects.all()
form = WarrantyClaimForm()
warrantyformset = inlineformset_factory(WarrantyClaim, PartsRequired, form=WarrantyClaimForm, extra=1)
if request.user.is_authenticated:
if request.method == 'POST':
form = WarrantyClaimForm(request.POST, request.FILES)
formset = warrantyformset(request.POST, request.FILES, instance=WarrantyClaim())
if form.is_valid() and formset.is_valid():
post = form.save(commit=False)
post.owner = request.user
post.save()
formset.instance = post
formset.save()
return redirect('spare-parts')
context = {'warranty': warranty, 'form': form, 'formset': warrantyformset}
return render(request, 'spareparts/parts_form.html', context)

JavaScript Spinner is conflicting with The input "required" attribute using WTForms

I am using Flask, and have some basic elements on a login page. A route, a form, and a template. I also have a spinner that is triggered on the submission of the form.
Where I am running into issues is when someone does not fill out the info, and hits submit. Basically, a spinner is triggered, but also the infobulle warning the user to fill out the field is triggered as well causing the spinner to be stuck spinning.
Form:
class LoginForm(FlaskForm):
email = StringField("Email Address", validators=[DataRequired(), Email()])
password = PasswordField("Password", validators=[DataRequired()])
submit = SubmitField("Login")
Route:
#auth.route("/login", methods=["Get", "Post"])
def login():
form = LoginForm()
if form.validate_on_submit():
# Login to the site
return render_template("login.html", form=form, title="Sign In")
Template:
<form class="text-left col-lg-12" action="" method="POST">
{{ form.hidden_tag() }}
<div class="form-group">
{% if form.email.errors %}
{{ form.email(class_="form-control is-invalid", placeholder="Email") }}
<div class="invalid-feedback">
{% for error in form.email.errors %}
<span> {{ error }} </span>
{% endfor %}
</div>
{% else %}
{{ form.email(class_="form-control ", placeholder="Email") }}
{% endif %}
</div>
<div class="form-group">
{% if form.password.errors %}
{{ form.password(class_="form-control is-invalid", placeholder="Password") }}
<div class="invalid-feedback">
{% for error in form.password.errors %}
<span> {{ error }} </span>
{% endfor %}
</div>
{% else %}
{{ form.password(class_="form-control ", placeholder="Password") }}
<span class="p-viewer"><i class="icon-eye" id="togglePassword" style="margin-left: 100px; cursor: pointer;"></i></span>
{% endif %}
</div>
<div class="text-right mt-3">
<i class="icon-chevron-left"></i> Homepage
{{ form.submit(class="btn btn-primary", onclick='spinner()') }}
</div>
</form>
JavaScript for Spinner:
<script>
function spinner() {
$('#spinner').modal('show');
return true;
}
var FirstLoading = true;
function RestoreSubmitButton() {
if (FirstLoading) {
FirstLoading = false;
return;
}
$('#spinner').modal('hide');
}
document.onfocus = RestoreSubmitButton;
</script>
Because I am using DataRequired() in the form, it is inserting a "required" element in the input field. When I click submit without data, it fires the spinner and then raises the info box:
My question is, is there a way to handle this conflict properly? Or is there a way to trigger the spinner to run after the validation process is complete?
Thanks
I just needed to put an id on the form instead of on the button.
So, I changed:
<div class="text-right mt-3">
<i class="icon-chevron-left"></i> Homepage
{{ form.submit(class="btn btn-primary", onclick='spinner()') }}
</div>
to:
<div class="text-right mt-3">
<i class="icon-chevron-left"></i> Homepage
{{ form.submit(class="btn btn-primary") }}
</div>
and:
<form method="POST" acion="">
to:
<form method="POST" acion="" id="submit_id">
and added a bit of javascript:
$(document).ready(function() {
$("#submit_id").submit(function(e) {
spinner();
});
});
Now, the spinner is not kicked off until after the form itself has had a chance to see if required fields are needed

Make Django Data-Url Attribute string form to work in .prepend

I have a django web app (esr_submit) that has a form and the ability to upload files. When a file is uploaded (using fileupload jquery), the list of uploaded files needs updated via a prepend action, replicating the html for the and . The button contains a data-url attribute which calls on the django url and its respective view to accomplish the delete action. How do I make the data-url attribute a string so the script works?
views.py
from django.shortcuts import render, redirect
from .forms import ServiceRequestForm, FileForm
from .models import RequestAttachment
from django.http import JsonResponse
def delete_file(request, pk):if request.method == 'POST':
file = RequestAttachment.objects.get(pk=pk)
file.delete()
return redirect('/esr_submit/files')
def esr_submit(request):
files = RequestAttachment.objects.all()
if request.user.is_authenticated:
initial_data = {'first_name': request.user.first_name,
'last_name': request.user.last_name,
'email': request.user.email,
'contact': request.user.phone,
}
request_form = ServiceRequestForm(initial=initial_data)
else:
request_form = ServiceRequestForm()
if request.method == 'POST':
if 'submit_request' in request.POST:
request_form = ServiceRequestForm(request.POST)
if request_form.is_valid():
request_form.save()
return redirect('/esr_submit/')
else:
print(request_form.errors)
return redirect('/esr_submit/')
if 'file' in request.FILES:
form = FileForm(request.POST, request.FILES)
if form.is_valid():
file = form.save()
data = {'is_valid': True, 'name': file.file.name, 'url': file.file.url}
else:
data = {'is_valid': False}
return JsonResponse(data)
else:
print("not a file form")
else:
files_list = RequestAttachment.objects.all()
return render(request, 'esr_submit/esr_submit.html',
{'request_form': request_form,
'files': files,
'photos': files_list
}
)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.esr_submit, name='esr_submit'),
path('files/', views.file_list, name='file_list'),
path('files/<int:pk>/', views.delete_file, name='delete_file'),
esr_submit.html
{% extends "main/base.html" %}
{% block title %}
Submit an ESR
{% endblock %}
{% load crispy_forms_tags %}
{% block content %}
<head>
</head>
<br>
<div class="container">
<div class="row justify-content-md-center mb-2">
<div class="col-sm-8 mb-4 shadow-lg p-3 bg-white rounded">
<div class="header mb-2">
<h3 class="header mb-0 text-center">New Engineering Service Request</h3>
{% if not request.user.is_authenticated %}
<div class="text-center">
<small class="text-muted"><strong>Want to speed things up? </strong>
Log In |
</small>
<small class="text-muted">
Create an account
</small>
</div>
{% endif %}
</div>
<form method="post" enctype="multipart/form-data" id="request_form"
class="request_form">
<div class="col">
{% csrf_token %}
{% crispy request_form %}
<span class="helper-text"></span>
</div>
</form>
<div class="container">
<button type="button" class="btn btn-primary js-upload-photos">
<span class="glyphicon glyphicon-cloud-upload"></span> Upload File(s)
</button>
{# 2. FILE INPUT TO BE USED BY THE PLUG-IN #}
<form id="fileForm">
<input id="fileupload" type="file" name="file" class="file" multiple
style="display: none;"
data-url="{% url 'esr_submit:esr_submit' %}"
data-form-data='{"csrfmiddlewaretoken": "{{ csrf_token }}"}'>
</form>
{# 3. TABLE TO DISPLAY THE UPLOADED FILES #}
<div id="myTable">
<table id="file-table" class="file-table table-borderless">
<thead>
<tr>
{% if photo in photos %}
<th>Attached Files</th>
{% else %}
{% endif %}
</tr>
</thead>
<tbody class="file-body">
{% for file in files %}
{% if file.file %}
<tr id="file-{{file.id}}"
class="file">
<td class="w-50">
{{ file.file }}
</td>
<td class="delete" id="delete">
<button type=submit data-id="file-{{file.id}}" data-url="{% url 'esr_submit:delete_file' file.pk %}" class="file-delete-btn btn btn-danger btn-sm">X</button>
</td>
</tr>
{% else %}
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="pl-2 mt-2">
<button type="submit" name="submit_request" value="submit_request" class="btn btn-primary" form="request_form">Submit Request</button>
<button type="submit" name="save_draft" value="save_draft" id="save_draft" class="btn btn-primary" form="request_form">Save Draft</button>
</div>
</div>
</div>
</div>
{% endblock content%}
script.js (loaded on the base.html)
/* Multiple File Upload Using AJAX - FROM SIMPLE IS BETTER THAN COMPLEX EXAMPLE */
$(function fileupload() {
/* 1. OPEN THE FILE EXPLORER WINDOW */
$(".js-upload-photos").click(function () {
$("#fileupload").click();
});
/* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
$("#fileupload").fileupload({
dataType: 'json',
done: function (e, data) { /* 3. PROCESS THE RESPONSE FROM THE SERVER */
if (data.result.is_valid) {
console.log(data)
$("#file-table tbody").prepend(
"<tr><td><a href='" + data.result.url + "' target='_blank'>" + data.result.name + "</a></td><td class='delete'><button type=submit data-id='file-{{file.id}}' data-url="{% url 'esr_submit:delete_file' file.pk %}" class='file-delete-btn btn btn-danger btn-sm'>X</button></td></tr>"
)
}
}
});
});
$('#myTable').on('click', '.file-delete-btn', function(e){
e.preventDefault();
var tableId = $(this).attr('file-table')
var fileId = $(this).attr('data-id');
var formAction = $(this).attr('data-url');
$.ajax({
url: formAction,
method: 'POST',
success: function(){
$("#file-" + fileId).remove()
console.log("file removed")
$("#myTable").load(" #myTable");
},
error: function(errorData){
console.log("error")
console.log(errorData)
}
});
You cannot use the {% url %} tag in Javascript if this is a separate file.
If you just have a file script.js, it will not work.
In this case you want to generate the correct URL for the delete part. The best way to do this in the most reliable way (so it would still work if you edit the urls.py) is to use the Django urlresolvers.
Then return the data back to your upload process.
On the top of your Python file, place this:
from django.core.urlresolvers import reverse
The last bit of the POST-part in esr_submit() could look like this:
if 'file' in request.FILES:
form = FileForm(request.POST, request.FILES)
if form.is_valid():
file = form.save()
data = {'is_valid': True, 'name': file.file.name, 'url': file.file.url, 'file_id': file.id, 'remove_url': reverse('esr_submit:delete_file', args=[file.id])}
else:
data = {'is_valid': False}
return JsonResponse(data)
else:
print("not a file form")
The Javascript bit could look like this:
/* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
$("#fileupload").fileupload({
dataType: 'json',
done: function (e, data) { /* 3. PROCESS THE RESPONSE FROM THE SERVER */
if (data.result.is_valid) {
$("#file-table tbody").prepend(
"<tr><td><a href='" + data.result.url + "' target='_blank'>" + data.result.name + "</a></td><td class='delete'><button type=submit data-id='file-" + data.result.file_id + "' data-url='" + data.result.remove_url + "' class='file-delete-btn btn btn-danger btn-sm'>X</button></td></tr>"
)
}
}
});
I don't have your app running to this is from the top of my head.

Symfony2 jQuery div refresh without

i have a form and i use jQuery for submit and work without refresh page..
$('#formnotifica').submit(
function(event){
event.preventDefault();
term = $(this).serialize();
url = $(this).attr('action');
$.post(
url,
term,
function(data){
}
)
}
);
Now i would refresh only my content the form and the date after click of submit or some second..
I use this code when i tried but give me another page Symfony and give me problems..
$(document).ready(function() {
$("#aggiorna").load("");
var refreshId = setInterval(function() {
$("#aggiorna").load("" + Math.random());
}, 1000);
});
Where #aggiorna is the "ID" of my .. How can i do for refresh my result of query in controller?? Thanks and sorry for my english
THIS IS THE CODE HTML
<div id="aggiorna">
<ul>
{% for n in notifications %}
{% if n.is_displayed == 0 %}
<li>
<form id="formnotifica" action="{{ path('profilo_index', {'id': n.id}) }}" method="post">
<input name="id" type="hidden" value="{{ n.id }}" />
<button class="submit" id="submit" type="submit" >{{n.text|raw}}</button>
</form>
</li>
{% else %}
<li>{{n.text|raw}}
</li>
{% endif %}
{% endfor %}
</ul>
</div>
In the controller there is only DQL query for update a field in my db
Remove:
event.preventDefault();

jquery .click() event issue (django)

I have created a view that generates posts and each post have a comments_set which produces all the comments made by user. When new comment is posted, below function is executed.
$("#comment-post-button").click(function(){
var event_id = document.getElementById('event-id').value;
var url= '/post-comments/'+event_id +'/';
var data = {csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
content:document.getElementsByName('comment-post-content')[0].value
}
$.post(url , data, function(){
$("#refresh-comments").load("/comments/" + event_id + '/', function(){
$("#comment-post-content").val("");
});
});
$("#comment-post-content").val("");
return false;
});
The problem is that the page contains multiple posts and each comment submission button has the same id "comment-post-button". So the above function works only for the top post and not for the rest. I can see what the problem is but don't know how to solve this. Please help.
Here is the html markup:
{% for event in events %}
<div class="post">
<div class="post-right">
<div class="post-author">{{ event.author.first_name }}</div>
<div class="post-content">{{ event.description }}</div>
<div class="post-bar">
<div class="post-likes">{{ event.up_votes }}<img src="/site-media/static/images/like.png" /></div>
<div class="post-dislikes">{{ event.down_votes }}<img src="/site-media/static/images/dislike.png" /></div>
<div class="post-timestamp">{{ event.pub_date }}</div>
Comment
</div>
<div class="post-comment">
<form method="post" action="/post-comments/{{ event.id }}/">
{% csrf_token %}
<input type="text" id="comment-post-content" name="comment-post-content" maxlength="200" placeholder="Add a comment..." />
<input type="hidden" id="event-id" value="{{ event.id }}">
<input type="submit" id="comment-post-button" class="comment-post-button" value="Post comment" />
</form>
</div>
<div id="refresh-comments" class="comments">
{% include "comments.html" %}
</div>
</div>
<div class="post-left">
<img src="../FestJanta/site-media/static/images/post.jpg" />
</div>
</div>
{% endfor %}
comments.html:
{% for comment in event.comment_set.all|slice:"3" %}
<div class="comments-right">
{{ comment.author.first_name }}
{{ comment.content }}<br>
<div class="comment-timestamp">{{ comment.pub_date }}</div>
</div>
<div class="comments-left"><img src="../FestJanta/site-media/static/images/comment.jpg" /></div>
{% endfor %}
Final working solution:
$(".comment-post-button").click(function(){
var btn = $(this);
var currentPost = btn.parents('.post');
var event_id = currentPost.find('.event-id').val();
var url= '/post-comments/'+event_id +'/';
var data = {csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
content:currentPost.find('input[name="comment-post-content"]').val()
}
$.post(url , data, function(){
$(currentPost.find('.refresh-comments')).load("/comments/" + event_id + '/', function(){
$(currentPost.find('.comment-post-content')).val("");
});
});
return false;
});
Remove id and add class:
<input type="hidden" class="event-id" value="{{ event.id }}">
Do something like this:
$('.comment-post-button').click(function(){
var $btn = $(this);
var $currentPost = $btn.parents('.post');
var event_id = $currentPost.find('.event-id').val();
//...
});
Find each element in $currentPost scope:
Instead of this:
content: document.getElementsByName('comment-post-content')[0].value
Do this:
content: $currentPost.find('input[name="comment-post-content"]').val()
You could do the following:
Identify all post buttons, e.g. by a class like .comment-button
Use the .on() notation of jQuery
Pass the event and use its target property to identify the DOM element that triggered the event
Use traversion to get the correct DOM element of the post
The result should look something like this (untested):
Markup (I basically just got rid of the IDs; not sure how/if django handles this automatically):
{% for event in events %}
<div class="post">
<div class="post-right">
<div class="post-author">{{ event.author.first_name }}</div>
<div class="post-content">{{ event.description }}</div>
<div class="post-bar">
<div class="post-likes">{{ event.up_votes }}<img src="/site-media/static/images/like.png" /></div>
<div class="post-dislikes">{{ event.down_votes }}<img src="/site-media/static/images/dislike.png" /></div>
<div class="post-timestamp">{{ event.pub_date }}</div>
Comment
</div>
<div class="post-comment">
<form method="post" action="/post-comments/{{ event.id }}/">
{% csrf_token %}
<input type="text" name="comment-post-content" maxlength="200" placeholder="Add a comment..." />
<input type="hidden" name="event-id" value="{{ event.id }}">
<input type="submit" class="comment-post-button" value="Post comment" />
</form>
</div>
<div class="comments">
{% include "comments.html" %}
</div>
</div>
<div class="post-left">
<img src="../FestJanta/site-media/static/images/post.jpg" />
</div>
</div>
{% endfor %}
Javascript:
$("body") // Could be any ancestor, body is not the best option
.on('click', '.comment-post-button' function(ev){
var clickTarget = ev.target, // The element clicked on
postElement = $(clickTarget).closest('.post'), // the div enclosing a post
commentSection = $(postElement).find(".comments"), // the div enclosing the comments
commentInputField = $(clickTarget).siblings("[name='comment-post-content']"),
event_id = $(clickTarget).siblings("[name='event-id']").val(),
url= '/post-comments/'+event_id +'/';
// Not sure what this token is, so I will not change that part
var data = {csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
content: commentInputField.val()
}
$.post(url , data, function(){
$(commentSection).load("/comments/" + event_id + '/', function(){
$(commentInputField ).val("").prop('disabled', false); // In the callback, empty and reenable
});
});
$(commentInputField ).prop('disabled', true); // I guess disabling the field makes sense
return false;
});
An additional advantage is that you will end up with only one click handler. Note that the solution could be optimized (e.g. by improving the selectors). In addition, jslint will give some warnings.
to give each post & post_Comment_button a unique id, as suggested by someone, change the markup as follows:
{% for event in events %}
<div class="post" id="post_{{forloop.counter}}">
[...]
<input type="submit" id="comment-post-button_{{forloop.counter}}" class="comment-post-button" value="Post comment" />
then change the js function as follows:
$("#comment-post-button").click(function(event){
var buttonNr = event.target.id.replace('comment-post-button_', '');
var postId = 'post_' + buttonNr;
$("#" + postId)... --> and do whatever with it..

Categories

Resources