ForeignKey Reverse relation query - django

class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __unicode__(self):
return self.title
i want to get all Publishers who has published at least one book.

Publisher.objects.filter(book__isnull=False).distinct()
This does a JOIN between the two tables and returns the rows where a book exists. distinct() is used to get rid of duplicate Publishers.

Related

Syntax to reverse-query a cached queryset

I have the following 3 models related by Foreign Key as following:
class Seller(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Genre(models.Model):
seller= models.ForeignKey(Seller, related_name="genre", on_delete=models.CASCADE)
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Book(models.Model):
genre= models.ForeignKey(Genre, related_name="book", on_delete=models.CASCADE)
name = models.CharField(max_length=20)
def __str__(self):
return self.name
And I want to retrieve the whole 3 tables in one database query, by querying the Seller objects, as following:
sellers = Seller.objects.select_related('genre', 'book').all().values('name')
seller_df = pd.DataFrame(list(sellers))
What is the syntax to filter for books carried by a particular seller, without hitting the database again (by utilizing either the Seller queryset or the pandas seller_df)
seller1 = seller_df ['name'].iloc[0]
seller1_books = Book.objects.filter(...)
seller_last = seller_df ['name'].iloc[-1]
seller_last_books = Book.objects.filter(...)
I dont know so mach about caching but I know something that you like:
We use select_related when the object is single like onetoone or fk.
.
for many to many or reverse fk like your example use prefetch_related

Django referencing a specific object in a many-to-one relationship

Let's say I have the following models:
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
I'd like to add a favorite_article field to my Reporter model that will reference a specific Article from reporter.articles.
One option is put the information into the Article model instead:
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
is_favorite = models.BooleanField()
But this doesn't seem like a very clean solution. Is there a better method to do this?
The approach you've suggested will work, however in its current form it allows for multiple Articles to be the favorite of one Reporter. With a bit of extra processing you can ensure that only one (at most) Article per Reporter is the favorite.
Making a few modifications to a couple of the answers to the question Unique BooleanField value in Django? we can restrict one True value per Reporter rather than one True value for the entire Article model. The approach is to check for other favorite Articles for the same Reporter and set them to not be favorites when saving an instance (rather than using a validation restriction).
I'd also suggest using a single transaction in the save method so that if saving the instance fails the other instances are not modified.
Here's an example:
from django.db import transaction
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
is_favorite = models.BooleanField(default=False)
def save(self, *args, **kwargs):
with transaction.atomic():
if self.is_favorite:
reporter_id = self.reporter.id if self.reporter is not None else self.reporter_id
other_favorites = Article.objects.filter(is_favorite=True, reporter_id=reporter_id)
if self.pk is not None: # is None when creating a new instance
other_favorites.exclude(pk=self.pk)
other_favorites.update(is_favorite=False)
return super().save(*args, **kwargs)
I've also changed the approach to use a filter rather than a get just in case.
Then to get the favorite article for a reporter, you can use:
try:
favorite_article = reporter.articles.get(is_favorite=True)
except Article.DoesNotExist:
favorite_article = None
which you could wrap into a method/property of the Reporter class.

Django Queryset of related objects, after prefiltering on original model

Given a queryset for one model, I want to get a queryset of another model that is related by foreign key. Take the Django project docs' weblog schema:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __unicode__(self):
return self.headline
Suppose I have an author object, and I want to get every blog that author has written for, as a queryset. I do something like author_blogs = [entry.blog for entry in author.entry_set]. But I'm left with a list in this case, not a queryset. Is there a way I can do this directly with ORM queries, so I can set it up via a custom Entry manager with use_for_related_fields = True and do something like author_blogs = author.entry_set.blogs, and get the benefits of delayed evaluation, etc., of a queryset?
Edited scenario and solution
So, I realized after the fact that the application of my question is slightly different than how I posed it above, for which Daniel Roseman's situation makes a lot of sense. My situation is really more like author.entry_set.manager_method().blogs, where manager_method() returns a queryset of Entry objects. I accepted his answer because it inspired the solution I found which is to do:
author_blogs = Blog.objects.filter(entry__in=author.entry_set.manager_method())
The nice thing is that it only uses one DB query. It's a bit tricky and verbose, so I think it's best to define blogs() as an object method of Author, returning the above.
The trick for this is to remember that if you want a queryset of Blogs, you should start with the Blog model. Then, you can use the double-underscore syntax to follow relations. So:
author_blogs = Blog.objects.filter(entry__authors=author)

Model with Foreign keys not behaving as expected - Django

I have a model with two foreign keys to create many to many relationship - I am not using many to many field in Django
class Story(models.Model):
title = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.title
class Category(models.Model):
categoryText = models.CharField(max_length=50)
parentCat = models.ForeignKey('self',null=True,blank=True)
def __unicode__(self):
return self.categoryText
class StoryCat(models.Model):
story = models.ForeignKey(Poll,null=True,blank=True)
category = models.ForeignKey(Category,null=True,blank=True)
def __unicode__(self):
return self.story
I would like to query for a category like 'short', and retrieve all the unique keys to all stories returned.
>>>c=Category(categoryText='short')
>>>s=StoryCat(category=c)
when I try this I get errors "AttributeError: 'NoneType' object has no attribute 'title'. How can I do this?
I would like to query for a category like 'short', and retrieve all the unique keys to all stories returned.
c=Category.objects.get(categoryText='short')
story_ids = StoryCat.objects.filter(category=c).values_list('story')
And about your models:
Category name should probably be unique. And declare your many-to-many relation.
class Category(models.Model):
categoryText = models.CharField(max_length=50, unique=True)
stories = models.ManyToManyField(Story, through='StoryCat')
...
It makes no sense for intermediary table FK fields to be nullable. Also I assume the same story should not be added to the same category twice, so set a unique constraint.
class StoryCat(models.Model):
story = models.ForeignKey(Poll)
category = models.ForeignKey(Category)
class Meta:
unique_together = ('story', 'category')
The lines you're executing in the interpreter are not queries - they are instantiating new objects (but not saving them).
Presumably you meant this:
>>>c=Category.objects.get(categoryText='short')
>>>s=StoryCat.objects.get(category=c)

Foreign key backward navigation in Django

For this example:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, related_name='entries')
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateTimeField()
mod_date = models.DateTimeField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __unicode__(self):
return self.headline
So, how to find all Blogs with its Entries?
I want to do something like this
q = Blog.objects.all().entries.filter(...)
But it gave me an error. So does Django only supports to use the backward navigation properties for only one object rather than a set of objects?
Let's say you want to filter on the entries of all blogs, and get the blogs that have entries you require:
Blog.objects.filter(entry__headline__icontains="cats").distinct()
There is a one blog to many entries relationship here, but this query works and gives you the blogs that have entries with cats in headlines.
If you want to get all entries associated with each Blog, you can do something like:
blogs = Blog.objects.all()
for blog in blogs:
blog.cached_entries = blog.entry_set.all()
Although this looks neat, n + 1 queries will be made -- 1 query for getting all blogs plus n queries for getting each entry associated with the blog.
It is possible to do this in just one query, but you'll have to do more work if you want to segregate entries by blog.
blog_with_entries = []
entries = Entry.objects.order_by('blog')
previous_blog = None
acc_entries = [] # accumulate entries of one blog
for entry in entries:
if previous_blog != entry.blog:
if previous_blog is not None:
blog_with_entries.append((previous_blog, acc_entries))
acc_entries = []
previous_blog = entry.blog
acc_entries.append(entry)
blog_with_entries.append((previous_blog, acc_entries))
Yes.
"Lookups that span relationships"