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:
...