Django do not allow another like if user already liked a post - python

I want to allow an user to be able to like an post only once. Here is my code so far:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
likes = models.PositiveIntegerField(default=0)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
In views.py:
def LikePostView(request, pk):
post = get_object_or_404(Post, pk=pk)
post.likes = post.likes+1
post.save()
return render(request, 'blog/like_done.html')
Code to like a post on frontend:
<p class="content">{{ post.likes }} likes</p>
{% if user.is_authenticated %}
</i>
{% else %}
<a onclick="alert('In order to like a post, you must be logged in!')" href='{% url "blog-home" %}'><i class="fa fa-thumbs-up" style="font-size:36px"></i></a>
{% endif %}
And like_done.html:
{% extends "blog/base.html" %}
{% block content %}
<h2 class="alert alert-success">Post Liked Successfuly!</h2>
<br>
<a class="btn btn-success" href="{% url 'blog-home' %}">Go Back Home?</a>
<br>
{% endblock content %}
So with all this, how would I make the user be allowed to only like a post once. I am only beginner, so please explain a little.

If I get your question right, You want a user to be able to like a post once, and when the user clicks on the like button the second time it should 'unlike' right?
If so, this is what you should do:
In you Post model, the like should be like this likes = models.ManyToManyField(User, related_name='user_likes'), a ManyToManyField.
Then in your views:
def LikePostView(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.user in post.likes.all():
post.likes.remove(request.user)
else:
post.likes.add(request.user)
return render(request, 'blog/like_done.html')
This way if the user clicks the like button he will be added to the likes Field and if he clicks it again he will be removed seems he is already there. So the user won't be able to like a post more than once.

Related

if and else - both are executing

I am building a simple question answer site, in which - I am trying to implement a like button But When i like the post then solid liked button is showing and blank liked button is also showing.
models.py
class Question(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title= models.CharField(max_length=300)
class Answer(models.Model):
user_by = models.ForeignKey(User, on_delete=models.CASCADE)
que = models.ForeignKey(Question, on_delete=models.CASCADE)
body = models.CharField(max_length=300)
class LikeAnswer(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
ans = models.ForeignKey(Answer, on_delete=models.CASCADE)
views.py
def question_detail(request, question_id):
obj = get_object_or_404(Question, pk=question_id)
answers = obj.answer_set.all()
context = {'obj':obj,'answers':answers}
return render(request, 'question_detail.html', context)
quesion_detail.html
{{obj.title}}
{% for anss in answers %}
{% for lks in anss.likeanswer_set.all %}
{% if request.user == lks.user %}
<button name='submit' type='submit' id="unlike" value="unlike">Unlike
</button> <i class="fas fa-thumbs-up"></i>
{% else %}
<button name='submit' type='submit' id="like" value="like">
</button><i class="fal fa-thumbs-up"></i>
{% endif %}
{% endfor %}
{% endfor %}
What i am trying to do :-
I am trying to show only filled like button after user like, But it is showing both buttons.
I have also tried {% empty %} but it didn't made impact on it, It showed same results.
They are showing like :-
Any help would be much Appreciated. Thank You in Advance.
You are iterating over all likes, whether they are from the request user or not, and as soon as there is any other user, there will be also the "unlike" button, so you will have both buttons.

Django custom template tags

Why my custom template tag doesn't work?
templatetags.py:
from django import template
from ..models import User
register = template.Library()
#register.inclusion_tag('main/post_detail.html', takes_context=True)
def get_user_liked_posts():
request = context['request']
user = User.objects.get(username=request.user.username)
liked_posts = []
for post in user.liked_posts.all():
liked_posts.append(post.name)
return {'liked_posts': liked_posts}
post_detail.html:
{% load static %}
{% load templatetags %}
<nav class="blog-pagination" aria-label="Pagination">
<span id="likes_count">{{ post.likes_count }}</span>
{% if post.name in liked_posts %}
<button id="like_button" class="btn btn-outline-primary btn-primary text-
white">Like</button>
{% else %}
<button id="like_button" class="btn btn-outline-primary">Like</button>
{% endif %}
</nav>
views.py:
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
class LikePostView(View):
def post(self, request, slug):
post = Post.objects.get(id=request.POST['id'])
user = User.objects.get(username=request.user.username)
if request.POST['like'] == 'true':
post.likes_count += 1
user.liked_posts.add(post)
else:
post.likes_count -= 1
user.liked_posts.remove(post)
user.save()
post.save()
return redirect('post_detail', slug)
models.py:
class Post(models.Model):
"""
This is post model
"""
name = models.CharField(max_length=150, blank=False)
article = models.TextField(blank=False)
image = models.ImageField(upload_to='uploads/', blank=True)
likes_count = models.IntegerField(default=0)
url = models.CharField(max_length=150, blank=False)
def get_absolute_url(self):
return reverse('post_detail', kwargs={'slug': self.url})
I want to check if the post is in the liked post of the current user, but it doesn't work.
It doesn't show any errors, it just does nothing.
User in my app must like or unlike posts. In models, I have many to many relationship user with the post. I want to check if the user likes this post
The problem is that you don't even use the template tag, furthermore this is not even needed as you can simply write something like so in the template:
{% if post in request.user.liked_posts.all %}
A Liked post
{% else %}
Not a liked post
{% endif %}
But this is a bit inefficient as we are getting all the posts liked by the user just to check if they like some post. Also if this were in a loop with multiple posts we would be making a query for each post.
Instead we can simply annotate whether the user likes a post in the view itself using an Exists subquery [Django docs] on the through model of the many to many:
from django.db.models import Exists, OuterRef
class PostDetailView(DetailView):
model = Post
slug_field = 'url'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.annotate(
liked_by_user=Exists(
User.liked_posts.through.objects.filter(
post_id=OuterRef("pk"),
user_id=self.request.user.id
)
)
)
return queryset
Now in the template we can simply write:
{% if post.liked_by_user %}
A Liked post
{% else %}
Not a liked post
{% endif %}
Note: Your way of saving the count similarly can simply be turned into an annotation using the Count aggregation function [Django
docs].
Generally one should not store calculated attributes in a column since
that might lead to inconsistent data when updating and forgetting to update the related count, etc.

Django queryset in view isn't returning anything to the html template

I want to show all posts of a specific user in their profile page but only their profile information is being displayed but not the posts.
My initial code was simply trying to loop through the posts in the template like this:
{% for post in user.posts.all %}
{% endfor %}
But it didn't work. So I tried this next.
views.py
user = get_object_or_404(User, username=username)
post = Post.objects.filter(created_by=user.id)
context = {
'user': user,
'post': post
}
return render(request, 'users/profile.html', context)
profile.html
<div class="user-profile">
<img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
{{ user.username }}
{{ user.email }}
</div>
<div class="user-post-model">
{% for post in posts %}
<img class="rounded-circle post-img" src="{{ post.created_by.profile.image.url }}">
{{ post.title }}
{{ post.content|safe }}
{{post.tag}}
{{ post.created_by }}
{{ post.created_at|naturaltime}}
{% endfor %}
</div>
It didn't work either
I also tried using a ListView to post the posts. It worked but instead of showing the specific user's profile information it showed the logged in user's profile information but the specific user's posts.
like this:
def profile(request, username):
user = get_object_or_404(User, username=username)
context = {
'user': user
}
return render(request, 'users/profile.html', context)
class UserPostListView(ListView):
model = Post
template_name = 'users/profile.html'
context_object_name = 'posts'
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(created_by=user).order_by('-created_at')
This is the post model
class Post(models.Model):
title = models.CharField(max_length=255)
content = RichTextField(blank=True, null=True, max_length=30000)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
tag = models.CharField(max_length=255, default='uncategorised')
class Meta:
ordering = ('-created_at',)
def __str__(self):
return self.title + ' | ' + str(self.created_by)
def get_absolute_url(self):
return reverse("post-detail", kwargs={"pk": self.pk})
What is wrong?
To loop over the related Post objects to User, use _set (Django Docs).
So in your case user.post_set:
{% for post in user.post_set.all %}
{{ post.title }}
{% endfor %}

How to access html element id, in django backend?

I'm learning Django and I am currently building blog app. I have a problem. I built functionalities to add new posts and to comment to new posts. Now I struggle with creating the possibility to delete comments by users.
I assigned an id (primary key) of given comment to button "delete" which is present in all comments fields in purpose to know which comment I want to delete. But now I don't know how to access to this HTML elements in backend and fetch this id's.
Part of my html file:
{% for comment in comments %}
{% if comment.post == object %}
<div class="article-metadata">
<small>{{ comment.add_date|date:"M/d/Y" }} <b>{{ comment.author }}</b></small>
{% if comment.author == user %}
Delete
{% endif %}
<p>{{ comment.comm_content }}</p>
</div>
{% endif %}
{% endfor %}
My class based view in views.py where I want to touch this id:
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['comments'] = Comment.objects.all()[::-1]
return context
# In this function I want to access my comment id and remove this comment from database
def delete_comment(self):
post_to_delete = Post.objects.get(id=my_comment_id)
del post_to_delete
I know this can be solved somehow using jquery but I don't know javascript and for now I would like to know how to do it using python only. Thanks for any hints.
My models.py file:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
class Comment(models.Model):
comm_content = models.TextField()
add_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
def __str__(self):
return f"Comment of post {self.post} posted at {self.add_date}."
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.post.pk})
First, add a URL pattern like:
# urls.py
path('comments/delete/<int:pk>/', views.CommentDeleteView.as_view(), name='delete_comment'),
next, you need a view to handle the logic:
# views.py
from django.urls import reverse_lazy
class CommentDeleteView(DeleteView):
model = Comment
def get_success_url(self):
return reverse_lazy('post-detail', kwargs={'pk': self.object.post.pk})
# if you don't want to send POST request, you can use:
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
Django's generic DeleteView will delete the object only if you send a POST request to it, so we will call the post method in get, to make it work with a GET request; Although, generally speaking, it's not a good idea; you should add a confirmation form (like a modal) and send a POST from there.
and finally, for your template:
{% for comment in comments %}
{% if comment.post == object %}
<div class="article-metadata">
<small>{{ comment.add_date|date:"M/d/Y" }} <b>{{ comment.author }}</b></small>
{% if comment.author == user %}
Delete
{% endif %}
<p>{{ comment.comm_content }}</p>
</div>
{% endif %}
{% endfor %}

Output username from user_id field on separate model in Django Template

When a post is created, the user's User ID is saved to the post table under the user_id field. On the home page (for right now) I'm outputting all the posts in the post table. I want to get the username into the template based off of the user ID on the Post object.
models.py
class Post(models.Model):
user_id = models.IntegerField()
post_name = models.CharField(max_length=300)
total_votes = models.IntegerField(default=0)
created_date = models.DateTimeField()
views.py
#login_required
def home(request):
posts = Post.objects.raw('SELECT * FROM posts ORDER BY created_date DESC')
return render(request, 'home.html', {'posts': posts})
home.html
{% for post in posts %}
<div>
{{ post.post_name }}
<p>{{ post.created_date }}</p>
<p>{{ post.total_votes }}</p>
<p>{{ post.user_id }}</p>
</div>
{% endfor %}
Edit: Ended up changing query to:
SELECT
*, u.username
FROM posts p
LEFT JOIN auth_user u ON (p.user_id = u.id)
I didn't think you could add to the Post object and even call it as post.username. Regardless, if there's a better way to do it, let me know. Thanks!
Your whole logic is wrong :(
The user should be stored as a ForeignKey into the Post model. That is, a user may have multiple Posts but one Post may belong to one user. Something like that:
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
post_name = models.CharField(max_length=300)
total_votes = models.IntegerField(default=0)
created_date = models.DateTimeField()
Note that the User model may be the built-in Django User model (from django.contrib.auth.models import User) or you own if you have customized it.
Database queries in Django, are not made with raw SQL. That's why the ORM exists. So, the view could be changed to:
#login_required
def home(request):
posts = Post.objects.order_by('-created_date')
return render(request, 'home.html', {'posts': posts})
Now you have a very verbose and pythonic access to the User model instance (and to the Post one) from your template.
{% for post in posts %}
<div>
{{ post.post_name }}
<p>{{ post.created_date }}</p>
<p>{{ post.total_votes }}</p>
<p>{{ post.user.username }}</p>
</div>
{% endfor %}
EXTRA TIP: Instead of href="posts/{{post.id}}", try to use the {% url %} template tag in order not to hardcode url paths like that.
It would be better to use ForeignKey for such purpose. For example:
class Post(models.Model):
user = models.ForeignKey(User)
post_name = models.CharField(max_length=300)
total_votes = models.IntegerField(default=0)
created_date = models.DateTimeField()
Make sure to import User in models.py.
Create a Form for creating Post:
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ('post_name',)
Now, in views:
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
return # Write return here
else:
form = PostForm()
context = {
'form': form
}
return render(request, 'template_name', context)
Create a template for this and use it to create posts.
In your view home:
#login_required
def home(request):
posts = Post.objects.order_by('-created_date')
return render(request, 'home.html', {'posts': posts})
In your template:
{% for post in posts %}
<div>
{{ post.post_name }}
<p>{{ post.created_date }}</p>
<p>{{ post.total_votes }}</p>
<p>{{ post.user.username }}</p>
</div>
{% endfor %}
It will work.

Categories

Resources