Search code examples
djangodjango-ormdjango-annotate

Django - Query : Annotate Queryset with Related Model fields


I have following Schema in Django with PostgreSQL.

Class Person (models.Model):
    name = models.CharField(max_length=255)
    email= models.CharField(max_legth = 255)
    created_at = models.DateTimeField()

Class PersonTask(models.Model):
   person = models.ForeignKey(Person)
   title = models.TextField()
   created_at = models.DateTimeField()

Class PersonHistory(models.Model):
   person = models.ForeignKey(Person)
   note = models.TextField()
   created_at = models.DateTimeField()

Now I need to query the DB like all values of Person with latest PersonTask__title as max_task and latest PersonHistory__note as max_note

Eg:

<Queryset: [
{name: "abc" ,email:"[email protected]",created_at :"2019-01-02", max_task:"This is my latest tasktitle" , max_note: "This is my latest history note"},
{name: "abcd" ,email:"[email protected]",created_at :"2019-03-02", max_task:"This is my latest tasktitle for abcd" , max_note: "This is my latest history note for abcd"}
]>

But, I could max get is either id of Latest Task and Latest History by

Person.objects.filter(customer_id= 1).\
               annotate( max_task = Max('persontask')).\
               annotate(max_note = Max('personhistory')).\
               order_by('-id')

Or a random task or note texts using below query

Person.objects.filter(customer_id= 1).\
               annotate( max_task = Max('persontask__title')).\
               annotate(max_note = Max('personhistory__note')).\
               order_by('-id')

How this can be tackled??


Solution

  • As you did not mention the ForeignKeys between these models, I suspect that Task and History have FK to Person in field named person.

    I would use Subquery with combination of OuterRef to tackle this query

    from django.db.models import OuterRef, Subquery
    
    result = (
        Person.objects
            .filter(customer_id=1)
            .annotate(
                task_title=Subquery(Task.objects.filter(person=OuterRef('pk')).order_by('-created_at').values('title')[:1]),
                history_note=Subquery(HistoryNote.objects.filter(person=OuterRef('pk')).order_by('-created_at').values('note')[:1])
            )
            .order_by('-id')
    )