Djangoで、ForeignKey、ManyToManyFieldに初期値を与える

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年11月22日21:15に更新(約21日前)
2018年11月12日23:03に作成(約31日前)

旧ブログ移行記事です。

概要

DjangoのForeignKeyManyToManyFieldに初期値を与える方法を紹介します。

サンプルモデル

以下のようなモデルを例に考えます。

from django.db import models


class Category(models.Model):
    """カテゴリ"""
    name = models.CharField('カテゴリ名', max_length=255)

    def __str__(self):
        return self.name


class Tag(models.Model):
    """カテゴリ"""
    name = models.CharField('タグ名', max_length=255)

    def __str__(self):
        return self.name


class Post(models.Model):
    """記事"""
    title = models.CharField('タイトル', max_length=255)
    text = models.TextField('本文')
    category = models.ForeignKey(
        Category, verbose_name='カテゴリ', on_delete=models.PROTECT
    )
    tag = models.ManyToManyField(Tag, verbose_name='タグ', blank=True)

    def __str__(self):
        return self.title

カテゴリはForeignKey、タグはManyToManyFieldで紐づいています。

ForeignKeyの初期値

数値で指定

通常のフィールドと同様に、default引数を指定します。簡単な方法としては、default=1のようにします。

通常のフィールドならば数値の1という意味ですが、ForeignKeyではpkが1のもの、という意味になります。つまりカテゴリのpkが1のデータを初期値とする、ということですね。

    category = models.ForeignKey(
        Category, verbose_name='カテゴリ', on_delete=models.PROTECT,
        default=1,
    )

関数で指定

default引数には、関数も指定できます(正確に言えば呼び出し可能オブジェクト)。よく、以下のようなコードを見かけることでしょう。記事作成時の時間が、作成日欄に自動で入るようにしたい場合に使うイディオムです。

created_at = models.DateTimeField('作成日', default=timezone.now)

これはdefault引数にtimezone.nowという関数オブジェクトそのものを渡します。このように関数オブジェクト(呼び出し可能オブジェクト)を渡すと、データの作成時にtimezone.now()と関数を呼び出してくれます。

timezone.now()

間違っても以下のようにはしないようにしましょう。

# 関数をこの場で呼び出してしまってる!!
created_at = models.DateTimeField('作成日', default=timezone.now())

モデルのフィールドはモジュールのimport時に評価されます。なので、上のコードはモジュールがimportされた時間...仮に2018/11/12 15:00だとすると、default="2018/11/12 15:00" のような指定になってしまい、記事の作成時は毎回"2018/11/12 15:00"が作成日欄に入ります。

関数オブジェクトを渡すことで、記事の作成時にtimezone.now()という呼び出しが行われるようになり、その記事の作成時の時間が入る、というわけです。

ForeignKeyに初期値を指定する場合でよくあるのは、初期値でAというデータを指定しつつ、Aがまだ作られてなければ作るという処理です。これは以下のように実装できます。

def get_or_create_curry_category():
    category, _ = Category.objects.get_or_create(name='カレー')
    return category


class Post(models.Model):
    """記事"""
    ...
    ...
    category = models.ForeignKey(
        Category, verbose_name='カテゴリ', on_delete=models.PROTECT,
        default=get_or_create_curry_category,
    )
    ...
    ...

今回defaultに指定した関数は、カレーというカテゴリがあればそのカテゴリを返し、なければ作って返すという処理をします。ちなみにですが、ForeignKeyのdefault値にはpkだけでなく、モデルインスタンスを直接渡しても良いです。

以下の部分が、なければ作り、あればそのまま返す処理です。get_or_create()というそのままなメソッドがあります。(モデルインスタンス, 新規作成されたか否かのbool)というタプルを返すのですが、今回は新規作成されたかどうかは興味がないので_という変数名にしました。よくあります。

category, _ = Category.objects.get_or_create(name='カレー')

ManyToManyFieldの初期値

ほとんどForeignKeyと同様に扱えます。

数値で指定

ManyToManyFieldは複数のデータを指定できます。default値として複数指定する場合は、リストやタプル等...イテラブルなデータで渡します。

tag = models.ManyToManyField(Tag, verbose_name='タグ', blank=True, default=[1, 2, 3])

関数で指定

def get_or_create_default_tags():
    default_tag1, _ = Tag.objects.get_or_create(name='美味しい')
    default_tag2, _ = Tag.objects.get_or_create(name='安い')
    return default_tag1, default_tag2


class Post(models.Model):
    """記事"""
    ...
    tag = models.ManyToManyField(Tag, verbose_name='タグ', blank=True, default=get_or_create_default_tags)
    ....
Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする