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

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'),
....

Related

Django DetailView + Delete Item from Ajax Form

So here is my issue.
The user can create a Client using a form.
Once created, the created client appears in a DetaiLView
In this DetailView, I put a form to add specific events related to the client, thanks to an ajaxified form so that new events appear without refreshing the page. So far everything is okay.
Now I would like to allow the user to delete one event if he wants to.
So I have done the HTML/AJAX parts. However, since it is a DetaiLView, I am having troubles to delete a specific event.
Here is my Views.py :
class CollectionDetail(LoginRequiredMixin, FormMixin, DetailView):
model = Collection
form_class = ImportantFactsForm
template_name = 'taskflow/collection_detail.html'
success_url = None
def get_context_data(self, **kwargs):
context = super(CollectionDetail, self).get_context_data(**kwargs)
context['important_facts'] = ImportantFactsForm()
return context
def post(self, request, *args, **kwargs):
form = ImportantFactsForm(request.POST)
tgt = self.get_object()
if form.is_valid():
new_fact = form.save(commit=False)
new_fact.collection_important_facts = tgt
new_fact.save()
return JsonResponse({'new_fact': model_to_dict(new_fact)}, status=200)
else:
return redirect('taskflow:collection_all')
#here I need to implement the delete function.
Here is my collection_detail.html
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="Canal1">
<form class="justify-content-center mx-3" id="createFactForm" method="post" data-url="{% url 'taskflow:collection_detail' pk=object.pk %}">
{% csrf_token %}
<div class="form-group">
{{important_facts.doc_ref|as_crispy_field}}
</div>
<div class="form-group">
{{important_facts.note|as_crispy_field}}
</div>
<button type="submit" class="btn btn-outline-success" id="createButton">Enregistrer</button>
</form>
{% if object.has_important_facts %}
{% for fact in object.has_important_facts.all %}
<div class="card mb-1" id="factCard" data-id="{{fact.id}}" data-url="{% url 'taskflow:collection_detail' pk=object.pk %}">
<div class="card-body">
{{fact.note}}
<form method="post">
{% csrf_token %}
<button type="submit" formmethod="post" name="DeleteFactButton" id="DeleteButtonFact" class="btn btn-danger float-right" data-id="{{fact.id}}">Delete</button>
</form>
</div>
</div>
{% endfor %}
{% endif %}
</div>
Here is my facts.js file
var csrfToken = $("input[name=csrfmiddlewaretoken]").val();
$(document).ready(function(){
$("#createFactForm").on('submit', function(evt) {
evt.preventDefault();
var CreateFactForm = $("#createFactForm");
$.ajax({
url: CreateFactForm.data('url'),
data: CreateFactForm.serialize(),
method: 'POST',
success: function(response) {
console.log(response)
$("#Canal1").append(
'<div class="card mb-1" id="taskCard" data-id="' + response.new_fact.id + '">'+
' <div class="card-body">'+
response.new_fact.note+
' <button type="button" class="btn btn-danger float-right" name="DeleteFactButton" id="DeleteButtonFact" data-id="' + response.new_fact.id + '">'+
' Supprimer'+
' </button>'+
' </div>'+
'</div>');
}
})
$("#createFactForm")[0].reset();
});
$("#DeleteButtonFact").on('click', function(response) {
console.log(response)
var dataID = $(this).data('id');
$.ajax({
url: $("#DeleteButtonFact").data('url'),
data:{
csrfmiddlewaretoken: csrfToken,
id: dataID
},
method: 'POST',
dataType: 'json',
success: function() {
$('#factCard[data-id="' + dataID + '"]').remove();
}
})
});
});
Thanks for your help !
You can use deleteView, add another url path and link it to class which inherits deleteView
also for you can check the response in ajax if an error occurred and do action accordingly

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

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.

how to add ajax to django webpage

I am working on a django application. This application hold two forms. In the first form (image_form) the user can upload an image. In the second form (image_description) the user can fill some description about the image. when the image is uploaded in the first form, an image classifier runs on the image tries to fill parts of the image_description form. Once the second form is submitted the item is displayed in a new page (item_list). This is the urlpatterns for the application.
urls.py
urlpatterns = [
path('', views.index, name='home'),
path('accounts/', include('django.contrib.auth.urls')),
path('signup/', views.signup, name='signup'),
path('items/', views.item_list, name='item_list'),
path('items/upload/description/', views.upload_item, name='upload_item'),
path('items/<int:pk>/', views.delete_item, name='delete_item'),
path('items/upload/image_classification/', views.image_classification, name='image_classification'),
]
here item_list is the page where all items are displayed. This page is displayed once the image_description form is submitted.
upload_item page contains both the forms.
image_classification runs when upload image button in the first form is clicked. This happens in the upload_item page.
views.py
def item_list(request):
items = Item.objects.all()
return render(request, 'item_list.html', {'items':items})
def upload_item(request):
if request.method == 'POST':
form_des = ItemForm(request.POST, request.FILES)
if form_des.is_valid():
form_des.save()
return redirect('item_list')
else:
form_des = ItemForm()
return render(request, 'upload_item.html', {'form_des': form_des})
def image_classification(request):
form_des = ItemForm()
if request.method == 'POST':
if 'file' in request.FILES:
handle_uploaded_file(request.FILES['file'], str(request.FILES['file']))
img = np.expand_dims(cv2.resize(cv2.imread(os.path.join('./media/item/img/', str(request.FILES['file']))), (170, 100)), axis=0)
cat_prediction = cat_classifier.predict_classes(img)[0]
pattern_prediction = pat_classifier.predict_classes(img)[0]
form_des.fields['title'].widget.attrs['value'] = cat_prediction
form_des.fields['pattern'].widget.attrs['value'] = pattern_prediction
form_des.fields['user'].widget.attrs['value'] = request.user
form_des.fields['user'].widget.attrs['readonly'] = True
return render(request, 'upload_item.html', {'form_des': form_des})
else:
return redirect('upload_item')
upload_item.html template
<div class="container">
<div class="row justify-content-center">
<div class="col-7">
<center>
<h2>Upload image</h2>
<!-- <div class="row justify-content-center"> -->
<div class="upload-btn-wrapper">
<form action="{% url 'image_classification' %}" method="POST" enctype="multipart/form-data" data-url="image_classification/" class="my_form">
{% csrf_token %}
<input type="file" name="file" id="file" class="inputfile" multiple/>
<label for="file" class="btn btn-outline-dark btn-lg mt-5 select">Choose a file</label>
<input class='btn btn-primary btn-lg btn-block upload_image_button' type="submit" value="Upload image" disabled/>
</form>
</div>
<!-- </div> -->
<center>
<p class='font-weight-bold mt-5 mb-5 text-danger'>Step: 1 of 2</p>
</center>
<div class="separation"></div>
<h2>Item description</h2>
</center>
<div class="card mb-2 mt-3">
<div class="card-body">
<form method="post" enctype="multipart/form-data" action="{% url 'upload_item' %}" id='item_des'>
{% csrf_token %}
{{form_des.title|as_crispy_field}}
{{form_des.pattern|as_crispy_field}}
{{form_des.color|as_crispy_field}}
{{form_des.user|as_crispy_field}}
<button type="submit" class='btn btn-primary btn-lg btn-block save_btn'>Save item</button>
</form>
</div>
</div>
<center>
<p class='font-weight-bold mt-2 mb-5 text-danger'>Step: 2 of 2</p>
</center>
</div>
</div>
</div>
My problem is when the upload image button is clicked in the first form the url changes from items/upload/description/ to items/upload/image_classification/ and the page reloads with the auto filled sections in the second form.
I want to use AJAX to auto-fill the second form without reloading the page but am not sure how to do it. I followed a few tutorials but am not able to achieve this.
Please help me
Thank you
[EDIT1]
Based on Adeel Siddiqui's answer, I made a few changes to views.py
views.py
def image_classification(request):
form_des = ItemForm()
user =str(request.user)
if request.method == 'POST':
if 'file' in request.FILES:
handle_uploaded_file(request.FILES['file'], str(request.FILES['file']))
img = np.expand_dims(cv2.resize(cv2.imread(os.path.join('./media/item/img/', str(request.FILES['file']))), (170, 100)), axis=0)
cat_prediction = cat_classifier.predict_classes(img)[0]
pattern_prediction = pat_classifier.predict_classes(img)[0]
form_des.fields['title'].widget.attrs['value'] = cat_prediction
form_des.fields['pattern'].widget.attrs['value'] = pattern_prediction
form_des.fields['user'].widget.attrs['value'] = request.user
form_des.fields['user'].widget.attrs['readonly'] = True
context = {
'tops_prediction' :tops_prediction,
'pattern_prediction':pattern_prediction,
'user' :user,
}
return HttpResponse(json.dumps(context))
else:
return redirect('upload_item')
How do I access this context from fillImageDescriptionText in jquery and auto-fill the second form in the upload_item page?
[EDIT-2] screenshot of the output after using the updated answer
You could prevent the default event happening when the form is submitted. Suppose your image classification form has the id image-classify-form, then:
$("#image-classify-form").submit(function(event) {
event.preventDefault();
imageClassifyAjax();
}
where your imageClassifyAjax function does this (i previously forgot to create the FormData object on line 2 which was causing the POST request to fail):
function imageClassifyAjax() {
let $form = $("#image-classify-form");
let form_data = new FormData($form[0]);
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: form_data,
processData: false,
contentType: false,
dataType: 'json',
success: function (data) {
fillImageDescriptionText(data);
},
error: function (xhr) {
console.log("Something went wrong: " + xhr.responseText);
}
});
}
and fillImageDescriptionText uses the json data returned by the view to fill up the form for the image description. So you have to modify what your view does on the POST method. You have to return an HttpResponse instead of doing a render template:
import json
from django.http import HttpResponse
def image_classification(request):
form_des = ItemForm()
if request.method == 'POST':
...
...
return HttpResponse(json.dumps({'title': cat_prediction, 'pattern': pattern_prediction, 'user': request.user.email}))
fillImageDescriptionText receives this json object as input, so you can basically:
function fillImageDescriptionText(data) {
$("#item_des #id_title").val(data.title);
$("#item_des #id_pattern").val(data.pattern);
$("#item_des #id_user").val(data.user);
$("#item_des #id_user").prop('readonly', true);
}
where id_ is the id generated for the elements by django crispy forms or whatever package you are using for the forms. It is mostly prefixed with an "id_". You can check it up by inspecting elements from the browser.

Django : SQLite Database is Locked when I build like feature on my home page

I am implementing Like functionality in Web application. Idea is Simple to List number of Post(Blogs) on Home page and Add a Like Button to each post(Blog). It works fine when I build it with normal
<form action='{% url target %}' method='POST'>
But when I implemented this with AJAX call It only allows me to like or dislike a particular post(blog) a single time.i.e One I liked a Post for first time it works, also when I dislike the same Post It works fine, But when I again Like that Post it throws an django.db.utils.OperationalError: database is locked
Also, when I like same post multiple(4 to 5) times it respond in a weird way. I goes in Loop of liking and dislikig post.
like_section.html
<form id="like-form{{ post.id }}">
{% csrf_token %}
<button type="submit" id="{{ post.id }}btn" name="like" value="{{ post.id }}" class="btn upvote">Like</button>
<script type="text/javascript">
{% for like in post.likes.all %}
{% if like != user %}
dislikingPost("{{ post.id }}btn");
{% else %}
likingPost("{{ post.id }}btn");
{% endif %}
{% endfor %}
$(document).ready(function(event){
$(document).on('click', '#{{ post.id }}btn', function(event){
event.preventDefault();
pk = $(this).attr('value');
$.ajax({
type: 'POST',
url: '{% url "like_post" %}',
data: {
'id': pk,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success:function(response){
$('#like-form{{ post.id }}').html(response['form'])
// $('#{{ post.id }}btn').style.color = 'green';
}, error: function(er, e){
console.log(er.responseText);
}
});
});
});
</script>
</form>
views.py:
def like_post(request):
all_posts = Posts.objects.all()
print("Insisde Like Post")
print('ID coming from form is', request.POST.get('id'))
post = get_object_or_404(Posts, id=request.POST.get('id')) # for AJAX call
context = {
'all_posts': all_posts,
'post': post
}
if post.likes.filter(id=request.user.id).exists():
post.likes.remove(request.user) # Liking The Post
print("DisLiking the post")
else:
post.likes.add(request.user)
print("Liking the post")
if request.is_ajax():
print('Hey its an AJAX calls') # TEsting AJAX request
html = render_to_string('like_section.html', context, request=request)
return JsonResponse({'form': html})
Note:
1. I am beginner in AJAX.
2. I know SQLite can not handle redundant calls, But why it was working before without AJAX calls.
3. I am not providing models.py and complete template of home page and I don't think they are required.
The Problem was not with the Django code. It was ajax call which was triggering Database more then one time on a single Button Click.
Adding event.stopImmediatePropagation(); below event.preventDefault(); in ajax call works perfect

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.

Categories

Resources