リレーションの理解が悪くて酷いコードを組んでいて、それを直した話。

article

key title content author_key
1 DjangoでHello World作ってみた 出来なかった! 1

author

key name
1 shigepon

こんなテーブルがあって、author.keyとarticle.author_keyが繋がっている場合に ArticleモデルをDjango使い始めの時はこんな風に作ってた

# models.py
class Article(models.Model):
    key=models.IntegerField(primary_key=True)
    title=models.TextField()
    content=models.TextField()
    author_key=models.IntegerField()
    def author(self):
        return Author.objects.get(key=self.author_key)

class Author(models.Model):
    key=models.IntegerField(primary_key=True)
    name=models.TextField()

いやー改めて見ると酷い。Articleを100個読み込んで、そのAuthorを出力するだけで、100回以上dbにアクセスしないといけない。という訳でこの酷いコードからリレーションを貼って、さらにdbアクセスが少なくなるように修正したらこうなった

参考:

# models.py
class Article(models.Model):
    key=models.IntegerField(primary_key=True)
    title=models.TextField()
    content=models.TextField()
    author=models.ForeignKey('Author', db_column='author_key', to_field='key', related_name='articles')

class Author(models.Model):
    key=models.IntegerField(primary_key=True)
    name=models.TextField()

こんな感じで適切にForeignKeyフィールドを設定することで、上の構成のテーブルにもリレーションを貼ることが出来た。db_columnでArticle側のリレーションキーになるカラムを指定して、to_fieldでAuthor側のリレーションキーになるカラムを指定する。related_nameは指定しないとエラーが出た。

んで、dbアクセスがなるべく少なくなるように(sqlでjoinを組んでくれるように)views.pyを書いてみると

# views.py
from app.models import Article
def index(request):
    articles = Article.objects.select_related('author').all()
    ...

みたいにselect_relatedメソッドを使うとjoinしてくれた。prefetch_relatedってメソッドでも同じように出来る(ちょっと役割は違う)らしいが試していない。ざっくりドキュメントを読むと、ForeignKeyと1対1リレーションにはselect_relatedが良く、Many to ManyやMany to Oneにはprefetch_relatedが良いらしい。