Django include custom @property on ORM model into queryset for future use inside customer filters

Hey there and thank you in advance.

I have defined custom property on my ORM model:

class MyModel(BaseModel):
  optional_prop_1 = models.ForeignKey(Model, null=True)
  optional_prop_2 = models.ForeignKey(AnotherModel, null=True)
  optional_prop_2 = models.ForeignKey(DifferentModel, null=True)

  @property
  def external_reference(self):
    if self.optional_prop_1:
       return self.optional_prop_1
    if self.optional_prop_2:
       return self.optional_prop_2
    ...

All those three fields have a common field that I want to access inside my custom filer query, but because external_reference is defined as "virtual" property I know that I cannot access it inside queryset, so when I do this it would actually work:

queryset.filter(top_level_relation__my_model__external_reference__common_field="some_value")

I think I got an idea that I need to somehow convert my "virtual" property into a field dynamically with custom models.Manager and with queryset.annotate() but this didn't seem to be working. I tried this:

def _get_external_reference(model) -> str:
    if model.optional_prop_1:
        return "optional_prop_1"
    elif model.optional_prop_2:
        return "optional_prop_1"
    ...

    return ""

def get_queryset(self):
    external_reference = _get_external_reference(self.model)

    return super().get_queryset().annotate(external_reference=models.F(external_reference))

But inside my custom filter I always get Related Field got invalid lookup: external_reference telling me that this field doesn't exist on queryset. Any ideas how to convert property (@property) into a field that I could use later inside queryset

The error in your code is that you are trying to access the model's properties directly in the _get_external_reference function, but the argument passed to this function is not an instance of MyModel, it is the model class itself.

Also, the return value of _get_external_reference is a string representing the name of a property, but you are using this string directly in the annotate call as a field name.

To solve this issue, you can use Func and F expressions to dynamically create the expression for the annotated field, based on the value of the optional_prop_1, optional_prop_2 and optional_prop_3 fields:

from django.db.models import F, Func, Value

class MyModelManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(
            external_reference=Func(
                F('optional_prop_1'),
                F('optional_prop_2'),
                F('optional_prop_3'),
                function='COALESCE',
                output_field=models.ForeignKey(Model),
            )
        )

class MyModel(BaseModel):
    ...
    objects = MyModelManager()

    class Meta:
        ...
Back to Top