Django efficient Queryset with Foreign Key Models - python

I'm trying to find the most efficient way (as less db queries as possible) for the following model structure.
In my template I then want to pass all the data from all 3 models because I would have to show the post data as well as looping through the comments to create a comments list and display all the attachments for the different comments.
class Post(BaseModel):
user = models.ForeignKey('User', blank=True, null=True,
title = models.CharField(max_length=128)
content = models.TextField()
class Comment(BaseModel):
post = models.ForeignKey('Post', on_delete=models.CASCADE)
user = models.ForeignKey('User', on_delete=models.SET_NULL)
text = models.TextField()
class CommentAttachment(BaseModel):
comment = models.ForeignKey('Comment', on_delete=models.CASCADE)
name = models.CharField(max_length=128)
Should I fetch all data from CommentAttachment direction (meaning fetching all CommentAttachments where comment__post__id is the post id and then get all other data with select_related) or is there another way to start from the Post Model?

You can use prefetch_related or select_related in your query:
posts = Post.objects.filter(user=some_user).prefetch_related(
'comment_set', 'comment_set__commentattachment_set'
)
For example, after making a query as mentioned, the following command may retrieve all the comments for the first post in the queryset without making a SQL query:
posts.first().comment_set.all()

Related

Django - Query by foreign key in views

I am trying to make a button on the post that when a user cliks on it, is requesting to be added to the post as an attendance and then, the author of the post has to approve that request.
Models.py
class Attending(models.Model):
is_approved = models.BooleanField(default=False)
attending = models.ManyToManyField(User, related_name='user_event_attending')
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField(blank=True)
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
attending = models.ForeignKey(Attending, on_delete=models.CASCADE, verbose_name='atending', null=True)
My problem here is that every time I writte a query for the button is giving me erros and I couldn`t figure it out how to get the reverse of the foreign key.
This is my code on my views.py
def request_event(request, pk):
previous = request.META.get('HTTP_REFERER')
try:
query = Attending.objects.get(pk=pk)
request_attending = query.post_set.add(request.user)
messages.success(request, f'Request sent!')
return redirect(previous)
except query.DoesNotExist:
return redirect('/')
Thank you very much for your help in advance!
This: query.post_set is just relationship. You cannot call method add just like that. You can add to ManyToMany relation and I believe you want to add user to Attending.attending field, not directly to Post object. Change that to:
...
query = Attending.objects.get(pk=pk)
query.attending.add(request.user)
messages.success(request, f'Request sent!')
....
| Update |
I think you should consider rearraning your relationships. If I understand your plan, you should go this way:
class Attending(models.Model):
...
attendant = models.ForeignKey(User, related_name='events_attending', on_delete=models.CASCADE)
post = models.ForeignKey('Post', on_delete=models.CASCADE)
class Post(models.Model):
...
author = models.ForeignKey(User, on_delete=models.CASCADE)
For one Post object there can be many Attending objects, then you can use relations like that:
att = Attending.objects.first()
att.post # get related Post object from ForeignKey | there is only one
post = Post.objects.first()
post.attending_set.all() # get all related Attending objects
Post.objects.get(attending=att) # get Post object that the Attending object have in ForeignKey field
user = User.objects.first()
user.post_set.all() # get all Post objects that User is author in
user.events_attending.all() # get all related Attending objects
For more check Django Docs.

Add entries to model with ForeignKey

I been struggling with this for a while and can't seem to find an answer on any of the other threads.
I am trying to programmatically add some entries to a model in Django, the model I am trying to add to has a foreign key, and this is what I am getting hung up on.
My two models are:
class Post(models.Model):
direct_url = models.URLField(unique=True)
post_url = models.URLField()
post_title = models.CharField(max_length=300)
time_posted = models.DateTimeField(default=timezone.now)
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
content = models.CharField(max_length=500)
author = models.CharField(max_length=60)
date_created = models.DateTimeField(default=timezone.now)
I am trying to run some code to add some data I am pulling from another location in the DetailView (Class based view)
The code that I have for that is here:
class PostDetailView(DetailView):
model = Post
for i in hot:
if i.url.endswith(Endings):
post_to = model.objects.get(direct_url=i.url)
submission = reddit.submission(url=f'https://www.reddit.com/r/{i.permalink}')
submission.comments.replace_more(limit=None)
for comment in submission.comments.list():
Comment.objects.create(post=f'{post_to}', content=f'{comment.body}', author=f'{comment.author}', date_created=f'{datetime.datetime.fromtimestamp(comment.created)}')
I am trying to pull the reddit comments, and store them in a database. The problem I am running into is the following:
ValueError: Cannot assign "'Post object (22)'": "Comment.post" must be a "Post" instance.
What am I doing wrong?
As per this meta directive, I have turned the following comment by #Sajad into a community wiki, signalling that this question is solved.
In the last line, you're formatting post_to to string and assigning it to post which must be a Post instance and not str. Just write post=post_to.
The following code should work:
class PostDetailView(DetailView):
model = Post
for i in hot:
if i.url.endswith(Endings):
post_to = model.objects.get(direct_url=i.url)
submission = reddit.submission(url=f'https://www.reddit.com/r/{i.permalink}')
submission.comments.replace_more(limit=None)
for comment in submission.comments.list():
Comment.objects.create(post=post_to, content=f'{comment.body}', author=f'{comment.author}', date_created=f'{datetime.datetime.fromtimestamp(comment.created)}')

Retrieve all values from join?

I have 3 tables. User, Post, and Share. Post is a table that contains all posts. Post has a one to many relationship with Share. That being said, the Share table is a table where it indicates which posts a user has shared. Here's the structure of the tables:
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.CharField(max_length=200)
share_count = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
url = models.CharField(max_length=150)
class Share(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
shared_at = models.DateTimeField(auto_now_add=True)
Let's say we have a User with id 1. What I want to do is to show all posts + shares of that user ordered by date. Just like how Twitter does when you go to a User's Twitter profile. It shows all their Tweets + their Retweets ordered by date and time. What I've tried doing is this query:
Share.objects.all().select_related('post').get(user=1)
If I do this, I get an error that there is more than one share for this user. What am I doing wrong?
The error:
Example.models.MultipleObjectsReturned: get() returned more than one
Share -- it returned 2!
You should replace get() with filter()
See documentation for get and filter
.get() will return an error if none or more than one object is returned. Use .filter() instead to get a QuerySet:
Share.objects.all().select_related('post').filter(user=1)

In Django, how do I get all the instances of a model that have other instances of a related model attached to it with a ForeignKey field?

I am trying to write a query that retrieves all the categories that have at least one post attached to it. In other words, I want to 'exclude' any category that doesn't have any post.
These are my models for Category and Post:
class Category(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique=True)
class Post(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250, unique=True)
body = models.TextField()
category = models.ForeignKey(Category, blank=True, default="")
This is the code I am using in my query, currently it fetches all the categories, even if no Post is 'attached' to it:
categories = Category.objects.all()
What I want is something like this:
categories = Category.objects.filter(
# Only ones that have at least one Post that has it's 'category' field set to it.
)
I've searched the docs and everywhere else, but I can't figure out a solution.
Please let me know how this can be done.
You can use following query:
categories = Category.objects.filter(post__isnull=False).distinct()
This will get all the categories where post is not null. Since, one category could have multiple posts, you will get duplicate instances with same id. Take a distinct to remove duplicate categories.
Note, that distinct(*fields) is postgresql specific. If you are using a different database, just use distinct().
Get all unique category ids by querying Post for distinct category and then filter Category by ids.
id_list = Post.objects.values_list('category_id').distinct()
catgories = Category.objects.filter(id__in=id_list)

What's the best django way to do a query that spans several tables?

I have a reviews/ratings web application, a la Digg. My django app content has the following model:
class Content(models.Model):
title = models.CharField(max_length=128)
url = models.URLField(max_length=2048)
description = models.TextField(blank=True)
class Recommendation(models.Model):
user = models.ForeignKey(User)
content = models.ForeignKey(Content)
review = models.TextField()
rating = models.PositiveSmallIntegerField()
class Meta:
unique_together = ('user', 'content')
class Subscription(models.Model):
subscriber = models.ForeignKey(User, related_name='subscription_set')
publisher = models.ForeignKey(User, related_name='publication_set')
class Meta:
unique_together = ('subscriber', 'publisher')
I want to construct a page with all the recommendations of all the users to whom a current user (request.user) subscribes.
If I write this in SQL, I believe I'll end up with a query similar to the following:
select content_content.*, content_recommendation.*, auth_user.*
from content_content, content_recommendation, content_subscription, auth_user
where content_content.id = content_recommendation.content_id
and content_recommendation.user_id = content_subscription.publisher_id
and content_subscription.subscriber_id = ?
and auth_user.id = content_subscription.publisher_id;
How would I express this using Django's query APIs? I've read the docs, but just can't get my head around it.
I would use:
Recommendation.objects.filter(user__publication_set__subscriber=request.user).select_related()
That will get you all the Recommendation objects as you requested, and the select_related will load all the related User and Content objects into memory so that subsequent access of them won't hit the DB again.
How you'd contruct this query really has a lot to do with your handling of the returned data afterwards though. It may be more or less efficient to go one way vs. another based on what you do with it.alt text http://sonicloft.net/im/52
I think that it's:
Content.objects.filter(recommendation_set__user__publication_set__subscriber__pk=request.user.pk)/.distinct()/
or
Recommendation.objects.filter(user__publication_set__subscriber__pk=request.user.pk)/.distinct()/
-- depending on instances of which model you want to get. Distinct() might be needed to avoid duplicates.

Categories