If I have 2 models related through ForeignKey I can easily get a queryset with both models joined using select_related
class Foo(Model):
data = IntegerField()
bar = ForeignKey('Bar', on_delete=CASCADE)
class Bar(Model):
data = IntegerField()
foos_with_joined_bar = Foo.objects.select_related('bar')
for foo in foos_with_joined_bar:
print(foo.data, foo.bar.data) # this will not cause extra db queries
I want to do the same thing but in the case where Foo keeps its reference to bar in a JSONField
class Foo(Model):
data = IntegerField()
bar = JSONField() # here can be something like {"bar": 1} where 1 is the ID of Bar
class Bar(Model):
data = IntegerField()
foos_with_joined_bar = ???
Is it possible to get foos_with_joined_bar in this case using Django ORM?
P.S. We're not discussing the reasoning behind storing foreign keys in the JSONField, of course it's better to just use ForeignKey.
Finally I've found a bit tricky way to annotate Foo with complete Bar model.
TLDR: use JSONObject to annotate Foo with dict representation of Bar and then manually create foo.bar using this dict
managers.py
from django.db.models import QuerySet, F
from django.db.models.functions import JSONObject, Cast
from django.db.models.fields import BigIntegerField
class BarQS(QuerySet):
def as_json(self):
return self.values(json=JSONObject(**{f: f for f in self.model.json_fields}))
class FooQS(QuerySet):
def annotate_bar(self):
from .models import Bar
return (
self
.annotate(bar_id=Cast(F('bar__bar'), BigIntegerField()))
.annotate(bar=Bar.objects.filter(pk=OuterRef('bar_id')).as_json())
)
def foobar_iter(self):
from .models import Bar
for foo in self:
bar_model = Bar(**foo.bar)
foo.bar = bar_model
yield foo
models.py
from django.db.models import Model, IntegerField, JSONField
from .managers import FooQS, BarQS
class Foo(Model):
data = IntegerField()
bar = JSONField()
objects = FooQS.as_manager()
class Bar(Model):
data = IntegerField()
data2 = IntegerField()
objects = BarQS.as_manager()
json_fields = ('data', 'data2') # here we define fields translated into JSON
It's done, now we can do something like this
foos_with_joined_bar = Foo.objects.annotate_bar().foobar_iter()
for foo in foos_with_joined_bar:
print(foo.data, foo.bar.data, foo.bar.data2)