Opened 5 years ago

Closed 5 years ago

#30653 closed New feature (wontfix)

Annotate a whole ORM object with Subquery instead of just a column.

Reported by: Ivaylo Donchev Owned by: Ivo Donchev
Component: Database layer (models, ORM) Version: dev
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

It would be very useful to have a QuerySet method that annotates whole ORM object instead of separate columns.

Use cases

  1. select_related does not support using annotations of selected object. Example:
class User(Model):
    first_name = CharField(max_length=20)
    last_name = CharField(max_length=20)

    objects = UserQueySet.as_manager()


class UserQuerySet(QuerySet):
    def with_annotated_full_name(self):
        return self.annotate(
            full_name=ExpressionWrapper(
                expression=OuterRef('first_name') + Value(' ') + OuterRef('last_name')
                output_field=CharField()
            )
        )


class Car(Model):
    brand = CharField(max_length=20)
    owner = ForeignKey(User)
    

users_with_annotated_full_names = User.objects.with_annotated_full_name()  # each user will have `.full_name`
cars_with_users = Car.objects.select_realted('user')  # No way to get the `user.full_name` annotation :(


# With annotated object it'll look like:
cars_with_users_with_annotated_full_name = Car.objects.annotate_object(
    field_name='owner_with_annotated_full_name',
    queryset=User.objects.filter(id=OuterRef('owner'))[:1]
)



for car in cars_with_users_with_annotated_full_name:
    print(car.user.full_name)  # :)
  1. You need to make a query for every object to get some non-related object. Example:
class Teacher(Model):
    name = CharField(max_length=20)
    grade = IntegerField()


class Student(Model):
    name = CharField(max_length=20)
    grade = IntegerField()


def get_students_with_their_teacher():
    # Students must be ditributed between 3 teachers according to `grade` so each student should know it's teacher
    return Student.objects.annotate_object(
        field_name='teacher',
        queryset=Teacher.objects.filter(grade=OuterRef('grade'))[:1]
    )

students = get_students_with_their_teacher()

for student in students:
    print(student.teacher)  # == Teacher.objects.get(grade=...) but not making an extra query

Having this as additional method in the ORM may be really useful when you want to preserve the declarative syntax of Django ORM when using List APIs/Views and avoid additional methods that attaches objects dynamically and mutates the ORM objects

Change History (1)

comment:1 by Mariusz Felisiak, 5 years ago

Resolution: wontfix
Status: assignedclosed
Summary: Annotate a whole ORM object with Subquery instead of just a columnAnnotate a whole ORM object with Subquery instead of just a column.
Version: 2.2master

Thanks for this ticket, however you can user Prefetch() to prefetch related objects with a custom queryset. I don't think that a new API is necessary, e.g.:

>>> qs = Car.objects.prefetch_related(models.Prefetch('owner', queryset=User.objects.with_annotated_full_name(), to_attr='annotated_users'))
>>> for car in qs:
...     print(car, car.annotated_users.full_name)
Car object (1) a B
Car object (2) e f
....
Note: See TracTickets for help on using tickets.
Back to Top