Opened 6 years ago

Closed 6 years ago

Last modified 16 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 (7)

comment:1 by Tim Graham, 6 years ago

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 by Tim Graham, 6 years ago

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

in reply to:  1 comment:3 by Kal Sze, 6 years ago

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 6 years ago by Kal Sze (previous) (diff)

comment:4 by Tim Graham, 6 years ago

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.

in reply to:  4 comment:5 by Kal Sze, 6 years ago

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.

comment:6 by GitHub <noreply@…>, 16 months ago

In b126f69:

Refs #30052 -- Clarified that defer() and only() do not work with aggregated fields.

comment:7 by Natalia <124304+nessita@…>, 16 months ago

In da92a971:

[4.2.x] Refs #30052 -- Clarified that defer() and only() do not work with aggregated fields.

Backport of b126f694160c4641e64e57dba6b022f06fbfa745 from main

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