心蓝的博客 心蓝的博客
首页
  • 零基础

    • python零基础入门
  • 专项

    • 正则表达式
  • web框架

    • django框架
    • drf
技术
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档

心蓝

人生苦短,我用python
首页
  • 零基础

    • python零基础入门
  • 专项

    • 正则表达式
  • web框架

    • django框架
    • drf
技术
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
  • 零基础

  • 专项

  • web框架

    • django框架

      • web框架介绍
      • python虚拟环境
      • 创建django项目与应用
      • django中的路由系统
      • web框架设计模式
      • 模板
      • 静态文件引用
      • web应用开发模式
      • 请求和响应
      • 视图
      • ORM与模型
        • ORM
          • 安装数据库
          • django配置数据库
          • 安装驱动
          • windows环境
          • mac环境
          • linux环境
          • 连接配置
        • 模型
          • 创建模型
          • 字段类型
          • 字段参数
          • 自动设置主键
          • Meta选项
          • __str__方法
          • 数据库迁移
          • 安装应用
          • 生成迁移文件
          • 执行迁移
          • 表关系
          • 表设计
          • 多对一
          • 级联操作
          • 多对多
          • 自定义中间表
          • 一对一
      • 数据库操作
      • 项目实战一
      • 表单
      • RESTful API
      • 项目实战二
      • djangoadmin
    • drf

  • python
  • web框架
  • django框架
心蓝
2022-12-21
目录

ORM与模型

# ORM与模型

# ORM

对象关系映射(Object Relational Mapping,简称ORM)!简单的说就是用面向对象的方式,描述数据库,操作数据库,达到不用编写SQL语句就能对数据库进行增删改查。

django内置了一套ORM框架,它的映射关系是:

1660816727671

然后通过对类,类属性,实例的各种操作,达到操作数据库的功能,底层是生成原生sql语句进行数据库操作。

# 安装数据库

我们的项目选择使用MariaDB,它是MySQL的一个分支,开源免费,越来越多的web项目开始使用它。直接安装数据库相对比较麻烦和难以维护,推荐使用docker安装数据库。安装docker的方案如下:

  • 方案一:

windows直接安装Docker Desktop,不推荐,个人感觉还是很影响系统的使用,且需要开启虚拟服务,会与某些软件冲突。

mac可以直接安装Docker Desktop。

  • 方案二:

windows系统,安装虚拟机,然后安装linux的虚拟机,再到虚拟机中安装docker环境。

虚拟机软件有,virtualbox(免费开源),vimware(收费),安装简单,使用稍复杂,资源占用大。

用过虚拟机的童靴可以选择此方案,从来没用过的可以忽略。

mac不建议。

  • 方案三:

买一台云服务器,在云服务器中安装docker。

阿里云,百度云,腾讯云,华为云,都有新用户优惠,几十块钱一年,推荐使用。

最后项目的部署也会使用云服务器。

阿里云新人优惠连接 (opens new window)

注意:系统选择ubuntu或者centos。

docker命令:

docker run --name mariadb --restart=always -d -v mariadb:/var/lib/mysql -e MARIADB_ROOT_PASSWORD=pythonvip -p 4000:3306 -e MARIADB_DATABASE=easytest mariadb:latest
1
python
1

上面这条命令会根据mariadb数据库镜像mariadb:latest,创建一个名为mariadb的容器,并创建一个名为easytest的数据库,设置root账号的密码为pythonvip,映射3306端口到宿主机4000端口。

# django配置数据库

# 安装驱动

django官方支持一下数据库:

  • PostgreSQL (opens new window)
  • MariaDB (opens new window)
  • MySQL (opens new window)
  • Oracle (opens new window)
  • SQLite (opens new window)

不同的数据库都需要对应的python数据库驱动程序。

我们接下来使用MariaDB数据库,它是MySQL的一个分支,在Django中它的配置和MySQL一致。

django推荐使用mysqlclient作为MySQL或MariaDB的数据库驱动。

# windows环境

在网站https://www.lfd.uci.edu/~gohlke/pythonlibs/#mysqlclient (opens new window)下载与python版本对应的mysqlclient本地安装文件,再使用pip命令安装,例如:

pip install mysqlclient‑1.4.6‑cp38‑cp38‑win_amd64.whl  # py3.8 64位
1
# mac环境

mac环境下依赖mysql客户端。

$ brew install mysql-client
$ echo 'export PATH="/usr/local/opt/mysql-client/bin:$PATH"' >> ~/.bash_profile
$ source .bash_profile
$ pip install mysqlclient
1
2
3
4
# linux环境

linux环境下需要对应的依赖,根据环境不同依赖有所不同,下面的只是基本的步骤,不能保证在所有的环境上都有效。

Debian/Ubuntu

$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essential
$ pip install mysqlclient
1
2

Red Hat /CentOS

sudo yum install python3-devel mysql-devel
pip install mysqlclient
1
2
# 连接配置

安装好数据库和数据库驱动后,还需要在settings.py配置模块中进行连接配置:

  1. 直接配置
# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',		# 对应数据库引擎
        'NAME': 'easytest',							# 数据库名
        'USER': 'root',								# 用户名
        'PASSWORD': '12345678',						# 密码
        'HOST': '127.0.0.1',						# 主机
        'PORT': '3306',								# 端口
    }
}
1
2
3
4
5
6
7
8
9
10
11
  1. 通过配置文件配置
# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/path/to/my.cnf',		# 配置文件路径
        },
    }
}

# my.cnf
database = easytest				# 数据库名
user = root						# 用户名
password = 12345678				# 密码
host = 127.0.0.1				# 主机
port = 3306						# 端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

配置好后,运行python manage.py runserver如果可以正常运行说明数据库配置成功,项目成功连接数据库。

# 模型

Django中模型准确且唯一的描述了数据。它包含储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。

  • 每个模型都是一个 Python 的类,这些类继承django.db.models.Model
  • 模型类的每个属性都相当于一个数据库的字段
  • 利用这些,Django 提供了一个自动生成访问数据库的 API

# 创建模型

在创建模型前,先为我们的crm系统设计一张student表,表结构如下:

  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NUll,
  `age` tinyint(1) DEFAULT NULL,
  `sex` tinyint(1) DEFAULT 1,
  `phone` varchar(20) DEFAULT NULL UNIQUE,
  `c_time` datetime(6) NOT NULL
1
2
3
4
5
6

然后根据表设计,在crm/models.py中编写如下模型类:

# models.py
class Student(models.Model):
    name = models.CharField('姓名', max_length=20, help_text='姓名')
    age = models.SmallIntegerField('年龄', null=True, blank=True, help_text='年龄')
    sex = models.SmallIntegerField('性别', default=1, help_text='性别')
    phone = models.CharField('手机号码', max_length=20, null=True, blank=True, unique=True, help_text='手机号码')
    c_time = models.DateTimeField('创建时间', auto_now_add=True)
    
    class Meta:
        db_table = 'tb_student'
        verbose_name = '学生信息'
        verbose_name_plural = verbose_name
        ordering = ['-c_time']
        
    def __str__(self):
		return self.name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面的代码很简单,Student类就是一个模型,表示一张数据库表。类中有几个变量,每个变量表示数据库中的一个字段。

# 字段类型

每个字段由一个字段类的实例表示。

例如:字符字段使用CharField,日期时间使用DateTimeField。

每个字段实例的名称(例如,name,age)就是字段的名称。数据库将会使用它作为列名。

下面是常用字段类型和数据库字段类型的映射关系:

1660827565665

更多字段类型见官方文档 (opens new window)

# 字段参数

每个字段在实例化时可以接收多个参数,用来提供不同的功能。常用的字段参数有:

  • verbose_name

    人类可读的字段详细名称,如果没有给定会使用字段的属性名自动创建。

  • primary_key

    主键设置,如果为True,则将该字段设置为模型的主键。如果编写模型是没有设置任何字段为主键,django会自动添加一个id字段作为主键。

  • unique

    唯一索引,如果为True,这个字段会在数据库层面创建唯一索引。

  • blank

    如果为True,该字段允许为空。默认为False。注意该字段只与验证相关。

  • null

    如果为True,django将在数据库存储空值为NULL。默认为False。注意如果希望表单中允许空值,还需要设置blank=True,因为null参数只影响数据库的存储。

  • default

    默认值。可以是一个值或者一个调用对象。当创建模型实例且没有为该字段提供值时,使用默认值。

  • help_text

    额外的帮助文本,会随表单控件一同显示。它对生成文档也很有用。

  • db_column

    这个参数可以设置字段的数据库列名。如果不设置默认使用字段名作为数据库列名。

  • validators

    该字段运行的验证器列表。

  • error_messages

    这个参数可以覆盖字段引发的默认错误信息。传入一个与你想覆盖的错误信息相匹配的键值的字典。

  • max_length

    CharFiled类型字段必须传递的参数,表示该字段的最大长度(以字符为单位)。max_length会在数据库层面强制执行。

# 自动设置主键

默认情况下,django给每个模型一个自动递增的主键,其类型在AppConfig.default_auto_field中指定,或者在DEFAULT_AUTO_FIELD配置中全局指定。例如:

id = models.BigAutoField(primary_key=True)
1

如果你想自己指定主键,在你想要设置为主键的字段上设置参数primary_key=True。

注意每个模型都需要一个主键字段。

# Meta选项

在模型内定义Meta类来给模型赋予元数据。

在我们的例子中db_table='tb_project'表示创建表的时候指定表名为tb_project,不使用默认的表名。默认会用应用名_模型名小写作为表名。

verbose_name和verbose_name_plural表示模型的可读名称的单数和复数,主要在admin站点中使用。

ordering=['-c_time']表示查询数据时默认按照c_time字段降序排列。

Meta选项较多,且和很多高级功能有关,在后面的课程中用到再详细讲解。

更多的选项参考官方文档 (opens new window)

# __str__方法

每个模型都应该定义一个__str__方法,当打印模型对象时,它的值友好的展示一个对象。

# 数据库迁移

编写模型后,django会将模型的修改应用至数据库进行关联,还需要将模型和数据库进行关联,这个操作称为迁移。

# 安装应用

要进行模型数据库迁移,首先需要将应用安装到项目中。在配置项INSTALLED_APPS中添加要安装的应用的设置类。例如,安装crm应用到项目中的配置如下:

study_django/settings.py
INSTALLED_APPS = [
    ...
    'crm.apps.CrmConfig'
]
1
2
3
4
5

应用的设置类是自动生成的,在应用根目录的apps.py模块中,安装应用时,使用点式路径,例如crm.apps.CrmConfig。

# 生成迁移文件

在命令行输入如下命令:

python mamage.py makemigrations crm
1

你将会看到类似于下面这样的输出:

Migrations for 'crm':
  crm\migrations\0001_initial.py
    - Create model Student
1
2
3

通过运行makemigrations命令,django会检测你对模型文件的修改(当前是创建),并把修改的部分存储为一次迁移。迁移是django对模型定义的变化的记录。上面的命令会在crm/migrations目录中生成0001_initial.py,这就是迁移文件。感兴趣可以打开阅读它们,别担心,不需要每次都阅读迁移文件,但是它被设计成人类可阅读的形式,这是便于手动调整django的修改方式。

# 执行迁移

生成迁移文件之后,还需要执行迁移,同步数据库。django通过命令migrate来执行迁移。

在运行迁移之前,我们可以看看,迁移会执行哪些SQL语句。

命令sqlmigrate接收一个迁移的名称,然后返回对应的SQl:

python manage.py sqlmigrate crm 0001
1

输出如下:

--
-- Create model Student
--
CREATE TABLE "tb_student" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL, "age" smallint N
ULL, "sex" smallint NOT NULL, "phone" varchar(20) NULL UNIQUE, "c_time" datetime NOT NULL);

1
2
3
4
5
6

输出格式跟你使用的数据库有关。

现在,运行migrate命令,在数据库里创建新定义的模型的数据表:

python .\manage.py migrate crm
Operations to perform:
  Apply all migrations: crm
Running migrations:        
  Applying crm.0001_initial... OK
1
2
3
4
5

django通过在数据库中创建一个特殊的表django_migrations来跟踪执行过哪些迁移。

migrate命令会执行所有没有执行过的迁移,将模型的修改同步到数据库结构上。

迁移时非常强大的功能,是的开发过程中持续的改变数据库结构而不需要重新删除和创建表,它专注于使用数据库平滑升级而不丢失数据。

记住,改变模型需要这三步:

  • 编辑models.py文件,修改模型
  • 运行python manage.py makemigrations为模型的改变生成迁移文件
  • 运行python manage.py migrate来应用数据库迁移

# 表关系

显然,关系型数据库的强大之处在于各表之间的关联关系。 Django 提供了定义三种最常见的数据库关联关系的方法:多对一,多对多,一对一。

# 表设计

为我们的crm系统设计学生表(tb_student),学生详情表(tb_student_detail),渠道表(tb_channel),课程表(tb_course),报名表(tb_entry),表关系和字段如下图:

1660892273362

# 多对一

上图中的学生表和渠道表,一个学生会对应一个渠道,一个渠道对应多个学生,学生表中的一条数据和渠道表中的一条数据对应,渠道表中的一条数据与学生表中的多条数据对应,学生表和渠道表形成多对一的关系。

在django中要表达多对一的关系需要使用django.db.models.ForeignKey字段,创建一个渠道模型,然后在Student模型中添加一个外键字段如下:

from django.db import models


class Student(models.Model):
	...
    channel = models.ForeignKey('Channel', null=True, on_delete=models.SET_NULL, help_text='渠道')
    ...
    
class Channel(models.Model):
    title = models.CharField('名称', max_length=20, help_text='渠道名称')

    class Meta:
        db_table = 'tb_channel'
        verbose_name = '渠道表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

外键字段定义在多的一方,所以Student模型中定义了channel字段,这个字段名一般使用关系模型的小写名。

外键字段的第一个参数是一个位置参数,即需要关联的模型,可以是模型本身,也可以是模型的字符串形式的导入路径(当引用后定义的模型时很有用)。

在数据库层面,django会在字段名后附加_id来创建数据库列名。所以Student模型的数据库表将有一个channel_id列,然后会为这个列创建一个外键约束,被引用表为tb_channel,被引用字段为tb_channel.id。

注意:有时候为了效率,在数据库不创建外键约束,而是通过代码逻辑来保证数据的完整性。在django中可以在ForeiginKey字段中指定db_constraint=False来控制不创建外键约束。

# 级联操作

当一个由ForeignKey引用的对象被删除时,django通过on_delete参数指定的方法实现数据库级联操作。

on_delete的可能值有:

  • CASCADE

    级联删除

  • PROTECT

    通过引发ProtectedErro防止删除被引用字段

  • RESTRICT

    通过引发RestrictedError方式删除被引用字段

  • SET_NULL

    设置外键为空,只有null为True是才能

  • SET_DEFAULT

    将外键设置为默认值,必须为外键设置默认值

注意:外键字段必须指定参数on_delete。

当删除一个渠道时,在业务层面不大可能会删除对应的学生,应该是设置channel_id字段为null,所以这个外键字段的级联操作设置为null=True, on_delete=models.SET_NULL

# 多对多

数据库中多对多的关系通过第三张表来表示。

一个学生可以报名多门课程,一个课程可以被多名学生报名。在我们的案例中学生表和课程表通过报名表实现多对多的关系。

在django中要表达多对多的关系要使用django.db.models.ManyToManyField字段。

对于多对多关系的两个模型,可以在其中任意一个模型中定义多对多字段,但只能选择一个模型设置该字段,不能同时在两个模型中添加该字段。

一般来说应该把多对多字段放到需要在表单中编辑的对象中,或者是在业务中需要查询更多的模型中。

在我们的案例中,编辑学生对象,或者编辑课程对象时都不需要彼此,而在查询时,"报名了某个课程的学生有哪些",这个需求会更多,所以把多对多的字段定义在课程表中。定义Course模型如下:

class Course(models.Model):
    name = models.CharField('名称', max_length=20, help_text='课程名称')
    students = models.ManyToManyField('Student', help_text='报名学生')

    class Meta:
        db_table = 'tb_course'
        verbose_name = '课程表'
        verbose_name_plural = verbose_name
1
2
3
4
5
6
7
8

多对多字段名一般设置为关系模型名的复数形式,表示关系模型的对象集合,所以Course模型中的多对多字段名为students。多对多字段的第一个参数是一个位置参数,既需要关联的模型,可以是模型本身,也可以是模型的字符串形式的导入路径(当引用后定义的模型时很有用)。

在数据库层面,django会自动创建一个中间表来表示多对多关系。默认情况下这个表名使用创建多对多字段的模型名和字段名生成,上面的例子会生成表名tb_course_students。然后包含两个字段,分别是两个模型的名字和_id组成(student_id,course_id),并创建外键引用对应表的id,还会对这两个字段创建联合唯一的约束。

# 自定义中间表

虽然django会自动创建第三张表,但是不能提供额外字段。如果中间表需要包含其他字段,就需要自定义中间表,然后在定义多对多字段时通过through参数指定第三张表。

所以创建一个Entry模型如下:

class Entry(models.Model):
    student = models.ForeignKey(Student, verbose_name='学生', help_text='学生', on_delete=models.PROTECT)
    course = models.ForeignKey(Course, verbose_name='课程', help_text='课程', on_delete=models.PROTECT)
    c_time = models.DateTimeField('报名时间', help_text='报名时间', auto_now_add=True)

    def __str__(self):
        return '{}-{}'.format(self.student.name, self.course.name)

    class Meta:
        db_table = 'tb_entry'
        verbose_name = '报名表'
        verbose_name_plural = verbose_name
1
2
3
4
5
6
7
8
9
10
11
12

在其中定义student和course外键字段分别和对应的模型形成多对一的关系。再定义c_time字段,用来记录报名时间。学生表,课程表通过报名来表达多对多关系。现在创建多对多字段主要是为了查询方便,可以在课程对象和学生对象上彼此关联,所以修改Course模型中的students字段如下:

class Course(models.Model):
    ...
    students = models.ManyToManyField('Student', help_text='报名学生', through='Entry')
	...
1
2
3
4

# 一对一

在django中要表达一对一的关系需要使用django.db.models.OneToOneField字段,概念上,这类似于ForeignKey与unique=True的结合。

在crm中,学生详情与学生表就是一对一的关系,创建模型如下:

class StudentDetail(models.Model):
    STATION_CHOICES = [
        ('功能测试工程师', '功能测试工程师'),
        ('自动化测试工程师', '自动化测试工程师'),
        ('测试开发工程师', '测试开发工程师'),
        ('测试组长', '测试组长'),
        ('测试经理', '测试经理')
    ]

    class SalaryChoice(models.TextChoices):
        FIRST = '5000以下', '5000以下'
        SECOND = '5000-10000', '5000-10000'
        THIRD = '10000-15000', '10000-15000'
        FOURTH = '15000-20000', '15000-20000'
        FIFTH = '20000以上', '20000以上'
    student = models.OneToOneField('Student', verbose_name='学生', on_delete=models.CASCADE, help_text='学生')
    city = models.CharField('城市', max_length=20, help_text='所在城市', null=True)
    company = models.CharField('就职公司', max_length=64, help_text='就职公司', null=True)
    station = models.CharField('岗位', choices=STATION_CHOICES, max_length=10, default='功能测试工程师', help_text='岗位')
    salary = models.CharField('薪资水平', choices=SalaryChoice.choices, max_length=20, default=SalaryChoice.THIRD, help_text='薪资水平')

    class Meta:
        db_table = 'tb_student_detail'
        verbose_name = '学生详情表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.student.name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

在数据库层面会创建student_id列,然后为这个列创建一个外键约束,引用表为tb_student,字段为tb_student.id。与多对一不同,这个列还会创建一个唯一约束,形成一对一的关系。其他的级联操作与多对一一样。

本文完,感谢你的耐心阅读,如有需要可加我微信,备注「博客」并说明原因,我们一起进步,下次见。

#django
上次更新: 2023/04/16, 21:41:27
视图
数据库操作

← 视图 数据库操作→

最近更新
01
requests让接口测试如此简单 原创
03-31
02
最简明的python正则教程
03-30
03
pycharm激活码
12-30
更多文章>
Theme by Vdoing | Copyright © 2019-2025 心蓝
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式