前言
在django中,视图是一个可调用的对象,能够接受用户的请求并返回响应。在视图中通过模型操作数据库,通过模板构造返回数据。
约定将视图放在名为views.py的文件里,这个文件放置在项目或者应用目录里。
django中视图有两种:
- 函数视图
- 类视图
一、视图
1、函数视图
最简单的视图,就是一个普通的python函数,它接受web请求并返回一个web响应
例如:
from django.http import HttpResponse
def index(request):
return HttpResponse("我是首页面")
特点:
- 逻辑清晰好理解
- 复用性较差
2、类视图
类视图是以类的形式组织视图,类视图并不能替代基于函数的视图,与函数视图相比,类视图有如下优势:
- 与特定的HTTP方法关联 ,能通过单独的方法替代条件分支的方式 处理不同的HTTP请求
- 面向对象技术,可用于将代码分解为可重用的组件。
2.1 类视图的基本使用
本质上来说,基于类的视图允许你使用不同的实例方法
响应不同 HTTP 请求方法,而不是在单个视图函数里使用有条件分支的代码。
因此在 函数视图 里处理 HTTP GET
的代码应该像下面这样:
from django.http import HttpResponse
def my_view(request):
if request.method == 'GET':
# <view logic>
return HttpResponse('result')
而在基于类的视图里,会变成:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
return HttpResponse('result')
2.2 类视图的路由
Django的路由系统会使用请求和相关参数来调动一个函数而不是一个类。基于类的视图有一个as_view()
类方法,这个方法会返回一个函数。
# 应用中的路由urls.py
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('about/', MyView.as_view()),
]
二、ORM
对象关系映射(Object Relational Mapping,简称ORM)!简单的说就是用面向对象的方式,描述数据库,操作数据库,达到不用编写SQL语句就能对数据库进行增删改查。
django内置了一套ORM框架,它的映射关系是:
然后通过对类,类属性,实例的各种操作,达到操作数据库的功能,底层是生成原生sql语句进行数据库操作
2.1安装数据库
我们的项目选择使用MariaDB
,它是MySQL的一个分支,开源免费,越来越多的web项目开始使用它。直接安装数据库相对比较麻烦和难以维护,推荐使用docker安装数据库。安装docker的方案如下:
1. 方案一:
windows直接安装Docker Desktop,不推荐,个人感觉还是很影响系统的使用,且需要开启虚拟服务,会与某些软件冲突。
mac可以直接安装Docker Desktop。
2. 方案二:
windows系统,安装虚拟机,然后安装linux的虚拟机,再到虚拟机中安装docker环境。
虚拟机软件有,virtualbox(免费开源),vimware(收费),安装简单,使用稍复杂,资源占用大。
用过虚拟机的童靴可以选择此方案,从来没用过的可以忽略。
mac不建议。
3. 方案三:
买一台云服务器,在云服务器中安装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
上面这条命令会根据mariadb
数据库镜像mariadb:latest
,创建一个名为mariadb
的容器,并创建一个名为easytest
的数据库,设置root
账号的密码
为pythonvip
,映射3306
端口到宿主机4000
端口。
2.2 django配置数据库
2.2.1 安装驱动
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位
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
linux环境
linux环境下需要对应的依赖,根据环境不同依赖有所不同,下面的只是基本的步骤,不能保证在所有的环境上都有效。
Debian/Ubuntu
$ sudo apt-get install python3-dev default-libmysqlclient-dev build-essential
$ pip install mysqlclient
Red Hat /CentOS
sudo yum install python3-devel mysql-devel
pip install mysqlclient
2.2.2 连接配置
安装好数据库和数据库驱动后,还需要在settings.py
配置模块中进行连接配置:
- 直接配置
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 对应数据库引擎
'NAME': 'easytest', # 数据库名
'USER': 'root', # 用户名
'PASSWORD': '12345678', # 密码
'HOST': '127.0.0.1', # 主机
'PORT': '3306', # 端口
}
}
- 通过配置文件配置
# 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 # 端口
配置好后,运行python manage.py runserver
如果可以正常运行说明数据库配置成功,项目成功连接数据库。
三、模型(model)
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
然后根据表设计,在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
上面的代码很简单,Student类就是一个模型,表示一张数据库表。类中有几个变量,每个变量表示数据库中的一个字段
3.1 字段类型
每个字段由一个字段类的实例表示。
例如:字符字段使用CharField
,日期时间使用DateTimeField
。
每个字段实例的名称(例如,name,age)就是字段的名称。数据库将会使用它作为列名。
下面是常用字段类型和数据库字段类型的映射关系:
更多字段类型见官方文档
3.2 字段参数
每个字段在实例化时可以接收多个参数,用来提供不同的功能。常用的字段参数有:
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
会在数据库层面强制执行。
-
- 可以通过在
ForeignKey
定义中设置related_name
参数来覆盖FOO_set
名称 - 举例子:
- 可以通过在
-
- >>> b = Blog.objects.get(id=1)
- >>> b.entry_set.all() # Returns all Entry objects related to Blog.
- # b.entry_set is a Manager that returns QuerySets.
- >>> b.entry_set.filter(headline__contains="Lennon")
- >>> b.entry_set.count()
-
- 如果设置了
related_name
,则以上代码更改为
- 如果设置了
-
- >>> b = Blog.objects.get(id=1)
- >>> b.entries.all() # Returns all Entry objects related to Blog.
- # b.entries is a Manager that returns QuerySets.
- >>> b.entries.filter(headline__contains="Lennon")
- >>> b.entries.count()
3.3 自动设置主键
默认情况下,django给每个模型一个自动递增的主键,其类型在AppConfig.default_auto_field
中指定,或者在DEFAULT_AUTO_FIELD
配置中全局指定。
例如:
id = models.BigAutoField(primary_key=True)
如果你想自己指定主键,在你想要设置为主键的字段上设置参数primary_key=True。
注意:每个模型都需要一个主键字段
3.4 Meta选项
在模型内定义Meta
类来给模型赋予元数据。
在我们的例子中db_table='tb_project'
表示创建表的时候指定表名为tb_project
,不使用默认的表名。默认会用应用名_模型名
小写作为表名。
verbose_name
和verbose_name_plural
表示模型的可读名称的单数和复数,主要在admin
站点中使用。
ordering=['-c_time']
表示查询数据时默认按照c_time字段降序排列。
Meta
选项较多,且和很多高级功能有关,在后面的课程中用到再详细讲解。
更多的选项参考官方文档
3.5 __str()__
每个模型都应该定义一个__str__方法,当打印模型对象时,它的值友好的展示一个对象
3.6 数据库迁移
编写模型后,django会将模型
的修改应用至数据库进行关联,还需要将模型和数据库进行关联,这个操作称为迁移。
3.6.1 安装应用
要进行模型数据库迁移,首先需要将应用
安装到项目
中。在配置项INSTALLED_APPS
中添加要安装的应用的设置类
。
例如,安装crm应用到项目中的配置如下:
study_django/settings.py
INSTALLED_APPS = [
...
'crm.apps.CrmConfig'
]
应用的设置类是自动生成的,在应用根目录的apps.py
模块中,安装应用时,使用点式路径,例如crm.apps.CrmConfig
3.6.2 生成迁移文件
在命令行输入如下命令:
python mamage.py makemigrations crm
你将会看到类似于下面这样的输出:
Migrations for 'crm':
crm\migrations\0001_initial.py
- Create model Student
通过运行makemigrations
命令,django会检测你对模型文件的修改(当前是创建),并把修改的部分存储为一次迁移。迁移是django对模型定义的变化的记录。上面的命令会在crm/migrations
目录中生成0001_initial.py
,这就是迁移文件。感兴趣可以打开阅读它们,别担心,不需要每次都阅读迁移文件,但是它被设计成人类可阅读的形式,这是便于手动调整django的修改方式
3.6.3 执行迁移
生成迁移文件之后,还需要执行迁移,同步数据库。django通过命令migrate
来执行迁移。
在运行迁移之前,我们可以看看,迁移会执行哪些SQL语句。
命令sqlmigrate
接收一个迁移的名称,然后返回对应的SQl:
python manage.py sqlmigrate crm 0001
输出如下:
--
-- 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);
输出格式跟你使用的数据库有关。
现在,运行migrate
命令,在数据库里创建新定义的模型的数据表:
python .\manage.py migrate crm
Operations to perform:
Apply all migrations: crm
Running migrations:
Applying crm.0001_initial... OK
django通过在数据库中创建一个特殊的表django_migrations
来跟踪执行过哪些迁移。
migrate
命令会执行所有没有执行过的迁移,将模型的修改同步到数据库结构上。
迁移是非常强大的功能,是在开发过程中持续的改变数据库结构而不需要重新删除和创建表,它专注于使用数据库平滑升级而不丢失数据。
记住,改变模型需要这三步:
- 编辑
models.py
文件,修改模型 - 运行
python manage.py makemigrations
为模型的改变 生成迁移文件 - 运行
python manage.py migrate
来应用数据库迁移
3.6.4 数据库的增删改查
在执行数据的增删改查操作时,可以先进入django的调试 shell
先安装一个ipython,这个是一个交互式工具,有利于调试数据库,然后执行命令:
命令:
py.exe .\manage.py shell
增
stu = Student(name="小简", age=19)
stu.save()
删
stu.delete() # 删除当前对象
改
# 修改某个字段的值
stu.age = 10
stu.save()
查
# 查询某个字段的值
stu.name
# 查询表中的所有数据
queryset = Student.objects.all() #查询所有,等价于 select *
print(queryset.query) # 输入执行的sql
# out:SELECT `Student`.`id`, `Student`.`name`, `Student`.`age`, `Student`.`sex`, `Student`.`phone`, `Student`.`c_time` FROM `Student`
print(queryset)
# out:<QuerySet [<Student: 心蓝>, <Student: 张三>]>
3.7 表设计
为我们的crm
系统设计学生表(tb_student)
,学生详情表(tb_student_detail)
,渠道表(tb_channel),
课程表(tb_course),
报名表(tb_entry)
,表关系和字段如下图:
3.8 表关系
显然,关系型数据库的强大之处在于各表之间的关联关系。 Django 提供了定义三种最常见的数据库关联关系的方法:多对一,多对多,一对一
3.8.1 多对一
上图中的学生表和渠道表,一个学生会对应一个渠道,一个渠道对应多个学生,学生表中的一条数据和渠道表中的一条数据对应,渠道表中的一条数据与学生表中的多条数据对应,学生表和渠道表形成多对一的关系。
在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
外键字段定义在多的一方,所以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
3.8.2 多对多
django自动创建第三张关联表
数据库中多对多的关系通过第三张表来表示。
一个学生可以报名多门课程,一个课程可以被多名学生报名。在我们的案例中学生表和课程表通过报名表实现多对多的关系。
在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
多对多字段名一般设置为关系模型名的复数形式,表示关系模型的对象集合,所以Course
模型中的多对多字段名为students
。多对多字段的第一个参数是一个位置参数,既需要关联的模型,可以是模型本身,也可以是模型的字符串形式的导入路径(当引用后定义的模型时很有用)。
在数据库层面,django会自动创建一个中间表来表示多对多关系。默认情况下这个表名使用创建多对多字段的模型的表名
和字段名
生成,上面的例子会生成表名tb_course_students
。然后包含两个字段,分别是两个模型的名字和_id
组成(student_id,course_id
),并创建外键引用对应表的id,还会对这两个字段创建联合唯一的约束
自定义中间表
虽然django会自动创建第三张表,但是不能提供额外字段。如果中间表需要包含其他字段,就需要自定义中间表,然后在定义多对多字段时通过through
参数指定第三张表。
【意思是:通过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
在其中定义student
和course
外键字段分别和对应的模型形成多对一的关系。再定义c_time
字段,用来记录报名时间。学生表,课程表通过报名表来表达多对多关系。现在创建多对多字段主要是为了查询方便,可以在课程对象和学生对象上彼此关联,所以修改Course
模型中的students
字段如下:
class Course(models.Model):
...
students = models.ManyToManyField('Student', help_text='报名学生', through='Entry')
...
3.8.3 一对一
在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
在数据库层面会创建student_id
列,然后为这个列创建一个外键约束,引用表为tb_student
,字段为tb_student.id
。与多对一不同,这个列还会创建一个唯一约束,形成一对一的关系。其他的级联操作与多对一一样。