[PYTHON] How to create a property of relations that can be prefetch_related by specific conditions

How to create a property of relations that can be prefetch_related by specific conditions

I want to set a prefetch_related relation that can be narrowed down to a certain model under specific conditions.

Introduction

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")

Requirements

--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)

Implementation

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

How to create a property of relations that can be prefetch_related by specific conditions
How to install a Python library that can be used by pharmaceutical companies
How to make a rock-paper-scissors bot that can be easily moved (commentary)
How to create a wrapper that preserves the signature of the function to wrap
I want to create a priority queue that can be updated in Python (2.7)
How to create a USB that Linux and Win10 installer and winpe can boot UEFI
How to extract conditions (acquire all elements of Group that satisfy the conditions) for Group by Group
[Python] How to make a list of character strings character by character
Implement a thread that can be paused by exploiting yield
How to start a simple WEB server that can execute cgi of php and python
How to set up a simple SMTP server that can be tested locally in Python
How to display hover text and text that can be executed by clicking with Minecraft plugin
[Python] A program to find the number of apples and oranges that can be harvested
Convert mesh data exported from SpriteUV2 to a format that can be imported by Spine
[Python] How to force a method of a subclass to do something specific
Investigation of DC power supplies that can be controlled by Python
How to create a virtual bridge
How to create a Dockerfile (basic)
How to create a large amount of test data in MySQL? ??
Comparison of 4 styles that can be passed to seaborn with set_context
[Python] How to use the for statement. A method of extracting by specifying a range or conditions.
How to create a config file
[Python] A program that finds a pair that can be divided by a specified value
How many bytes of files can be placed on a 50M disk?
Create a web app that can be easily visualized with Plotly Dash
A memorandum of how to write pandas that I tend to forget personally
How to create a clone from Github
How to create a repository from media
I wanted to quickly create a mail server that can be used freely with postfix + dovecot on EC2
[Python3] Code that can be used when you want to cut out an image in a specific size
A memo for making a figure that can be posted to a journal with matplotlib
I tried to create a class that can easily serialize Json in Python
What I learned by implementing how to create a Default Box for SSD
[python] How to sort by the Nth Mth element of a multidimensional array
I want to create a web application that uses League of Legends data ①
How to make a Raspberry Pi that speaks the tweets of the specified user
A new form of app that works with GitHub: How to make GitHub Apps
How to calculate the volatility of a brand
A set of integers that satisfies ax + by = 1.
A simple example of how to use ArgumentParser
How to create a function object from a string
How to create a JSON file in Python
How to create a shortcut command for LINUX
[Note] How to create a Ruby development environment
How to create a Kivy 1-line input box
How to create a multi-platform app with kivy
How to create a Rest Api in Django
How to count numbers in a specific range
[Note] How to create a Mac development environment
Convert images from FlyCapture SDK to a form that can be used with openCV
What you can understand because you are a beginner How to create a file (first post)
A script that can perform stress tests according to the number of CPU cores
How to create a Python 3.6.0 environment by putting pyenv on Amazon Linux and Ubuntu
[Python] Introduction to web scraping | Summary of methods that can be used with webdriver
I tried to make a memo app that can be pomodoro, but a reflection record
[Ruby] How to replace only a part of the string matched by the regular expression?
A mechanism to call a Ruby method from Python that can be done in 200 lines
Read the Python-Markdown source: How to create a parser
Create a dataset of images to use for learning
How to create a submenu with the [Blender] plugin
[Go] How to create a custom error for Sentry