Post

【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应用会根据每个模型的字段自动生成表单,如下图所示)。

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:指定列表界面每页显示的条目数,默认为100
  • ordering:字段名列表,指定对象的排序方式,字段名前加"-"表示倒序排序,例如['-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.字段

官方文档:

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,则将blanknull均设置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/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的id
  • question.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)

例如:UserReader一一对应,则在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/

下面以QuestionChoice两个模型为例(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构造一个QuerySetQuerySet相当于一个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)连接查询

QuestionChoice是一对多的关系,q.choice_set是一个包含q关联的所有ChoiceQuerySetc.questionc关联的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']xxxDATABASES设置中定义的数据库别名。

This post is licensed under CC BY 4.0 by the author.