简体   繁体   中英

Django Queryset with annotate

I am writing one method in Django Manager model. I want to write method that finds out number of all sold copies (books) per author.

I have two models and method written in Manager . My problem is that method should also be chainable from any Author queryset, for example something like

Author.objects.filter(...).exlucde(...).total_copies_sold()

should also work.

Example:

author = Author.objects.create(...)
Book.objects.create(..., author=author, copies_sold=10)
Book.objects.create(..., author=author, copies_sold=20)

author_total_books = Author.objects.total_copies_sold().first()
>>> author_total_books.copies
30

Below my code. It works like in example above, but then I try something like:

author_books = Author.objects.filter(id=2).total_copies_sold()

I got

'QuerySet' object has no attribute 'annotate'

class AuthorManager(models.Manager):

    def total_copies_sold(self):
        return self.get_queryset().annotate(copies=Sum('book__copies_sold')



class Author(models.Model):
    first_name = models.CharField(max_length=120)
    last_name = models.CharField(max_length=120)

    objects = AuthorManager()


class Book(models.Model):
    title = models.CharField(max_length=120)
    copies_sold = models.PositiveIntegerField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

[Edited]

Thank you schillingt for reply. I added:

class AuthorQueryset(models.QuerySet):
    def total_copies_sold(self):
        return self.annotate(copies=Sum('books__copies_sold'))

I tried something like:

author_books = Author.objects.filter(id=2).total_copies_sold()

>>> author_books.copies

I got

'AuthorQueryset' object has no attribute 'copies'

You need to use Manager.from_queryset to set your manager. Here are the docs .

class AuthorQueryset(models.QuerySet):
    def total_copies_sold(self):
        ...

class Author(models.Model):
    objects = models.Manager.from_queryset(AuthorQueryset)()

What you are lookig for is :

from django.db import models
from django.db.models import Sum
from django.db.models.functions import Coalesce


class AuthorManager(models.Manager):
    def get_queryset(self):
        return AuthorQuerySet(self.model, using=self._db)

    def annotate_with_copies_sold(self):
        return self.get_queryset().annotate_with_copies_sold()


class AuthorQuerySet(models.QuerySet):
    def annotate_with_copies_sold(self):
        return self.annotate(copies_sold=Sum('books__copies_sold'))


class Author(models.Model):
    objects = AuthorManager()
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)


class Book(models.Model):
    title = models.CharField(max_length=30)
    copies_sold = models.PositiveIntegerField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')

Now it is possible to chain queries eg:

author_total_books = Author.objects.total_copies_sold().first()

However you will no be able to use it on QuerySet object like:

author_books = Author.objects.filter(id=2).total_copies_sold()

That is because you are annotating Author object, not a QuerySet. To obtain that result you should execute:

Author.objects.annotate_with_copies_sold().get(id=2)
author.copies_sold 
15
from django.db import models
from django.db.models import Sum
from django.db.models.functions import Coalesce

class AuthorManager(models.Manager):
    def get_queryset(self):
        return AuthorQuerySet(self.model, using=self._db)
    
    def annotate_with_copies_sold(self):
        return self.get_queryset().annotate_with_copies_sold()

class AuthorQuerySet(models.QuerySet):
    def annotate_with_copies_sold(self):
        # Write your solution here
        return self.annotate(copies_sold=Coalesce(Sum('books__copies_sold'), 0))


class Author(models.Model):
    # Make sure this manager is available.
    objects = AuthorManager()
    # objects = models.Manager.from_queryset(AuthorQuerySet)()
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)


class Book(models.Model):
    title = models.CharField(max_length=30)
    copies_sold = models.PositiveIntegerField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    enter code here
class AuthorManager(models.Manager):
    def get_queryset(self):
        return AuthorQuerySet(self.model, using=self._db)

    def annotate_with_copies_sold(self):
        return self.get_queryset().annotate_with_copies_sold()

class AuthorQuerySet(models.QuerySet):
    def annotate_with_copies_sold(self):
        return self.annotate(copies_sold=Sum('Book_Author__copies_sold'))


class Author(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    objects = AuthorManager()

    class Meta:
        verbose_name_plural = "Author" 
        verbose_name = 'Author'
        ordering = ('first_name','last_name')

class Book(models.Model):
    title = models.CharField(max_length=250, unique=True)
    copies_sold = models.PositiveIntegerField(default=0)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='Book_Author')

    class Meta:
        verbose_name_plural = "Book" 
        verbose_name = 'Book'
        ordering = ('title','copies_sold')




from vehicles.models import Author, Book
try:
    author  = Author.objects.get(first_name='Mark', last_name='Twain')
except Author.DoesNotExist:
    author  = Author.objects.create(first_name='Mark', last_name='Twain')

try:
    book = Book.objects.get(author=author, title='Adventrure of Huckleberry Finn', copies_sold=7)
except Book.DoesNotExist:
    book = Book.objects.create(author=author, title='Adventrure of Huckleberry Finn', copies_sold=7)
    pass
try:
    book = Book.objects.get(author=author, title='Adventrure of Tomm Saywer', copies_sold=4)
except Book.DoesNotExist:
    book = Book.objects.create(author=author, title='Adventrure of Tomm Saywer', copies_sold=4)
    pass

author = Author.objects.annotate_with_copies_sold().first()
print(author.copies_sold)
11

1.Create AuthorManager, AuthorQuerySet classes from Author and Books
2.Create Author, Book models 
3.Prepare Test Data and use model manager to filter the queryset

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM