Django: CSRF verification fail despite token being provided - html

I get 403 error due to csrf_token verification failure in spite of token explicit declaration in an ajax call. I for my data dictionary in the same manner in other functions and it works just great.
Here is JS:
$(document).on("click", ".update-cleaning", function(event){
event.preventDefault();
var input_field_name = $(this).attr('name')
var obj_id = $(this).attr('value')
var model_name = $(this).attr('data-model')
var field_name = $(this).attr('data-field')
var value = $("#" + input_field_name + obj_id).val();
if (!value) {
alert('Fill the field!')
}
else {
$.ajax({
url: "{% url 'ajax_update_cleaning' %}",
type: "POST",
data: {'object_id': obj_id, 'model_name': model_name, 'field_name': field_name, 'value': value, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
})
.done(function(response){
console.log(response);
})
}
});
My html form is in a popover which is toggled by a click on <td> and looks like this:
<td class="text-md-center with-popover" name="frequency" value="{{cleaning.id}}">
{{ cleaning.frequency }}
<div id="frequency{{cleaning.id}}" style="display: none">
<form method="POST">
<label for="cleaning_frequency">Frequency: </label>
<input id="cleaning_frequency{{cleaning.id}}" type="number" name="cleaning" value="{{cleaning.frequency}}">
<button type="submit" class="btn btn-success btn-sm update-cleaning" name="cleaning_frequency" data-model="Cleaning" data-field="frequency" value="{{cleaning.id}}"> Change </button>
</form>
</div>
</td>
Could you give me some ideas? Thank you.

In any template that uses a POST form, use the csrf_token tag inside the element if the form is for an internal URL, e.g.:
<form method="post">{% csrf_token %}
Read more in the Django Documentation

Related

Laravel pass id to my sql on button click from view

I am trying to activate or deactivate the products for a form using $product->status
The active button shows if $product->status is 0 and
The deactive button shows if $product->status is 1
I want to toggle the value of $product->status in the mysql database every time I click on the button
<form action="{{route('invetory.create')}}" method="POST">
#csrf
<table class="table table-bordered" id="dynamicTable">
<tr>
<th>item</th>
<th>tax</th>
<th>Action</th>
</tr>
#forelse($products as $product)
<input type="text" name="item" value="{{$product->id}}
class="form-control" hidden />
<td>
<input type="text" name="item" value="{{$product->item}}
class="form-control" />
</td>
<td>
<input type="text" name="tax" value="{{$product->tax}}
class="form-control" />
</td>
#if($product->status =='0')
<td>
<button type="button" data-id="{{ $product->id }}" class="btn btn-success remove-tr active_btn">active</button>
</td>
#else
<td>
<button type="button" data-id="{{ $product->id }}" class="btn btn-danger remove-tr deactive_btn">Deactive</button>
</td>
#endif
</table>
</form>
here i have given the route i have used
web.php
Route::post('/update', 'App\Http\Controllers\InventoryController#update')>name('invetory.update');
here i have added the controler i have used
InventoryController.php
public function update(Request $REQUEST){
dd($REQUEST->all());
Inventory::update( $REQUEST->invetory as $key => $value);
return redirect()->action([InventoryController::class, 'index']);
}
i am geting 500 error when i click button
You can achieve this using POST request which will refresh the page each time you toggle a product or you can use AJAX to do the change asynchronously. Using Javascript and AJAX would be the preferred way so you don't lose selected filters, pagination etc.
You don't need external packages to implement that, you can use JavaScript's fetch method. Also, instead of having 2 separate functions and routes, I would suggest having one route that would toggle the product's status, i.e. if it is active, make it inactive and vice versa. That method by definition should be a POST request, by I prefer doing GET requests for this in order to avoid CSRF protection and use middleware to protect the request.
Here is the complete code.
Register a web route that toggles the state inside web.php
Route::get('projects/toggle', [ProjectController::class, 'toggle'])->name('projects.toggle');
Implement the toggle method in ProjectController.php
public function toggle(Request $request) {
$project = Project::find($request->project_id);
$project->status = !$project->status;
$project->save();
return response()->json(['status' => (int) $project->status]);
}
Notice that I am returning a json response which includes the new project status. We will use this in the JavaScript code to dinamically update the column where the status is shown.
Finally, in the blade file, when iterating through the projects, the button click calls a function that will do the AJAX request. Notice that I am also adding an id attribute to the columns that contains the status so I can access it dinamically in order to update it.
#foreach($projects as $project)
<tr>
<td>{{$project->title}}</td>
<td id="project-status-{{$project->id}}">{{$project->status}}</td>
<td><button onClick="toggleStatus('{{$project->id}}')">Toggle</button></td>
</tr>
#endforeach
In this same file, we add the following JavaScript code. It accepts project_id as parameter which is passed from the button click, makes the ajax request to backend which updates the status and then updates the appropriate DOM element to show the new status.
function toggleStatus(project_id) {
fetch("{{ route('projects.toggle') }}?project_id=" + project_id)
.then(response => response.json())
.then(response => {
document.querySelector("#project-status-" + project_id).innerHTML = response.status;
})
}
As I mentioned, you can use multiple options in the JavaScript part. Instead of calling a function you can register an event listener to each button, but this approach with function call is a bit quicker. Also, I am passing the project_id as GET parameter, you can define the route to contain it as route parameter, but then you'll need to do some string replacements in order to do in dinamically in JavaScript. All in all, the proposed is a good solution that will serve your purpose.
p.s. For stuff like this, LiveWire is a perfect fit.
Using dd (e.g. in your controller) will throw a 500 error. It literally stands for "dump and die".
check you routes in form use{{route('invetory.create')}}
and in routes you given inventory.update
public function Stauts(Request $request)
{
$product= Product::findOrFail($request->id);
$product->active == 1 ? $product->active = 0 : $product->active = 1 ;
$product->update();
return response()->json(['status' => true,'msg' => 'Staut updated']);
}
in blade use ajax
<script>
$(document).on('click', '.status-product', function (e) {
e.preventDefault();
var product_id = $(this).data('id');
var url ="{{ route('product.status') }}";
$.ajax({
type: "post",
url: url,
data: {
'_token': "{{csrf_token()}}",
'id': product_id
},
success: function (data) {
if (data.status == true) {
$('#deactive_ajax').show();}
}
})
})
</script>
Route::post('product/stauts/', [productController::class,'Stauts'])->name('product.Stauts');
First of all
Using a form with tables is not ideal and some browsers already made changes to prevent that.
Secondly
The best way is as DCodeMania said, the ajax request is the best way to solve this, I'll just modify his answer a bit and use Patch instead of PUT, so it'll look like this:
$(document).on('click', '.active_btn', function(e) {
e.preventDefault();
let id = $(this).data('id');
$.ajax({
url: '{{ route("products.update") }}',
method: 'PATCH',
data: {
id: id,
_token: '{{ csrf_token() }}'
},
success: function(response) {
console.log(response);
}
});
});
and you'll only be needing one button so no need to make the check for $product->status he added, just a single button for the toggle will make your code cleaner.
As for using PATCH instead of PUT, because you're only updating one single column and not the whole thing getting updated, and no need for the status parameter, you'll just reverse what's inside the database
$product = Product::find($request->id);
Product::where('id', $product->id)->update([
'status' => $product->status ? 0 : 1,
]);
You'll also need one button with different text based on status
like this
<td>
<button type="button" data-id="{{ $product->id }}" class="btn btn-success remove-tr active_btn">{{ $product->status == 1 ? 'deactivate' : 'activate' }}</button>
</td>
I did it this way and it works for me
First in view does this
<td>
#if ($producto->estado == '1')
<a href="{{ url('/status-update-producto', $producto->id) }}"
class="btn btn-success" id="btn_estado">Activo</a>
#else
<a href="{{ url('/status-update-producto', $producto->id) }}"
class="btn btn-danger" id='btn_estado'>Inactivo</a>
#endif
</td>
Controller
function updateStatusProducto($id)
{
//get producto status with the help of producto ID
$producto = DB::table('productos')
->select('estado')
->where('id', '=', $id,)
->first();
//Check producto status
if ($producto->estado == '1') {
$estado = '0';
} else {
$estado = '1';
}
//update producto status
$values = array('estado' => $estado);
DB::table('productos')->where('id', $id)->update($values);
return redirect()->route('productos.index');
}
Route
Route::get('/status-update-producto/{id}', [ProductoController::class, 'updateStatusProducto']);
You could add some data attributes to your buttons, and use them in js to send an ajax request
#if ($product->status =='0')
<td>
<button type="button" class="btn btn-success toggle-tr" data-product="{{ $product->id }}" data-status="{{ $product->status }}">active</button>
</td>
#else
<td>
<button type="button" class="btn btn-danger toggle-tr" data-product="{{ $product->id }}" data-status="{{ $product->status }}">Deactive</button>
</td>
#endif
document.querySelectorAll('.toggle-tr').forEach(el => el.addEventListener('click', e => {
const product = e.target.dataset.product;
const status = e.target.dataset.status == 0 ? 1 : 0;
// send ajax or fetch request passing in product_id. If we're going with a RESTful approach,
axiox.patch(`/products/${product}`, { status })
.then(res => { ... })
.catch(err => { ...});
}));
You can use jQuery ajax here:
Pass product id in data-id attribute
#if($product->status =='0')
<td>
<button type="button" data-id="{{ $product->id }}" class="btn btn-success remove-tr active_btn">active</button>
</td>
#else
<td>
<button type="button" data-id="{{ $product->id }}" class="btn btn-danger remove-tr deactive_btn">Deactive</button>
</td>
#endif
then use ajax:
$(document).on('click', '.active_btn', function(e) {
e.preventDefault();
let id = $(this).data('id');
$.ajax({
url: '{{ route("products.update") }}',
method: 'PUT',
data: {
id: id,
status: 1,
_token: '{{ csrf_token() }}'
},
success: function(response) {
console.log(response);
}
});
});
$(document).on('click', '.deactive_btn', function(e) {
e.preventDefault();
let id = $(this).data('id');
$.ajax({
url: '{{ route("products.update") }}',
method: 'PUT',
data: {
id: id,
status: 0,
_token: '{{ csrf_token() }}'
},
success: function(response) {
console.log(response);
}
});
});
Now you can handle the request in the controller.

django: html form submit without jumping into new page or refreshing/reloading

I am new to django and html. below is my first test web page of a simple online calculator.
I found a problem that when clicking the "submit" button, it tends to jump to a new web page or a new web tab. this is not what I want. Once the user input the data and click "submit" button, I want the "result" field on the page directly show the result (i.e. partially update only this field) without refresh/jump to the new page. Also I want the user input data kept in the same page after clicking "submit".
I saw there might be several different ways to do this work, iframe/AJAX. Since I am new, what is the really simplest way to achieve this goal? BTW, I dont write javascripts.
html:
<form method="POST">
{% csrf_token %}
<div>
<label>num_1:</label>
<input type="text" name="num_1" value="1" placeholder="Enter value" />
</div>
<div>
<label>num_2:</label>
<input type="text" name="num_2" value="2" placeholder="Enter value" />
</div>
<br />
<div>{{ result }}</div>
<button type="submit">Submit</button>
</form>
view.py
def post_list(request):
result = 0
if request.method == "POST":
num1 = request.POST.get('num_1')
num2 = request.POST.get('num_2')
result = int(num1) + int(num2)
print(request.POST)
print(result)
context = {
'result': result
}
return render(request, 'blog/post_list.html', context)
This is a simple example of using Ajax, which I hope will be useful to you.
first you need change post_list view:
view
from django.http import JsonResponse
def post_list(request):
if request.method == "POST":
num1 = request.POST.get('num_1')
num2 = request.POST.get('num_2')
result = int(num1) + int(num2)
return JsonResponse({"result":result})
else:
return render(request, 'blog/post_list.html', context={"result":0})
I use JsonResponse because I just want to get the result data in ajax and display it in the html , for GET request render the html file and for POST request use JsonResponse to return a json like context.
And your html file should to be a look like this:
html
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<form method="POST" id="post-form">
{% csrf_token %}
<div>
<label>num_1:</label>
<input type="text" name="num_1" value="1" placeholder="Enter value" />
</div>
<div>
<label>num_2:</label>
<input type="text" name="num_2" value="2" placeholder="Enter value" />
</div>
<br />
<div id="result" >{{ result }}</div>
<button type="submit" >Submit</button>
</form>
<script>
$(document).ready(
$('#post-form').submit(function(e){
e.preventDefault();
var serializedData = $(this).serialize();
$.ajax({
type:"POST",
url: "/your_url/",
data: serializedData,
success: function(data){
$("#result").text(data["result"]);
}
});
})
);
</script>
First I added jQuery cdn and then your html file, except that I added attribute id=post-form to the form and added id=result, then <script> tag was added and jquery function inside the tag was execute when your form Submited(detect event by the id #post-form).
And get the data(num_1, num_2) by serialize method then use Ajax to send POST reqeust to the view function(post_list), in Ajax you just need to pass serializedData and url(also you can use the Django url tag or set it in action form or...), After that we need to send data to the html(means the result data we received from the View).
in success function Ajax you can add html tag to your html file or
replace the some values,...
In Ajax, you must specify your URL to send the data.
for example if you have this url.py
urls.py
from .views import post_list
urlpatterns = [
path("posts_list/", post_list, name="post_list"),
]
In ajax you can add an address like this:
$.ajax({
type:"POST",
url: "/posts_list/",
....
Or
$.ajax({
type:"POST",
url: "{% url 'post_list' %}",
....
And if you have app_name in urls.py you can added url: "{% url 'app_name:post_list' %}",

Django crispy input from Ajax data

I need to Auto-fill up the crispy fields so I called the needed data from my database using ajax function as:
views.py
def load_record(request):
PUITS_id = request.GET.get('PUITS')
record = SurveillanceDesPuits.objects.filter(PUITS_id__id__exact=PUITS_id)[:1]
my_record= [str(record[0].PUITS) , str(record[0].MODE), str(record[0].CS)]
print(my_record)
return render(request, 'measure/Surveill_Wells/Add_wellMntr.html', {'record': my_record})
And my HTML file is :
<form method="POST" id="SurveillanceDesPuits_F" data-record-url="{% url 'ajax_load_record' %}">
{% csrf_token %}
<!-- form from views.py-->
<div class="border p-2 mb-3 mt-3 border-secondary">
<div class="form-row">
<div class="form-group col-md-3 mb-0">
{{ form.PUITS|as_crispy_field }}
</div>
<div class="form-group col-md-3 mb-0">
{{ form.CS|as_crispy_field }}
</div>
<div class="form-group col-md-3 mb-0">
{{ form.MODE|as_crispy_field }}
</div>
</div>
</div>
<input class="btn btn-success mb-4" type="submit" value="ADD Record">
</form>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$("#id_PUITS").change(function () {
var url = $("#SurveillanceDesPuits_F").attr("data-record-url");
var PUITSId = $(this).val();
$.ajax({
url: url,
data: {
'PUITS': PUITSId
},
success: function (data) {
$("#id_MODE").html(data);
}
});
});
</script>
After selecting an item (PUITS) from the dropdown list, I want to set the value of CS and MODE automatically from the received data.
so in the console, it gives me this error:
File "D:\WikiPED\venv\lib\site-packages\crispy_forms\templatetags\crispy_forms_filters.py", line 102, in as_crispy_field
raise CrispyError("|as_crispy_field got passed an invalid or inexistent field")
crispy_forms.exceptions.CrispyError: |as_crispy_field got passed an invalid or inexistent field
[07/Sep/2021 17:30:05] "GET /ajax/load-record/?PUITS=1 HTTP/1.1" 500 25693
what I missed in this code?
Thanks
I changed the views.py as:
def load_record(request):
PUITS_id = request.GET.get('PUITS')
record = SurveillanceDesPuits.objects.filter(PUITS_id__id__exact=PUITS_id)[:1]
return JsonResponse({'record2': list(record2.values())}, safe=False)
the scripts will be:
<script type="text/javascript">
$.ajax({
type: 'GET' ,
url: url,
data: {'PUITS': PUITSId },
dataType: "json",
success: function (response){
const object = response.record2[0]
$("#id_PUITS").val(object.PUITS_id);
$("#id_DATE_TEST").val(object.DATE_TEST);
$("#id_MODE").val(object.MODE);
$("#id_CS").val(object.CS);
$("#id_SITUATION").val(object.SITUATION);
$("#id_DUSE").val(object.DUSE);
$("#id_PRES_TBG").val(object.PRES_TBG);
$("#id_PRES_CSG").val(object.PRES_CSG);
$("#id_PRES_AVD").val(object.PRES_AVD);
$("#id_RESEAU_GL").val(object.RESEAU_GL);
$("#id_ANNULAIRE_TECH").val(object.ANNULAIRE_TECH);
$("#id_OBSERVATION").val(object.OBSERVATION);
$("#id_Controle_Pression_ENSP").val(object.Controle_Pression_ENSP);
$("#id_Test_Puits").val(object.Test_Puits);
$("#id_Controle_Pression_DP").val(object.Controle_Pression_DP);
},
});
return false;
});
</script>

Why ajax returns the latest variable of a loop in html template?

I have a html file as bellow:
<div id="preview_updated_notifications">
{% for lst in unread_list %}
<div >
<span data-notification-item="{{ lst.id }}" id="_mark_as_read_id"> ●</span>
</div>
{% endfor %}
</div>
and in js file:
$(document).on('click', "#_mark_as_read_id", function() {
var object_id = $('#_mark_as_read_id').data('notification-item');
console.log('--------------object_id:------------')
console.log(object_id)
console.log('--------------------------')
$.ajax({
type: "GET",
url: "{% url '_mark_as_read' object_id %}",
dataType: 'json',
data: {
'object_id': object_id,
},
dataType: 'json',
success: function (data) {
$('#preview_updated_notifications').html('**TEST**');
}
});
event.preventDefault();
});
But the problem is that this always prints the latest value of loop, while I expect after clicking on each item ● retrieve the relative id!
Ignoring the issue of the non-unique IDs you can delegate like this instead
$("#preview_updated_notifications").on("click","[data-notification-item]",function() {
const id = $(this).data("notification-item");
....
})
in
{% for lst in unread_list %}
<div >
<span data-notification-item="{{ lst.id }}" id="_mark_as_read_id"> ●</span>
</div>
{% endfor %}
many spans are rendered, and each of them has the same id. id should be unique in HTML, that is why only one span response to you.
{% url '_mark_as_read' object_id %} is a Django template tag, meaning that it gets rendered when the page first loads, so the value of object_id will always be the one initially passed as context, from the view.
You could use another HTML attribute, to store the update URL for each span, which would allow you to use the Django {% url ... %} tag.
Furthermore, as you have id="_mark_as_read_id" inside a for loop, every <span> element will have the same ID. IDs should be unique. You should use a class instead, and update the selector in the click function accordingly.
HTML:
<span data-notification-item="{{ lst.id }}" class="mark-as-read" data-update-link="{% url '_mark_as_read' lst.id %}"> ●</span>
jQuery:
$(document).on('click', ".mark-as-read", function() {
Then in your jQuery you could access it like this:
var update_url = $(this).data('update-link');
You are using django for loop for html only, the javascript code also needs to be in your for loop like this:
<div id="preview_updated_notifications">
{% for 1st in unread_list %}
<div >
<span data-notification-item="{{ lst.id }}" id="_mark_as_read_id{{ lst.id }}"> ●</span>
</div>
<script>
$(document).on('click', "#_mark_as_read_id{{ lst.id }}", function() {
var object_id = $('#_mark_as_read_id{{ lst.id }}').data('notification-item');
console.log('--------------object_id:------------')
console.log(object_id)
console.log('--------------------------')
$.ajax({
type: "GET",
url: "{% url '_mark_as_read' object_id %}",
dataType: 'json',
data: {
'object_id': object_id,
},
dataType: 'json',
success: function (data) {
$('#preview_updated_notifications').html('**TEST**');
}
});
event.preventDefault();
});
</script>
{% endfor %}
</div>
For every span html id, added {{ lst.id }}. So the id's will be like (_mark_as_read_id1, _mark_as_read_id2, _mark_as_read_id3, ....).
Now, it will get after clicking on each item ● retrieve the relative id!

How can I get post id from my html form to views.py

I am using Django, jQuery and Ajax. But, I am confused about how I can get the post id in ajax data to use that in views.py. I am adding code here and must read comments in the code so you can understand better what i am actually trying to explain or what problem I am facing. If this question require your little bit more time than answering other questions than please do not skip this question if you know the solution. All i can do for you is that i can up vote you 10 to 15 answers so that your reputation can increase.
I am beginner with JQuery so please explain your answer briefly.
So ,Here down below i have div tag which will provide me post id. if user click on the reply button.
<div id='post_id' post="{{post.id}}">
{% if node.level < 3 %}
<button class='btn btn-success' onclick="myFunction({{node.id}})">Reply</button>
{% endif %}
</div>
Than I have also form for comment.
<form id='commentform' class='commentform' method='POST'>
{% csrf_token %}
{% with allcomments as total_comments %}
<p>{{ total_comments }} comment{{total_comments|pluralize}}</p>
{% endwith %}
<select name='post' class='d-none' id='id_post'>
<option value="{{ post.id }}" selected="{{ post.id }}"></option>
</select>
<label class='small font-weight-bold'>{{comment_form.parent.label}}</label>
{{comment_form.parent}}
<div class='d-flex'>
<img class='avatar_comment align-self-center' src="{% for data in avatar %}{{data.avatar.url}}{%endfor%}">
{{comment_form.body }}
</div>
<div class='d-flex flex-row-reverse'>
<button type='submit' class='newcomment btn btn-primary' value='commentform' id='newcomment'>Submit</button>
</div>
</form>
between script tags I have set an event which is linked with form.
$(document).on('click', '#newcomment, #newcommentinner', function (e) {
e.preventDefault();
var button = $(this).attr("value");
var post_id = document.getElementById('post_id').getAttribute('post'); #Here I am trying to take post id from div tag with id='post_id'.
console.log(post_id,'postid') #In console it is returning me 2 which is right post id.
var placement = "commentform"
if (button == "newcommentform") {
var placement = "newcommentform"
}
$.ajax({
type: 'POST',
url: '{% url "posts:addcomment" pk=post.pk slug=post.slug %}',
data: $("#" + button).serialize() + {'post_id' : post_id},#Here I am trying to take that post id in data so i can use that in views.py. But in views.py it is returning me none And I don't understand why ? Because this is a post_id variable which is returning me 2 in console but in terminal it is returning me none. Please tell me how can i fix it.
cache: false,
success: function (json) {
console.log(json)
$('<div id="" class="my-2 p-2" style="border: 1px solid grey"> \
<div class="d-flex justify-content-between">By ' + json['user'] + '<div></div>Posted: Just now!</div> \
<div>' + json['result2'] + '</div> \
<hr> \
</div>').insertBefore('#' + placement);
$('.commentform').trigger("reset");
formExit()
},
error: function (xhr, errmsg, err) {
}
});
})
If more information is require than tell me. I will update my question with that information.
in console:
in terminal:
it worked for me.
data: $("#" + button).serialize() +"&post_id="+post_id