Include a django model object property using annotate()
Asked Answered
M

1

5

I have a model that includes a @property that I want included when I do things like vals = MyModel.objects.values() Another S/O post suggests the way to do this is by overriding the get_queryset method of a manager class, augmented with a call to annotate(xtra_field=...).

The problem is I can find no example of how to do this with a model property. I have tried things like super(SuperClass, self).get_queryset().annotate(xtra_fld=self.model.xtra_prop) and numerous variations thereof, none of which work.

The question, I think is What should the RHS of the annotation argument(s) be?

(And, yes, I know there are other, clunkier ways to do this, like iterating over what values() returns and adding the property as I go. Just not a very elegant (or even django) solution, to me.

Moir answered 17/5, 2018 at 23:46 Comment(2)
You can not do that for a @property, since properties are at the Python level. The database does not know anything about the properties.Ofelia
Yeah, makes sense now that I think about it. On further research, I think the solution will be to create a custom field that can construct/deconstruct the actual db field. What I'm doing is not unlike the Hand example in the docu.Moir
K
8

Unless you're generating the property value in the database, you can't do this without overriding django's internal methods – which I recommend against.

A more elegant solution might be to add an extra method to a custom queryset which generates the extra field on request:

from django.db import models

class AreaQuerySet(models.QuerySet):
    def values_with_area(self, *fields):
        # width & height are always required to calculate the area
        fields = set(fields)
        fields.update(['width', 'height'])

        for row in self.values(*fields):
            row['area'] = Widget.calculate_area(row['width'], row['height'])
            yield row

class Widget(models.Model):
    # ...
    objects = AreaQuerySet.as_manager()

    @classmethod
    def calculate_area(cls, width, height):
        return width * height

    @property
    def area(self):
        return Widget.calculate_area(self.width, self.height)
Kohler answered 18/5, 2018 at 0:16 Comment(2)
I think you meant to reference AreaQuerySet as manager in Widgets model.Niagara
Something like this might work. But, I feel like this is a more involved solution. I'm hoping it's some kind of semantic like the double underscore. To wit: ...annotate(xtra_field='self__xtraproperty'). It should be that simple.Moir

© 2022 - 2025 — McMap. All rights reserved.