模型
模型准确且唯一的描述了数据。它包含存储的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。
基础:
-
每个模型都是一个Python的类,继承django.db.models.Model
-
模型类的每个属性都相当于一个数据库的字段
-
Django提供了一个自动生成访问数据库的API
示例
- 定义一个Person模型,拥有first_name和last_name
form django.db import modelsclass Person(models.Model):first_name = models.CharField(max_length=30)last_name = models.CharField(max_length=30)
first_name和last_name是模型的字段,每个字段都被指定为一个类属性,并且每个属性映射为一个数据库列。
Person模型会创建一个如下的数据库表
CREATE TABLE myapp_person ("id" serial NOT NULL PRIMARY KEY,"first_name" varchar(30) NOT NULL,"last_name" varchar(30) NOT NULL
);
说明:
-
该表的名称myapp_person是自动从某些模型元数据中派生出来,可以被改写。
-
id字段会被自动添加,可以被改写。
-
例子中创建数据库的语法是PostgreSQL。Django会依据配置文件中指定的数据库后端生成对应的SQL语句。
使用模型
一旦定义了模型,就需要配置这些模型。
修改设置文件中的INSTALLED_APPS
示例
- 若模型位于项目中的myapp.models模块,INSTALLED_APPS配置如下
INSTALLED_APPS = [#...'myapp',#...
]
添加模型后,需要使用
python manage.py makemigrations # 迁移
python manage.py migrate
字段
模型中最重要且唯一必要的是数据库的字段定义。
示例
from django.db import modelsclass Musician(models.Model):first_name = models.CharField(max_length=50)last_name = models.CharField(max_length=50)instrument = models.CharField(max_length=100)class Album(models.Model):artist = models.ForeignKey(Musician, on_delete=models.CASCADE)name = models.CharField(max_length=100)release_date = models.DateField()num_stars = models.IntegerField()
字段类型
模型中每一个字段都应该是某个Field类的实例
- 字段类型用以指定数据库数据类型(如:INTEGER,VARCHAR)
- 在渲染表单字段时默认使用HTML
- 基本的有效性验证功能,用于Django后台和自动生成的表单。
字段选项
每一种字段都需要指定一些特定的参数。例CharField(以及它的子类)需要接受一个max_length参数,用以指定数据库存储VARCHAR数据时用的字节数。
一些可选的参数是通用的
- null
如果设置为Ture,当该字段为空时,Django会将数据库中该字段设置为NULL。默认为False
- blank
如果这是为True,当该字段允许为空,默认为False
该选项与null不同,null选项仅仅是数据库层面的设置,然后blank是涉及表单验证方面。如果一个字段设置为blank=True,在进行表单验证时,接受的数据该字段值允许为空,而设置为False时,不允许为空。
- choices
一系列二元组,用作此字段的选项,如果提供了二元组,默认表单小部件是一个选择框,而不是标准文字段,并将限制给出的选项。
示例
YEAR_IN_SCHOOL_CHOICES = [('FR', 'Freshman'),('SO', 'Sophomore'),('JR', 'Junior'),('SR', 'Senior'),('GR', 'Graduate'),
]
注:每当choices的顺序变动时将会创建新的迁移
每个二元组的第一个值会存储在数据库中,而第二个值将只会用于存在表单中显示。
对于一个模型实例,要获取该字段二元组中相对应的第二个值,使用get_FOO_display()方法。
示例
from django.db import modelsclass Person(models.Model):SHIRT_SIZES = (('S', 'Small'),('M', 'Medium'),('L', 'Large'),)name = models.CharField(max_length=60)shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
- default
该字段的默认值,可以是一个值或者是个可调用的对象,如果是个可调用对象,每次实例化模型时都会调用该对象。
- help_text
额外的“帮助”文本,随表单空间一同显示。
- primary_key
如果设置为True,将该字段设置为该模型的主键
在一个模型中,如果没有对任何一个字段设置primary_key=True选项。Django会自动添加一个IntegerField字段,并设置为主键。
主键字段是只可读的,如果修改一个模型实例的主键并保存,等同于新创一个模型实例。
示例
from django.db import modelsclass Fruit(models.Model):name = models.CharField(max_length=100, primary_key=True)
>>> fruit = Fruit.objects.create(name='Apple')
>>> fruit.name = 'Pear'
>>> fruit.save()
>>> Fruit.objects.values_list('name', flat=True)
<QuerySet ['Apple', 'Pear']>
- unique
如果设置为True,这个字段的值必须在整个表中保持唯一。
自动设置主键
默认情况下,Django会给每一个模型添加下面的字段
id = models.AutoField(primary_key=True)
这是一个自增主键。
每个模型都要有一个设置了primary_key=True的字段。
字段备注名
除了ForeignKey,ManyToManyField和OneToOneField,任何字段类型都接受一个可选的位置参数verbose_name,如果未指定该参数值,Django会自动使用字段的属性名作为该参数值,并且把下划线转换为空格。
示例
# 备注名:person's first name
first_name = models.CharField("person's first name", max_length=30)# 备注名:first name
first_name = models.CharField(max_length=30)
ForeignKey,ManyToManyField和OneToOneField接受的第一个参数为模型的类名,后面可以添加一个verbose_name参数。
poll = models.ForeignKey(Poll,on_delete=models.CASCADE,verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(Place,on_delete=models.CASCADE,verbose_name="related place",
)
关联关系
Django提供三种常见的数据库关联关系:多对一,多对多,一对一
多对一关联
定义一个多对一的关联关系,使用django.db.models.ForeignKey类。
ForeignKey类需要添加一个位置参数,即想要关联的模型类名。
示例
- 如果一个Car模型有一个制造者Manufacturer(一个制造者制造许多辆车,但是每辆车都仅有一个制造者)
from django.db import modelsclass Manufacturer(models.Model):# ...passclass Car(models.Model):manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)# ...
多对多关联
定义一个多对多的关联关系,使用django.db.models.ManyToManyField类。
需要一个位置参数,即想要关联的模型类名
示例
- 如果Pizza含有多种配料(也就是一种配料可能存在多个Pizza中,并且每个Pizza含有多种Topping)
from django.db import modelsclass Topping(models.Model):# ...passclass Pizza(models.Model):# ...toppings = models.ManyToManyField(Topping)
对于多对多关联关系的两个模型,可以在任何一个模型中添加ManyToManyField字段,但只能选择一个模型设置该字段,既不能同时在两个模型中添加该字段。
在多对多关系中添加额外的属性字段
如果需要在两个模型的关系中记录更多数据,需要使用throuht。
示例
- 一个需要跟踪音乐人属于哪个音乐组的应用程序,在任何他们所在的组之间有一个多对多的关系,此时还想要记录更多的信息,例某人是何时加入一个组的。
from django.db import modelsclass Person(models.Model):name = models.CharField(max_length=128)def __str__(self):return self.nameclass Group(models.Model):name = models.CharField(max_length=128)members = models.ManyToManyField(Person, through='Membership')def __str__(self):return self.nameclass Membership(models.Model):person = models.ForeignKey(Person, on_delete=models.CASCADE)group = models.ForeignKey(Group, on_delete=models.CASCADE)date_joined = models.DateField()invite_reason = models.CharField(max_length=64)
在设置中间模型的时候,显式地为多对多关系中涉及的中间模型指定外键。
中间模型中的一些限制条件
- 中间模型有且仅有一个指向源模型(例子中的Group)的外键。
- 在一个用于描述模型当中自己指向自己的多对多关系的中间模型当中,可以有两个指向同一个模型的外键,但这两个外键分别代表多对多关系(不同)的两端。
- 定义模型自己指向自己的多对多关系时,如果使用中间模型,必须定义symmetrical=False。
通过实例化中间模型来创建关系
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
一对一关联
使用OneToOneField来定义一对一关系。
OntToOneField需要一个位置参数:与模型相关的类。
例
- 建立一个有关位置信息的数据库,如果想建立一个关于餐厅的数据库,除了将位置数据库当中的字段复制到Restaurant模型,也可以将一个指向Place OneToOneField放到Restaurant当中
跨文件模型
Django支持关联另一个应用中的模型,需要在定义模型的文件开头导入需要被关联的模型
from django.db import models
from geography.models import ZipCodeclass Restaurant(models.Model):# ...zip_code = models.ForeignKey(ZipCode,on_delete=models.SET_NULL,blank=True,null=True,)
字段名限制
Django对模型的字段名有一些限制:
- 一个字段的名称不能是Python保留字,因为会导致Python语法错误
class Example(models.Model):pass = models.IntegerField() # 'pass' is a reserved word!
- 一个字段名称不能包含连续的多个下划线,原因在于Django查询语法的工作方式
class Example(models.Model):foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
- 字段名不能以下划线结尾,原因同上
自定义的字段类型
Meta选项
使用内部Meta类来给模型赋予元数据
from django.db import modelsclass Ox(models.Model):horn_length = models.IntegerField()class Meta:ordering = ["horn_length"]verbose_name_plural = "oxen"
模型的元数据即“所有不是字段的东西”,例排序选项(ordering),数据库表明(db_table),或是阅读友好的单复数明哥(verbose_name和verbose_name_plural)。在模型中添加Meta类是可选的。
模型属性
objects
模型当中最重要的属性是Manager。它是Django模型和数据库查询操作之间的接口,并且它被用作从数据库中当中获取实例,如果没有指定自定义的Manager默认名称是objects。
模型方法
- _str_()
一个Python的魔法方法,返回值友好地展示了一个对象。Python和Django在要将模型实例展示为纯文本时调用。
- get_absolute_url()
该方法告诉Django如何计算一个对象的URL。
任何需要一个唯一URL的对象需要定义此方法。
重写之前定义的模型方法
可以重写定制的方法(例:save()和delete())来更改方法的行为。
示例
from django.db import modelsclass Blog(models.Model):name = models.CharField(max_length=100)tagline = models.TextField()def save(self, *args, **kwargs):do_something()super().save(*args, **kwargs) # Call the "real" save() method.do_something_else()
阻止保存
from django.db import modelsclass Blog(models.Model):name = models.CharField(max_length=100)tagline = models.TextField()def save(self, *args, **kwargs):if self.name == "Yoko Ono's blog":return # Yoko shall never have her own blog!else:super().save(*args, **kwargs) # Call the "real" save() method.
调用父类的方法非常重要——super().save(*args, **kwargs)
。确保对象正确的写入数据库。如果没有调用父类方法,默认行为不会被触发,数据库也不会被操作。传递模型方法接受的参数也很重要。
模型继承
模型继承在Django中与普通类继承在Python中的工作方式几乎完全相同。
抽象基类
抽象基类在将公共信息放入很多模型时会很有用。
编写基类,并在Meta类中填入abstract=True
,该模型将不会创建任何数据表。
当其用作其他模型类的基类时,它的字段会自动添加至子类。
示例
from django.db import modelsclass CommonInfo(models.Model):name = models.CharField(max_length=100)age = models.PositiveIntegerField()class Meta:abstract = Trueclass Student(CommonInfo):home_group = models.CharField(max_length=5)
Student模型拥有三个字段:name,age和home_group。CommonInfo模型不能用作普通的Django模型,因为它是一个抽象基类。不会生成数据表,也没有管理器,也不能被实例化和保存。
从抽象基类继承来的字段可被其他字段或值重写,或用None删除。
Meta继承
当一个抽象基类被建立,Django将所有基类中声明的Meta内部以属性的形式提供。若子类未定义自己的Meta类,会继承父类的Meta。
子类也可以继承父类的Meta。
from django.db import modelsclass CommonInfo(models.Model):# ...class Meta:abstract = Trueordering = ['name']class Student(CommonInfo):# ...class Meta(CommonInfo.Meta):db_table = 'student_info'
Django允许继承一个抽象基类创建另一个抽象基类。
多表继承
Django支持的第二种模型继承方式是层次结构中的每个模型都是一个单独的模型。
某个模型都指向分离的数据表,且可被独立查询和创建。
from django.db import modelsclass Place(models.Model):name = models.CharField(max_length=50)address = models.CharField(max_length=80)class Restaurant(Place):serves_hot_dogs = models.BooleanField(default=False)serves_pizza = models.BooleanField(default=False)
Place的所有字段均可在Restaurant中可用,虽然数据分别存在不同的表中。
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")
Restaurant中自动创建的连接至Place的OneToOneField类似于
place_ptr = models.OneToOneField(Place, on_delete=models.CASCADE,parent_link=True,
)
Meta和多表继承
多表继承情况下,子类不会继承父类的Meta。
子类模型无法访问父类的Meta类。
不过,有几种情况除外:若子类未指定ordering属性或get_latest_by属性,子类会从父类继承这些。
如果父类有排序,子类不期望有排序,可以禁止
class ChildModel(ParentModel):# ...class Meta:# Remove parent's ordering effectordering = []
继承与反向关系
由于多表继承使用隐式的OneToOneField连接子类和父类,所以直接从父类访问子类是可能的。
然而,使用的名字是ForeignKey和ManyToManyField关系的默认值。如果在继承父类模型的子类中添加了这些关联,必须指定related_name属性,否则会报错
class Supplier(Place):customers = models.ManyToManyField(Place)#### 报错信息
Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.HINT: Add or change a related_name argument to the definition for
'Supplier.customers' or 'Supplier.place_ptr'.
解决办法
models.ManyToManyField(Place, related_name='provider')
代理模型
使用多表继承,每个子类模型都会创建一个新表。
如果有时只想修改模型的Python级行为,可能是修改默认管理器,或添加一个方法。
代理模型继承的目的:为原模型创建一个代理,可以创建,删除和更新代理模型的实例,所有的数据都会存储的像使用原模型一样。不同点是可以修改代理默认的模型排序和默认管理器,而不需要修改原模型。
代理模型和普通模型一样声明,将Meta类的proxy属性设置为True
实例
- 为Person模型添加一个方法
from django.db import modelsclass Person(models.Model):first_name = models.CharField(max_length=30)last_name = models.CharField(max_length=30)class MyPerson(Person):class Meta:proxy = Truedef do_something(self):# ...pass
MyPerson类与父类Person操作同一张数据表,Person的实例能通过MyPerson访问,反之亦然。
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>
可以用代理模型定义模型的类一种不同的默认排序方法。
class OrderedPerson(Person):class Meta:ordering = ["last_name"]proxy = True
普通的Person查询结果不会被排序,但OrderPerson查询结果会按last_name排序。
QuerySet仍会返回请求的模型
用Person对象查询时,Django永远不会返回MyPerson对象。代理对象存在的意义是复用Person提供的代码和自定义的功能代码(并未依赖其他代码)。
不存在方法能在创建代理后,替换所有Person模型。
基类约束
一个代理模型必须继承自一个非抽象模型类。
代理模型管理器
在代理模型中指定模型管理器,它会从父类模型中继承。如果在代理模型中指定了管理器,它会成为默认管理器,但父类中定义的管理器仍是可用的。
from django.db import modelsclass NewManager(models.Manager):# ...passclass MyPerson(Person):objects = NewManager()class Meta:proxy = True
多重继承
Django模型也能继承自多个父类模型。
注:继承自多个包含id主键的字段会抛出错误。
class Article(models.Model):article_id = models.AutoField(primary_key=True)...class Book(models.Model):book_id = models.AutoField(primary_key=True)...class BookReview(Book, Article):pass
或者在公共祖先中存储AutoField。并要求每个父类模型和公共祖先使用显性的OneToOneField,避免与子类自动生成或继承的字段发生冲突。
class Piece(models.Model):passclass Article(Piece):article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)...class Book(Piece):book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)...class BookReview(Book, Article):pass