I am creating a Blog website where for each blog a user can like or dislike the post.
Now every time user goes on Index page I want to change the like button i.e if user has liked the post then a dislike button otherwise like button.
For this I need to get the variable IsLiked from the views.py file.
This is the Query that I have written.
posts = Post.objects.exclude(users=request.user).select_related('user__people','ProductAvailability').prefetch_related('images_set').annotate(comments_Count = Count('comments_post',distinct=True)).annotate(Count('Likes',distinct=True)).all().order_by('-id')
for post in posts:
if(post.Likes.filter(id=user_id).exists()):
isLiked = True
else:
isLiked = False
Here the problem is that for every post there is a separate query sent to DB.
This is my Blog Post Model ->
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
# category = models.ForeignKey(Category, on_delete=models.PROTECT)
ProductAvailability = models.ForeignKey(ProductAvailability, on_delete=models.PROTECT, null=True, blank=True)
title = models.CharField(max_length=255,null=True)
description = models.CharField(max_length=1000,null=True)
Likes = models.ManyToManyField(to=User, related_name='Post_likes')
favourites = models.ManyToManyField(to=User,blank=True,related_name="favourite")
Tag1 = models.CharField(max_length=255,null=True,blank=True)
Tag2 = models.CharField(max_length=255,null=True,blank=True)
Tag3 = models.CharField(max_length = 255, null = True, blank = True)
Tag1_Name = models.CharField(max_length=255,null=True,blank=True)
Tag2_Name = models.CharField(max_length=255,null=True,blank=True)
Tag3_Name = models.CharField(max_length=255,null=True,blank=True)
users = models.ManyToManyField(User, related_name='users_hidden_from_post')
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
PS: Please Ignore the redundant Info in the Post Model
I want to send the User id with the Query and check if individual post is liked by the user or not.
You can annotate the Posts with a condition is_liked that checks if such User appears in the through model of the likes of the Post with an Exists subquery [Django-doc]:
from django.db.models import Exists, OuterRef
posts = Post.objects.exclude(
users=request.user
).select_related(
'user__people', 'ProductAvailability'
).prefetch_related(
'images_set'
).annotate(
comments_Count = Count('comments_post',distinct=True),
Count('Likes',distinct=True),
is_liked=Exists(
Post.Likes.through.objects.filter(
post_id=OuterRef('pk'), user_id=user_id
)
)
).order_by('-id')
The Post objects that arise from this queryset will have an extra attribute named .is_liked that is True if the user_id appears in the Likes for that Post.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: likes instead of Likes.
Related
I am making a blog website and I am facing some issues with the Query performance.
I have 3 models
User Model -> Users (To store user email, Password etc)
Post Model -> Actual Posts
people Model -> (To store users extra information)
Post Model ->
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
title = models.CharField(max_length=255,null=True)
description = models.CharField(max_length=1000,null=True)
Likes = models.ManyToManyField(to=User, related_name='Post_likes')
favourites = models.ManyToManyField(to=User,blank=True,related_name="favourite")
People Model ->
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.ImageField(upload_to='profile_pics', blank=True,null=True)
Phone_number = models.CharField(max_length=255,null=True,blank=True)
Birth_Date = models.DateField(null=True,blank=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
Now as both of these models are connected to User model. I want to query the Post model and get the user photo in the template. Now when I use post.user.people.photo then for every post it generates a seperate query to DB resulting in slowness. I would like to use Join here to combines multiple tables and fetch all the records at once.
I am currently using following Query ->
posts = Post.objects.select_related().prefetch_related('images_set').annotate(comments_Count = Count('comments_post',distinct=True)).annotate(Count('Likes',distinct=True)).all().order_by('-id')
You can perform a .select_related(…) [Django-doc] on the user and the people with user__people, so:
posts = Post.objects.select_related(
'user__people', 'category'
).prefetch_related('images_set').annotate(
comments_Count = Count('comments_post',distinct=True),
Count('Likes',distinct=True)
).order_by('-id')
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I'm working on a project in CS50w where I have to show the posts of the user I'm following and I'm getting the following error
ValueError: Cannot use QuerySet for "Following": Use a QuerySet for "User".
models.py:
class Post(models.Model):
"""Tracks all the posts"""
text = models.TextField(max_length=256)
user = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(auto_now_add=True)
class Following(models.Model):
"""Tracks the following of a user"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
following = models.ForeignKey(User, on_delete=models.CASCADE, related_name="followers")
And this how I'm trying to retrieve the posts of the users I'm following:
views.py
# Gets all the posts from the users the current user is following
followings_usernames = Following.objects.filter(user=request.user)
posts = Post.objects.filter(user=followings_usernames)
Any help is appreciated.
You can filter based on a field (Following.user) through a reverse relation (followers) through the Post.user field:
posts = Post.objects.filter(user__followers__user=request.user)
See the Django documentation for lookups that span relationships and the usage of double underscores to separate models and fields.
Try using this, it might help
# Gets all the posts from the users the current user is following
followings_usernames = list(Following.objects.filter(user=request.user).values_list('user', flat=True))
posts = Post.objects.filter(user__in=followings_usernames)
I try to build follow system, when I tried to let the users see the posts of the users which that follow them I got error Unsupported lookup 'following' for CharField or join on the field not permitted
The profiles model
class Account(AbstractBaseUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
date_joined = models.DateTimeField(
verbose_name="date joined", auto_now_add=True
)
following = models.ManyToManyField(
settings.AUTH_USER_MODEL, blank=True, related_name="follow"
)
The posts model
class Video(models.Model):
author = models.ForeignKey(Account, on_delete=models.CASCADE)
video = models.FileField(upload_to='post-videos')
title = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
my view
class home_screen_view(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
logged_in_user = request.user
post = Video.objects.filter(
author__username__following__in=[logged_in_user.id]
).order_by('-created_date')
context = {
'post_list': post,
}
return render(request, "personal/home.html", context)
Your post = ... query is wrong because you are retrieving the author's username (which exists), but then treating it as if this field were a foreign key or m2m with a property following, which is flat out wrong. username is a CharField.
Instead let's try to clarify the concepts that will be useful to making our solution:
Each Video, has an author.
Each author, is a user.
Each user has an m2m relationship following which is the users he follows.
Each user has a REVERSE m2m relationship follow which is the users that follow him.
Therefore in your view, your query should be:
post = Video.objects.filter(
author__follow=logged_in_user
).order_by('-created_date')
With django M2Ms, so long as the logged_in_user is in that list, ...follow=logged_in_user will return true and filter the item in.
Sorry I am quite new to Django.
Ok in my project, every blogpost has their own group of members. You are only a member if your interest status is 'Accepted', and you get auto added to the member list. Every user can "submit interest" to any blogpost.
So now on the account page of the user, i want to query the blog posts that the user is a member to (aka interest status = "accepted")
I also want to query ALL the blog posts that the user has submitted interest to and that they are waiting to be accepted. to (aka interest status = "pending" and not a member yet)
In case you are confused, the members is a field of BlogPost model, and the status is a field in the InterestInvite model :).....
so in my template i want to have the title of ALL those blogpost that I am a member to and those blogpost where my interest is still pending status to be displayed. And when I click on the title, I will be directed to that particular blog post. Can anyone share how this querying can be done?
Problem i am facing is that i cannot use blog_post = get_object_or_404(BlogPost, slug=slug) in my views.py because they will say NameError because slug not defined. And I have no foreign key in my Account model to the BlogPost model. My foreign key is only in the BlogPost model.
Not sure if anyone has any solution to the querying and how I can display the info on the template. Thank you!
models.py
class Account(AbstractBaseUser):
email = models.EmailField(verbose_name="email", max_length=60, unique=True)
username = models.CharField(max_length=30, unique=True)
class BlogPost(models.Model):
title = models.CharField(max_length=50, null=False, blank=False, unique=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(blank=True, unique=True)
members = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="members")
class Interest(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
blog_post = models.ForeignKey(BlogPost, on_delete=models.CASCADE)
class InterestInvite(models.Model):
ACCEPT = "ACCEPT"
DECLINE = "DECLINE"
PENDING = "PENDING"
STATUS_CHOICES = [
(ACCEPT, "accept"),
(DECLINE, "decline"),
(PENDING, "pending"),
]
interest = models.OneToOneField(Interest, on_delete=models.CASCADE, related_name="interest_invite")
status = models.CharField(max_length=25, choices=STATUS_CHOICES, default=PENDING)
urls.py
path('<user_id>/', account_view, name="view"),
views.py
def account_view(request, *args, **kwargs):
context = {}
user_id = kwargs.get("user_id")
try:
account = Account.objects.get(pk=user_id)
context['account'] = account
except:
return HttpResponse("Something went wrong.")
if account:
context['id'] = account.id
context['username'] = account.username
context['email'] = account.email
context['profile_image'] = account.profile_image.url
context['hide_email'] = account.hide_email
context['biography'] = account.biography
blog_posts = BlogPost.objects.filter(author=account)
context['blog_posts'] = blog_posts
return render(request, "account/account.html", context)
account.html
{% if request.user in blog_post.members.all %}
{% endif %}
Django automatically sets an auto primary field in your BlogPost Model. So you can access it via:
blog_post = get_object_or_404(BlogPost, pk=pk)
And since you have a ForeignKey to the user you can query everything like:
user.blogpost_set.all() # get all blog posts of the user
user.interest_set.all() # get all interests of the user
user.interest_set.filter(
interest_invite__status="PENDING"
) # get all interest with status pending of the user
Or you can go through User Model like:
User.objects.filter(
blogpost__interest__interest_invite__status="PENDING"
).distinct() # get all users with interestinvite status PENDING
User.objects.filter(
blogpost=b, blogpost__interest__interest_invite__status="ACCEPTED"
).distinct() # get all users of a specific BlogPost with status ACCEPTED
My models.py looks like this:
class Post(models.Model):
user = models.ForeignKey(User, related_name = 'user_posts')
title = models.CharField(max_length = 140)
votes = models.IntegerField(default = 0)
class Vote(models.Model):
user = models.ForeignKey(User, related_name = 'user_votes')
post = models.ForeignKey(Post, related_name = 'post_votes')
is_voted = models.BooleanField(default = True)
class Meta:
unique_together = [('user', 'post')]
Let me explain how my voting system is set up. When a user votes for a post for the first time, a new Vote object is created. A user can down vote a post if he has voted for it first. In that case, the is_voted property in the Vote object is set to False.
Now in a view, I need a list of Posts which the user has upvoted. Which means a Vote object for the post and user combination exists, AND, the is_voted property of that object is True.
Here's how I am currently trying to do it:
views.py
def user_profile(request, pk):
# Get user using pk
u = User.objects.get(pk = pk)
# Get a list of Votes using the user instance
votes = Vote.objects.filter(user = u, is_voted = True)
# Getting the list of posts using the votes list
post_list = Post.objects.none() # Generating empty list
for vote in votes:
# Adding vote.post to post_list using chaining.
.....
This actually works, but feels very hack-y. Is there any way to generate the query-set without the for loop? I am guessing I can use the related_name, but I am not sure how to do it.
I think this queryset should give all posts upvoted by a user (I haven't tried it though):
votes = Post.objects.filter(post_votes__user=u, post_votes__is_voted=True)
It's a reverse lookup from the model to which points the foreign key to the model which contains it.
you can try this:
post_list = [p.post for p in Vote.objects.filter(user = u, is_voted = True).select_related('post')]