I am trying to use Ajax to submit a like button, I believe everything is in order but I keep getting PAge 404 error after submitting the like button,
I am not sure what is the reason. Need help to identify the error.
I have made some amendments to the URL path moving it from app urls.py to the main project urls.py but the error showing now is
LikeView() missing 1 required positional argument: 'pk'
Here is the view
class PostDetailView(DetailView):
model = Post
template_name = "post_detail.html"
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data()
stuff = get_object_or_404(Post, id=self.kwargs['pk'])
total_likes = stuff.total_likes()
liked = False
if stuff.likes.filter(id=self.request.user.id).exists():
liked = True
context["total_likes"] = total_likes
context["liked"] = liked
return context
def LikeView(request, pk):
# post = get_object_or_404(Post, id=request.POST.get('post_id'))
post = get_object_or_404(Post, id=request.POST.get('id'))
like = False
if post.likes.filter(id=request.user.id).exists():
post.likes.remove(request.user)
like = False
else:
post.likes.add(request.user)
like = True
context["total_likes"] = total_likes
context["liked"] = liked
if request.is_ajax:
html = render_to_string('like_section.html', context, request=request)
return JsonResponse({'form': html})
Here is the url.py in the main project, I have moved it from the app called Score the main 'url.py'
urlpatterns = [
path('like/', views.LikeView, name='like_post'),
here is the updated template
<form class="mt-0" action="{% url 'like_post' %}" method='POST'>
{% csrf_token %}
<strong> Likes
: {{total_likes}} </strong>
{% if user.is_authenticated %}
{% if liked %}
<button id='like' type='submit' name='post_id' class= "btn btn-danger btn-sm" value="{{post.id}}"> Unlike </button>
{% else %}
<button id='like' type='submit' name='post_id' class= "btn btn-primary btn-sm" value="{{post.id}}"> Like </button>
{% endif %}
{% else %}
<p><small> Login to Like </small></p>
{% endif %}
</form>
here is the ajax
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function(event){
$(document).on('click','#like', function(event){
event.preventDefault();
$var pk= $(this).attr('value');
$.ajax({
type:'POST',
url:'{% url "like_post" %}',
data:{'id': pk, 'csrfmiddlewaretoken':'{{csrf_token}}'},
dataType:'json',
success:function(response){
$('#like-section').html(response['form'])
console.log($('#like-section').html(response['form']));
},
error:function(rs, e){
console.log(rs.responseText);
},
});
});
});
</script>
Thank you
As error indicates, ListView class requires a positional argument which is pk. So your view class should change.
def LikeView(request):
Use this code as ListView definition.
Related
I have a form in a bootstrap modal, and I want to keep that modal open after submission. I am using CreateView and trying to pass an additional variable to the template in front-end where I could check if the flag is set or not, but the flag is always False even after submission. Here is what I have:
url.py
from django.urls import path
from .views import MescData
urlpatterns = [
path('mesc', MescData.as_view(), name='mesc')
]
views.py
from django.urls import reverse
from django.views.generic.edit import CreateView
from .forms import MescForm
from .models import Mesc
class MescData(CreateView):
model = Mesc
form_class = MescForm
template_name = 'Materials/mesc_data.html'
successful_submit = False # Flag to keep the add entry modal open
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['successful_submit'] = self.successful_submit
return context
def get_success_url(self):
return reverse('mesc')
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
self.successful_submit = True
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form, **kwargs):
# self.successful_submit = True
return super(MescData, self).form_valid(form, **kwargs)
And in Template, I'm checking it like this:
{% if successful_submit %}
<h1>Flag is set</h1>
{% endif %}
Is there a way to pass non-form-related data in CreateView to the template without having to change the url.py (i.e. adding variable data to url path)?
EDIT:
I tried printing the self.successful_submit in form_valid() and post() methods and it is indeed being updated to True, but in the template it is still being passed on as False.
This is the core problem: "I have a form in a bootstrap modal, and I want to keep that modal open after submission."
Simple answer: Use Ajax.
We do now have HTML over the wire as a paradigm gaining popularity, but I'm not familiar enough with it to discuss it. So I will use Ajax to show a solution. This particular solution uses a general ajax template, and the result of a post is a rendered Django template that you can use to just replace the HTML in your already rendered page.
Also, few people like JavaScript, but that is not a good enough reason to avoid using it. It is basically mandatory in any web application you're running. Even HTML over the wire uses a minimal amount of JavaScript to accomplish its goals.
First, write your Ajax view. I'm using django-rest-framework classes for this, and I'm providing an example for filling out a Calibration record. You can use whatever form you want; this is just my recipe for handling modals that you want keep open. In this view, I return a JSON response if the POST was successful. Otherwise, I return a rendered Django template.
from rest_framework.generics import (CreateAPIView, RetrieveAPIView,
UpdateAPIView, get_object_or_404)
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response
class CalibrationCreateAjaxView(CreateAPIView, UpdateAPIView, RetrieveAPIView):
renderer_classes = (TemplateHTMLRenderer,)
template_name = "documents/form/cal.html"
def post(self, request, *args, **kwargs):
context = self.get_context(request)
calibration_form = context['calibration_form']
if calibration_form.is_valid():
calibration_form.save()
request.accepted_renderer = JSONRenderer()
return Response(status=201)
return Response(context, status=400)
def get(self, request, *args, **kwargs):
return Response(self.get_context(request))
#staticmethod
def get_context(request):
pk = request.GET.get("pk")
calibration_entry = get_object_or_404(CalibrationEntry, pk=pk) if pk else None
return {
'calibration_form': CalibrationFormAjax(request.POST or None, instance=calibration_entry)
}
I have my view template as well. It takes advantage of request.is_ajax, which is being deprecated. You'll need to add some middleware to keep using it. Here's my middleware. Add it to your settings file as well.
class IsAjaxMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
"""
request.is_ajax is being removed in Django 4
Since we depend on this in our templates, we are adding this attribute to request
Please review:
https://docs.djangoproject.com/en/4.0/releases/3.1/#id2
"""
def __call__(self, request):
request.is_ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest'
return self.get_response(request)
general/ajax_modal.html
<!-- {% block modal_id %}{% endblock %}{% block modal_title %}{% endblock %} -->
{% block modal_body %}
{% endblock modal_body %}
general/modal.html
<div class="modal fade" id="{% block modal_id %}{{ modal_id }}{% endblock modal_id %}" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
<span class="sr-only">Close</span>
</button>
<h4 class="modal-title">
{% block modal_title %}{{ modal_title }}{% endblock modal_title %}
</h4>
</div>
<div class="modal-body">
{% block modal_body %}
{% endblock modal_body %}
</div>
</div>
</div>
</div>
Even though, we're using Crispy Forms, you can get away without using it. I also have a general templatetag library that renders any errors on a form. You can write your own.
documents/form/cal.html
{% extends request.is_ajax|yesno:'general\ajax_modal.html,general\modal.html' %}
{% load crispy_forms_tags general %}
{% block modal_id %}new-cal-modal{% endblock modal_id %}
{% block modal_title %}Enter Calibration Information{% endblock modal_title %}
{% block modal_body %}
<div id="new-cal-form-container">
<form action="{% url 'calibration-create' %}" method="post" id="new-cal-modal-form" autocomplete="off" novalidate>
{% if request.is_ajax %}
{% crispy calibration_form %}
{% form_errors calibration_form %}
{% endif %}
<button type="submit" name="Submit" value="Save" class="btn btn-success button" id="submit">save</button>
</form>
</div>
{% endblock modal_body %}
So now that the Ajax view is all set up, I go back to the main page that will render the modal dialog when the user clicks a button. I have a block called "extraContent" in which I include the template of the modal form.
{% block extraContent %}
{% include 'documents/form/cal.html' %}
{% endblock extraContent %}
And now, the JavaScript, which requires jQuery, that I've added to the template. I guess I made my own jQuery plugin on top of that...
$.fn.modalFormContainer = function(optionsObject) {
//call it on the modal div (the div that contains a modal-dialog, which contains modal-header, modal-body, etc
// we expect there to be a form within that div, and that form should have an action url
// when buttons that trigger the modal are clicked, the form is fetched from that action url and replaced.
// optionsObject has formAfterLoadFunction and ajaxDoneFunction
var options = $.extend({}, $.fn.modalFormContainer.defaults, optionsObject);
var $modalFormContainer = $(this);
// get the buttons that trigger this modal to open
// add a click event so that the form is fetched whenever the buttons are clicked
// if data-pk is an attribute on the button, apply that to the querystring of the
// ajaxURL when fetching the form
var modalID = $modalFormContainer.prop("id");
var modalFormButtonSelector = "[data-target=#" + modalID + "][data-toggle=modal]";
function handleModalButtonClick(event) {
//does the button have an associated pk? if so add the pk to the querystring of the ajax url
// this is wrapped in a form so that it gets replaced by the ajax response.
var $button = $(this);
if (!$button.hasClass("disabled") && !$button.prop("disabled")) { //only do it if the button is "enabled"
var $placeholder = $("<form><h1>loading...</h1></form>");
var $modalForm = $modalFormContainer.find("form");
var ajaxURL = $modalForm.prop("action");
$modalForm.replaceWith($placeholder);
var pk = $button.data().pk;
if (pk) {
if (ajaxURL.indexOf("?") > 0) {
ajaxURL += "&pk=" + pk;
} else {
ajaxURL += "?pk=" + pk;
}
}
//fetch the form and replace $modalFormContainer's contents with it
$.ajax({
type: "GET",
url: ajaxURL
}).done(function(response) {
// re-create the form from the response
$modalFormContainer.find(".modal-body").html(response);
$modalForm = $modalFormContainer.find("form"); //we would still need to find the form
options.formAfterLoadFunction($modalForm);
});
} else {
return false; //don't trigger the modal.
}
}
//using delegation here so that dynamically added buttons will still have the behavior.
// maybe use something more specific than '.main-panel' to help with performance?
$(".main-panel").on("click", modalFormButtonSelector, handleModalButtonClick);
$modalFormContainer.on("submit", "form", function(event) {
// Stop the browser from submitting the form
event.preventDefault();
var $modalForm = $(event.target);
var ajaxURL = $modalForm.prop("action");
$modalForm.find("[type=submit]").addClass("disabled").prop("disabled", true);
var formData = $modalForm.serialize();
var internal_options = {
url: ajaxURL,
type: "POST",
data: formData
};
// file upload forms have and enctype attribute
// we should not process files to be converted into strings
if ($modalForm.attr("enctype") === "multipart/form-data") {
internal_options.processData = false;
internal_options.contentType = false;
internal_options.cache = false;
formData = new FormData($modalForm.get(0));
internal_options.data = formData;
}
$.ajax(internal_options).done(function(response) {
// blank out the form
$modalForm.find("input:visible, select:visible, textarea:visible").val("");
// remove errors on the form
$modalForm.find(".has-error").removeClass("has-error");
$modalForm.find("[id^=error]").remove();
$modalForm.find(".alert.alert-block.alert-danger").remove();
// hide the modal
$(".modal-header .close").click();
options.ajaxDoneFunction(response);
}).fail(function(data) {
// re-create the form from the response
$modalFormContainer.find(".modal-body").html(data.responseText);
options.formAfterLoadFunction($modalForm);
});
});
return this;
};
$.fn.modalFormContainer.defaults = {
formAfterLoadFunction: function($form) { return; },
ajaxDoneFunction: function(response) { return; }
};
$("#new-cal-modal").modalFormContainer({
formAfterLoadFunction: function($modalForm) {
$(".datetimeinput").datepicker('destroy');
$(".datetimeinput").datepicker();
},
ajaxDoneFunction: function(event) {
location.reload();
}
});
So upon reviewing this, I've realized that this recipe is far more complicated than I have tricked myself into believing. I sincerely apologize for that. I hope that you can review the code and get an idea of what is happening. There are some edge cases, such as dealing with dates and file uploads, that this recipe handles right now, but you may not actually need them. I should mention that the application that this came from is using Bootstrap 3, so its styling is not updated to the current Bootstrap 5 as of this writing. I should add that the main content of the app has a class of "main-panel" as used in this not-so-generic jQuery plugin.
I'm worried that I've gone and overwhelmed you into maintaining your position of trying to keep using a standard POST request. I guess you could just re-render the template with your POST since it'll be standard practice in your project. You could still get away without using a query string that way.
I am trying to make a upvote and downvote functionality on my website. however there is a particular behaviour that i don't like which is that whenever a user clicks the button, he should not be redirect to another page but should remain on the same page.
What happens after clicking the upvote button is that it goes to the url http://localhost:8001/upvote/2/ which i don't want. I want it to remain on the same page which is http://localhost:8001/view-supplier/
models.py
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=254, unique=True)
# CUSTOM USER FIELDS
firstname = models.CharField(max_length=30)
lastname = models.CharField(max_length=30)
upvotes = models.IntegerField(default=0)
downvotes = models.IntegerField(default=0)
objects = UserManager()
def get_absolute_url(self):
return "/users/%i/" % (self.pk)
def get_email(self):
return self.email
views.py
def Viewsupplier(request):
title = "All Suppliers"
suppliers = User.objects.filter(user_type__is_supplier=True)
context = {"suppliers":suppliers, "title":title}
return render(request, 'core/view-suppliers.html', context)
#login_required
def upvote(request, pk):
supplier_vote = get_object_or_404(User, id=pk)
supplier_vote.upvotes += 1
supplier_vote.save()
upvote_count = supplier_vote.upvotes
context = {"supplier_vote":supplier_vote, "upvote_count":upvote_count}
return render(request, "core/view-suppliers.html", context)
#login_required
def downvote(request, pk):
supplier_vote = get_object_or_404(User, id=pk)
supplier_vote.downvotes -= 1
supplier_vote.save()
downvote_count = supplier_vote.downvotes
context = {"supplier_vote":supplier_vote, "downvote_count":downvote_count}
return render(request, "core/view-suppliers.html", context)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('upvote/<int:pk>/', views.upvote, name='upvote'),
path('downvote/<int:pk>/', views.downvote, name='downvote'),
]
view-supplier.html
<table class="table table-borderless table-data3">
<thead>
<tr>
<th>No</th>
<th>Country</th>
<th>Votes</th>
</tr>
</thead>
<tbody>
{% for supplier in suppliers %}
<tr>
<td>{{forloop.counter}}</td>
<td>{{supplier.country}}</td>
<td>
<div class="table-data-feature">
<a href="{% url 'upvote' supplier.id %}" class="m-r-10">
<button class="item" data-toggle="tooltip" data-placement="top" title="Like">
<i class="zmdi zmdi-thumb-up"></i>{{upvote_count}}</button>
</a>
<a href="{% url 'downvote' supplier.id %}">
<button class="item" data-toggle="tooltip" data-placement="top" title="Dislike">
<i class="zmdi zmdi-thumb-down"></i>{{downvote_count}}</button>
</a>
</div>
</td>
</tr>
{% empty %}
<tr><td class="text-center p-5" colspan="7"><h4>No supplier available</h4></td></tr>
{% endfor %}
</tbody>
</table>
You need to implement an API (application programming interface) to send the upvote and downvote asynchronously. Django REST framework is the way to go to create your very own API. You can watch hours of video tutorials on that subject on YouTube. The docs for Django REST framework is really great and easy to read as well. Django is a server-side web-framework which means that it can only help you if you submit to the server. You can definitely reload the same page:
return HttpResponseRedirect(reverse('<app_name>:<url_name>'))
However, there will be an interruption. So, the recommended way to handle this type of behavior is through asynchronous call using JavaScript's APIs such as the Fetch API to the REST framework.
If, for probably misplaced fear of learning asynchronous coding, you decide to send data to your server the old-fashion way, you can always use your upvote and downvote to submit user data and update the counts. Then, in your view_supplier view, you need to get the updated view count and pass it to the context. So, your upvote view changes the upvote counts and trigger the Viewsupplier view. Then, inside the ViewSupplier view, you get the counts and add it to the context
# in your template
<a href="{% url 'upvote' supplier.id %}" class="m-r-10">
<button class="item" data-toggle="tooltip" data-placement="top" title="Like">
<i class="zmdi zmdi-thumb-up"></i>{{upvote_count}}</button>
</a>
# in your view
def Viewsupplier(request):
title = "All Suppliers"
suppliers = User.objects.filter(user_type__is_supplier=True)
# Get the updated count:
suppliers_votes_count = {}
for supplier in suppliers:
upvote_count = supplier.upvotes
downvote_count = supplier.upvotes
supplier_count = {supplier: {'upvote': upvote_count, 'downvote': downvote_count } }
suppliers_votes_count.update(supplier_count)
context = {"suppliers":suppliers, "title":title, "suppliers_votes_count": suppliers_votes_count }
return render(request, 'core/view-suppliers.html', context)
#login_required
def upvote(request, pk):
supplier_vote = get_object_or_404(User, id=pk)
supplier_vote.upvotes += 1
supplier_vote.save()
upvote_count = supplier_vote.upvotes
context = {"supplier_vote":supplier_vote, "upvote_count":upvote_count}
return HttpResponseRedirect(reverse('core:view_supplier'))
This can be easily achieved by using AJAX.
Rather than providing the supplier_id as part of URL, it would be better to send the supplier_id in AJAX to your Django Upvote view. You could do like this.
HTML:
<button id="{{ supplier_id }}" class="item upvote" data-toggle="tooltip" data-placement="top" title="Like">
<i class="zmdi zmdi-thumb-up"></i>{{upvote_count}}
</button>
Extract the supplier_id from the button on click event using Javascript.
JavaScript:
$('.upvote').on('click', function () {
var supp_id = $(this).attr('id');
$ajax({
type: 'POST',
url: '/upvote/',
data: {
supplier_id: supplier_id,
},
success: function (data) {
if (data.status == 'success') {
/* Your upvote logic like updating the color of button or showing Unlike etc.,*/
}
}
});
});
Change your url pattern in Django urls.py to
urlpatterns = [path('upvote/', views.upvote, name='upvote'),
Views.py
def upvote(request):
if request.method == 'POST':
supplier_id = request.POST['supplier_id']
# Your DB update logic goes here....
return JsonResponse({'status': 'success'})
else:
return JsonResponse({'status': 'Error'})
Similarly you could do the same for downvote.
Here's how I would achieve this:
first I'd have simple forms handling the upvotes/downvotes:
<form method="POST" action="{% url 'view-supplier' %}"> //upvote form
{% csrf_token %}
<input type="hidden" name="upvote-button">
<button type="submit" style="width:100%">Upvote Button</button>
</form>
<form method="POST" action="{% url 'view-supplier' %}"> // downvote form
{% csrf_token %}
<input type="hidden" name="downvote-button">
<button type="submit" style="width:100%">Downvote Button</button>
</form>
Then I'd have the view set up like:
def supplierView(request):
supplier_vote = get_object_or_404(User, id=pk)
if 'upvote-button' in request.POST:
supplier_vote.upvotes += 1
supplier_vote.save()
upvote_count = supplier_vote.upvotes
elif 'downvote-button' in request.POST:
supplier_vote.downvotes -= 1
supplier_vote.save()
downvote_count = supplier_vote.downvotes
else:
downvote_count = supplier_vote.downvotes
upvote_count = supplier_vote.upvotes
context = {
'upvote_count': upvote_count,
'downvote_count': upvote_count,
}
return render(request, 'main/view-suppliers.html', context)
This way when someone clicks the upvote button, they're redirected to the same view, which then handles adding the upvotes and updates the context.
If view-supplier is a detail view, you'll also have to pass in the PK to the view.
I want to Ajax feature for the like button in my blog. I have implemented like feature first, it was working as expected(reloads page after clicking like button). Then, i want to implement Ajax so the page should not reload after clicking like button. But, it is not working as expected.
views.py:
def like_post(request):
user = request.user
if request.method == 'POST':
post_id = request.POST.get('id')
post_obj = get_object_or_404(Post, id=post_id)
if user in post_obj.liked.all():
post_obj.liked.remove(user)
else:
post_obj.liked.add(user)
like, created = Like.objects.get_or_create(user=user, post_id=post_id)
if not created:
if like.value == 'Like':
like.value = 'Unlike'
else:
like.value = 'Like'
like.save()
if request.is_ajax():
post = Post.objects.all()
context={
'post': post
}
html = render_to_string('blog/like_section.html', context, request=request)
return JsonResponse({'form': html})
Jquery in base.html:
<script src="https://code.jquery.com/jquery-3.1.1.min.js">
<script type="text/javascript">
$(document).ready(function(event){
$(document).on('click', '#like', function(event){
event.preventDefault();
console.log("hello")
var pk = $(this).attr('value');
$.ajax({
type : 'POST',
url : {% url 'like-post' %},
data : {'id' : pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType : 'json',
success : function(response){
$('#like-section').html(response['form'])
console.log($('#like-section').html(response['form']));
},
error : function(rs, e){
console.log(rs.responseText);
},
});
});
});
</script>
like_section.html:
<form action="{% url 'like-post' %}" method="POST">
{% csrf_token %}
<input type="hidden" name="post_id" value="{{ post.id }}">
{% if user not in post.liked.all %}
<button id="like" class="btn btn-outline-info mb-4" type="submit">Like</button>
{% else %}
<button id="like" class="btn btn-info mb-4" type="submit">Unlike</button>
{% endif %}
</form>
urls.py:
path('likes/', views.like_post, name='like-post'),
It returns 404 with No Post matches the given query. My observation is, it looks it is not detecting Jquery part because if I replace 'id' with 'post_id' in request.POST.get('id') & return redirect under request.method == 'POST', it works like my previous version(Reloads page if I hit like button). So the control never goes into request.is_ajax() loop, How do I make Django to use Ajax?
I'm learning Ajax on how I can submit a comment form without page reload. I'm using Django, I have list of posts in homepage each with comment form. When I submit a comment it is not saving in database and also it doesn't display in browser. When I check on Chrome console, I got an error 2 elements with non-unique id.
from django.http import JsonResponse
from django.template.loader import render_to_string
def home(request):
all_images = Post.objects.filter(poster_profile=request.user)
if request.method == 'POST':
post_id = request.POST.get("post_comment")
post_obj = Post.objects.get(pk=post_id)
form = CommentForm(request.POST)
if form.is_valid():
comment=form.save(commit=False)
comment.user=request.user
comment.commented_image=post_obj
comment.save()
else:
form=CommentForm()
context = {'all_images':all_images, 'form':form}
if request.is_ajax():
html=render_to_string('ajax_feeds_comment.html', context, request=request)
return render(request, 'home.html', context)
#home.html
{% for post in all_images %}
<img src="{{ post.image.url }}">
{% for comment in post.comments.all %}
<p>{{ comment.comment_post }}</p>
{% endfor %}
<div class="reload-form">
{% include "ajax_feeds_comments.html" %}
</div>
{% endfor %}
#ajax_feeds_comments.html
<form method="POST" class="feeds-comment" action=".">
{% csrf_token %}
<input type="hidden" value="{{post.id}}" name="post_comment">
<textarea name="comment_post" class="form-control" id="id_comment_post{{post.id}}"></textarea>
<button type="submit" class="btn btn-primary">submit</button>
</form>
<script>
$(document).on('submit', '.feeds-comment', function(event){
event.preventDefault();
console.log($(this).serialize());
$.ajax({
type: 'POST',
url: $(this).attr('action'),
data: $(this).serialize(),
dataType: 'json',
success: function(response){
$('.reload-form').html(response['form']);
},
error: function(rs, e) {
console.log(rs,responseText);
},
});
});
</script>
There are multiple things not correct here:
In your "urls.py" you should not send every request to your views. This is where you get the "favico.ico" error 500 get from. Having no favico is ok, but getting a Error 500 is not.
Check the html code for duplicate id's! These have to be unique.
If you use django variables for html, do it like this: Instead of "{{post.id}}" use: "{{ post.id }}" with spaces around the var.
In the
document.on("submit", "feeds-comment", function(){...})
you're not using the id of that element but it's class name.
Check where the submit is going to. Check Django if the request is being handled. (You see that in the console where Django is running). Also maybe Post a screenshot here!
I follow Tango with Django's tutorial for adding the like button. I thought it worked, but it only seems to be working for the very first model instance in a list. You see, I have a view where all the objects in a list is displayed. My code looks like this:
views.py
def content_list(request):
posts = Post.objects.all()
return render(request, 'app/content_list.html', {'posts' : posts})
models.py
class Post(models.Model):
user = models.ForeignKey(User, related_name = 'user_posts')
title = models.CharField(max_length = 140)
votes = models.IntegerField(default = 0)
content_list.html - the template
{% for post in posts %}
<h1>{{post.title}}</h1>
<p><strong id="like_count">{{post.votes}}</strong> points</p>
{% if request.user.is_authenticated %}
<button id="likes" data-post-id="{{post.id}}" class="btn btn-purple" type="button">
<span>Upvote</span>
</button>
{% endif %}
{% endfor %}
In views.py, I have another function which handles the post-liking:
#login_required
def like_post(request):
post_id = None
# Getting the id of the post from the template
if request.method == 'GET':
post_id = request.GET['post_id']
likes = 0
if post_id:
post = Post.objects.get(id = int(post_id))
if post:
likes = post.votes + 1
post.votes = likes
post.save()
return HttpResponse(likes)
And finally, my ajax file which does the actual work:
$('#likes').click(function(){
var p_id;
p_id = $(this).attr("data-post-id");
$.get('/like_post/', {post_id: p_id}, function(data){
$('#like_count').html(data);
$('#likes').hide();
});
});
My urls.py in my app folder has the following line in the url patterns:
# Liking a post
url(r'^like_post/$', views.like_post, name='like_post'),
What am I missing? The uproot button for the other posts don't hide either, which they should because of my ajax code. How can I fix this problem?
Thanks!
By applying what was mentionned in comments, your server code could become:
{% for post in posts %}
<h1>{{post.title}}</h1>
<p><strong id="like_count">{{post.votes}}</strong> points</p>
{% if request.user.is_authenticated %}
<button data-post-id="{{post.id}}" class="likes btn btn-purple" type="button">
<span>Upvote</span>
</button>
{% endif %}
{% endfor %}
And the javascript part is now:
$('.likes').click(function(){
var p_id;
p_id = $(this).attr("data-post-id");
$.get('/like_post/', {post_id: p_id}, function(data){
$('#like_count').html(data);
$(this).hide();
});
});