I have a fairly straightforward issue.
I have three models:
class Author(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class Publisher(models.Model):
name = models.CharField(max_length=300)
class Book(models.Model):
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
The user can filter books and selects a filter for "Publisher-A" .Now I want to update the displayed filters for authors - not showing all authors - but only the ones that that published a book with the selected publisher. So I need to retrieve a list of unique authors from the queryset, I suppose.
I found this post - but it seems a like a hack to me. Isn't there a standard solution for this? The problem seems so straightforward.
Thanks.
You can obtain a list of Authors for a given Publisher with:
Author.objects.filter(book__publisher=my_publisher).distinct()
Where my_publisher is the publisher. The .distinct() will guarantee that an Author appears at most once in the queryset. This is necessary if an Author has written multiple books for my_publisher. If you would omit .distinct() it will appear several times: exactly the number of times that Author wrote a book for that publisher.
Related
My models.py:
class Author(models.Model):
name = models.CharField(max_length=100)
books = models.ManyToManyField(
"Book", related_name="books", blank=True
)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ManyToManyField(Author)
In Django admin I first created an instance of Author, without assigning him any Book:
Then I created a new Book and assigned Leo Tolstoy as theAuthor:
In Django shell, I am able to access the Book's Author:
>>> Book.objects.filter(title="War and Peace").first().author.first()
<Author: Leo Tolstoy>
But I'm unable to access the Author's Books:
>>> Author.objects.filter(name="Leo Tolstoy").first().books.all()
<QuerySet []>
The book isn't assigned in the Admin view either:
I would like to access the Author's Books as well as for the Books
to show in the Author's Admin view.
You only need one ManyToManyField. You should also pass "books" as the related_name on Book.authors as this is the name Django will use for the many-to-many manager attribute it automatically adds to Author instances.
See the example in the Django docs for more info.
When a ManyToManyField is created on both sides of a relation, Django is creating two associative tables between the book and author tables under the hood. Changes to one table don't affect the other.
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(
Author, related_name="books", blank=True
)
I suggest you do this:
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ManyToManyField(Author, related_name="books",blank=True)
You need one ManyToManyField, then django will create two associative tables between the book and author tables. Changes to one table will not affect the other. You can see in the django documentation about many to many relationships. I have removed your books field in my answer because you have created an instance of your Author but you did not assign his 'book' and you have created a Book instance with giving title and author in your Book model. So so that any of your data won't be deleted. I also added related_name to your author field in your Book model because when you try to get the books related to the author (for example "Loe Tolstoy") you can get by this:
Author.objects.get(name="Leo Tolstoy").books.all()
Please I need help with getting the result of querying my ManyToMany fields.
Basically I have the following models:
class Book(models.Model):
title = models.CharField(max_length=50)
authors = models.ManyToManyField(Author)
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
How should I query my book model to show all the authors of each book, based on the book_id and without the book_id, and how to query all the books that were written by one author?
I have tried to follow the Docs where Pizza and Topping but I'm not getting it, so thanks in advance friends and have a good day.
To get all the authors given a book you just do:
my_book.authors.all()
The second query is much more complicated: you need to use annotations to get the count of authors for each book, and filter on those whose count is one:
from django.db.models import Count
Book.objects.annotate(author_count=Count('authors')).filter(author_count=1)
For getting all the authors of a book using ManyToManyField you should use related_name attribute of your Book. authors field. ex: authors = models.ManyToManyField(Author, related_name="Books").
And then the query is my_author.books.all() which is syntactic sugar of my_author.book_set.all()
If having different books stored in django database, each book has a date in which it was added to the database. Is their a way of filtering books written by a certain author that was within a date range only using django views?
Not sure what you mean by only django views, I assume you want to use querysets. Your question is poorly written - read this.
class Book(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField()
author = models.ForeignKey(Author)
class Author(models.Model):
name = models.CharField(max_length=200)
And Queryset would be something like this.
books = Book.objects.filter(author__name=authors_name,
date__range=["2011-01-01", "2011-01-31"])
Given the following models:
class Author(models.Model):
name = models.CharField()
class Book(models.Model):
title = models.CharField()
published_year = models.PositiveIntegerField()
authors = models.ManyToManyField(Author)
Let's say I want to get all of the authors who have authored a book published in the year 2008. I can do the following:
Book.objects.filter(published_year=2008).values_list('authors__name').distinct()
That'll get me a list of authors - almost exactly what I want, except that instead of just the names, I want the Author objects. I can achieve that somewhat by doing this:
authors = []
for b in Book.objects.filter(published_year=2008):
for a in b.authors.all():
if a not in authors:
authors.append(a)
But that seems totally unnecessary. Is it possible to get the QuerySet to do that work for me? Thanks!
Just use backward relationship
Author.objects.filter(book__published_year=2008).all()
From Django docs
Reverse m2m queries are supported (i.e., starting at the table that
doesn’t have a ManyToManyField):
In my Django app I allow users to create collections of movies by category. This is represented using 3 models, Movie, Collection, and Addition (the Addition model stores movie, collection, and user instances). Simplified versions of all three models are below.
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
class Addition(models.Model):
user = models.ForeignKey(User)
movie = models.ForeignKey(Movie)
collection = models.ForeignKey(Collection)
So for example a user could create a collection called "80's movies", and add the movie "Indiana Jones" to their collection.
My question is: how do I display a distinct list of movies based on a set of query filters? Right now I am getting a bunch of duplicates for those movies that have been added to more than one collection. I would normally use distinct() to get distinct objects, but in this case I need distinct movies rather than distinct additions, but I need to query the Addition model because I want to allow the user to view movies added by their friends.
Am I setting up my models in an optimal way? Any advice/help would be much appreciated.
Thanks!
First. I don't think you need Addition model here. You try to create many-to-many relation, but there's documented way of doing this:
class Movie(models.Model):
name = models.CharField(max_length=64)
class Collection(models.Model):
name = models.CharField(max_length=64)
user = models.ForeignKey(User)
movies = models.ManyToManyField('Movie', blank=True, null=True)
Second. The documentation says: "To refer to a "reverse" relationship, just use the lowercase name of the model".
So the answer is (for the setup above):
Movie.objects.filter(collection__user=user).distinct()