I want to set a prefetch_related relation that can be narrowed down to a certain model under specific conditions.
For example, suppose you have models X and Y with an M: N relationship.
class X(models.Model):
name = models.CharField(max_length=32, null=False, default="")
ctime = models.DateTimeField(auto_now_add=True, null=False)
is_valid = models.BooleanField(default=True, null=False)
class Y(models.Model):
name = models.CharField(max_length=32, null=False, default="")
ctime = models.DateTimeField(auto_now_add=True, null=False)
is_valid = models.BooleanField(default=True, null=False)
xs = models.ManyToManyField(X, related_name="ys")
I want to get the relation of X narrowed down by the following conditions for the object with Y.
--order by -ctime
--Filter with ʻis_valid = True`
If you write it down directly, it will look like the following
y.xs.all().filter(is_valid=True).order_by("-ctime")
--I want to get the relation narrowed down by the corresponding conditions from the instance of the model. ――I want this narrowed relationship to be able to prefetch_related as well.
For example, if you name the relation that satisfies the above conditions as valid_xs
, you can use it as follows.
#from instance
y.valid_xs # => [X,X,X,X]
# prefetch_related (N+1 query can be suppressed)
for y in Y.objects.all().prefetch_related("valid_xs"):
print(y, y.valid_xs)
prefetch_related ("valid_xs ")
was difficult, so prefetch_related (Y.prefetch_valid_xs ())
is fine.
Define the following function.
def custom_relation_property(getter):
name = getter.__name__
cache_name = "_{}".format(name)
def _getter(self):
result = getattr(self, cache_name, None)
if result is None:
result = getter(self)
setattr(self, cache_name, result)
return result
def _setter(self, value):
setattr(self, cache_name, value)
prop = property(_getter, _setter, doc=_getter.__doc__)
return prop
Change the definition of the model as follows
class X(models.Model):
name = models.CharField(max_length=32, null=False, default="")
ctime = models.DateTimeField(auto_now_add=True, null=False)
is_valid = models.BooleanField(default=True, null=False)
@classmethod
def valid_set(cls, qs=None):
if qs is None:
qs = cls.objects.all()
return qs.filter(is_valid=True).order_by("-ctime")
class Y(models.Model):
name = models.CharField(max_length=32, null=False, default="")
ctime = models.DateTimeField(auto_now_add=True, null=False)
is_valid = models.BooleanField(default=True, null=False)
xs = models.ManyToManyField(X, related_name="ys")
@custom_relation_property
def valid_xs(self):
return X.valid_set(self.xs.all())
@classmethod
def prefetch_valid_xs(cls):
return Prefetch("xs", queryset=X.valid_set(), to_attr="valid_xs")
It can be used as follows.
#from instance
Y.objects.get(id=1).valid_xs # => [X,X,X,X]
# prefetch_related (N+1 query can be suppressed)
for y in Y.objects.all().prefetch_related(Y.prefetch_valid_xs()):
print(y, y.valid_xs)
reference
-Think a little more about django's prefetch_related (conditionally added relation eager loading)
It's a blog I wrote myself.
Recommended Posts