Django学習小記[2]——Model

41520 ワード

djangoのmodelを勉強し始めました.djangoを勉強する目的は簡単です.djangoで自分のブログを作りたいと思っています.今、オープンソースにはdjango-zinniaというブログエンジンがありますが、それを理解し、修正するにはdjangoという関門を通らなければなりません.以前のdjangoに対する理解は、何を使ったかに限られ、何を知っているのか、システム的な学習が欠けているので、djangoのドキュメントを一度書き直して、簡単なメモを取ります.
今日のテーマはModelで、ModelはMVCの中のMで、代表するデータの対象、データベースの中でデータの表に反映して、Modelの中の属性は表の1列で、DjangoはModelに対してカプセル化を行って、Modelに対して豊富なクエリーのインターフェースを提供して、データベースの中に反映して、豊富なselectクエリーの機能を提供して、これはDjangoの強大なところです.
まず簡単な例を見てみましょう.appのmodelsです.pyで、Modelを定義します.
from django.db import models

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

すべてのModelはdjangoから継承されています.db.models.Modelクラス、Modelクラスの各属性はdjangoから継承する.db.models.Field、このFieldにはいくつかの役割があります.
データベース内のFieldのタイプを決定するFieldがフロントエンドにどのように表示するかを決定する.
簡単な検証Djangoには内蔵のFieldがたくさんありますが、もちろんカスタマイズもできます.
では、Djangoでリレーショナル・データベースをどのように実現するかの3つの典型的な関係に重点を置きます.多対一、多対多、一対一です.

マルチペア


多対一を実現するにはdjangoを用いる.db.models.ForeignKeyクラス、ForeignKeyにはpositionalのパラメータが必要です.本Model関連のModelを指定します.ForeignKey関連のModelは「一」です.ForeignKeyが存在するModelは「多」です.例えば、自動車とメーカーの例では、1つの自動車は1つのメーカーにしか属しませんが、1つのメーカーは複数の自動車を持っています.この関係は、DjangoのModelで表されます.
class Manufacturer(models.Model):
    name = models.CharField(max_length=30)

class Car(models.Model):
    Manufacturer = models.ForeignKey(Manufacturer)
    name = models.CharField(max_length=30)

この関係は、sql文で表されます.
CREATE TABLE `model_test_manufacturer` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL );
CREATE TABLE `model_test_car` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `Manufacturer_id` integer NOT NULL, `name` varchar(30) NOT NULL );
ALTER TABLE `model_test_car` ADD CONSTRAINT `Manufacturer_id_refs_id_da7168cb` FOREIGN KEY (`Manufacturer_id`) REFERENCES `model_test_manufacturer` (`id`);

削除操作:
>>> from model_test.models import Car
>>> from model_test.models import Manufacturer
>>> m = Manufacturer.objects.get(name="xxx")
>>> m.car_set.all()
[]
>>> m.car_set.create(name="yyy")
<Car: Car object>
>>> c = Car(name="zzz")
>>> m.car_set.add(c)

複数対1の詳細については、「Many-to-On」を参照してください.

多対多


多対多を実現するにはdjangoを用いる.db.models.ManyToManyFieldクラスは、ForeignKeyと同様に、関連するModelを指定するためのpositionalパラメータもあります.
2つのModelの間が多対多の関係であることを知るだけでなく、PersonとGroupが多対多の関係であることを知る必要があります.1つのPersonがどのGroupに属しているかを知る以外に、このPersonがいつこのGroupに追加されたのかを知りたい場合は、これらの情報を記録する中間テーブルが必要です.それはManyToManyFiledのoptionalパラメータ:throughを使用します.次の例を示します.
class Person(models.Model):                        
    name = models.CharField(max_length=30)

    def __unicode__(self):                         
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=30)
    members = models.ManyToManyField(Person, through='Membership')

    def __unicode__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person)
    group = models.ForeignKey(Group)
    date_joined = models.DateField()
    invite_person = models.CharField(max_length=30)

中間テーブルでは、PersonとGroupに外部キーで関連付けられています.実際には、SQL文に対応する2つの多対1の関係です.
CREATE TABLE `model_test_person` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL ) ;
CREATE TABLE `model_test_group` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL ) ;
CREATE TABLE `model_test_membership` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `person_id` integer NOT NULL, `group_id` integer NOT NULL, `date_joined` date NOT NULL, `invite_person` varchar(30) NOT NULL ) ;
ALTER TABLE `model_test_membership` ADD CONSTRAINT `group_id_refs_id_be33a6a7` FOREIGN KEY (`group_id`) REFERENCES `model_test_group` (`id`);
ALTER TABLE `model_test_membership` ADD CONSTRAINT `person_id_refs_id_90aaf3d5` FOREIGN KEY (`person_id`) REFERENCES `model_test_person` (`id`);

Modelを追加/削除するには、次の手順に従います.
>>> import datetime
>>> from model_test.models import Person
>>> from model_test.models import Group
>>> from model_test.models import Membership
>>> 
>>> suo = Person.objects.create(name="suo")
>>> piao = Person.objects.create(name="piao")
>>> english = Group.objects.create(name="English")
>>> 
>>> m1 = Membership(person=suo, group=english, date_joined=datetime.date(2014, 9, 9), invite_person="summer")           
>>> m1.save()
>>> m2 = Membership.objects.create(person=piao, group=english, date_joined=datetime.date(2014, 8, 8), invite_person="spring")
>>> 
>>> english.members.all()
[<Person: suo>, <Person: piao>]

なお、この形式の多対多は、2つのModelの関係を追加する場合、Modelの関連付け属性で直接追加することはできません.中間関係を作成するオブジェクトであるべきです.つまり、このような操作は実行できません.
english.members.add(suo)

関係の追加には他の属性(date_jointed/invite_person)も指定する必要があるため、直接追加することはできません.

一対一


1対1はdjangoを通過する.db.models.OneToOneFieldで実現され、関連付けられたModelにUniqueの制限が加えられます.例えばPersonとIdCardは一対一の関係で、DjangoのModelで表します.
class IdCard(models.Model):
    number = models.CharField(max_length=30)
    person = models.OneToOneField(Person)

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

    def __unicode__(self):
        return self.name

対応するSQL文は次のとおりです.
CREATE TABLE `model_test_person` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL ) ;
CREATE TABLE `model_test_idcard` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `number` varchar(30) NOT NULL, `person_id` integer NOT NULL UNIQUE ) ;
ALTER TABLE `model_test_idcard` ADD CONSTRAINT `person_id_refs_id_c2c57084` FOREIGN KEY (`person_id`) REFERENCES `model_test_person` (`id`);

注意model_test_idcardテーブルのperson_idにはunique制限が加わっています.
次に、Modelの継承について説明します.Abstract base classes、Multi-table inheritance、Proxy modelsの3種類があります.

Abstract base classes


Model間の継承関係はPythonの継承であり、子は親の属性を継承するが、各Modelはデータベース内のデータテーブルに対応しているため、親としてのModelはMetaに表示される宣言が抽象的である必要がある場合がある.そうしないと親のためにデータベーステーブルを作成する場合もある.一般的には、共通コードを格納する場所として親を使用するだけで、独自のデータベース・テーブルは必要ありません.なお、子クラスはMetaの属性を含む親クラスの属性を継承するが、Metaのabstract属性のみを継承しない.子クラスも抽象Modelである場合、表示するパラメータを再度指定する.次の例を示します.
class CommonInfo(models.Model):
    name = models.CharField(max_length=30)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    score = models.IntegerField()

class Teacher(CommonInfo):
    rating = models.CharField(max_length=30)

対応するSQL文は次のとおりです.
CREATE TABLE `model_test_student` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL, `age` integer UNSIGNED NOT NULL, `score` integer NOT NULL ) ;
CREATE TABLE `model_test_teacher` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL, `age` integer UNSIGNED NOT NULL, `rating` varchar(30) NOT NULL ) ;

Multi-table inheritance


このような違いは、親クラスのMetaのabstractを削除することです.つまり、親クラスにも独自のデータベーステーブルがあり、この親クラスと子クラスの間には一対一の関係があります.例は次のとおりです.
class Place(models.Model):
    name = models.CharField(max_length=30)
    address = models.CharField(max_length=30)

    def __unicode__(self):
        return self.name

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

得られたSQLは以下の通りです.
CREATE TABLE `model_test_place` ( `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(30) NOT NULL, `address` varchar(30) NOT NULL ) ;
CREATE TABLE `model_test_restaurant` ( `place_ptr_id` integer NOT NULL PRIMARY KEY, `serves_hot_dogs` bool NOT NULL, `serves_pizza` bool NOT NULL ) ;
ALTER TABLE `model_test_restaurant` ADD CONSTRAINT `place_ptr_id_refs_id_cc7b5838` FOREIGN KEY (`place_ptr_id`) REFERENCES `model_test_place` (`id`);

Placeを追加するのは簡単ですが、Restaurantを追加するにはどうすればいいのでしょうか.そして両者は一対一の関係で、どのように彼らの間の関係を追加しますか?RestaurantはPlaceのサブクラスであり、Placeのプロパティを継承しているので、Restaurantを直接作成すればいいことを覚えておいてください.
>>> from model_test.models import Restaurant
>>> Restaurant.objects.create(name="r1", address="a1", serves_hot_dogs=True, serves_pizza=False)
<Restaurant: r1>

これにより、データベースではplaceテーブルとrestaurantテーブルにそれぞれレコードが追加され、restaurantテーブルではplace idがプライマリ・キーとして使用されます.
このタイプの詳細については、Multi-table inheritanceを参照してください.

Proxy models


このタイプの継承用は比較的少なく、主に元のModelの動作を変更せずに、元のModelの動作を拡張するために使用されます.すなわち、元のModelにエージェントを設定し、そのエージェントの動作を再定義することができますが、元のModelの動作を保持します.例えば、元のモデルはソート方法にしたいのですが、私も別のソート方法にしたいのですが、どうすればいいですか?では、Modelにエージェントを作成し、このエージェントに別のソート方法を指定します.このエージェントにアクセスすると、新しいソートが得られます.
ここには、親モデルが抽象的ではないという制限があります.例を挙げます.
class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

    class Meta:
        ordering = ['first_name']

    def __unicode__(self):
        return self.name

class MyPerson(Person):
    class Meta:
        proxy = True
        ordering = ['last_name']

    def do_something(self):
        pass

サブクラスのMetaにproxy=Trueが設定されているのは、同じデータベースに対応しているが、異なるアクセス動作を持つモデルをプロキシモデルとして指定することです.MyPersonでアクセスしたデータはlast_nameはソートされ、新しい方法を定義することで機能を拡張することもできます.次のようになります.
>>> from model_test.models import Person
>>> from model_test.models import MyPerson

>>> Person.objects.create(first_name="suo", last_name="guangyu")
<Person: suo guangyu>
>>> Person.objects.create(first_name="xing", last_name="demo")
<Person: xing demo>

>>> Person.objects.all()
[<Person: suo guangyu>, <Person: xing demo>]
>>> MyPerson.objects.all()
[<MyPerson: xing demo>, <MyPerson: suo guangyu>]
>>>

このタイプの継承はやはり役に立ちます.