Django: Model

Last Change:30-Apr-2016.
Author:qh73xe

このページでは Django のモデルに関しての情報を記述します.

Model とは

Reference:https://docs.djangoproject.com/ja/1.9/topics/db/models/

Django は良くも悪くも Model ベースに web アプリを作成していきます。 ここで、Model とは、要はデータベースにおけるテーブル、リレーションのことをさします。

Django では、データベースを直接操作するのではなく, models.py に記述されたクラス(これが DB へのラッパークラスになります)を操作することで、 DB の構造を定義し、クエリを発行し、python への橋渡しをおこないます。

モデルの定義

モデルは以下のように記述を行います。 この例では first_name および last_name というフィールドを持った Person モデルを定義しています

models.py
1
2
3
4
5
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

上記の例は DB において Person テーブルを作成し、 first_name, last_name というフィールドを設定したことと同義になります。

  • 正確には DB 上での名前は少し異なりますが、機能的には同義です。

モデルの登録

モデルクラスを使用するためには、そのモデルを使用するように、 Django に指示を出す必要があります。

設定ファイルを編集し、 INSTALLED_APPS 設定の部分に登録したいモデル名を明記します。

settings.py
1
2
3
4
5
INSTALLED_APPS = [
    #...
    'myapp',
    #...
]

モデルメソッドとマネージャーメソッド

上記モデルクラスはクラスなので、当然関数の定義が可能です。

models.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    def _get_full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)
    full_name = property(_get_full_name)

この例の最後のメソッドは プロパティ (property) です。 これはPython バージョン 2.2 から導入された機能で、「マネージドアトリビュー ト (managed attribute)」ともいいます。 アトリビュートそっくりにアクセスできるのに実はメソッド呼び出しで実装されているアトリビュートを実装する上品な方法です。

ここで Model クラスのメソッドは個々のモデルインスタンス単位の操作であることに注意してください。 つまり、DB のテーブルに於ける行単位の操作を定義します。

一方でテーブル級 (table-wide)の操作を実現するには Model method ではなく、Manager method を利用します。 これは例えば以下のように定義します。

models.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from django.db import models

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

この例では Book.objects.all() とした場合では データベース上の全ての book が呼び出されるのに対し、 Book.dahl_objects.all() では、author が ‘Roald Dahl’ である book インスタンスのみが呼び出されます。

モデルメソッドのオーバーライド

Django ではいくつかのモデルメソッドを初めから定義してあります。 これらのモデルメソッドはオーバーライドすることが可能です。

一般的にオーバーライドをよく行う関数である save に対して例を示します。 これは、モデルを DB 上に保存する際に使用する関数です。

models.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.
        do_something_else()

抽象モデル

ある特定のフィールドや、関数に関しては複数のモデルにおいて同じものを定義する必要がある場合があります。 このような場合において、夫々のモデル内で別途同一のコードを用意するのは、 コードの保守や、制作においてはあまり良いものではありません。

このような場合に使用するのが 抽象モデルです。 抽象モデルを作成したい場合、 Meta class において、 abstract = True を宣言します。

models.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

この例において、 Student モデルは home_group, name, age のフィールドを持つことになります。 また、実際の DB において CommonInfo は作成されないことに注意してください。

マルチテーブル継承

上記の例では 親クラスのテーブルは DB 上には反映されません。 一方で、親クラスも含めて、 DB に反映したい場合 マルチテーブル継承 を行います。 これは以下のように記述します。

models.py
1
2
3
4
5
6
7
class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField()
    serves_pizza = models.BooleanField()

この例において Place の全てのフィールドは、 Restaurant からも使えます。 しかし、それぞれのフィールドのデータは別々のテーブルに格納されます。

当たり前な話ではありますが、この継承関係においては Place と Restaurant には親子関係が存在します。 つまり、Restaurant は 単純に Place のフィールドや関数を使える以上の効果を持ちます。

具体的には Place であり、かつ Restaurant でもあるようなオブジェクトがあれば、 モデル名を小文字にした属性を使って、 Place から Restaurant を取り出せます。

>>> p = Place.objects.get(id=12)
>>> p.restaurant
<Restaurant: ...>

ただし、p が Restaurant クラスでない場合には p.restaurant はエラーを引き起こします。 これは p が Place から 直接生成されたオブジェクトである場合や、他のクラスの親クラスである場合などが該当します。

プロキシモデル

プロキシモデルは、同じモデルに対して Python の動作を変えたいだけの時に使用します。 具体的には、標準の User モデルに対してテンプレートの中で用いるメソッドを追加したい場合などがありえます。

プロキシモデルは以下の様に記述します。

models.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

class MyPerson(Person):
    class Meta:
        proxy = True

    def do_something(self):
        # ...
        pass

このような継承を行った場合、 person インスタンスを Myperson から操作することが可能になります。

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

一つのモデル内で同一のモデルに複数回リレーションを貼る

Reference:

時々起きる問題で,あるモデルに対するリレーションを定義するとき, 同一のモデルを二回以上参照したい場合があります.

例えば質問フォームを作成している時に, 質問者と回答者に関して, Django の user モデルを参照したい場合などです. しかし 以下のようなスクリプトではエラーが起きます.

model.py (ダメな例)
 class User(models.Model):
     name = models.CharField(max_length=80)

 class Question(models.Model):
     content = models.CharField(max_length=80)
     questioner = models.ForeignKey(User)
     responser = models.ForeignKey(User)

でどうするのかというと, :command: related_name というオプションを設定します.

model.py
 class User(models.Model):
     name = models.CharField(max_length=80)

 class Question(models.Model):
     content = models.CharField(max_length=80)
     questioner = models.ForeignKey(User, related_name='questioner')
     responser = models.ForeignKey(User, related_name='responser')