The ability to like/vote posts from the post list
I have a problem. I made likes to the posts on the video from YouTube. It is possible to like a post only from post_detail view and it's works correctly! How to make it possible to like posts in the post_list (in my case it's 'feed')? Please help!
MY CODE:
utils.py
def is_ajax(request):
return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
views.py
from .utils import is_ajax
def feed(request):
comments = Comment.objects.all()
posts = Post.objects.all().order_by('-created_at')
return render(request, 'main/feed.html', {'posts': posts,
'comments': comments})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
comments = Comment.objects.filter(post=post, reply=None).order_by('-created_at')
is_voted = False
if post.votes.filter(id=request.user.id).exists():
is_voted = True
if request.method == 'POST':
comment_form = CommentForm(request.POST or None)
if comment_form.is_valid():
content = request.POST.get('content')
reply_id = request.POST.get('comment_id')
comment_qs = None
if reply_id:
comment_qs = Comment.objects.get(id=reply_id)
comment = Comment.objects.create(
post = post,
author = request.user,
content = content,
reply = comment_qs
)
comment.save()
return redirect(post.get_absolute_url())
else:
for error in list(comment_form.errors.items()):
messages.error(request, error)
else:
comment_form = CommentForm()
return render(request, 'main/post_detail.html', {'post':post,
'comment_form':comment_form,
'comments':comments,
'is_voted':is_voted})
@login_required
def post_vote(request, slug):
post = get_object_or_404(Post, id=request.POST.get('id'), slug=slug)
is_voted = False
if post.votes.filter(id=request.user.id).exists():
post.votes.remove(request.user)
is_voted = False
else:
post.votes.add(request.user)
is_voted = True
context = {
'post': post,
'is_voted': is_voted,
}
if is_ajax(request):
html = render_to_string('main/votes.html', context, request)
return JsonResponse({'form':html})
votes.html
<form action="{% url 'main:post-vote' slug=post.slug %}" method="post">
{% csrf_token %}
{% if is_voted %}
<button type="submit" id="vote" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Unvote</button>
{% else %}
<button type="submit" id="vote" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Vote</button>
{% endif %}
</form>
{{post.votes_count}}
post_detail.html
<div id="vote-section">
{% include 'main/votes.html' %}
</div>
<script>
$(document).ready(function(event){
$(document).on('click','#vote', function(event){
event.preventDefault();
var pk = $(this).attr('value');
$.ajax({
type: 'POST',
url: "{% url 'main:post-vote' slug=post.slug %}",
data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(resoponse){
$('#vote-section').html(resoponse['form'])
console.log($('#vote-section').html(resoponse['form']));
},
error: function(rs, e){
console.log(rs.responseText);
}
})
});
});
</script>
feed.html
...
{% for post in posts %}
<div id="vote-section">
{% include 'main/votes.html' %}
</div>
...
{% endfor %}
<script>
$(document).ready(function(event){
$(document).on('click','#vote', function(event){
event.preventDefault();
var pk = $(this).attr('value');
$.ajax({
type: 'POST',
url: "{% url 'main:post-vote' slug=post.slug %}",
data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(resoponse){
$('#vote-section').html(resoponse['form'])
console.log($('#vote-section').html(resoponse['form']));
},
error: function(rs, e){
console.log(rs.responseText);
}
})
});
});
</script>
models.py
class Post(models.Model):
...
votes = models.ManyToManyField(get_user_model(), related_name='votes', blank=True)
@property
def votes_count(self):
return self.votes.count()
urls.py
urlpatterns = [
path('', views.feed, name='feed'),
path('post_create/', views.post_create, name='post-create'),
path('post/<slug>/', views.post_detail, name='post-detail'),
path('post/<slug>/delete', views.post_delete, name='post-delete'),
path('post/<slug>/update', views.post_update, name='post-update'),
path('post/<slug>/vote', views.post_vote, name='post-vote'),
path('comment/<slug>/delete', views.comment_delete, name='comment-delete'),
path('comment/<slug>/update', views.comment_update, name='comment-update'),
path('comment_reply/<slug>/delete', views.reply_comment_delete, name='comment-reply-delete')
]
I understand that I need to do the same as in post detail. I tried to do this, but i got errors
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
comments = Comment.objects.filter(post=post, reply=None).order_by('-created_at')
is_voted = False
if post.votes.filter(id=request.user.id).exists():
is_voted = True
...
Tried to do:
def feed(request, slug=None):
comments = Comment.objects.all()
posts = Post.objects.all().order_by('-created_at')
v_post = get_object_or_404(Post, slug=slug)
is_voted = False
if v_post.votes.filter(id=request.user.id).exists():
is_voted = True
return render(request, 'main/feed.html', {'posts': posts,
'comments': comments,
'is_voted': is_voted})
got
Page not found (404)
Also tried
def feed(request, slug):
comments = Comment.objects.all()
count_filter = Q(votes=request.user)
vote_case = Count('votes', filter=count_filter, output_field=BooleanField())
posts = Post.objects \
.annotate(is_voted=vote_case) \
.all().order_by('-created_at')
return render(request, 'main/feed.html', {'posts': posts,
'comments': comments})
got
TypeError at /
feed() missing 1 required positional argument: 'slug'
OK - there's a fair bit to unpack here:
First, generally speaking, it looks you you want Ajax to handle the bulk of the work. The reason (or at least the initial reason) why that isn't working above is in how you replicate forms
For well constructed HTML, only one element per page should have a particular ID
Your form is generated like this
{% for post in posts %}
<div id="vote-section"> #This will repeat for every post
{% include 'main/votes.html' %}
</div>
What this means is that every form will have the same id
, vote-section, so
success: function(resoponse){
$('#vote-section').html(resoponse['form'])
won't know where to write.
Similarly, in the forms on list, every button has id="vote"
This isn't an issue on the details page, because there's only one form and one vote/unvote button. On the list page, with multiple forms, it is.
So what to do about it?
First off, add a class to your button.
votes.html
<button type="submit" id="vote" class="vote-button" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Unvote</button>
Next tweak your feed html so it uses IDs correctly. We'll use {{[forloop.counter][1]}}
to generate unique IDs.
We'll also move the ajax into the loop, as the slug for each post is different in the url:
generation section. We add the new div ID into the selector and the success function to make it unique.
feed.html
{% for post in posts %}
<div id="vote-section{{forloop.counter}}">
{% include 'main/votes.html' %}
</div>
...
<script>
$(document).ready(function(event){
$(document).on('click','#vote-section{{forloop.counter}} .vote-button', function(event){
event.preventDefault();
var pk = $(this).attr('value');
$.ajax({
type: 'POST',
//this needs to be in the for post in posts loop to get post.slug
url: "{% url 'main:post-vote' slug=post.slug %}",
data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
dataType: 'json',
success: function(resoponse){
$('#vote-section{{forloop.counter').html(resoponse['form'])
console.log($('#vote-section{{forloop.counter}}').html(resoponse['form']));
},
error: function(rs, e){
console.log(rs.responseText);
}
})
});
});
</script>
{% endfor %}
Now there are probably ways to make this more efficient, but hopefully it should get you over the hump to where it works.