Foreign key backward navigation in Django - 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"

Related

Django full text search with PostgreSQL, across multiple different models

I need to perform FTS across multiple different models. I want to get any model type in search result.
I would like to sort results by rank, to get most relevant result. I can run search one by one, but not sure how can I combine results, especially preserving rank relevancy.
Here are models, its an example from Making queries manual page.
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField()
number_of_pingbacks = models.IntegerField()
rating = models.IntegerField()
You can combine your multiple querysets into one with something like this.
from itertools import chain
blogs = Blog.objects.filter(...)
authors = Author.objects.filter(...)
entries = Entry.objects.search(...)
chain = chain(blog_results, lesson_results, profile_results)
qs = sorted(chain, key=lambda instance: instance.pk, reverse=True)

ForeignKey Reverse relation query

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.

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)

Django Many to Many Relationship backward querying

I have 2 models named 'Author' and 'Entry' as defined below.
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
msgtoauthor = models.TextField()
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateTimeField()
authors = models.ManyToManyField(Author)
I am trying to access 'Author.msgtoauthor' from 'Entry'.
I know, I can retrieve the relationship between Entry and Author by
e = Entry.objects.get(authors)
Is it possible to extract the author id?
I know in the backend, Django creates a table for Authors and Entries but I want to update 'msgtoauthors' from a method in 'Entry'.
Thanks In Advance.
Did you mean
for author in my_entry.authors.all():
author.msgtoauth = 'Here is new content'
author.save()
?
Entry.authors returns a RelatedManager and my_entry.authors.all() is a QuerySet, that returns the Author objects. See https://docs.djangoproject.com/en/1.3/ref/models/relations/ and https://docs.djangoproject.com/en/1.3/topics/db/models/#many-to-many-relationships.
(Updated.)
Try this:
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateTimeField()
authors = models.ManyToManyField(Author)
def msgtoat(self, message):
self.authors.update(msgtoauthor=message)
For example, to update an entry:
entry.msgtoat('Hello')
If all the authors get the same value you can do:
entry.authors.update(msgtoauthor='Hey there!')
https://github.com/Ry10p/django-Plugis/blob/master/courses/models.py
line 52
class name():
the_thing1 = models.CharField()
another = models.TextField()
class name2():
the_thing2 = models.ForignKey(the_thing1)
another2 = models.ForignKey(another)
-Cheers

django sql join with like

I have following models
class Artist(models.Model):
name = models.CharField(max_length=200, unique=True)
photo = models.CharField(max_length=250)
views = models.IntegerField()
class Meta:
verbose_name = "artist"
ordering = ['-views']
def __unicode__(self):
return self.name
class Song(models.Model):
artist = models.ForeignKey("Artist")
name = models.CharField(max_length=250)
lyrics = models.TextField(max_length=255)
path = models.CharField(max_length=200)
views = models.IntegerField()
date = models.DateField()
class Meta:
verbose_name = "song"
ordering = ['-views']
def __unicode__(self):
return self.name
and would like to get all the rows that match the keyword.
I can do it in sql and returns what I want. here is the sql;
SELECT song.name, song.path FROM song JOIN artist on song.artist_id = artist.id WHERE artist.name LIKE %keyword% || song.name LIKE %keyword% || song.lyrics LIKE %keyword%
but could not figure out how to do that in django. should I use custom sql? do you guys have better idea?
thanks in advance
matching_songs = Song.objects.filter(Q(name__contains=keyword) | Q(lyrics__contains=keyword) | Q(artist__name__contains=keyword))
See Django docs on Q objects for more details
After 2 hours of digging and asking myself why I am not getting expected results, I realised that __contains is case-sensitive while __icontains is not.
So, for example if we have person named "torres" in database and supply "Torres" to __contains then it will return nothing while __icontains will return "Torres".