【Django】模型
1.模型
模型(model, MVC中的M)对应一个数据库表,相当于实体类。
官方文档:https://docs.djangoproject.com/en/stable/topics/db/models/
1.1 定义模型
应用目录下的models.py定义应用中使用的模型,每个模型都是django.db.models.Model
的子类,类属性(Field
类的实例)对应数据库中的字段,默认情况下属性名即为字段名,主键(id字段)将被自动添加。
例如:
1
2
3
4
5
6
7
8
9
10
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
相当于创建了以下两个表:
1
2
Question(id, question_text, pub_date)
Choice(id, question_id, choice_text, votes)
1.2 修改模型
初次创建模型后,使用makemigrations
命令创建模型更改记录(migration,对应数据库表结构的修改):
1
python manage.py makemigrations polls
可使用sqlmigrate
命令查看migration对应的SQL:
1
python manage.py sqlmigrate polls 0001
使用migrate
命令将更改应用到数据库:
1
python manage.py migrate
修改模型的三个步骤:
- 修改models.py中的模型定义
- 运行
python manage.py makemigrations
,为更改创建migration - 运行
python manage.py migrate
,将更改应用到数据库
1.3 CRUD
模型类本身充当DAO,模型类本身及其objects
属性(django.db.models.manager.Manager
类型的对象)提供CRUD接口,查询语法见第3节。
1.4 admin应用
官方文档:https://docs.djangoproject.com/en/stable/ref/contrib/admin/
Django默认提供了一个admin应用,可通过UI进行模型的增删改查操作(admin应用会根据每个模型的字段自动生成表单,如下图所示)。
创建管理员用户:
1
python manage.py createsuperuser
在admin.py中注册想要管理的模型:
1
admin.site.register(Question)
访问 http://127.0.0.1:8000/admin/ 并使用管理员用户登录后进入管理界面。
ModelAdmin选项
可通过继承admin.ModelAdmin
类来自定义admin应用中某个模型的展示界面
1
2
3
4
5
6
class BookAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'publisher')
list_filter = ['tag']
search_fields = ['title']
admin.site.register(Book, BookAdmin)
列表界面常用选项:
list_display
:字段名列表,指定显示的字段(默认只有一列显示__str__()
的结果)list_filter
:字段名列表,指定右侧显示过滤器的字段search_fields
:字段名列表,指定显示搜索框的字段list_per_page
:指定列表界面每页显示的条目数,默认为100ordering
:字段名列表,指定对象的排序方式,字段名前加"-"
表示倒序排序,例如['-pub_date', 'author']
表示先按pub_date
倒序排序、再按author
升序排序
详情界面常用选项:
readonly_fields
:字段名列表,指定表单中只读的字段exclude
:字段名称列表,指定表单中排除的字段raw_id_fields
:字段名列表,表单中的外键字段默认显示为<select>
或<select multiple>
标签(此时下拉列表需要查询全部关联对象,当数据量很大时会非常慢);raw_id_fields
中指定的(外键)字段将改为<input>
标签,内容为逗号分隔的外键id,从而省去大量数据库查询
1.5 为模型提供初始数据
https://docs.djangoproject.com/en/stable/howto/initial-data/
1.6 模型继承
https://docs.djangoproject.com/en/stable/topics/db/models/#model-inheritance
- 如果只是使用父模型保存公共字段,父模型不会被单独使用,则使用抽象基类
- 如果要继承现有模型,每个模型有自己的数据库表,则使用多表继承
- 如果只是改变模型类的行为,而不改变模型字段,则使用代理模型
1.7 Meta类
https://docs.djangoproject.com/en/stable/ref/models/options/
每个模型类都可以定义一个名为Meta
的内部类,用于指定元数据(例如数据库表名、人类可读名称、排序方式等)。
例如:
1
2
3
4
5
6
7
8
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
常用选项:
db_table
:数据库表名verbose_name
:人类可读名称verbose_name_plural
:名称复数形式ordering
:字段列表,指定查询时的默认排序方式,添加"-"
前缀表示倒序排序,例如ordering = ['-pub_date', 'author']
permissions
:二元组列表,自定义权限,二元组格式为(permission_code, human_readable_permission_name)
2.字段
官方文档:
- https://docs.djangoproject.com/en/stable/topics/db/models/#fields
- https://docs.djangoproject.com/en/stable/ref/models/fields/
2.1 Field类通用属性
db_column
:数据库字段名,若未指定则使用field的名字primary_key
:该字段是否为主键,如果一个模型未指定主键则Django将自动添加主键blank
:如果为True
则表单允许该字段为空,默认为False
null
:如果为True
则将空值存储为NULL
(数据库允许该字段为空),默认为False
default
:字段默认值,可以是一个值或callable
对象choices
:字段可选值,相当于枚举类型(见“choices和枚举类型”)db_index
:是否为该字段创建索引,默认False
unique
:如果为True
则该字段的值唯一,此时自动创建UNIQUE
索引verbose_name
:人类可读名称(对于普通字段作为第一个位置参数,对于外键字段作为关键字参数)
注:字段默认均为非空,即SQL类型后添加NOT NULL
。要允许字符串类型的字段为空则将null
设置为True
即可;要允许整型、日期等类型的字段为空,即要想实现表单中留空则数据库中存储为NULL
,则将blank
和null
均设置True
。例如:
1
2
foo = CharField(max_length=32) # foo VARCHAR(32) NOT NULL
bar = CharField(max_length=32, null=True) # bar VARCHAR(32) NULL
2.2 常用字段类型
Field子类 | 等价SQL类型(MySQL) |
---|---|
CharField(max_length=n) | VARCHAR(n) |
TextField() | LONGTEXT |
IntegerField() | INT |
BigIntegerField() | BIGINT |
AutoField() | INT AUTO_INCREMENT |
DecimalField(max_digits=m, decimal_places=n) | DOUBLE(m, n) |
DateField(auto_now=False, auto_now_add=False) | DATE |
DateTimeField(auto_now=False, auto_now_add=False) | DATETIME |
2.3 外键
官方文档:
- https://docs.djangoproject.com/en/stable/topics/db/models/#relationships
- https://docs.djangoproject.com/en/stable/ref/models/fields/#module-django.db.models.fields.related
查询语法:https://docs.djangoproject.com/en/stable/topics/db/queries/#related-objects
2.3.1 多对一
1
ForeignKey(to, on_delete)
例如,一个Question
对应多个Choice
,则在Choice
类中定义:
1
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice.question
是与choice
关联的Question
对象choice.question_id
是与choice
关联的Question
的idquestion.choice_set.all()
是与question
关联的所有Choice
对象
可通过related_name
属性覆盖"choice_set"
名称,设置为'+'
则不创建反向关联。
on_delete
指定当外键关联的对象被删除时执行的动作,可选值(都定义在django.db.models
模块):
CASCADE
:级联删除PROTECT
:阻止删除,抛出ProtectedError
异常SET_NULL
:置为NULL
,要求外键字段的null=True
SET_DEFAULT
:置为默认值,外键字段必须指定了default
属性SET(value)
:置为指定值,如果value
是函数则使用函数返回值
递归关系(一个模型和自己有多对一的关系):
1
models.ForeignKey('self', on_delete=models.CASCADE)
2.3.2 多对多
1
ManyToManyField(to)
例如:一个Author
对应多个Paper
,一个Paper
也对应多个Author
,则在Author
类中定义:
1
papers = models.ManyToManyField(Paper)
author.papers.all()
是与author
关联的所有Paper
对象paper.author_set.all()
是与paper
关联的所有Author
对象
2.3.3 一对一
1
OneToOneField(to)
例如:User
和Reader
一一对应,则在Reader
类中定义:
1
user = models.OneToOneField(User)
reader.user
是与reader
关联的User
对象user.reader
是与user
关联的Reader
对象
2.4 choices和枚举类型
https://docs.djangoproject.com/en/stable/ref/models/fields/#choices
Field.choices
属性是二元组(value, name)
的列表,二元组的第一项是字段的真实值,第二项是人类可读名称,例如:
1
2
3
4
5
6
7
8
9
class Student(models.Model):
YEAR_IN_SCHOOL_CHOICES = [
('FR', 'Freshman'),
('SO', 'Sophomore'),
('JR', 'Junior'),
('SR', 'Senior'),
('GR', 'Graduate'),
]
year_in_school = models.CharField(max_length=2, choices=YEAR_IN_SCHOOL_CHOICES, default='FR')
对于每一个设置了choices
的字段,Django会自动为模型添加一个get_foo_display()
方法,用于获取该字段当前值的人类可读名称:
1
2
3
>>> s = Student(year_in_school='GR')
>>> s.get_year_in_school_display()
'Graduate'
另外Django还专门为choices
提供了枚举类型(内部使用Python的enum
模块实现):
1
2
3
4
5
6
7
8
9
class Student(models.Model):
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR', 'Freshman'
SOPHOMORE = 'SO', 'Sophomore'
JUNIOR = 'JR', 'Junior'
SENIOR = 'SR', 'Senior'
GRADUATE = 'GR', 'Graduate'
year_in_school = models.CharField(max_length=2, choices=YearInSchool.choices, default=YearInSchool.FRESHMAN)
枚举类的values
, labels
, choices
, names
属性分别为值列表、标签(人类可读名称)列表、二元组列表(同上)和枚举成员名称列表,枚举成员的label
属性为对应的标签
1
2
3
4
5
class Suit(models.IntegerChoices):
SPADE = 1, 'Spade'
HEART = 2, 'Heart'
CLUB = 3, 'Club'
DIAMOND = 4, 'Diamond'
1
2
3
4
5
6
7
8
9
10
11
>>> Suit.values
[1, 2, 3, 4]
>>> Suit.labels
['Spade', 'Heart', 'Club', 'Diamond']
>>> Suit.choices
[(1, 'Spade'), (2, 'Heart'), (3, 'Club'), (4, 'Diamond')]
>>> Suit.names
['SPADE', 'HEART', 'CLUB', 'DIAMOND']
>>> suit = Suit.SPADE
>>> suit.label
'Spade'
3.查询
官方文档:https://docs.djangoproject.com/en/stable/topics/db/queries/
下面以Question
和Choice
两个模型为例(q
表示Question
类的对象,c
表示Choice
类的对象)。
3.1 创建对象
1
2
q = Question(question_text="What's new?", pub_date=datetime.now())
q.save()
捷径:create()
,以上两行代码等价于
1
q = Question.objects.create(question_text="What's new?", pub_date=datetime.now())
批量创建:bulk_create(objs, batch_size=None)
3.2 更新对象
假设q
已被保存到数据库
1
2
q.question_text = "What's up?"
q.save()
q.id
为空时q.save()
执行INSERT
语句;q.id
非空时q.save()
执行UPDATE
语句。- 设置外键的值:
c.question = q
3.3 删除对象
1
q.delete()
3.4 查询对象
查询的本质是通过模型类的Manager
构造一个QuerySet
。QuerySet
相当于一个SELECT
语句,每个模型类都有一个默认的Manager
,即objects
属性(注意是类属性,通过模型类访问,而不是模型类的对象)。
QuerySet API参考:https://docs.djangoproject.com/en/stable/ref/models/querysets/
查询全部对象:Question.objects.all()
查询记录数量:Question.objects.count()
条件过滤:
filter(**kwargs)
返回一个新的QuerySet
,包含满足指定条件的对象exclude(**kwargs)
返回一个新的QuerySet
,包含不满足指定条件的对象get(**kwargs)
返回满足条件的单个对象,如果不存在则产生Question.DoesNotExist
异常
注意:QuerySet
是惰性求值的,构造的过程不执行任何数据库查询,只有最终获取对象时才执行查询。
kwargs
格式:field__lookuptype=value
完整语法:https://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups
举例:
(1)基本查询
①查询主键(id)为1的问题
1
2
Question.objects.filter(id=1)
Question.objects.get(pk=1)
等价于
1
SELECT * FROM Question WHERE id = 1
②查询票数小于1的选项
1
Choice.objects.filter(votes__lt=1)
等价于
1
SELECT * FROM Choice WHERE votes < 1
③查询以 “What” 开头的问题
1
Question.objects.filter(question_text__startswith='What')
等价于
1
SELECT * FROM Question WHERE question_text LIKE 'What%'
④查询包含 “up” 的问题
1
Question.objects.filter(question_text__contains='up')
等价于
1
SELECT * FROM Question WHERE question_text LIKE '%up%'
⑤查询2019年发布的问题
1
Question.objects.get(pub_date__year=2019)
等价于
1
SELECT * FROM Question WHERE YEAR(pub_date) = 2019
(2)连接查询
Question
和Choice
是一对多的关系,q.choice_set
是一个包含q
关联的所有Choice
的QuerySet
,c.question
是c
关联的Question
。
①查询q
关联的所有选项
1
q.choice_set.all()
等价于
1
2
3
4
SELECT Choice.*
FROM Choice INNER JOIN Question
ON Choice.question_id = Question.id
WHERE Question.id = q.id
②为q
创建一个名为 “Not much” 、票数为0的选项
1
c = q.choice_set.create(choice_text='Not much', votes=0)
等价于
1
INSERT INTO Choice VALUES (NULL, 'Not much', 0, q.id)
③查询c
关联的问题
1
c.question
等价于
1
SELECT * FROM Question WHERE id = c.question_id
④查询2019年发布的问题的所有选项
1
Choice.objects.filter(question__pub_date__year=2019)
等价于
1
2
3
4
SELECT Choice.*
FROM Choice INNER JOIN Question
ON Choice.question_id = Question.id
WHERE YEAR(Question.pub_date) = 2019
⑤查询q
关联的以 “Just hacking” 开头的选项
1
c = q.choice_set.filter(choice_text__startswith='Just hacking')
等价于
1
SELECT * FROM Choice WHERE Choice.question_id = q.id AND Choice.choice_text LIKE 'Just hacking%'
4.使用现有数据库表
官方文档:https://docs.djangoproject.com/en/stable/ref/models/options/#managed
在模型的内部类Meta
中指定managed = False
,则Django不对该模型类进行创建表等操作,此时应明确定义主键字段。
1
2
3
4
5
6
class Foo(models.Model):
id = models.AutoField(primary_key=True)
# ...
class Meta:
managed = False
在单元测试中需要手动创建表
1
2
3
4
5
6
7
8
9
10
11
from django.db import connections
from django.test import TestCase
from foo.models import Foo
class FooTests(TestCase):
@classmethod
def setUpClass(cls):
with connection.schema_editor() as editor:
editor.create_model(Foo)
super().setUpClass()
如果要使用其他数据库则将connection
改为connections['xxx']
,xxx
为DATABASES
设置中定义的数据库别名。