How can I render a Django render() response in an ajax function? - python

In my view, I return html if a form is valid:
if form_valid():
return render(request, 'home.html', context=context)
else:
return HttpResponse(status=204)
I'm submitting multiple forms via ajax and want to render the response, if the status code is not 204:
$.ajax({
data: $(this).serialize(),
type: $(this).attr('method'),
url: $(this).attr('action'),
success: function (response, status, jqXHR) {
if (jqXHR.status !== 204) {
document.write(response); // this works, but I lose some functionality. Some buttons stop working.
// How can I render the response correctly?
}
}
});
EDIT: The buttons that don't work anymore. It's a form using bootstrap collapse with some workarounds
main.html
<form action="." method="post">
{% csrf_token %}
{% include 'collapse_form.html' with form=mysql_form %}
{% include 'collapse_form.html' with form=postgres_form %}
{% include 'collapse_form.html' with form=sqlite_form %}
<input type="hidden" name="databases-section"/>
<a id="download-btn" class="btn btn-success">Download</a>
<a id="hidden-download-link" class="js-scroll-trigger" href="#download"></a>
</form>
collapse_form.html
{% load crispy_forms_filters %}
<div class="collapseForm">
<div class="collapseFormButton">
<button id="collapseBtn" class="d-none btn btn-check p-0 m-0" type="button" data-toggle="collapse"
data-target="#{{ form.prefix }}-collapseTarget"
aria-expanded="false">
</button>
{{ form.active|as_crispy_field }}
</div>
<div class="collapse" id="{{ form.prefix }}-collapseTarget">
<div class="card card-body">
{{ form|as_crispy_errors }}
{% for field in form.visible_fields|slice:"1:" %}
{{ field|as_crispy_field }}
{% endfor %}
</div>
</div>
</div>
js
$('.collapseFormButton').find("input").change(function () {
toggleCollapseForm()
});
function toggleCollapseForm() {
let collapseForm = $(".collapseForm");
collapseForm.each(function () {
let collapseCheckbox = $(this).find("input[id*='active']");
let collapseTarget = $(this).find("div[id*='collapseTarget']");
if (collapseCheckbox.is(':checked')) {
collapseTarget.collapse('show');
} else {
collapseTarget.collapse('hide');
}
});
}

instead of document.write there is more functions like append, you could use that to append the rendered htmls cooming to ajax as a response to a specific element selected by id to avoid any problem like lossing the documents html tags(html, body, header) .
and there is big point!
javascript has different behavior regarding dynamically added elements!
to be more clear, I think js and/or jQuery has different behavior for keword this which doesn't work on dynamically added elements. so you sould use another query-selector like $(document).on('click', element, function (){....} and this would work as expected.

Related

How do I prevent Jinja links from nesting?

I have a FastAPI app which takes any endpoint foo/{page} and uses the page variable to decide what template to render.
It looks kind of like this:
#api_router.get("/foo/{page}/")
def foo_router(page: str, request: Request):
return TEMPLATES.TemplateResponse(
f"{page}.html",
{
"request": request,
'page_title':page,
'schema':schema[page],
}
)
The templates contain buttons that are created from a schema that has button text and link destination as key/value pairs.
Something like this:
schema = {
"index": {
"Look At Our Menu":"foo/menu",
"Find Our Store":"foo/location",
}
}
And the Jinja template looks like this:
<div class="form_wrapper">
{% for k in buttons %}
<a href="foo/{{buttons[k]}}/">
<div class="button_text full_button">
{{ k.upper() }}
</div>
</a>
{% endfor %}
</div>
My problem is if I have a link within foo/menu/ that I want to direct to foo/drinks, it tries to load foo/menu/foo/drinks. How do I reset the path so it doesn't nest?
OK I figured it out using request to get the base path
<div class="form_wrapper">
{% for k in buttons %}
<a href="{{ request.base_url.url + 'foo/' + buttons[k] }}/">
<div class="button_text full_button">
{{ k.upper() }}
</div>
</a>
{% endfor %}
</div>
I'm not sure this is the best way but it does work so far!

redirect() / render_template() enters the route once again when called from the same route in Flask

I have created a wishlist page which lists the items wishlisted by a user. The user can remove any item from the wishlist by clicking on the Remove button. On clicking the remove button, the HTML <form> submits it to the back-end application. The back-end application then removes the item from the wishlist database and redirects back to the same page using return redirect(url_for('wishlist')).
The problem I am facing is that if the user goes back after removing the item from the wishlist he has to go back twice to reach the page from where the user came from. This is caused due to the redirect that I am performing after removing the item which is necessary to show the updated wishlist.
I have also tried render_template() instead of redirect() but it is also causing the same problem.
Code for back-end:
#app.route('/wishlist/',methods=['GET','POST'])
#login_required
def wishlist():
userid=current_user.get_id()
if request.method=='POST':
toRemove=request.form['remove']
deleteWish=session.query(Wishlist).filter_by(userId=userid,productId=toRemove).one()
session.delete(deleteWish)
session.commit()
return redirect(url_for('wishlist'))
subquery=session.query(Wishlist.productId).filter(Wishlist.userId==userid).subquery()
wishes=session.query(Products).filter(Products.id.in_(subquery))
return render_template("wishlist.html",wishes=wishes)
HTML:
<html>
<body>
{% for wish in wishes %}
<img src={{wish.image_path}} width="150" height="200">
</br>
{{wish.material}}
{{wish.productType}}
</br>
{{wish.price}}
</br>
<form action="{{url_for('wishlist')}}" method="POST" target="_self">
<button name="remove" type="submit" value="{{wish.id}}">Remove</button>
</form>
</br>
{% endfor %}
</body>
</html>
Please suggest me a way to prevent this.
You may want to create a different end point for deleting wishes. This endpoint then redirects to your wish list once deletion is done.
FLASK
#app.route('/wishlist/',methods=['GET','POST'])
#login_required
userid=current_user.get_id()
def wishlist():
subquery=session.query(Wishlist.productId).filter(Wishlist.userId==userid).subquery()
wishes=session.query(Products).filter(Products.id.in_(subquery))
return render_template("wishlist.html")
#app.route('/deletewish', methods = ['GET', 'POST']
def deletewish():
if request.method=='POST':
toRemove=request.form['wish_delete_id']
deleteWish=...
session.delete(deleteWish)
session.commit()
return redirect(url_for('wishlist'))
HTML
<html>
<body>
{% for wish in wishes %}
<img src={{wish.image_path}} width="150" height="200">
</br>
{{wish.material}}
{{wish.productType}}
</br>
{{wish.price}}
</br>
<form method="POST" target="_self">
<button class="remove_wish" type="submit" value={{wish.id}}>Remove</button>
</form>
</br>
{% endfor %}`
<script src='path_to_jquery.js'></script>
<script src='path_to_deletewish.js'></script>
</body>
</html>
JS //deletewish.js content
<script>
$(document).ready(function() {
$('.remove_wish').click(function (event) {
event.preventDefault();
$.ajax({
data : {
wish_delete_id : $(this).val();
},
type : 'POST',
url : '/deletewish',
success: function (data) {
location.reload();
},
error: function (e) {
alert('something went wrong')
}
});
});
})
</script>

Progress Bar Upload to S3 Django

I would like to show an upload progress bar for S3 uploads from a Django site. Currently without the progress bar being manipulated by JQuery, the uploads are working direct to S3. Once I try to implement the progress bar using JQuery, the upload form still functions as far accepting input and the file, but will not communicate the file to S3.
Here is the upload view:
from .forms import UploadForm
def upload(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('upload')
else:
form = UploadForm()
context = {
'form': form
}
return render(request, 'content/upload.html', context)
Here is the HTML:
{% extends "about/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
{% block navbar %}{% endblock %}
<div class="site-section mb-5">
<div class="container">
<div class="form-register">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<legend>Upload Content</legend>
<div class="form-group">
{{ form | crispy }}
</div>
<button class="btn btn-outline-info" type="submit">Upload</button>
</form>
<div class="progress">
<div id="progressBar" class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
0%
</div>
</div>
</div>
</div>
{% endblock %}
Here is the JQuery:
$(document).ready(function() {
$('form').on('submit', function(event) {
event.preventDefault();
var formData = new FormData($('form')[0]);
$.ajax({
xhr : function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
console.log('Bytes Loaded: ' + e.loaded);
console.log('Total Size: ' + e.total);
console.log('Percentage Uploaded: ' + (e.loaded / e.total))
var percent = Math.round((e.loaded / e.total) * 100);
$('#progressBar').attr('aria-valuenow', percent).css('width', percent + '%').text(percent + '%');
}
});
return xhr;
},
type : 'POST',
url : '/upload',
data : formData,
processData : false,
contentType : false,
success : function() {
alert('File uploaded!');
}
});
});
});
I assume there is something I need to change in the JQuery since I am overriding the default submit button action, but I am not sure what exactly to change in order to make sure the file goes to S3 via django-storages and boto3, the packages being used to communicate with S3.

Flask - Update the page content without redirection in Ajax/JQuery

I am developing a website that allows a user to follow tourist attractions. When a user clicks the follow button, the button will change to unfollow. The page is then refreshed due to redirection. But what I want is to make this page update but not reload. I got some relevant information from the Internet. The solution is to use Ajax or JQuery, but I still have no idea how to solve this problem.
View
#main.route('/place/<tourist_attraction_name>')
def place_detail(tourist_attraction_name):
place = Place.query.filter_by(tourist_attraction_name=tourist_attraction_name.first()
if place is None:
abort(404)
return render_template('main/detail/place_detail.html', place=place)
#main.route('/follow/<tourist_attraction_name>', methods=['GET', 'POST'])
#login_required
def follow(tourist_attraction_name):
place = Place.query.filter_by(tourist_attraction_name=tourist_attraction_name).first()
if place is None:
flash('Invalid place name.')
return redirect(url_for('.index'))
tourist_attraction_name=place.tourist_attraction_name)
current_user.follow(place)
flash('You are now following %s.' % tourist_attraction_name)
return redirect(url_for('.place_detail', tourist_attraction_name=place.tourist_attraction_name)
#main.route('/unfollow/<tourist_attraction_name>', methods=['GET', 'POST'])
#login_required
def follow(tourist_attraction_name):
place = Place.query.filter_by(tourist_attraction_name=tourist_attraction_name).first()
if place is None:
flash('Invalid place name.')
return redirect(url_for('.index'))
tourist_attraction_name=place.tourist_attraction_name)
current_user.unfollow(place)
flash('You are now following %s.' % tourist_attraction_name)
return redirect(url_for('.place_detail', tourist_attraction_name=place.tourist_attraction_name)
place_detail.html
<h3>{{ place.tourist_attraction_name }}</h3>
<p>{% if current_user.is_authenticated %}
{% if not current_user.is_following(place) %}
<a href="{{ url_for('.follow', tourist_attraction_name=tourist_attraction_name) }}"
class="btn btn-primary">Add</a>
{% else %}
<a href="{{ url_for('.unfollow',tourist_attraction_name=tourist_attraction_name) }}"
class="btn btn-default">Remove</a>
{% endif %}
<b><font color="navy"> Followers: </font></b> <span class="badge">{{ place.followers.count() }}</span>
{% endif %}</p>
Additional issue:
{%if condition%} only can be rendered once
<h3>{{ place.tourist_attraction_name }}</h3>
<p>{% if current_user.is_authenticated %}
{% if not current_user.is_following(place) %}
<button id="follow" onclick="ajax_follow('{{place.tourist_attraction_name}}')" class="btn btn-primary">
{% else %}
<button id="unfollow" onclick="ajax_unfollow('{{place.tourist_attraction_name}}')" class="btn btn-primary">
class="btn btn-default">Remove</a>
{% endif %}
<b><font color="navy"> Followers: </font></b> <span class="badge">{{ place.followers.count() }}</span>
{% endif %}</p>
JS
<button id="follow" onclick="ajax_follow('{{place.tourist_attraction_name}}')" class="btn btn-primary"> <script>
function ajax_follow(name) {
$.ajax({
url: "/follow/" + name, success: function (result) {
if (result == 'success') {
$("#follow").text("Remove");
}
}
});
}
function ajax_unfollow(name) {
$.ajax({
url: "/unfollow/" + name, success: function (result) {
if (result == 'success') {
$("#unfollow").text("Add");
}
}
});
}
</script>
You need to change your follow/unfollow routes so that instead of returning a flask redirect object, they return a response to an AJAX request. You'll also need to change how your buttons work so that you are making an AJAX request, rather than pointing to a link.
Let's start with the buttons. Right now they're just links, what you need is for them to initialize a piece of JavaScript which contains your AJAX request, which means changing this:
Add
to something more like this:
<button id="follow" onclick="ajax_follow('{{tourist_attraction_name}}')">Add</button>
and adding the relevant script like this:
<script>
function ajax_follow(name) {
$.ajax({url: "/follow/" + name, success: function(result){
if (result == 'success') {
$("#follow").text("Remove");
$("#follow").attr("onclick", "ajax_unfollow('{{tourist_attraction_name}}')"
}
}});
}
</script>
This will execute your flask '/follow/' route with tourist_attraction_name as the attraction name and change the content of your button without reloading the page. Note the need to have jQuery imported first for ajax to work properly. You still have a problem with the flask route though, in that it isn't returning 'success', but a flask redirect object. To fix this, change:
return redirect(url_for('.place_detail', tourist_attraction_name=place.tourist_attraction_name)
to:
return "success", 200
To finish, you should add error handling and do something similar for unfollowing. Id recommend handling errors by returning a non-success descriptive error text if your flask route logic goes bad and then using javascript to alert that error text.

HTML/Django/Jinja/Python : How to post a fixed value back

This is a HTML template that displays all of the proposals in a database (passed through views.py as a list in the dictionary parameter). I then use a jinja for-loop to go through all the proposals in the database and display their attributes.
How can I Post-request the {{ proposal.id }} back to my python code when the "Learn more" button is clicked? I need this to allow me to display the corresponding values in my other html template.
Sorry if this is a basic question, i'm a high school student and extremely new to django! Thanks alot in advance!
{% block body %}
{% for proposal in proposals %}
<div class="jumbotron">
<h2> Proposal : {{ proposal.title }} </h2>
<h4> Status : {{ proposal.status }} </h4>
<h4> Out of --- Votes: </h4>
<div class="progress">
<div class="progress-bar progress-bar-success" style="width: {{ proposal.votes_for }}%">
<span class="sr-only">35% Complete (success)</span>
{{ proposal.votes_for }}% For
</div>
<div class="progress-bar progress-bar-danger" style="width: {{ proposal.votes_against }}%">
<span class="sr-only">10% Complete (danger)</span>
{{ proposal.votes_against }}% Against
</div>
</div>
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
</div>
If you just want to go to the Proposal details you should definitely look to a class-based DetailView.
You can make it with AJAX request or you can make it with form. For the both of types you should have a View to catch it.
HTML Form:
In your template you should have:
<form id="formId" method="post" action="{% url 'catch-proposal' %}">
{% csrf_token %}
<input type="hidden" name="proposal_id" value="{{ proposal.id }}"/>
<p><button type="submit" class="btn btn-primary btn-lg">Learn more</a></p>
<!-- <input type="submit" class="btn btn-primary btn-lg" value="Learn more"/> -->
</form>
It will go to your View from urls.py:
url(r'^post/for/proposal/$', catch_proposal, name='catch-proposal'),
# if your view class-based
# url(r'^post/for/proposal/$', CatchProposal.as_view(), name='catch-proposal')
Then in your view you will catch POST data:
def catch_proposal(request):
if request.method == "POST":
print(request.POST) # have a look for your post params
return reverse_lazy('index') # your response, you can make it on your own
AJAX:
Check it! AJAX and Django
Page uses AJAX without any HTML form
A page makes a POST request via AJAX, and the page does not have an HTML form with a csrf_token that would cause the required CSRF cookie to be sent.
Solution: use ensure_csrf_cookie() on the view that sends the page.
In your scripts define:
function sendPost(proposalId) {
$.ajax({
url: '{% url 'catch-proposal' %}', // or just /ajax/catch/proposal/
method : "POST",
data: {
// your data to send key => val
'id': proposalId
},
dataType: 'json', // it can be xml, json, script, html
success: function (result) {
// Do something if your request was successful (code=200)
// All response data stored in result
console.log(result)
},
error : function(xhr,errmsg,err) {
// Error case
console.log(xhr.status + ": " + xhr.responseText);
}
});
}
For your Learn More button:
<p><button class="btn btn-primary btn-lg" role="button" onclick="sendPost({{ proposal.id }})">Learn more</button></p>
And you will catch it in your View:
#ensure_csrf_cookie # Since you sending POST request without form tag
def catch_proposal(request):
response_data = {} # your response
if request.method == 'POST':
# your post request
if 'id' not in request.POST: # check the param from POST
# Provide error message
response_data['error_message'] = "Can't find ID in POST params..."
else:
# Do whatever
proposal_id = int(request.POST.get('id'))
try:
proposal = Proposal.objects.get(id=transport_id)
response_data['success'] = True
except Proposal.DoesNotExist:
response_data['success'] = False
return JsonResponse(response_data)
else:
response_data = {
'error_message': 'Something is going very strange and wrong...'
}
return JsonResponse(response_data)
Adding created View to urls.py:
from .views import catch_proposal # or yourapp.views
....
url(r'^ajax/catch/proposal/$', catch_proposal, name='catch_proposal'),
....

Categories

Resources