Django group by and count(*), includes zero counts - django

class Author(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
name = models.CharField(max_length=50)
author = models.ForeignKey(Author,related_name='book' on_delete=models.CASCADE)
is_deleted = models.BooleanField(default=False)
I want to display the number of books in each author (include zero counts).
My code:
Book.objects.filter(is_deleted =False).values('author').annotate(num=Count('id'))
but it only returns values greater than zero and lost many authors.
How can I get result like this:
author1:0,
author2:1,
author3:2,
Please help me and thanks in advance ^^.

Maybe you can try like this(using related objects):
authors = Author.objects.annotate(
book_count=Count(
'book',
filter=Q(book__is_deleted=False)
)
)
authors.values('name', 'book_count')

Related

How can I do this complex Django queryset correctly?

Let's say I have these three models:
class Author(models.Model):
name = models.CharField(max_length=64)
class Book(models.Model):
author = models.ForeignKey(
Author,
blank=True,
null=True,
on_delete=models.SET_NULL
)
name = models.CharField(max_length=64)
class Store(models.Model):
books = models.ManyToManyField(Book)
name = models.CharField(max_length=64)
I don't know the author of some books. In these cases, the author is null.
When I query Store, I would like to know how many books each store has and sort them. So my queryset is something like this:
Store.objects.all().annotate(books_count=Count('books')).order_by('-books_count')
Now, what if I want to count only the books that have an author?
I tried this queryset, but it is clearly not correct:
filter = Q(books__author__isnull=False)
Store.objects.annotate(books_count=Count(filter)).all().order_by('-books_count')
Does anyone know the correct way to do this query?
I believe following query is what you are looking for.
books_count = (
Book.objects.filter(
store=OuterRef("pk"),
author__isnull=False,
)
.annotate(count=Func(F("id"), function="COUNT"))
.values("count")
)
Store.objects.annotate(count=Subquery(books_count)).order_by("-count")
You can go the other way around:
Book.objects.filter(author__isnull=False).values('store_set').annotate(count=Count('store_set')).order_by('-count')
I'll take a bit of a wild guess, but im thinking your usage of a Q object might be incorrect inside of theCount.
Count is expecting an expression like object, this could be a string, F, OuterRef, SubQuery and so on.
Store.objects.filter(filter).annotate(
books_count=Count("books")
).order_by('-books_count')
I think this is what you're after.

django-filters using ModelChoiceFilter to get value of ForeignKey

I'm trying to use ModelChoiceFilter to filter a database of letters based on the author. Author is a ForeignKey, and I can't seem to get it to display the "name" value of the ForeignKey.
Here is what I have:
models.py (limited to relevant bits)
class Person(models.Model):
name = models.CharField(max_length=250, verbose_name='Full Name')
...
def __str__(self):
return self.name
class Letter(models.Model):
author = models.ForeignKey(Person, related_name='author', on_delete=models.PROTECT, verbose_name='Letter Author')
recipient = models.ForeignKey(Person, related_name='recipient', on_delete=models.PROTECT, verbose_name='Recipient')
...
title = models.CharField(max_length=250, verbose_name='Title of Letter')
def __str__(self):
return self.title
letter_filters.py
class LetterFilter(django_filters.FilterSet):
...
author = django_filters.ModelChoiceFilter(queryset=Letter.objects.order_by('author__name'))
class Meta:
model = Letter
fields = ['author', 'recipient']
I can see that this kind of works. It is indeed limiting and ordering it properly, but instead of the author name being presented in the select box, it's presenting "title" from the letter (but I can tell from the title, in the proper order).
What I thought should work is this:
fields = ['author__name', 'recipient']
But that too continues to list "title" from Letter instead of "name" from Person.
I know it has what I need, because if I do:
author = django_filters.ModelChoiceFilter(queryset=Letter.objects.order_by('author__name').values('author__name'))
I get exactly what I want! But, it's presented as {'author__name':'Jane Doe'} with fields author or author_name. I just can't seem to get the right syntax.
Finally, I know I can do:
author = django_filters.ModelChoiceFilter(queryset=Person.objects.order_by('name'))
Which returns all Persons, properly ordered. However there are many more persons in the database than just authors. This is the same result as just allowing the default fields['author'... without setting the author= in the class (though unordered).
Well the queryset you specify deals with Letters, so as a result the Letters are in that cases added in the ModelChoiceFiler, which is not ideal at all.
You can however generate a list of Persons that has written at least one letter like:
django_filters.ModelChoiceFilter(
queryset=Person.objects.filter(letter_set__isnull=False).order_by('name').distinct()
)
So here we filter on the fact that the letter_set is not empty, and since this will result in a JOIN where a Person can occur multiple times, we add .distinct() to it.
I find this modeling however very weird (in your three examples). It basically means that you only can assign Persons that already wrote a Letter. What if a person that has never written a Letter wants to write a Letter?
Usually in case there are different such roles, you can for example add a BooleanField:
class Person(models.Model):
name = models.CharField(max_length=250, verbose_name='Full Name')
is_author = models.BooleanField(verbose_name='Is the person an author')
# ...
Then we can filter on Persons that are Authors:
django_filters.ModelChoiceFilter(
queryset=Person.objects.filter(is_author=True).order_by('name')
)

django conditional max for each with foreign key

consider these models:
class Author(models.Model):
name = models.CharField(max_length=200)
class Book(models.Model):
author = models.ForigenKey(Author)
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
active = models.BooleanField(default=True)
I want to query all the Authors, with the latest(e.g Max('created_at') ) book of them in the same row, but only when the book is active=True.
I need to whole related book object - not just the max date.
If there is no such a book - all are active=False, or they simply do not exist - the query should output NULL where needed instead of the a book .
I've tried to write something like this:
Author.objects.annotate(max_book_date=Max('book'))
which does work, but it's missing additional fields on the book, and I'm not sure how it works in case of null (i.e no books), and the active=True condition is not there..
First of all, you should define a related_name param in the author fk field on the Book model:
author = models.ForeignKey(Author, related_name='libro')
Then, you should be able to do something like:
Author.objects.filter(libro__active=True).annotate(max_book_date=Max('libro__created_at')).all()

django: Count objects with the same value

Lets say I have following models:
class Book(models.Model):
name = models.CharField(max_length=70)
lang = models.CharField(max_length=70)
author = models.FK(Author)
class Author(models.Model):
name = models.CharField(max_length=70)
And I want to write something to get a list of authors with annotated field which shows amount of books in each language. Can't imagine an annotation for it :(, e.g. {'en': 10, 'ru': 1...etc}
e.g. just counts all, Author.objects.annotate(languages=Count(book__lang))
Simple annotation should help you:
Book.objects.values('lang').annotate(lang=Count('author')).order_by('lang')

get value from manytomany field in django model?

I Have to tables
class Book(models.Model):
title = models.CharField(max_length=200)
authors = models.ManyToManyField(Individual, related_name="author_for", blank=True, null=True)
illustrators = models.ManyToManyField(Individual, related_name="illustrator_for", blank=True, null=True)
class Unitary_Sale(models.Model):
book = models.ForeignKey(Book)
quantity = models.IntegerField()
unit_price = models.DecimalField(max_digits=15, decimal_places=3)
sale = models.ForeignKey(Sale)
How can report book has been sold by author or illustrator?
by_author = {}
for unit_sale in Unitary_sale.objects.all():
author = unit_sale.book.authors
by_authors[author] = (unit_sale.quantity, unit_sale.quantity *unit_sale.unit_price)
Author Qty Amount($)
A 2 20
A&B 3 30
***one book has many author
Just be mindful about the number of db queries that are executed under the hood. I was hypnotized by the easy way to access and iterate over db relations in my code which resulted in ~900 db queries per one page.
authors is many-to-many, so you'll need to nest another loop. The author object you made like a list, eg:
for unit_sale in Unitary_sale.objects.all():
for x in author:
by_authors[x] = ....
Edit: actually, I noticed a mistake in how you are creating author. It should be:
author = unit_sale.book.authors.all()
Then you can use a for loop to iterate through all the Author objects as above.