Opened 12 months ago

Closed 12 months ago

Last modified 11 months ago

#30052 closed Bug (invalid)

QuerySet.only() doesn't support annotations

Reported by: Kal Sze Owned by: nobody
Component: Database layer (models, ORM) Version: 2.1
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Example:

from django.db import models


class School(models.Model):
    full_name = models.TextField()


class Student(models.Model):
    full_name = models.TextField()
    date_of_birth = models.DateField()
    school = models.ForeignKey(School, on_delete=models.PROTECT)

And then:

from django.db.models import Subquery, OuterRef
from schools.models import School, Student

eldest_student_name = Student.objects.filter(school=OuterRef('id')).order_by('date_of_birth').values('full_name')[:1]

schools = School.objects.annotate(eldest_student_name=Subquery(eldest_student_name)).only('id', 'eldest_student_name')

for school in schools:
    # This line results in FieldDoesNotExist: School has no field named 'eldest_student_name'
    print(school.id, school.eldest_student_name)

Not sure if this is a bug or an (undocumented) limitation of only() (and maybe of defer(), by extension).

Change History (5)

comment:1 Changed 12 months ago by Tim Graham

Resolution: invalid
Status: newclosed
Summary: QuerySet's `only()` method does not work with annotated fieldsQuerySet.only() doesn't support annotatations
Type: UncategorizedBug

As far as I tested, annotations don't need to be included in only() in order to be included in the query. The documentation for only() and defer() only discuss using it with fields, not with annotations.

comment:2 Changed 12 months ago by Tim Graham

Summary: QuerySet.only() doesn't support annotatationsQuerySet.only() doesn't support annotations

comment:3 in reply to:  1 Changed 11 months ago by Kal Sze

Replying to Tim Graham:

As far as I tested, annotations don't need to be included in only() in order to be included in the query. The documentation for only() and defer() only discuss using it with fields, not with annotations.

I know it doesn't need to be included, but I thought the point of only() and defer() is to exclude the fields that we are not interested in, when executing the initial database query. Having only() support annotations is useful if I'm only interested in the annotated field, and not in the regular fields.

EDIT: unless you mean that I can defer the regular fields if I call only() with no argument at all, and still get the annotated fields immediately?

Last edited 11 months ago by Kal Sze (previous) (diff)

comment:4 Changed 11 months ago by Tim Graham

I'm not sure. Please test only() without arguments. If it works, it might be worth adding tests for (if not already in Django's tests) and documenting.

comment:5 in reply to:  4 Changed 11 months ago by Kal Sze

Replying to Tim Graham:

I'm not sure. Please test only() without arguments. If it works, it might be worth adding tests for (if not already in Django's tests) and documenting.

So I did a quick TIAS and here is what I found out, by printing QuerySet.query:

  • QuerySet.only() without any argument doesn't defer any field; it would select every field of the model plus any annotation added to the QuerySet;
  • QuerySet.only('some_field') would select the id, some_field, plus any annotation added to the QuerySet.

Ideally, it would be great if only() behaved in one of two ways:

  • without any argument, defer all fields except the id and annotations;

or

  • accept annotated field names

But it might break old apps and it's too late to change.

Note: See TracTickets for help on using tickets.
Back to Top