model
一般来说,每一个模型都映射一张数据库表。
- 每个模型都是一个 Python 的类,这些类继承
django.db.models.Model
- 模型类的每个属性都相当于一个数据库的字段。
字段
命名限制
- 定义字段名时应小心避免使用与模型 API冲突的名称, 如
clean
,save
, ordelete
等. - 不能为python 关键字
- 一个字段名称不能包含连续的多个下划线,会影响 Django 查询语法。
- 字段名不能以下划线结尾,会影响 Django 查询语法。
- SQL保留字,例如 join, where 或 select, 是 可以被用在模型字段名当中的,因为 Django 在对底层的 SQL 查询当中清洗了所有的数据库表名和字段名,通过使用特定数据库引擎的引用语法。
字段类型
模型中每一个字段都应该是某个 Field
类的实例。字段类型用以指定数据库数据类型。
Django 内置了数十种字段类型;你可以在 模型字段参考 中看到完整列表。如果 Django 内置类型不能满足你的需求,你可以很轻松地编写自定义的字段类型;参见 编写自定义模型字段。
常用的几种
BigAutoField
& AutoField
一个 IntegerField,根据可用的 ID 自动递增
BigIntegerField
它保证适合从 -9223372036854775808 到 9223372036854775807 的数字。该字段的默认表单部件是一个 NumberInput。
BooleanField
一个 true/false 字段。
该字段的默认表单部件是 CheckboxInput,或者如果 null=True 则是 NullBooleanSelect。
当 Field.default 没有定义时,BooleanField 的默认值是 None。
CharField
一个字符串字段,适用于小到大的字符串。
对于大量的文本,使用 TextField。
DateField
一个日期,在 Python 中用一个 datetime.date 实例表示
默认表单部件是一个 DateInput
选项
auto_now
每次保存对象时,自动将该字段设置为现在。对于“最后修改”的时间戳很有用。
只有在调用 Model.save() 时,该字段才会自动更新。当以其他方式对其他字段进行更新时,如 QuerySet.update(),该字段不会被更新
auto_now_add
当第一次创建对象时,自动将该字段设置为现在。对创建时间戳很有用。请注意,当前日期是 始终 使用的;它不是一个你可以覆盖的默认值。因此,即使你在创建对象时为该字段设置了一个值,它也会被忽略。如果你想修改这个字段,可以设置以下内容来代替 auto_now_add=True :
EmailField
一个 CharField,使用 EmailValidator 来检查该值是否为有效的电子邮件地址。
FileField
一个文件上传字段
选项
primary_key
参数不支持,如果使用,会引起错误。
upload_to
这个属性提供了一种设置上传目录和文件名的方式,可以有两种设置方式。在这两种情况下,值都会传递给 Storage.save() 方法。
如果你指定一个字符串值或一个 Path,它可能包含 strftime() 格式,它将被文件上传的日期/时间所代替(这样上传的文件就不会填满指定的目录)。例如:
class MyModel(models.Model):
# file will be uploaded to MEDIA_ROOT/uploads
upload = models.FileField(upload_to="uploads/")
# or...
# file will be saved to MEDIA_ROOT/uploads/2015/01/30
upload = models.FileField(upload_to="uploads/%Y/%m/%d/")
如果你使用的是默认的 FileSystemStorage,这个字符串的值将被附加到你的 MEDIA_ROOT 路径后面,形成本地文件系统中上传文件的存储位置。
storage
一个存储对象,或是一个返回存储对象的可调用对象。它处理你的文件的存储和检索。
FloatField
当 localize 为 False 时是 NumberInput 否则,该字段的默认表单部件是 TextInput。
IntegerField
一个整数。从 -2147483648 到 2147483647 的值在 Django 支持的所有数据库中都是安全的
JSONField
一个用于存储 JSON 编码数据的字段。
TextField
一个大的文本字段。该字段的默认表单部件是一个 Textarea。
URLField
URL 的 CharField,由 URLValidator 验证。
该字段的默认表单部件是一个 URLInput。
on_delete
当一个由 ForeignKey 引用的对象被删除时,Django 将模拟 on_delete 参数所指定的 SQL 约束的行为。
CASCADE
: 级联删除PROTECT
: 防止删除被引用对象。RESTRICT
: 通过引发 RestrictedError ( django.db.IntegrityError 的一个子类)来防止删除被引用的对象。与 PROTECT 不同的是,如果被引用的对象也引用了一个在同一操作中被删除的不同对象,但通过 CASCADE 关系,则允许删除被引用的对象。SET_NULL
: 设置 ForeignKey 为空;只有当 null 为 True 时,才有可能。SET_DEFAULT
将 ForeignKey 设置为默认值,必须为 ForeignKey 设置一个默认值。SET()
: 将 ForeignKey 设置为传递给 SET() 的值,或者如果传递的是可调用对象,则调用它的结果。DO_NOTHING
: 不采取任何行动
def get_sentinel_user():
return get_user_model().objects.get_or_create(username="deleted")[0]
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
limit_choices_to
当使用 ModelForm 或管理中渲染该字段时,设置该字段的可用选择限制(默认情况下,查询集中的所有对象都可以选择)。可以使用字典、 Q 对象,或者返回字典或 Q 对象的可调用对象。
related_name
用于从相关对象到这个对象的关系的名称。这也是 related_query_name 的默认值(用于从目标模型反向过滤名称的名称)。
related_query_name
目标模型中反向过滤器的名称。如果设置了,它默认为 related_name 或 default_related_name 的值,否则默认为模型的名称
to_field
关联对象的字段。默认情况下,Django 使用相关对象的主键。如果你引用了一个不同的字段,这个字段必须有 unique=True。
db_constraint
控制是否应该在数据库中为这个外键创建一个约束。
swappable
控制迁移框架的反应,如果这个 ForeignKey 指向一个可交换的模型。
ManyToManyField
一个多对多的关系。
through
Django 会自动生成一个表来管理多对多关系,但是,如果你想手动指定中间表,你可以使用 through 选项来指定代表你要使用的中间表的 Django 模型。
through_fields
只有当指定了一个自定义的中间模型时才会使用,Django 通常会决定使用中介模型的哪些字段来自动建立多对多的关系。
OneToOneField
一对一的关系。类似于 ForeignKey 与 unique=True
parent_link
当 True 并用于从另一个 concrete model 继承的模型中时,表示该字段应被用作回到父类的链接,而不是通常通过子类隐含创建的额外 OneToOneField。
ForeignKey
一个多对一的关系。需要两个位置参数:模型相关的类和 on_delete 选项。
字段选项
null
默认为 False , 相当于数据库 NULL
blank
默认False, 与html 中 input 相关联
choices
存储两个值的元组, 相当于 html 中 select, 元组的第一个值为 index, 第二个值为 value。
数据库中存储的是第一个值。表单中显示的是第二个值。
在 view 中,如果要获取第二个值,可以使用 get_field_display()
, 其中field 为具体的字段名。
from django.db import models
class 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)
# ==============================
# view.py
p = Person(name="Fred Flintstone", shirt_size="L")
p.save()
p.get_shirt_size_display()
也可以使用枚举类定义 choices
class Runner(models.Model):
MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
name = models.CharField(max_length=60)
medal = models.CharField(blank=True, choices=MedalType, max_length=10)
default
该字段的默认值。可以是一个值或者是个可调用的对象,如果是个可调用对象,每次实例化模型时都会调用该对象。
db_default
字段的数据库计算的默认值。这可以是一个字面值或一个数据库函数。
如果同时设置了 db_default 和 Field.default,在 Python 代码中创建实例时 default 会优先生效。db_default 仍然会在数据库级别设置,并且在使用 ORM 之外插入行或在迁移中添加新字段时仍然会使用它。
help_text
随表单控件一同显示。
primary_key
bool值,如果为True,则把当前字段设置为主键。
键字段是只可读的,如果你修改一个模型实例的主键并保存,这等同于创建了一个新的模型实例。
默认情况下,Django 给每个模型一个自动递增的主键,其类型在 AppConfig.default_auto_field
中指定,或者在 DEFAULT_AUTO_FIELD
配置中全局指定。
id = models.BigAutoField(primary_key=True)
如果手动设置主键,django将不会自动生成id。
unique
bool, 这个字段唯一
verbose_name
与 html 中 的 label
关联
如果未指定该参数值, Django 会自动使用字段的属性名作为该参数值,并且把下划线转换为空格。
字段之间的关系
多对一 ForeignKey
在表单中呈现方式为select
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
如果一个模型中有多个字段关联到同一个字段,需要使用related_name
加以区分。
class Order(models.Model):
# 订单创建者,关联到User模型
creator = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="created_orders" # 反向引用名称
)
# 订单接收者,同样关联到User模型
receiver = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="received_orders" # 另一个反向引用名称
)
多对多 ManyToManyField
在表单中呈现为多选ckeck
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
使用 through
参数指定多对多关系使用哪个中间模型。
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through="Membership")
def __str__(self):
return self.name
class 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)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["person", "group"], name="unique_person_group"
)
]
相互访问
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.",
)
beatles.members.all()
ringo.group_set.all()
还可以使用 add()、create() 或 set() 来创建关系,只要为任何必需的字段指定 through_defaults
:
beatles.members.add(john, through_defaults={"date_joined": date(1960, 8, 1)})
beatles.members.create(name="George Harrison", through_defaults={"date_joined": date(1960, 8, 1)} )
beatles.members.set( [john, paul, ringo, george], through_defaults={"date_joined": date(1960, 8, 1)} )
remove
如果由中介模型定义的自定义中介表不对 (model1, model2) 对进行唯一性强制,允许多个值,则 remove() 调用将删除所有中介模型实例
>>> Membership.objects.create(
... person=ringo,
... group=beatles,
... date_joined=date(1968, 9, 4),
... invite_reason="You've been gone for a month and we miss you.",
... )
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>
clear
clear() 方法可以用来删除一个实例的所有多对多关系
beatles.members.clear()
一对一 OneToOneField
本质上是一种特殊的 ForeignKey,但会自动添加 unique=True 约束
反向关联时直接返回单个对象(而非查询集)
常用于扩展现有模型(如用户资料扩展)
from django.contrib.auth.models import User
# 示例:为用户扩展详细资料
class UserProfile(models.Model):
# 与User模型建立一对一关联
user = models.OneToOneField(
User,
on_delete=models.CASCADE, # 当用户被删除时,关联的资料也会被删除
related_name='profile' # 反向引用名称
)
phone = models.CharField(max_length=11, blank=True)
address = models.TextField(blank=True)
birth_date = models.DateField(null=True, blank=True)
def __str__(self):
return f"{self.user.username}的资料"
跨文件
1 导入需要被关联的模型
2. lazy reference。
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE, # 当用户被删除时,关联的资料也会被删除
related_name='profile' # 反向引用名称
)
# 或者
user = models.OneToOneField(
'django.contrib.auth.models.User',
on_delete=models.CASCADE, # 当用户被删除时,关联的资料也会被删除
related_name='profile' # 反向引用名称
)
Meta 选项
在 模型可选参数参考 中列出了 Meta
可使用的全部选项。
abstract
如果 abstract = True
,这个模型将是一个抽象基类。
base_manager_name
管理器的属性名,例如,'objects'
,用于模型的 _base_manager
。
db_table
用于模型的数据库表的名称, 是用小写,方便和数据库对应。
db_table_comment
用于此模型的数据库表的注释
managed
默认为 True,意味着 Django 会在 migrate 中创建相应的数据库表,或者作为迁移的一部分,并作为 flush 管理命令的一部分删除它们。
如果 False,将不对该模型进行数据库表的创建、修改或删除操作。
ordering
对象的默认排序,用于获取对象列表时
ordering = ["-pub_date", "author"]
indexes
索引
class Customer(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
class Meta:
indexes = [
models.Index(fields=["last_name", "first_name"]),
models.Index(fields=["first_name"], name="first_name_idx"),
]
constraints
class Customer(models.Model):
age = models.IntegerField()
class Meta:
constraints = [
models.CheckConstraint(check=models.Q(age__gte=18), name="age_gte_18"),
]
verbose_name
verbose_name = "pizza"
verbose_name_plural
verbose_name_plural = "stories"
模型属性
模型当中最重要的属性是 Manager
。它是 Django 模型和数据库查询操作之间的接口,并且它被用作从数据库当中 获取实例,如果没有指定自定义的 Manager
默认名称是 objects
。Manager 只能通过模型类来访问,不能通过模型实例来访问。
模型方法
__str__()
显示名称
get_absolute_url()
该方法告诉 Django 如何计算一个对象的 URL。Django 在后台接口使用此方法,或任意时间它需要计算一个对象的 URL。
任何需要一个唯一 URL 的对象需要定义此方法。
自定义
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"
@property
def full_name(self):
"Returns the person's full name."
return f"{self.first_name} {self.last_name}"
重写
class 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()
执行自定义SQL
objects.raw()
name_map = {"first": "first_name", "last": "last_name", "bd": "birth_date", "pk": "id"}
Person.objects.raw("SELECT * FROM some_other_table", translations=name_map)
模型继承
Django 有三种可用的集成风格
抽象基类
父类用于子类公共信息的载体
在 Meta 类中填入 abstract=True,该模型将不会创建任何数据表
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)
当一个抽象基类被建立,Django 将所有你在基类中申明的 Meta 内部类以属性的形式提供。若子类未定义自己的 Meta 类,它会继承父类的 Meta。当然,子类也可继承父类的 Meta
注意事项
对 related_name 和 related_query_name 要小心使用,因为是唯一的,如果继承,会导致重复。
为了解决此问题,当你在抽象基类中(也只能是在抽象基类中)使用 related_name 和 related_query_name,部分值需要包含 ‘%(app_label)s’ 和 ‘%(class)s’。
- ‘%(class)s’ 用使用了该字段的子类的小写类名替换。
- ‘%(app_label)s’ 用小写的包含子类的应用名替换。每个安装的应用名必须是唯一的,应用内的每个模型类名也必须是唯一的。因此,替换后的名字也是唯一的。
class Base(models.Model):
m2m = models.ManyToManyField(
OtherModel,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",
)
class Meta:
abstract = True
class ChildA(Base):
pass
class ChildB(Base):
pass
多表继承
每个模型都是一个单独的模型。每个模型都指向分离的数据表,且可被独立查询和创建。
class 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)
多表继承情况下,子类不会继承父类的 Meta。所以的 Meta 类选项已被应用至父类,在子类中再次应用会导致行为冲突
代理模型
代理模型就像普通模型一样申明。你需要告诉 Django 这是一个代理模型,通过将 Meta 类的 proxy 属性设置为 True。
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
MyPerson 类操作的是与其父类 Person 类相同的数据库表。特别是,任何新的 Person 实例也可以通过 MyPerson 访问,反之亦然
>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
多重继承
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
子类不能和父类的字段名相同
在正常的 Python 类继承中,允许子类覆盖父类的任何属性。在 Django 中,模型字段通常不允许这样做。如果一个非抽象模型基类有一个名为 author 的字段,你就不能在继承自该基类的任何类中,创建另一个名为 author 的模型字段或属性。(抽象基类除外)
在一个包中管理模型
如果有很多 models.py 文件,用独立的文件管理它们会很实用。
为了达到此目的,创建一个 models 包。删除 models.py,创建一个 myapp/models 目录,包含一个 init.py 文件和存储模型的文件。你必须在 init.py 文件中导入这些模块。
# __init__.py
from .organic import Person
from .synthetic import Robot