I would like to understand how to submit 2 forms simultaneously using ajax.
I display a form composed of 2 model forms, for 2 models linked with OneToOne relationship. Managing the display is not a big deal, I can also manage the submit using a Django view; but I would like to know how to deal with javascript.
Why? The form is called from a global menu and displayed in a modal, and I would like only to close the modal on submit, but not refresh the page.
Here are some (simplified) code snippets I tried so far:
user_profile.html (modal form):
<form id="upd-user" action="." method="post" url-endpoint="{% url 'polls:upd_user_detail' %}"
comp-slug="{{ comp_slug }}" usr-id="{% if usr_id %} {{ usr_id }} {% else %} 0 {% endif %}">
{% csrf_token %}
{{ user_form }}
{{ usercomp_form }}
<button class="btn btn-success" type="submit">{% if usr_id %}Mettre à jour{% else %}Créer{% endif %}</button>
     
<a class="btn btn-secondary back_btn" data-dismiss="modal">Annuler</a>
</form>
The modal is embedded in this snippet:
<div id="usr_detail" class="modal fade hide" role="dialog" tabindex='-1'>
<div class="modal-dialog modal-lg">
<div class="modal-content usr_form_content">
{% include './user_profile.html' %}
</div>
</div>
</div>
Javascript submit function:
$(".usr_form_content").on("submit", "#upd-user", function(event) {
event.preventDefault();
$.ajax({
method: 'POST',
url: $(this).attr('url-endpoint'),
data: {
comp_slug: $(this).attr('comp-slug'),
usr_id: $(this).attr('usr-id'),
user_form: user_form.serialize(),
usercomp_form: usercomp_form.serialize()
},
success: handleSuccess,
error: handleError,
});
function handleSuccess(data) {
$("#usr_detail").modal("hide");
};
function handleError(jqXHR, textStatus, errorThrown){
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
}
})
The error message is quite clear:
Uncaught ReferenceError: user_form is not defined
I now I need to make the transition between the form and js, I probably need to link these values to $('this) but I don't know how.
If it helps, here's the GET view:
comp_slug = request.GET["comp_slug"]
company = Company.get_company(comp_slug)
usr_id = int(request.GET["usr_id"]) # comes as string when 0
if usr_id > 0:
profile_user = User.objects.get(pk=usr_id)
user_form = UserBaseForm(instance=profile_user)
usercomp_form = UserCompForm(instance=profile_user.usercomp)
user_form.fields['username'].disabled = True # Disable updates of the field
else:
user_form = UserBaseForm()
usercomp_form = UserCompForm()
context = {
"comp_slug": comp_slug,
"company": company,
"usr_id": usr_id,
"user_form": user_form,
"usercomp_form": usercomp_form,
}
template = render_to_string('polls/user_profile.html', context=context, request=request)
return JsonResponse({"user_template": template})
And related js part (it works fine - I tried to generated necessary variables at this stage, but it did not work neither)
$('.update-user').click(function() {
$.ajax({
method: 'GET',
url: $(this).attr('url-endpoint'),
data: {
comp_slug: $(this).attr('comp-slug'),
usr_id: $(this).attr('usr-id'),
},
success: handleSuccess,
error: handleError,
});
function handleSuccess(data) {
$(".usr_form_content").html(data.user_template);
};
function handleError(error_data) {
console.log("error");
console.log(error_data);
};
})
Related
On site exists form where users able to comment products. Comments are connected with products. jQuery is used from Bootstrap base.html (it's worked with another ajax-call). I'm fighting with ajax comment during the week). Trying to do it only for one product to understand how it works. Without ajax system of comments works fine, but I decided to add smooth in comment add without refreshing of the page and I do task with POST and ajax for the first time (before I did two pretty simple examples with get and form reset after successful response to ajax from back-end). Could somebody advice what should be add in template and view ? I suppose problem is connected with my poor knowledge of js.
I am using
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
because this is working with django 4.0, and old function is_ajax() was removed.
views.py
def ajax_test(request):
product = Product.objects.get(id=4)
comments = product.comment_set.order_by('-created_at')
form = UserCommentForm
context = {'product':product,'comments':comments,'form': form}
return render(request, 'store/ajax_test.html', context)
def ajax_receiver(request):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
if request.method == 'POST' and is_ajax:
product = Product.objects.get(id=4)
form = UserCommentForm(data=request.POST)
user = Comment.objects.get(author=user)
if form.is_valid() and request.user.is_authenticated:
new_comment = form.save(commit=False)
new_comment.author = request.user
new_comment.product = product
new_comment.save()
comment_info = {
"author": new_comment.author,
"new_comment": new_comment.content,
"created_at": new_comment.created_at,
}
return JsonResponse({"comment_info": comment_info}, status=200)
else:
return JsonResponse({"success": False}, status=400)
template
{% extends "store/base.html" %}
{% load bootstrap4 %}
{% block page_header %}
<h1>тестирую аякс</h1>
{% endblock page_header %}
{% block content %}
<div class="container">
<div class="row py-3">
<div class="col-3 order-2" id="sticky-sidebar">
<div class="sticky-top">
<form id="UserCommentForm" method='post' class="form">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button name="submit" class="btn btn-primary" name="comment">Добавить комментарий</button>
{% endbuttons %}
</form>
</div>
</div>
<div class="col" id="main">
<h3>Комментарии:</h3>
{% for comment in comments %}
<h6 class="card-header">
{{ comment.author }}<small> добавлен {{ comment.created_at|date:'M d, Y H:i' }} </small>
</h6>
<div class="card-body">
<h4>{{ comment }}</h4>
<form action="" method="POST">
{% csrf_token %}
</form>
</div>
{% empty %}
<p>Для данного товара ещё нет комментариев.</p>
{% endfor %}
</div>
</div>
</div>
{% block javascript %}
<script type="text/javascript">
$(document).ready(function(){
$("#UserCommentForm").submit(function(e) {
// prevent from normal form behaviour
e.preventDefault();
// serialize the form data
var serializedData = $(this).serialize();
$.ajax({
type : 'POST',
url : "{% url 'store:ajax_receiver' %}",
data : serializedData,
success : function(response){
??? what should be there to post ???
},
error : function(response){
console.log(response)
}
});
});
});
</script>
{% endblock javascript %}
{% endblock content %}
Finally I found the decision, it's just to reload block of comments without refreshing, there is the code, the space before ' #main' is important!:
<script type="text/javascript">
$(document).ready(function(){
$("#UserCommentForm").submit(function(e){
// prevent from normal form behaviour
e.preventDefault();
// serialize the form data
var serializedData = $(this).serialize();
$.ajax({
type : 'POST',
url : "{% url 'store:ajax_receiver' %}",
data : serializedData,
success : function(response){
$('#main').load(' #main', function(){
/// can add another function here
});
$("#UserCommentForm")[0].reset();
},
error : function(response){
console.log(response)
}
});
});
});
</script>
In your ajax success write the following code to show new comment:
let comment_info = response["comment_info"];
let author = comment_info["author"];
let comment = comment_info["comment"];
let created_at = comment_info["created_at"];
html = "<h6 class='card-header'> "+author+"</h6>"
// i have writting only part of html write the full just like you do when you show comments
$("#main").append(html);
I'm fighting with this problem during several days and can not find the solution for my case. I'm trying to make system of likes without refreshing the page. In synchronous mode system of likes and dislikes works fine, but when I'm trying to add AJAX, I'm getting 405 and only one last comment is working for one click, I understand problem that Ajax doesn't understand django urls with id or pk like my variant {% url 'store:like' comment.pk %} , but how it can be solved?
There is this part from the template:
{% for comment in comments %}
<h6 class="card-header">
{{ comment.author }}<small> добавлен {{ comment.created_at|date:'M d, Y H:i' }} </small>
</h6>
<div class="card-body">
<h4>{{ comment }}</h4>
<form id="like-{{comment.pk}}" method="POST" action="{% url 'store:add_like' comment.pk %}">
{% csrf_token %}
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
<a class="btn btn-success" id="like-count-{{comment.pk}}"> Likes {{ comment.likes.all.count }}</a>
</button>
</form>
</div>
{% empty %}
<p>Для данного товара ещё нет комментариев.</p>
{% endfor %}
my ajax call in the same template:
<script type="text/javascript">
$(document).ready(function(){
$('[id^="like-"]').submit(function(e){
e.preventDefault();
var endpoint = $(this).attr('action');
var serializedData = $(this).serializeArray();
$.ajax({
type: 'POST',
url: endpoint,
data: serializedData,
success: function(response) {
$( "#like-count-"+response["id"].toString()).text("Likes "+response["like_count"]);
},
error: function(rs, e) {
alert(rs.responseText);
}
});
})
This part from urls:
path('products/<int:pk>/like/', addlike, name='like'),
View for like:
#api_view(['POST'])
def addlike(request, pk, *args, **kwargs):
is_ajax = request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
if request.method == 'POST' and is_ajax:
post = Comment.objects.get(pk=pk)
is_dislike = False
for dislike in post.dislikes.all():
if dislike == request.user:
is_dislike = True
break
if is_dislike:
post.dislikes.remove(request.user)
is_like = False
for like in post.likes.all():
if like == request.user:
is_like = True
break
if not is_like:
post.likes.add(request.user)
if is_like:
post.likes.remove(request.user)
all_post_likes = post.total_likes() (function from models)
return JsonResponse({"success": True, "like_count": all_post_likes, "id": pk}, status=200)
else:
return JsonResponse({"success": False}, status=400)
How to force AJAX to call in pk what I need? (Finally I found the solution, updated the final version of the code)
Remove the data-url and set the action attribute because when you click the submit button, by default this POST request will be sent to the current URL and you will receive the 405 status code, but if you set action this POST request will be sent to the like url:
<form id="like" method="POST" action="{% url 'store:like' comment.pk %}">
{% csrf_token %}
<input type="hidden" value="{{comment.pk}}" name="id">
<input type="hidden" name="next" value="{{ request.path }}">
<button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
<a class="btn btn-success" id="like"> Likes {{ comment.likes.all.count }}</a>
</button>
</form>
And in js you can get the URL like this
var endpoint = $(this).attr('action');
Update
All forms have the same "ID" and therefore only the first one is executed and the rest of the forms are not managed by Ajax, to solve this problem you can give different ID to the forms(This condition also applies to a tags):
<form id="like-{{comment.pk}}" method="POST" action="{% url 'store:like' comment.pk %}">
{% csrf_token %}
........
.....
<a class="btn btn-success" id="likes-count-{{comment.pk}}"> Likes {{ comment.likes.all.count }}</a>
And you can receive events this way and update the number of likes.
$(document).ready(function(){
$('[id^="like-"]').submit(function(e){
e.preventDefault();
var endpoint = $(this).attr('action');
var serializedData = $(this).serializeArray();
$.ajax({
url: endpoint,
method: "POST",
data: serializedData,
success: function(response){
$("#likes-count-" + serializedData[1].value).text("Likes "+response["like_count"]);
}
});
})
});
in the view you should return the new number of the likes :
return JsonResponse({"success": True, "like_count": new_like_count}, status=200)
And in the success function you can access to the new number by the response and now we change the text value of a tag to change the like count number.
^= means: selects elements that have the specified attribute with a
value beginning exactly with a given string. attributeStartsWith1
I think you should send the csrf_token with the ajax request add
headers: { "X-CSRFToken": token } to your ajax request, "token is the csrf_token" or add #csrf_exempt decorator to your function but it will keep your view unsafe against CSRF attacks.
you can find more info here https://docs.djangoproject.com/en/4.0/ref/csrf/
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)
Description
I am creating a like button for a comment that was made on a post.
When the user clicks on the like button, Django gives a NoReverseMatch and the page doesn't update automatically.
After I refresh the page, it shows that the like was updated in the database, despite the NoReverseMatch error.
What I tried?
Initially, the issue was with the csrf_tokens. So, I added the csrf_exempt decorator, so it is definitely not the issue.
I played a lot with the urls, and tried adding the comment id to the parameter, but it doesn't seem to help.
Question
How can I resolve the NoReverseMatch error and make it so that AJAX automatically changes the button from like to dislike.
urls.py
path('post/<int:pk>/like-comment', like_comment, name="like_comment")
view.py
#csrf_exempt
def like_comment(request, pk):
comment = get_object_or_404(Comment, id=request.POST.get('id'))
liked = False
if comment.likes.filter(id=request.user.id).exists():
comment.likes.remove(request.user)
liked = False
else:
comment.likes.add(request.user)
liked = True
context = {
'comment': comment,
'is_liked': liked,
'total_likes': comment.likes.count()
}
if request.is_ajax():
html = render_to_string('blog/like_section_comment.html', context, request=request)
return JsonResponse({'form': html})
main.js
$(document).ready(function(event){
$(document).on('click', '#like-comment', function(event){
event.preventDefault();
var pk1 = $(this).attr('value'); // comment id
var pk = $(this).attr('data-value'); // post id
$.ajax({
type: 'POST',
url: "like-comment",
data: {'id': pk1, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(response) {
$('#like-section-comment').html(response['form'])
console.log($('#like-section-comment').html(response['form']));
},
error: function(rs, e) {
console.log(rs.responseText);
},
});
});
});
comment_section.html
<article class="media content-section">
{% if user.is_authenticated %}
<form id="like-form-comment" method="post">
{% csrf_token %}
{% if request.user in comment.likes.all %}
<button type="submit" id="like-comment" name="comment_id" data-value="{{ comment.post.id }}" value="{{ comment.id }}" class="btn btn-danger btn-sm">
Dislike
</button>
{% else %}
<button type="submit" id="like-comment" name="comment_id" data-value="{{ comment.post.id }}" value="{{ comment.id }}" class="btn btn-primary btn-sm">
Like
</button>
{% endif %}
</form>
{% endif %}
You write url: "like-comment", how would that map to the correct url? Javascript does not know anything about Django. Try this instead:
In your html:
<button type="submit" id="like-comment" name="comment_id" data-value="{{ comment.post.id }}" value="{{ comment.id }}" data-submit-url="{% url 'like_comment' comment.id %}" class="btn btn-primary btn-sm">
Now in your JavaScript:
url: $(this).attr('data-submit-url'),
New to Django/Python/stackexchange...
I have a Jquery Datatable and I'm having difficulty passing along the value of a cell (called email_id) of the table as an HTTP parm to be used when handling a new form page.
The view is initially receiving the request correctly per the debug output:
WSGIRequest: GET '/main_app/makeTask/?csrfmiddlewaretoken=gDeTwaNfGNLO7cdMk1
B9gsdpcGYpKAyL&email_id=14d2a002852e1738'
It successfully extracts email_id with request.GET.get() on the first call (then proceeds to construct the form and render it) but it gets dropped when the form is being rendered. I.e., email_msg_id is extracted correctly on the GET but not on the subsequent POST.
Here is the extraction code along with the render statement. The problem is the 'email_id' is not propagating when I render the form so it cannot be used by view when it processes the form input.
email_msg_id = request.GET.get('email_id', "GET - no email_id")
...
return render(request, 'main_app/makeTask.html', {'form': form, 'email_id': email_msg_id})
Debug message:
<WSGIRequest: GET '/main_app/makeTask.html'>
Here are relevant sections of urls.py:
url(r'^makeTask', views.make_task, name='makeTask'),
My Jquery call (which appears to be working/passing along the email_ID correctly:
$('#make_task').click( function () {
alert( table.cell('.selected',4).data()+' Converted to Task');
// alert(table.cell('.selected',0).data()+' Make Task selected:');
$.ajax({
type: "GET",
url: "/main_app/makeTask/",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'email_id' : table.cell('.selected',4).data(),
},
success: makeTaskSuccess,
dataType: 'html'
});
function makeTaskSuccess(data)
{
alert('Convert email to task ');
}
} );
Here is the view (with unrelated stuff removed):
def make_task(request):
if request.method == "GET":
email_msg_id = request.GET.get('email_id', "GET - no email_id") # from the post dictionary of the request object passed in
else:
email_msg_id = request.POST.get('email_id', "POST - no email_id")
print "EMAIL_MSG_ID: ", email_msg_id, "METHOD: ", request.method
if request.method == 'POST':
form = MakeTaskForm(request.POST or None) # Create a form instance and populate it with the data
if form.is_valid():
# process and save to db
#...
return HttpResponseRedirect(reverse('main_app.views.index'))
else:
print form.errors
return
else:
form = MakeTaskForm()
return render(request, 'main_app/makeTask.html', {'form': form, 'email_id': email_msg_id})
#return render(request, 'main_app/makeTask.html', context_dict)
UPDATE: added relevant template code:
<form id="make_task_form" method="post" action="/main_app/makeTask/", method="Post">
{% csrf_token %}
<div class="row">
<div class="col-lg-8 col-xs-12">
<div class="form-group">
<label>Due Date:  </label>
<label class="radio-inline">
<input type="radio" name="due_selection" id="optionsRadiosInline1" value="TODAY" {{ form.due_selection }} Today
</label>
</div>
</div>
<div class="col-lg-4 col-xs-12">
<p>Specific Date: <input type="text" id="datepicker" {{ form.due_date }}</p>
</div>
</div>
<div class="row">
<div class="col-lg-4 col-xs-12">
<button type="submit" class="btn btn-info btn-med" id="btn-make-task">
Make Task
</button>
<i class="fa fa-trash-o fa-2x pull-right"></i>
</div>
</div>
</form>
From the code you posted I assume you're using GET for AJAX and POST in your form.
The form code you posted is wrong, access your form fields in your template like this (also get rid of the duplicated method attribute in <form>):
<form id="make_task_form" method="post" action="/main_app/makeTask/">
{% csrf_token %}
<div class="row">
<div class="col-lg-8 col-xs-12">
{{ form.due_selection }}
</div>
<div class="col-lg-4 col-xs-12">
{{ form.due_date }}
</div>
</div>
<div class="row">
<div class="col-lg-4 col-xs-12">
<button type="submit" class="btn btn-info btn-med" id="btn-make-task">
Make Task
</button>
<i class="fa fa-trash-o fa-2x pull-right"></i>
</div>
</div>
</form>
To use <input type="radio"> for the field due_selection you should specify the RadioSelect widget in your ModelForm class:
CHOICES = (('1', 'First',), ('2', 'Second',))
due_selection = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)