赞
踩
如何构造响应数据(构造response对象)
框架提供的其他功能组件的使用
模板
admin
重点
创建Django项目和应用
django-admin startproject name
python manager.py startapp name
视图和ULR
视图的请求和响应
URL的匹配路径
Django,发音为[`dʒæŋɡəʊ],是用python语言写的开源web开发框架,并遵循MVC设计。劳伦斯出版集团为了开发以新闻内容为主的网站,而开发出来了这个框架,于2005年7月在BSD许可证下发布。这个名称来源于比利时的爵士音乐家DjangoReinhardt,他是一个吉普赛人,主要以演奏吉它为主,还演奏过小提琴等。由于Django在近年来的迅速发展,应用越来越广泛,被著名IT开发杂志SDTimes评选为2013SDTimes100,位列"API、库和框架"分类第6位,被认为是该领域的佼佼者。
Django的主要目的是简便、快速的开发数据库驱动的网站。它强调代码复用,多个组件可以很方便的以"插件"形式服务于整个框架,Django有许多功能强大的第三方插件,你甚至可以很方便的开发出自己的工具包。这使得Django具有很强的可扩展性。它还强调快速开发和DRY(DoNotRepeatYourself)原则。
对比Flask框架,Django原生提供了众多的功能组件,让开发更简便快速。
提供项目工程管理的自动化脚本工具
数据库ORM支持(对象关系映射,英语:Object Relational Mapping)
模板
表单
Admin管理站点
文件管理
认证权限
session机制
有一种程序设计模式叫MVC,其核心思想是分工、解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容。
MVC的全拼为Model-View-Controller,最早由TrygveReenskaug在1978年提出,是施乐帕罗奥多研究中心(Xerox PARC)在20世纪80年代为程序语言Smalltalk发明的一种软件设计模式,是为了将传统的输入(input)、处理(processing)、输出(output)任务运用到图形化用户交互模型中而设计的。随着标准输入输出设备的出现,开发人员只需要将精力集中在业务逻辑的分析与实现上。后来被推荐为Oracle旗下Sun公司Java EE平台的设计模式,并且受到越来越多的使用ColdFusion和PHP的开发者的欢迎。现在虽然不再使用原来的分工方式,但是这种分工的思想被沿用下来,广泛应用于软件工程中,是一种典型并且应用广泛的软件架构模式。后来,MVC的思想被应用在了Web开发方面,被称为Web MVC框架。
MVC模式说明
Django的MVT
注:差异就在于黑线黑箭头标识出来的部分
举例子:
不装个虚拟环境那就难搞了,python环境中不能同时安装django1.1和django2.2,
所以就需要虚拟环境进行隔离
- pip install virtualenv
- pip install virtualenvwrapper-win
pip install virtualenv
虚拟环境名称为my_django,自己命名即可
virtualenv my_django
去到虚拟环境中的Script目录下
执行activate 进行激活虚拟环境
下面这个是真实环境
这样环境就隔离了。
直接在虚拟环境中执行deactivate命令,就可以退出虚拟环境了,有的可能需要.bat,有的不需要
上面每次进入virtual我们都需要进入到virtualenv的目录下,一旦virtualenv过多,就麻烦了,我们可以使用Virtualenvwrapper来方便地管理python虚拟环境。
pip install virtualenvwrapper-win
给虚拟环境设置一个专门的目录
配置电脑系统环境变量,新建系统变量 -> 变量名:WORKON_HOME ->指定路径:
G:\python_web\codeworkpace\envs
通过设置WORKON_HOME路径,就给我们的python虚拟环境指定了一个存放位置
注意: 环境变量配置好之后,要重开cmd,这样环境变量才生效!!
mkvirtualenv wwhoenv
因为前一步设置了WORK_HOME,所有虚拟环境将安装到:G:\python_web\codeworkpace\envs
deactivate
workon #列出所以目录下虚拟环境
workon 虚拟环境名称 # 进入虚拟环境
只要把pycharm的环境变量选择为虚拟环境的中的python环境即可。
要选择虚拟环境中的 python.exe 文件
- sudo pip install virtualenv
- sudo pip install virtualenvwrapper
安装完虚拟环境后,如果提示找不到mkvirtualenv命令,须配置环境变量:
- # 1、创建目录用来存放虚拟环境
- mkdir
- $HOME/.virtualenvs
-
- # 2、打开~/.bashrc文件,并添加如下:
- export WORKON_HOME=$HOME/.virtualenvs
- source /usr/local/bin/virtualenvwrapper.sh
-
- # 3、运行
- source ~/.bashrc
在python2中,创建虚拟环境
- mkvirtualenv 虚拟环境名称
- 例 :
- mkvirtualenv py_django
在python3中,创建虚拟环境
- mkvirtualenv -p python3 虚拟环境名称
- 例 :
- mkvirtualenv -p python3 py3_django
提示 :
workon
- workon 虚拟环境名称
-
- 例 :使用py3_django的虚拟环境
- workon py3_django
deactivate
- rmvirtualenv 虚拟环境名称
-
- 例 :删除虚拟环境py3_django
-
- 先退出:deactivate
- 再删除:rmvirtualenv py3_django
提示 : 工具包安装的位置 :
~/.virtualenvs/py_flask/lib/python2.7/site-packages/
~/.virtualenvs/py3_flask/lib/python3.5/site-packages
python3版本下安装django-1.11.11的包 :
- pip install 包名称
-
- 例 : 安装django-1.11.11的包
- pip install django==1.11.11
pip list
创建Django项目
创建子应用
在使用Flask框架时,项目工程目录的组织与创建是需要我们自己手动创建完成的。
在django中,项目工程目录可以借助django提供的命令帮助我们创建。
创建工程的命令为:
django-admin startproject 工程名称
例如:想要在桌面的code目录中创建一个名为bookmanager的项目工程,可执行如下命令:
- cd ~/Desktop/Code
- django-admin startproject bookmanager
执行后,会多出一个新目录名为bookmanager,此即为新创建的工程目录。
查看创建的工程目录,结构如下
在开发阶段,为了能够快速预览到开发的效果,django提供了一个纯python编写的轻量级web服务器,仅在开发阶段使用。
运行服务器命令如下:
- python manage.py runserver ip:端口
- 或:
- python manage.py runserver
可以不写IP和端口,默认IP是127.0.0.1,默认端口为8000。
启动后可见如下信息:
在浏览器中输入网址“127.0.0.1:8099”便可看到效果。
在Web应用中,通常有一些业务功能模块是在不同的项目中都可以复用的,故在开发中通常将工程项目拆分为不同的子功能模块,各功能模块间可以保持相对的独立,在其他工程项目中需要用到某个特定功能模块时,可以将该模块代码整体复制过去,达到复用。
在Flask框架中也有类似子功能应用模块的概念,即蓝图Blueprint。
Django的视图编写是放在子应用中的。
在django中,创建子应用模块目录仍然可以通过命令来操作,即:
python manage.py startapp 子应用名称
manage.py为上述创建工程时自动生成的管理文件。
例如,在刚才创建的bookmanager工程中,想要创建一个用户book子应用模块,可执行:
python manage.py startapp book
执行后,可以看到工程目录中多出了一个名为book的子目录。
查看此时的工程目录,结构如下:
创建出来的子应用目录文件虽然被放到了工程项目目录中,但是django工程并不能立即直接使用该子应用,需要注册安装后才能使用。
在工程配置文件settings.py中,INSTALLED_APPS项保存了工程中已经注册安装的子应用,初始工程中的INSTALLED_APPS如下:
注册安装一个子应用的方法,即是将子应用的配置信息文件apps.py中的Config类添加到INSTALLED_APPS列表中。
例如,将刚创建的book子应用添加到工程中,可在INSTALLED_APPS列表中添加'book.apps.BookConfig'。
以下为书籍信息管理的数据关系:书籍和人物是 :一对多关系
要先分析出项目中所需要的数据, 然后设计数据库表.
书籍信息表
字段名 | 字段类型 | 字段说明 |
---|---|---|
id | AutoField | 主键 |
name | CharField | 书名 |
id | name |
---|---|
1 | 西游记 |
2 | 三国演义 |
人物信息表
字段名 | 字段类型 | 字段说明 |
---|---|---|
id | AutoField | 主键 |
name | CharField | 人名 |
gender | BooleanField | 性别 |
book | ForeignKey | 外键 |
id | name | gender | book |
---|---|---|---|
1 | 孙悟空 | False | 1 |
2 | 白骨精 | True | 1 |
3 | 曹操 | False | 2 |
4 | 貂蝉 | True | 2 |
使用Django进行数据库开发的提示 :
MVT
设计模式中的Model
, 专门负责和数据库交互.对应(models.py)
Model
中内嵌了ORM框架
, 所以不需要直接面向数据库编程.模型类和对象
完成数据库表的增删改查
.ORM框架
就是把数据库表的行与相应的对象建立关联, 互相转换.使得数据库的操作面向对象.book = models.ForeignKey(BookInfo)
根据数据库表的设计
models.py
中定义模型类,继承自models.Model
- from django.db import models
-
-
- # Create your models here.
-
-
- # Create your models here.
- # 准备书籍列表信息的模型类
- class BookInfo(models.Model):
- # 创建字段,字段类型...
- name = models.CharField(max_length=10)
-
-
- # 准备人物列表信息的模型类
- class PeopleInfo(models.Model):
- name = models.CharField(max_length=10)
- gender = models.BooleanField()
- # 外键约束:人物属于哪本书
- book = models.ForeignKey(BookInfo)
迁移由两步完成 :
生成迁移文件:根据模型类生成创建表的语句
python manage.py makemigrations
执行迁移:根据第一步生成的语句在数据库中创建表
python manage.py migrate
运行迁移命令报错
原因:
在django2.0后,定义外键和一对一关系的时候需要加on_delete选项,此参数为了避免两个表里的数据不一致问题,不然会报错:
TypeError: __init__() missing 1 required positional argument: 'on_delete'
举例:
book = models.ForeignKey(BookInfo)
应该为
book = models.ForeignKey(BookInfo,on_delete=models.CASCADE)
参数说明:
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五个可选择的值
一般情况下使用CASCADE就可以了。
内容发布
和公共访问
两部分Django
能够根据定义的模型类自动地生成管理模块Django
的管理模块, 需要按照如下步骤操作 :
简体中文
, 时区使用亚洲/上海时区
, 注意这里不使用北京时区.本地化前
本地化后
创建管理员的命令 :
python manage.py createsuperuser
按提示输入用户名、邮箱、密码
重置密码
python manager.py changepassword 用户名
登陆站点 :http://127.0.0.1:8099/admin ()
需要服务器是启动状态
登陆站点成功
站点界面中没有书籍和人物管理入口,因为没有注册模型类
在应用
的admin.py
文件中注册模型类
需要导入模型模块 :from book.models import BookInfo,PeopleInfo
注册模型后
注册模型成功后, 就可以在站点管理界面方便快速的管理数据.
然后就报错了
原来不会去数据库生成数据表,还要自己手动去创建
公共访问
的页面了.Django
的设计框架MVT
.
Python
函数,被定义在应用
的views.py
中.HttpRequest
类型的对象reqeust
,包含了所有请求信息
.HttpResponse对象
,包含返回给请求者的响应信息
.需要导入HttpResponse
模块 :from django.http import HttpResponse
定义视图函数 : 响应字符串OK!
给客户端
查找视图的过程 :
5.如果所有的URLconf都没有匹配成功.则返回404错误.
URLconf
入口
需要两步完成URLconf
配置
项目
中定义URLconf
应用
中定义URLconf
在项目
中定义URLconf
根据django版本,有两种路由形式:
- from django.urls import path
-
- from django.conf.urls import url
django.urls 中的 path() 和 django.conf.urls 中的 url() 都是 Django 中用于 URL 路由的函数,它们的作用是定义 URL 和视图函数之间的映射关系,即当用户访问某个 URL 时,Django 如何将请求发送给对应的视图函数来处理。
两者的区别如下:
path() 函数是 Django 2.0 版本引入的新函数,它更加直观和易用,支持使用 str 类型的路由,可以通过等转换器来定义动态参数,而且不再需要使用正则表达式来匹配 URL,大大简化了 URL 配置的过程。例如:
- from django.urls import path
- from . import views
-
- urlpatterns = [
- path('', views.index, name='index'),
- path('about/', views.about, name='about'),
- path('article/<int:id>/', views.article_detail, name='article_detail'),
- ]
需要注意的是,在 Django 3.1 版本以后,url() 函数已经被标记为过时,建议使用 path() 函数来进行 URL 配置。因此,如果使用较新版本的 Django,应该尽量使用 path() 函数来定义 URL 路由。
看下path()函数用法:
在 Django 中,path() 函数是用来定义 URL 路由的。它的语法如下:
path(route, view, kwargs=None, name=None)
其中:
path() 函数支持的 URL 模式有以下几种:
在定义 URL 路由时,可以使用多个 path() 函数来定义不同的路由,并将它们作为列表传递给 urlpatterns 变量。例如:
- from django.urls import path
- from . import views
-
- urlpatterns = [
- path('articles/', views.article_list),
- path('articles/<int:pk>/', views.article_detail),
- ]
上面的代码中,定义了两个 URL 路由,分别对应 /articles/ 和 /articles/int:pk/ 两个 URL。其中,views.article_list 和 views.article_detail 分别是处理请求的视图函数。
Django2. 0中可以使用 re_path() 方法来兼容 1.x 版本中的 url() 方法,一些正则表达式的规则也可以通过 re_path() 来实现。
参考资料:django2笔记:路由path语法 | 程序员Barnes的博客
我们采用的是django2.0所以我们采用path语法
在应用
中定义URLconf
提示:一条URLconf
包括URL规则、视图两部分
视图就是在views.py
中定义的视图函数.
from book import views
url匹配过程
视图处理过程如下图:
使用视图时需要进行两步操作,两步操作不分先后
URLconf
应用/views.py
中定义视图思考 : 网站如何向客户端返回一个漂亮的页面呢?
提示 :
html
、css
、js
.HttpResponse()
的参数,响应给客户端.问题 :
设想 :
解决问题 :模板
MVT
设计模式中的T
,Template
在Django
中, 将前端的内容定义在模板中, 然后再把模板交给视图调用, 各种漂亮、炫酷的效果就出现了.
应用
同级目录下创建模板文件夹templates
. 文件夹名称固定写法.templates
文件夹下, 创建应用
同名文件夹. 例, Book
在应用
同名文件夹下创建网页模板
文件. 例 :index.html
视图模板加载
传递上下文到模板
- # 定义视图:提供书籍列表信息
- def bookList(request):
- # 查询数据库书籍列表数据
- books = BookInfo.objects.all()
- # 构造上下文
- context = {'books':books}
- # 数据交给模板处理,处理完成后通过视图响应给客户端
- return render(request, 'Book/booklist.html', context)
进入应用
中的urls.py
文件
BASE_DIR = Path(__file__).resolve().parent.parent
解释:__file__
代表的是settings.py文件,那么Path(__file__).resolve()
的结果将会是settings.py文件的绝对路径;然后parent取其父目录,两个parent就是向上两层父级目录 ,也就是django-admin创建项目之后的路径。
调试模式,创建工程后初始值为True,即默认工作在调试模式下。
作用:
修改代码文件,程序自动重启
Django程序出现异常时,向前端显示详细的错误追踪信息,例如
而非调试模式下,仅返回Server Error (500)
注意:部署线上运行的Django不要运行在调式模式下,记得修改DEBUG=False和ALLOW_HOSTS。
Django支持本地化处理,即显示语言与时区支持本地化。
本地化是将显示的语言、时间等使用本地的习惯,这里的本地化就是进行中国化,中国大陆地区使用简体中文,时区使用亚洲/上海时区,注意这里不使用北京时区表示。
初始化的工程默认语言和时区为英语和UTC标准时区
- LANGUAGE_CODE = 'en-us' # 语言
- TIME_ZONE = 'UTC' # 时区# 时区
将语言和时区修改为中国大陆信息
- LANGUAGE_CODE = 'zh-Hans'
- TIME_ZONE = 'Asia/Shanghai'
项目中的CSS、图片、js都是静态文件。一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用时,也需要指定静态文件的路径,Django中提供了一种解析的方式配置静态文件路径。静态文件可以放在项目根目录下,也可以放在应用的目录下,由于有些静态文件在项目中是通用的,所以推荐放在项目的根目录下,方便管理。
为了提供静态文件,需要配置两个参数:
1) 在项目根目录下创建static目录来保存静态文件。
2) 在bookmanager/settings.py中修改静态文件的两个参数为
- STATIC_URL = '/static/'
- STATICFILES_DIRS = [
- os.path.join(BASE_DIR, 'static'),
- ]
3)此时在static添加的任何静态文件都可以使用网址/static/文件在static中的路径来访问了。
例如,我们向static目录中添加一个index.html文件,在浏览器中就可以使用127.0.0.1:8099/static/index.html来访问。
或者我们在static目录中添加了一个子目录和文件book/detail.html,在浏览器中就可以使用127.0.0.1:8099/static/book/detail.html来访问。
在每个应用目录中都包含了apps.py文件,用于保存该应用的相关信息。
在创建应用时,Django会向apps.py文件中写入一个该应用的配置类,如
- from django.apps import AppConfig
- class BookConfig(AppConfig):
- name = 'book'
我们将此类添加到工程settings.py中的INSTALLED_APPS列表中,表明注册安装具备此配置属性的应用。
AppConfig.name属性表示这个配置类是加载到哪个应用的,每个配置类必须包含此属性,默认自动生成。
AppConfig.verbose_name属性用于设置该应用的直观可读的名字,此名字在Django提供的Admin管理站点中会显示,如
- from django.apps import AppConfig
-
- class UsersConfig(AppConfig):
- name = 'book'
- verbose_name = '图书管理'
重点
数据的增删改
增:book = BookInfo() book.save()
和BookInfo.objects.create()
删:book.delete()
和BookInfo.objects.get().delete()
改:book.name='xxx' book.save()
和 BookInfo.objects.get().update(name=xxx)
数据的查询
基础查询
F对象和Q对象
关联查询
查询集QuerySet
Django默认初始配置使用sqlite数据库,我们现在使用MySQL数据来进行改造,下面是使用sqlite数据库的配置文件。
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
- }
- }
使用MySQL数据库首先需要安装驱动程序
pip install PyMySQL
在Django的工程同名子目录的__init__.py文件中添加如下语句
- import pymysql
-
- pymysql.install_as_MySQLdb()
作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。
修改DATABASES配置信息
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'HOST': '127.0.0.1', # 数据库主机
- 'PORT': 3306, # 数据库端口
- 'USER': 'root', # 数据库用户名
- 'PASSWORD': 'mysql', # 数据库用户密码
- 'NAME': 'book' # 数据库名字
- }
- }
在MySQL中创建数据库
create database book charset=utf8;
在models.py 文件中定义模型类。
- from django.db import models
-
- # Create your models here.
- # 准备书籍列表信息的模型类
- class BookInfo(models.Model):
- # 创建字段,字段类型...
- name = models.CharField(max_length=20, verbose_name='名称')
- pub_date = models.DateField(verbose_name='发布日期',null=True)
- readcount = models.IntegerField(default=0, verbose_name='阅读量')
- commentcount = models.IntegerField(default=0, verbose_name='评论量')
- is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
-
- class Meta:
- db_table = 'bookinfo' # 指明数据库表名
- verbose_name = '图书' # 在admin站点中显示的名称
-
- def __str__(self):
- """定义每个数据对象的显示信息"""
- return self.name
-
- # 准备人物列表信息的模型类
- class PeopleInfo(models.Model):
- GENDER_CHOICES = (
- (0, 'male'),
- (1, 'female')
- )
- name = models.CharField(max_length=20, verbose_name='名称')
- gender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
- description = models.CharField(max_length=200, null=True, verbose_name='描述信息')
- book = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # 外键
- is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
-
- class Meta:
- db_table = 'peopleinfo'
- verbose_name = '人物信息'
-
- def __str__(self):
- return self.name
数据库表名
数据库表名
模型类如果未指明表名,Django默认以小写app应用名_小写模型类名为数据库表名。
可通过db_table指明数据库表名。
关于主键
django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
属性命名限制
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性=models.字段类型(选项)
字段类型
类型 | 说明 |
---|---|
AutoField | 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性 |
BooleanField | 布尔字段,值为True或False |
NullBooleanField | 支持Null、True、False三种值 |
CharField | 字符串,参数max_length表示最大字符个数 |
TextField | 大文本字段,一般超过4000个字符时使用 |
IntegerField | 整数 |
DecimalField | 十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数 |
FloatField | 浮点数 |
DateField | 日期, 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误 |
TimeField | 时间,参数同DateField |
DateTimeField | 日期时间,参数同DateField |
FileField | 上传文件字段 |
ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片 |
选项
选项 | 说明 |
---|---|
null | 如果为True,表示允许为空,默认值是False |
blank | 如果为True,则该字段允许为空白,默认值是False |
db_column | 字段的名称,如果未指定,则使用属性的名称 |
db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False |
default | 默认 |
primary_key | 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用 |
unique | 如果为True, 这个字段在表中必须有唯一值,默认值是False |
null是数据库范畴的概念,blank是表单验证范畴的
外键
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:
CASCADE级联,删除主表数据时连通一起删除外键表中数据
PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
SET_NULL设置为NULL,仅在该字段null=True允许为null时可用
SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用
SET()设置为特定值或者调用特定方法
DO_NOTHING不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常
迁移是 Django 将你对模型的修改(例如增加一个字段,删除一个模型)应用至数据库架构中的方式。它们被设计的尽可能自动化,但你仍需要知道何时构建和运行迁移,你还需要了解一些常见问题。
以下是几个常用的与迁移交互的命令,即 Django 处理数据库架构的方式:
你应该将迁移看作是数据库架构的版本控制系统。 makemigrations
负责将模型修改打包进独立的迁移文件中——类似提交修改,而 migrate
负责将其应用至数据库。
每个应用的迁移文件位于该应用的 "migrations" 目录中,他们被设计成应用代码的一部分,与应用代码一起被提交,被发布。你只需在开发机上构建一次,就可以在同事的电脑或测试机上运行同样的迁移而保证结果一致。最后在生产环境运行同样的迁移。
简单来说就是将模型类同步到数据库中。
生成迁移文件
python manage.py makemigrations
同步到数据库中
python manage.py migrate
- insert into bookinfo(name, pub_date, readcount,commentcount, is_delete) values
- ('射雕英雄传', '1980-5-1', 12, 34, 0),
- ('天龙八部', '1986-7-24', 36, 40, 0),
- ('笑傲江湖', '1995-12-24', 20, 80, 0),
- ('雪山飞狐', '1987-11-11', 58, 24, 0);
- insert into peopleinfo(name, gender, book_id, description, is_delete) values
- ('郭靖', 1, 1, '降龙十八掌', 0),
- ('黄蓉', 0, 1, '打狗棍法', 0),
- ('黄药师', 1, 1, '弹指神通', 0),
- ('欧阳锋', 1, 1, '蛤蟆功', 0),
- ('梅超风', 0, 1, '九阴白骨爪', 0),
- ('乔峰', 1, 2, '降龙十八掌', 0),
- ('段誉', 1, 2, '六脉神剑', 0),
- ('虚竹', 1, 2, '天山六阳掌', 0),
- ('王语嫣', 0, 2, '神仙姐姐', 0),
- ('令狐冲', 1, 3, '独孤九剑', 0),
- ('任盈盈', 0, 3, '弹琴', 0),
- ('岳不群', 1, 3, '华山剑法', 0),
- ('东方不败', 0, 3, '葵花宝典', 0),
- ('胡斐', 1, 4, '胡家刀法', 0),
- ('苗若兰', 0, 4, '黄衣', 0),
- ('程灵素', 0, 4, '医术', 0),
- ('袁紫衣', 0, 4, '六合拳', 0);
Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。
通过如下命令进入shell
python manage.py shell
导入两个模型类,以便后续使用
from book.models import BookInfo,PeopleInfo
增加数据有两种方法。
save
通过创建模型类对象,执行对象的save()方法保存到数据库中。
create
通过模型类.objects.create()保存。
修改更新有两种方法
save
修改模型类对象的属性,然后执行save()方法
update
使用模型类.objects.filter().update(),会返回受影响的行数
删除有两种方法
模型类对象delete
模型类.objects.filter().delete()
get查询单一结果,如果不存在会抛出模型类.DoesNotExist异常。(pk指的就是主键id)
all查询多个结果。
count查询结果数量。
实现SQL中的where功能,包括
对于过滤条件的使用,上述三个方法相同,故仅以filter进行讲解。
过滤条件的表达语法如下:
- 属性名称__比较运算符=值
-
- # 属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线
- 查询编号为1的图书
- 查询书名包含'湖'的图书
- 查询书名以'部'结尾的图书
- 查询书名为空的图书
- 查询编号为1或3或5的图书
- 查询编号大于3的图书
- 查询1980年发表的图书
- 查询1990年1月1日后发表的图书
exact:表示判等。
例:查询编号为1的图书。
- BookInfo.objects.filter(id__exact=1)
- 可简写为:
- BookInfo.objects.filter(id=1)
contains:是否包含。
说明:如果要包含%无需转义,直接写即可。
例:查询书名包含'传'的图书。
BookInfo.objects.filter(name__contains='传')
startswith、endswith:以指定值开头或结尾。
例:查询书名以'部'结尾的图书
BookInfo.objects.filter(name__endswith='部')
以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.
例:查询书名为空的图书。
BookInfo.objects.filter(name__isnull=True)
in:是否包含在范围内。
例:查询编号为1或3或5的图书
BookInfo.objects.filter(id__in=[1,3,5])
例:查询编号大于3的图书
BookInfo.objects.filter(id__gt=3)
不等于的运算符,使用exclude()过滤器。
例:查询编号不等于3的图书
BookInfo.objects.exclude(id=3)
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
例:查询1980年发表的图书。
例:查询1990年1月1日后发表的图书。
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。
语法如下:
F(属性名)
例:查询阅读量大于等于评论量的图书。
- from django.db.models import F
- BookInfo.objects.filter(readcount__gt=F('commentcount'))
可以在F对象上使用算数运算。
例:查询阅读量大于2倍评论量的图书。
BookInfo.objects.filter(readcount__gt=F('commentcount')*2)
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询阅读量大于20,并且编号小于3的图书。
- BookInfo.objects.filter(readcount__gt=20,id__lt=3)
- #或者
- BookInfo.objects.filter(readcount__gt=20).filter(id__lt=3)
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值)
例:查询阅读量大于20的图书,改写为Q对象如下。
BookInfo.objects.filter(Q(readcount__gt=20))
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
- #引入Q对象
- from django.db.models import Q
-
- BookInfo.objects.filter(Q(readcount__gt=20)|Q(id__lt=3))
Q对象前可以使用~操作符,表示非not。
例:查询编号不等于3的图书。
BookInfo.objects.filter(~Q(id=3))
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg平均,Count数量,Max最大,Min最小,Sum求和,被定义在django.db.models中。
例:查询图书的总阅读量。
注意aggregate的返回值是一个字典类型,格式如下:
- {'属性名__聚合类小写':值}
-
- 如:{'readcount__sum': 126}
使用count时一般不使用aggregate()过滤器。
例:查询图书总数。
BookInfo.objects.count()
注意count函数的返回值是一个数字。
使用order_by对结果进行排序
- # 默认升序
- BookInfo.objects.all().order_by('readcount')
-
- # 降序
- BookInfo.objects.all().order_by('-readcount')
- 查询书籍为1的所有人物信息
- 查询人物为1的书籍信息
由一到多的访问语法:
一对应的模型类对象.多对应的模型类名小写_set 例:
- book = BookInfo.objects.get(id=1)
- book.peopleinfo_set.all()
由多到一的访问语法:
多对应的模型类对象.多对应的模型类中的关系类属性名 例:
- person = PeopleInfo.objects.get(id=1)
- person.book
访问一对应的模型类关联对象的id语法:
多对应的模型类对象.关联类属性_id
例:
- person = PeopleInfo.objects.get(id=1)
- person.book_id
由多模型类条件查询一模型类数据:
语法如下:
关联模型类名小写__属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。
- 查询图书,要求图书人物为"郭靖"
- 查询图书,要求图书中人物的描述包含"八"
例:
查询图书,要求图书人物为"郭靖"
- book = BookInfo.objects.filter(peopleinfo__name='郭靖')
- book
查询图书,要求图书中人物的描述包含"八"
- book = BookInfo.objects.filter(peopleinfo__description__contains='八')
- book
由一模型类条件查询多模型类数据:
语法如下:
一模型类关联属性名__一模型类属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。
- 查询书名为“天龙八部”的所有人物
- 查询图书阅读量大于30的所有人物
例:
查询书名为“天龙八部”的所有人物。
- people = PeopleInfo.objects.filter(book__name='天龙八部')
- people
查询图书阅读量大于30的所有人物
- people = PeopleInfo.objects.filter(book__readcount__gt=30)
- people
Django的ORM中存在查询集的概念。
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
对查询集可以再次调用过滤器进行过滤,如
books = BookInfo.objects.filter(readcount__gt=30).order_by('pub_date')
也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。
从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。
判断某一个查询集中是否有数据:
创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集books
books = BookInfo.objects.all()
继续执行遍历迭代操作后,才真正的进行了数据库的查询
- for book in books:
- print(book.name)
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
情况一:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。
- from book.models import BookInfo
-
- [book.id for book in BookInfo.objects.all()]
-
- [book.id for book in BookInfo.objects.all()]
情况二:经过存储后,可以重用查询集,第二次使用缓存中的数据。
- books=BookInfo.objects.all()
-
- [book.id for book in books]
-
- [book.id for book in books]
可以对查询集进行取下标或切片操作,等同于sql中的limit和offset子句。
注意:不支持负数索引。
对查询集进行切片后返回一个新的查询集,不会立即执行查询。
如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()如果没有数据引发DoesNotExist异常。
示例:获取第1、2项,运行查看。
- books = BookInfo.objects.all()[0:2]
- books
- #查询数据
- books = BookInfo.objects.all()
- #导入分页类
- from django.core.paginator import Paginator
- #创建分页实例
- paginator=Paginator(books,2)
- #获取指定页码的数据
- page_skus = paginator.page(1)
- #获取分页数据
- total_page=paginator.num_pages
Pagination | Django documentation | Django (djangoproject.com)
应用
中views.py
文件中的函数HttpRequest对象
,还可能包含下参数如
HttpResponse对象
或子对象
作为响应
JsonResponse
HttpResponseRedirect
HttpRequest
,进行逻辑处理,返回Web响应HttpResponse
给请求者
HTML内容
,404错误
,重定向
,json数据
...视图处理过程如下图:
使用视图时需要进行两步操作,两步操作不分先后
URLconf
应用/views.py
中定义视图创建项目+创建应用+安装应用+配置模板路径+本地化+mysql数据库+URLconf+视图
mysql数据库使用之前的book
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'HOST': '127.0.0.1', # 数据库主机
- 'PORT': 3306, # 数据库端口
- 'USER': 'root', # 数据库用户名
- 'PASSWORD': 'mysql', # 数据库用户密码
- 'NAME': 'book' # 数据库名字
- }
- }
URLconf
settings.py
中:指定url配置
ROOT_URLCONF = 'bookmanager.urls'
项目中urls.py
:只要不是admin/
就匹配成功,包含到应用中的urls.py
- """
- URL configuration for bookmanager project.
- The `urlpatterns` list routes URLs to views. For more information please see:
- https://docs.djangoproject.com/en/4.2/topics/http/urls/
- Examples:
- Function views
- 1. Add an import: from my_app import views
- 2. Add a URL to urlpatterns: path('', views.home, name='home')
- Class-based views
- 1. Add an import: from other_app.views import Home
- 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
- Including another URLconf
- 1. Import the include() function: from django.urls import include, path
- 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
- """
-
- from django.contrib import admin
- from django.urls import path, include
-
-
-
- urlpatterns = [
- path('admin/', admin.site.urls),
- #正则为,只要不是admin/就算匹配成功
- path('',include('book.urls')),
-
- ]
应用中urls.py
:匹配testproject/
成功就调用views
中的testproject
函数,测试项目逻辑
- from book.views import testproject
- from django.urls import path
-
-
- urlpatterns = [
-
- path('testproject',testproject)
- ]
视图:测试项目逻辑
- from django.http import HttpResponse
-
- # 测试项目逻辑
- def testproject(request):
- return HttpResponse('测试项目逻辑')
在models.py 文件中定义模型类
- from django.db import models
-
- # Create your models here.
- # 准备书籍列表信息的模型类
- class BookInfo(models.Model):
- # 创建字段,字段类型...
- name = models.CharField(max_length=20, verbose_name='名称')
- pub_date = models.DateField(verbose_name='发布日期',null=True)
- readcount = models.IntegerField(default=0, verbose_name='阅读量')
- commentcount = models.IntegerField(default=0, verbose_name='评论量')
- is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
-
- class Meta:
- db_table = 'bookinfo' # 指明数据库表名
- verbose_name = '图书' # 在admin站点中显示的名称
-
- def __str__(self):
- """定义每个数据对象的显示信息"""
- return self.name
-
- # 准备人物列表信息的模型类
- class PeopleInfo(models.Model):
- GENDER_CHOICES = (
- (0, 'male'),
- (1, 'female')
- )
- name = models.CharField(max_length=20, verbose_name='名称')
- gender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
- description = models.CharField(max_length=200, null=True, verbose_name='描述信息')
- book = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # 外键
- is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
-
- class Meta:
- db_table = 'peopleinfo'
- verbose_name = '人物信息'
-
- def __str__(self):
- return self.name
生成迁移文件
python manage.py makemigrations
同步到数据库中
python manage.py migrate
添加测试数据
- insert into bookinfo(name, pub_date, readcount,commentcount, is_delete) values
- ('射雕英雄传', '1980-5-1', 12, 34, 0),
- ('天龙八部', '1986-7-24', 36, 40, 0),
- ('笑傲江湖', '1995-12-24', 20, 80, 0),
- ('雪山飞狐', '1987-11-11', 58, 24, 0);
- insert into peopleinfo(name, gender, book_id, description, is_delete) values
- ('郭靖', 1, 1, '降龙十八掌', 0),
- ('黄蓉', 0, 1, '打狗棍法', 0),
- ('黄药师', 1, 1, '弹指神通', 0),
- ('欧阳锋', 1, 1, '蛤蟆功', 0),
- ('梅超风', 0, 1, '九阴白骨爪', 0),
- ('乔峰', 1, 2, '降龙十八掌', 0),
- ('段誉', 1, 2, '六脉神剑', 0),
- ('虚竹', 1, 2, '天山六阳掌', 0),
- ('王语嫣', 0, 2, '神仙姐姐', 0),
- ('令狐冲', 1, 3, '独孤九剑', 0),
- ('任盈盈', 0, 3, '弹琴', 0),
- ('岳不群', 1, 3, '华山剑法', 0),
- ('东方不败', 0, 3, '葵花宝典', 0),
- ('胡斐', 1, 4, '胡家刀法', 0),
- ('苗若兰', 0, 4, '黄衣', 0),
- ('程灵素', 0, 4, '医术', 0),
- ('袁紫衣', 0, 4, '六合拳', 0);
1.settings.py
中
指定url配置
ROOT_URLCONF = '项目.urls'
2.项目中urls.py
匹配成功后,包含到应用的urls.py
path(路由, include('应用.urls'))
3.应用中urls.py
匹配成功后,调用views.py
对应的函数
path(路由, views.函数名)
路由命名是给你写的路由起一个名字,reverse反解析能给根据名字解析出对应的路由。
在定义路由的时候,可以为路由命名,方便查找特定视图的具体路径信息。
1) 在使用include函数定义路由时,可以使用namespace参数定义路由的命名空间,如
path('booklist', include(('book.urls','book'), namespace='book'))
命名空间表示,凡是book.urls中定义的路由,均属于namespace指明的book名下。
命名空间的作用:避免不同应用中的路由使用了相同的名字发生冲突,使用命名空间区别开。
2) 在定义普通路由时,可以使用name参数指明路由的名字,如
- urlpatterns = [
- path('',index),
- # 匹配书籍列表信息的URL,调用对应的bookList视图
- path('booklist/',bookList,name='index'),
- path('/testproject/',views.testproject,name='test'),
- ]
使用reverse函数,可以根据路由名称,返回具体的路径,如:
- from django.core.urlresolvers import reverse
- #或者
- from django.urls import reverse
-
- def testproject(request):
-
- return HttpResponse("OK")
-
- # 定义视图:提供书籍列表信息
- def bookList(request):
-
- url = reverse('book:test')
- print(url)
- return HttpResponse('index')
PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件,可以直接去对我们写出来的路由和视图函数进行调试,作为后端程序员是必须要知道的一个工具。
Postman API Platform官网下载桌面版
回想一下,利用HTTP协议向服务器传参有几种途径?
分组
,18 188
应用中urls.py
path('testPath/<num1>/<num2>/', testPath, name='testPath')
视图中函数: 参数的位置不能错
- def testPath(request,num1,num2):
- print(num1)
- print(num2)
- return HttpResponse('测试路径参数')
如果将<num1>改为 <int:num1>,会进行类型约束,如果传入参数不为int型报错,相似的有:
int:接收整数
str:字符串
path:可以接收包含"/"的路径
slug:接收【数字、字母、下划线、中划线】四种字符
如果,使用path函数并不能满足你匹配URL的要求,那么可以使用re_path函数来使用正则表达式来匹配URL路径中的参数。需要注意在Django中,使用正则表达式来获取分组中的值的语法是(?P<name>pattern)
,其中 name 是组名,pattern 是要匹配的模式。
应用中urls.py
?P<value1>
部分表示为这个参数定义的名称为value1
可以是其它名称,起名要做到见名知意
re_path(r'testLocalPath/(?P<value1>\d+)/(?P<value2>\d+)/$',testLocalPath,name='testLocalPath')
视图中函数: 参数的位置可以变,跟关键字保持一致即可
- def testLocalPath(request,value2,value1):
- print(value2)
- print(value1)
- return HttpResponse('测试位置参数')
HttpRequest对象的属性GET、POST都是QueryDict类型的对象
与python字典不同,QueryDict类型的对象用来处理同一个键带有多个值的情况
方法get():根据键获取值
如果一个键同时拥有多个值将获取最后一个值
如果键不存在则返回None值,可以设置默认值进行后续处理
get('键',默认值)
方法getlist():根据键获取值,值以列表返回,可以获取指定键的所有值
如果键不存在则返回空列表[],可以设置默认值进行后续处理
getlist('键',默认值)
获取请求路径中的查询字符串参数(形如?k1=v1&k2=v2),可以通过request.GET属性获取,返回QueryDict对象。
- # /get/?a=1&b=2&a=3
-
- def get(request):
- a = request.GET.get('a')
- b = request.GET.get('b')
- alist = request.GET.getlist('a')
- print(a) # 3
- print(b) # 2
- print(alist) # ['1', '3']
- return HttpResponse('OK')
重要:查询字符串不区分请求方式,即假使客户端进行POST方式的请求,依然可以通过request.GET获取请求中的查询字符串数据。
请求体数据格式不固定,可以是表单类型字符串,可以是JSON字符串,可以是XML字符串,应区别对待。
可以发送请求体数据的请求方式有POST、PUT、PATCH、DELETE。
Django默认开启了CSRF防护,会对上述请求方式进行CSRF防护验证,在测试时可以关闭CSRF防护机制,方法为在settings.py文件中注释掉CSRF中间件,如:
前端发送的表单类型的请求体数据,可以通过request.POST属性获取,返回QueryDict对象。
path('post/',post,name='get'),
- def post(request):
- a = request.POST.get('a')
- b = request.POST.get('b')
- alist = request.POST.getlist('a')
- print(a)
- print(b)
- print(alist)
- return HttpResponse('OK')
因为浏览器不好发送post请求,所以我们用postman来测试
1填入的路径http://127.0.0.1:8099/booklist/post/
2是填入的参数
3是点击发送
4是结果
非表单类型的请求体数据,Django无法自动解析,可以通过request.body属性获取最原始的请求体数据,自己按照请求体格式(JSON、XML等)进行解析。request.body返回bytes类型。
例如要获取请求体中的如下JSON数据
{"a": 1, "b": 2}
可以进行如下方法操作:
- import json
-
- def post_json(request):
- json_str = request.body
- json_str = json_str.decode() # python3.6 无需执行此步
- req_data = json.loads(json_str)
- print(req_data['a'])
- print(req_data['b'])
- return HttpResponse('OK')
path('post_json/',post_json,name='post_json'),
可以通过request.META属性获取请求头headers中的数据,request.META为字典类型。
常见的请求头如:
CONTENT_LENGTH
– The length of the request body (as a string).CONTENT_TYPE
– The MIME type of the request body.HTTP_ACCEPT
– Acceptable content types for the response.HTTP_ACCEPT_ENCODING
– Acceptable encodings for the response.HTTP_ACCEPT_LANGUAGE
– Acceptable languages for the response.HTTP_HOST
– The HTTP Host header sent by the client.HTTP_REFERER
– The referring page, if any.HTTP_USER_AGENT
– The client’s user-agent string.QUERY_STRING
– The query string, as a single (unparsed) string.REMOTE_ADDR
– The IP address of the client.REMOTE_HOST
– The hostname of the client.REMOTE_USER
– The user authenticated by the Web server, if any.REQUEST_METHOD
– A string such as"GET"
or"POST"
.SERVER_NAME
– The hostname of the server.SERVER_PORT
– The port of the server (as a string).具体使用如:
- def get_headers(request):
- print(request.META['CONTENT_TYPE'])
- return HttpResponse('OK')
path('get_headers/', get_headers, name='get_headers')
encoding:一个字符串,表示提交的数据的编码方式。
FILES:一个类似于字典的对象,包含所有的上传文件。
视图在接收请求并处理后,必须返回HttpResponse对象或子对象。HttpRequest对象由Django创建,HttpResponse对象由开发人员创建。
可以使用django.http.HttpResponse来构造响应对象。
HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)
也可通过HttpResponse对象属性来设置响应体、响应体数据类型、状态码:
响应头可以直接将HttpResponse对象当做字典进行响应头键值对的设置:
- response = HttpResponse()
- response['itcast'] = 'Python' # 自定义响应头Itcast, 值为Python
示例:
- from django.http import HttpResponse
-
- def response(request):
- return HttpResponse('itcast python', status=400)
- 或者
- response = HttpResponse('itcast python')
- response.status_code = 400
- response['itcast'] = 'Python'
- return response
Django提供了一系列HttpResponse的子类,可以快速设置状态码
若要返回json数据,可以使用JsonResponse来构造响应对象,作用:
- from django.http import JsonResponse
-
- def response(request):
- return JsonResponse({'city': 'beijing', 'subject': 'python'})
- from django.shortcuts import redirect
-
- def response(request):
- return redirect('/get_header')
Cookie
Session
Cookie,有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等。服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。Cookies最典型记住用户名。
Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用。
第一次请求过程:
我们的浏览器第一次请求服务器的时候,不会携带任何cookie信息
服务器接收到请求之后,发现,请求中没有任何cookie信息
服务器设置一个cookie,这个cookie设置在应中
我们的浏览器接收到这个响应之后,发现响应中有cookie信息,浏览器会将cookie信息保存起来
第二次及其之后的过程
当我们的浏览器第二次及其之后的请求都会携带cookie信息
我们的服务器接收到请求之后,会发现请求中携带的cookie信息,这样的话就认识是谁的请求了
不设置cookie时浏览器控制台cookie情况
后端不设置cookie,也会返回一个默认的cookie
然后我们看看手动设置cookie的情况
会返回默认和自己设置的,访问服务器的时候会带上。
可以通过HttpResponse对象中的set_cookie方法来设置cookie。
HttpResponse.set_cookie(cookie名, value=cookie值, max_age=cookie有效期)
示例:
- def cookie(request):
- response = HttpResponse('ok')
- response.set_cookie('itcast1', 'python1') # 临时cookie
- response.set_cookie('itcast2', 'python2', max_age=3600) # 有效期一小时
- return response
可以通过HttpResponse对象的COOKIES属性来读取本次请求携带的cookie值。request.COOKIES为字典类型。
- def cookie(request):
- cookie1 = request.COOKIES.get('itcast1')
- print(cookie1)
- return HttpResponse('OK')
可以通过HttpResponse对象中的delete_cookie方法来删除。
response.delete_cookie('itcast2')
保存在服务器的数据叫做 session,session需要依赖于cookie,如果浏览器禁用了cookie,则session不能实现。
session流程:
第一次请求:
我们第一次请求的时候可以携带一些信息(用户名/密码) cookie中没有任何信息
当我们的服务器接收到这个请求之后,进行用户名和密码的验证,验证没有问题可以设置session信息
在设置session信息的同时(session信息保存在服务器端).服务器会在响应头中设置一个 sessionid 的cookie信息(由服务器自己设置的,不是我们设置的)
客户端(浏览器)在接收到响应之后,会将cookie信息保存起来(保存 sessionid的信息)
第二次及其之后的请求:
第二次及其之后的请求都会携带 session id信息
当服务器接收到这个请求之后,会获取到sessionid信息,然后进行验证, 验证成功,则可以获取 session信息(session信息保存在服务器端)
Django项目默认启用Session。
可以在settings.py文件中查看,如图所示
如需禁用session,将上图中的session中间件注释掉即可。
在settings.py文件中,可以设置session数据的存储方式,可以保存在数据库、本地缓存等。
存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式。
SESSION_ENGINE='django.contrib.sessions.backends.db'
如果存储在数据库中,需要在项INSTALLED_APPS中安装Session应用。
数据库中的表如图所示
由表结构可知,操作Session包括三个数据:键,值,过期时间。
存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE='django.contrib.sessions.backends.cache'
存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE='django.contrib.sessions.backends.cache'
在redis中保存session,需要引入第三方扩展,我们可以使用django-redis来解决。
1) 安装扩展
pip install django-redis
2)配置
在settings.py文件中做如下设置
- CACHES = {
- 'default': {
- 'BACKEND': 'django_redis.cache.RedisCache',
- 'LOCATION': 'redis://127.0.0.1:6379/1',
- 'OPTIONS': {
- 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
- }
- }
- }
- SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
- SESSION_CACHE_ALIAS = 'default'
通过HttpRequest对象的session属性进行会话的读写操作。
1) 以键值对的格式写session。
request.session['键']=值
2)根据键读取值。
request.session.get('键',默认值)
3)清除所有session,在存储中删除值部分。
request.session.clear()
4)清除session数据,在存储中删除session的整条数据。
request.session.flush()
5)删除session中的指定键及值,在存储中只删除某个键及对应的值。
del request.session['键']
6)设置session的有效期
request.session.set_expiry(value)
案列:使用数据库模式
- def set_session(request):
- """
- 第一次请求:
- ① 我们第一次请求的时候可以携带一些信息(用户名/密码) cookie中没有任何信息
- ② 当我们的服务器接收到这个请求之后,进行用户名和密码的验证,验证没有问题可以设置session
- 信息
- ③ 在设置session信息的同时(session信息保存在服务器端).服务器会在响应头中设置一个 sessionid 的cookie信息
- ④ 客户端(浏览器)在接收到响应之后,会将cookie信息保存起来(保存 sessionid的信息)
- """
- # 1.cookie中没有任何信息
- print(request.COOKIES)
-
- # 2.对用户名和密码进行验证
- # 假设认为 用户名和密码正确
- user_id = 6666
-
- # 3.设置session信息
- # request.session 理解为字典
- # 设置session的时候其实 session做了2件事
- # 第一件: 将数据保存在数据库中
- # 第二件: 设置一个 cookie信息,这个cookie信息是以 sessionid为key
- #request.session['user_id'] = user_id
-
- # 4. 返回响应
- return HttpResponse('set_session')
-
-
- def get_session(request):
- """
- 第二次及其之后的请求:
- ⑤ 第二次及其之后的请求都会携带 session id信息
- ⑥ 当服务器接收到这个请求之后,会获取到sessionid信息,然后进行验证,
- 验证成功,则可以获取 session信息(session信息保存在服务器端)
- """
- # 1. 请求都会携带 session id信息
- print(request.COOKIES)
-
- # 2. 会获取到sessionid信息,然后进行验证,
- # 验证成功,可以获取 session信息(
-
- # request.session 字典
- user_id = request.session['user_id']
- user_id = request.session.get('user_id')
-
- #
-
- # 3.返回响应
- return HttpResponse('get_session')
- from book.views import index, bookList, testproject, testPath,testLocalPath,get,post,post_json,get_headers,response,testCookie,set_session,get_session
- from django.urls import path,re_path
-
- urlpatterns = [
- # path('',index), #路由引导视图函数
- # path('booklist', bookList),
- path('testproject/', testproject, name='test'),
- path('testPath/<num1>/<num2>/', testPath, name='testPath'),
- path('get/',get,name='get'),
- path('post/',post,name='get'),
- path('post_json/',post_json,name='post_json'),
- path('get_headers/', get_headers, name='get_headers'),
- path('response/', response, name='response'),
- path('testCookie/', testCookie, name='testCookie'),
- path('set_session/', set_session, name='set_session'),
- path('get_session/', get_session, name='get_session'),
- re_path(r'testLocalPath/(?P<value1>\d+)/(?P<value2>\d+)/$',testLocalPath,name='testLocalPath')
- ]
不设置session,则控制台没有sessionId;
设置session,则控制台有sessionId;
访问获取session
思考:一个视图,是否可以处理两种逻辑?比如get和post请求逻辑。
如何在一个视图中处理get和post请求
注册视图处理get和post请求
以函数的方式定义的视图称为函数视图,函数视图便于理解。但是遇到一个视图对应的路径提供了多种不同HTTP请求方式的支持时,便需要在一个函数中编写不同的业务逻辑,代码可读性与复用性都不佳。
- def register(request):
- """处理注册"""
-
- # 获取请求方法,判断是GET/POST请求
- if request.method == 'GET':
- # 处理GET请求,返回注册页面
- return render(request, 'register.html')
- else:
- # 处理POST请求,实现注册逻辑
- return HttpResponse('这里实现注册逻辑')
在Django中也可以使用类来定义一个视图,称为类视图。
使用类视图可以将视图对应的不同请求方式以类中的不同方法来区别定义。如下所示
- from django.views.generic import View
-
- class RegisterView(View):
- """类视图:处理注册"""
-
- def get(self, request):
- """处理GET请求,返回注册页面"""
- return render(request, 'register.html')
-
- def post(self, request):
- """处理POST请求,实现注册逻辑"""
- return HttpResponse('这里实现注册逻辑')
类视图的好处:
-
- class View:
- """
-
- """
- # 定义 请求的方式
- http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
-
- def __init__(self, **kwargs):
- """
- 构建View对象,将格外传过来的字典中的键作为 View对象的属性, value作为属性值
- """
- # Go through keyword arguments, and either save their values to our
- # instance, or raise an error.
- for key, value in kwargs.items():
- setattr(self, key, value)
-
-
- @classonlymethod
- def as_view(cls, **initkwargs):
- """
- as_view 是将 视图类 转换为 视图函数
- """
- for key in initkwargs:
-
- # as_view 传过来的额外参数 不能是 http_method_names 定义的名字
-
- if key in cls.http_method_names:
- raise TypeError(
- 'The method name %s is not accepted as a keyword argument '
- 'to %s().' % (key, cls.__name__)
- )
-
- # as_view 传过来的额外参数 不能是 View对象的属性 或者 方法
- if not hasattr(cls, key):
- raise TypeError("%s() received an invalid keyword %r. as_view "
- "only accepts arguments that are already "
- "attributes of the class." % (cls.__name__, key))
-
- def view(request, *args, **kwargs):
- """
- 处理业务、返回 HttpResponse
- """
-
- # 初始化 View对象
- self = cls(**initkwargs)
-
- # 将 request, args, kwargs 赋值给 当前 View对象
- self.setup(request, *args, **kwargs)
-
- if not hasattr(self, 'request'):
- raise AttributeError(
- "%s instance has no 'request' attribute. Did you override "
- "setup() and forget to call super()?" % cls.__name__
- )
-
- # 通过请求方式名 或者 View对象的 方法名,并调用方法、返回HttpResponse
- return self.dispatch(request, *args, **kwargs)
-
-
- view.view_class = cls
- view.view_initkwargs = initkwargs
-
- # take name and docstring from class
- update_wrapper(view, cls, updated=())
-
- # and possible attributes set by decorators
- # like csrf_exempt from dispatch
- update_wrapper(view, cls.dispatch, assigned=())
- return view
-
- def setup(self, request, *args, **kwargs):
- """Initialize attributes shared by all view methods."""
-
- if hasattr(self, 'get') and not hasattr(self, 'head'):
- self.head = self.get
-
- self.request = request
- self.args = args
- self.kwargs = kwargs
-
-
- def dispatch(self, request, *args, **kwargs):
- """
- 将 请求 分发给 对应的 请求方法
- """
- if request.method.lower() in self.http_method_names:
-
- # 获取 View对象 和 请求方法名 相同 方法
- handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
- else:
- handler = self.http_method_not_allowed
-
- # 调用 视图函数、完成业务处理
- return handler(request, *args, **kwargs)
-
-
- def http_method_not_allowed(self, request, *args, **kwargs):
- """
- 405 请求方法不允许
- """
- logger.warning(
- 'Method Not Allowed (%s): %s', request.method, request.path,
- extra={'status_code': 405, 'request': request}
- )
- return HttpResponseNotAllowed(self._allowed_methods())
-
-
- def options(self, request, *args, **kwargs):
- """
- 获取允许的所有请求方法
- """
-
- """Handle responding to requests for the OPTIONS HTTP verb."""
- response = HttpResponse()
- response['Allow'] = ', '.join(self._allowed_methods())
- response['Content-Length'] = '0'
- return response
-
- def _allowed_methods(self):
- """
- 获取当前 View 允许的所有请求方法、并转为大写
- """
- return [m.upper() for m in self.http_method_names if hasattr(self, m)]
-
-
- class CenterView(View):
-
- def get(self,request):
- return HttpResponse("OK")
-
- def post(self,request):
- return HttpResponse("OK")
使用面向对象多继承的特性。
- class CenterView(LoginRequireMixin,View):
-
- def get(self,request):
- return HttpResponse("OK")
-
- def post(self,request):
- return HttpResponse("OK")
Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性。
我们可以使用中间件,在Django处理视图的不同阶段对输入或输出进行干预。
定义一个中间件工厂函数,然后返回一个可以被调用的中间件。
中间件工厂函数需要接收一个可以调用的get_response对象。
返回的中间件也是一个可以被调用的对象,并且像视图一样需要接收一个request对象参数,返回一个response对象。
- def simple_middleware(get_response):
- # 此处编写的代码仅在Django第一次配置和初始化的时候执行一次。
-
- def middleware(request):
- # 此处编写的代码会在每个请求处理视图前被调用。
-
- response = get_response(request)
-
- # 此处编写的代码会在每个请求处理视图之后被调用。
-
- return response
-
- return middleware
例如,在book应用中新建一个middleware.py文件,
- def my_middleware(get_response):
- print('init 被调用')
- def middleware(request):
- print('before request 被调用')
- response = get_response(request)
- print('after response 被调用')
- return response
- return middleware
定义好中间件后,需要在settings.py 文件中添加注册中间件
- MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- # 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'book.middleware.my_middleware', # 添加中间件
- ]
定义一个视图进行测试
- def middleware(request):
- print('view 视图被调用')
- return HttpResponse('OK')
注意:Django运行在调试模式下,中间件init部分有可能被调用两次。
示例:
定义两个中间件
- def my_middleware(get_response):
- print('init 被调用')
- def middleware(request):
- print('before request 被调用')
- response = get_response(request)
- print('after response 被调用')
- return response
- return middleware
-
- def my_middleware2(get_response):
- print('init2 被调用')
- def middleware(request):
- print('before request 2 被调用')
- response = get_response(request)
- print('after response 2 被调用')
- return response
- return middleware
注册添加两个中间件
- MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- # 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
- 'users.middleware.my_middleware', # 添加
- 'users.middleware.my_middleware2', # 添加
- ]
执行结果
在工程中创建模板目录templates。
在settings.py配置文件中修改TEMPLATES配置项的DIRS值:
- TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 此处修改
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
- ]
在templates目录中新建一个模板文件,如index.html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- {{ city }}
- </body>
- </html>
调用模板分为两步骤:
找到模板 loader.get_template(模板文件在模板目录中的相对路径) -> 返回模板对象
渲染模板 模板对象.render(context=None, request=None) -> 返回渲染后的html文本字符串 context 为模板变量字典,默认值为None request 为请求对象,默认值为None
例如,定义一个视图
- from django.http import HttpResponse
- from django.template import loader
-
- def index(request):
- # 1.获取模板
- template=loader.get_template('index.html')
-
- context={'city': '北京'}
- # 2.渲染模板
- return HttpResponse(template.render(context))
Django提供了一个函数render可以简写上述代码。
render(request对象, 模板文件路径, 模板数据字典)
- from django.shortcuts import render
-
- def index(request):
- context={'city': '北京'}
- return render(request,'index.html',context)
变量名必须由字母、数字、下划线(不能以下划线开头)和点组成。
语法如下:
{{变量}}
模板变量可以使python的内建类型,也可以是对象。
- def index(request):
- context = {
- 'city': '北京',
- 'adict': {
- 'name': '西游记',
- 'author': '吴承恩'
- },
- 'alist': [1, 2, 3, 4, 5]
- }
- return render(request, 'index.html', context)
for循环:
if条件:
比较运算符如下:
- ==
- !=
- <
- >
- <=
- >=
布尔运算符如下:
- and
- or
- not
注意:运算符左右两侧不能紧挨变量或常量,必须有空格。
单行注释语法如下:
{#.....#}
多行注释使用comment标签,语法如下:
{% coment %}
.........
{% endcomment %}
语法如下:
变量|过滤器:参数
列举几个如下:
safe,禁用转义,告诉模板这个变量是安全的,可以解释执行
length,长度,返回字符串包含字符的个数,或列表、元组、字典的元素个数。
default,默认值,如果变量不存在时则返回默认值。
data|default:'默认值'
date,日期,用于对日期类型的值进行字符串格式化,常用的格式化字符如下:
value|date:"Y年m月j日 H时i分s秒"
模板继承和类的继承含义是一样的,主要是为了提高代码重用,减轻开发人员的工作量。
父模板
如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中。
标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。 为了更好的可读性,建议给endblock标签写上名字,这个名字与对应的block名字相同。父模板中也可以使用上下文中传递过来的数据。
子模板
标签extends:继承,写在子模板文件的第一行。
子模版不用填充父模版中的所有预留区域,如果子模版没有填充,则使用父模版定义的默认值。
{% extends "父模板路径"%}
填充父模板中指定名称的预留区域。
Jinja2:是 Python 下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于 Django 的模板引擎,并扩展了其语法和一系列强大的功能,尤其是Flask框架内置的模板语言
由于django默认模板引擎功能不齐全,速度慢,所以我们也可以在Django中使用jinja2, jinja2宣称比django默认模板引擎快10-20倍。
Django主流的第三方APP基本上也都同时支持Django默认模板及jinja2,所以要用jinja2也不会有多少障碍。
- pip install jinja2
pip install jinja2
- from jinja2 import Environment
-
- def environment(**options):
- env = Environment(**options)
-
- return env
在settings.py文件
- TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.jinja2.Jinja2',#修改1
- 'DIRS': [os.path.join(BASE_DIR, 'templates')],
- 'APP_DIRS':True,
- 'OPTIONS':{
- 'environment': 'jinja2_env.environment',# 修改2
- 'context_processors':[
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
- ]
ERRORS:
?: (admin.E403) A 'django.template.backends.django.DjangoTemplates' instance must be configured in TEMPLATES in order to use the admin application.
所以需要保留他自身的默认配置:
如果不想使用他自身的配置给出两种方案:
第一种去除掉admin 模块:
- # project_name/settings.py
- INSTALLED_APPS = [
- # 注释掉 admin 应用
- # 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'app',
- ]
- # app/admin.py
- # 注释掉对 admin 的导入
- # from django.contrib import admin
-
- # Register your models here.
- # project_name/urls.py
- # 同样注释掉对 admin 的引用
- # from django.contrib import admin
- from django.urls import path, include
-
- from app import urls as app_urls
-
- urlpatterns = [
- path('admin/', admin.site.urls),
- path('app/', include(app_urls))
- ]
修改好上述三处代码之后,就可以正常使用了。
第二种方法直接建两个模板空间
- TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.jinja2.Jinja2', #修改为Jinja2模板 # 修改1
- # 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR,'jinja2')],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'environment':'book.jinja2_env.environmnet', #指定jinja2的环境
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
- {
-
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(BASE_DIR,'templates')],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
- ]
jinja2模板的使用绝大多数和Django自带模板一样,不一样有:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- {{ username }}
- <hr>
- 我的年龄是{{ age }}
- <hr>
- 我的朋友是 {{ friends }}
- <hr>
- 我的girl朋友是 {{friends.2}}
- <hr>
- {# 默认模板的for循环方法#}
- {# {% for item in friends %}#}
- {# <li> {{ forloop.counter }} {{ item }}</li>#}
- {# {% endfor %}#}
-
- {% for item in friends %}
- <li> {{ loop.index }} {{ item }}</li>
- {% endfor %}
- <hr>
- <hr>
- {% if age > 0 %}
- 大于十岁
- {% else %}
- 小于十岁
- {% endif %}
-
- {# 默认模板的字典值方法#}
- {# 我的第一年的月薪是 {{ score.2019 }}#}
- 我的第一年的月薪是 {{ score['2019'] }}
-
-
-
- {# 默认模板的过滤方法#}
- {# 我的生日是 {{ birthday|date:'Y年 m月 d日' }}#}
- {# <hr>ddx#}
- {# 我的简介:{{ desc|safe }}#}
- {# <hr>#}
- {# 我未定义的变量 {{ abcde|default:'我就是默认值' }}#}
-
- </body>
- </html>
- from jinja2 import Environment
-
- def environment(**options):
- env = Environment(**options)
-
- # 2.将自定义的过滤器添加到 环境中
- env.filters['do_listreverse'] = do_listreverse
-
- return env
-
- # 1.自定义过滤器
- def do_listreverse(li):
- if li == "B":
- return "哈哈"
CSRF
全拼为Cross Site Request Forgery
,译为跨站请求伪造。CSRF
指攻击者盗用了你的身份,以你的名义发送恶意请求。
- #定义路由
- from django.conf.urls import url
- from pay import views
- urlpatterns = [
- url(r'^$',views.LoginView.as_view(),name='index'), #登录路由
- url(r'^transfer/$',views.TransferView.as_view(),name='transfer'), #转账路由
- ]
-
- #定义视图
- class LoginView(View):
-
- def post(self,request):
-
- # 取到表单中提交上来的参数
- username = request.POST.get("username")
- password = request.POST.get("password")
-
- if not all([username, password]):
- print('参数错误')
- else:
- print(username, password)
- if username == 'laowang' and password == '1234':
- # 状态保持,设置用户名到cookie中表示登录成功
- response = redirect(reverse('transfer'))
- response.set_cookie('username', username)
- return response
- else:
- print('密码错误')
- return render(request,'login.html')
- def get(self,request):
- return render(request,'login.html')
-
- class TransferView(View):
-
-
- def post(self,request):
- # 从cookie中取到用户名
- username = request.COOKIES.get('username', None)
- # 如果没有取到,代表没有登录
- if not username:
- return redirect(reverse('index'))
-
-
- to_account = request.POST.get("to_account")
- money = request.POST.get("money")
-
- print('假装执行转操作,将当前登录用户的钱转账到指定账户')
- return HttpResponse('转账 %s 元到 %s 成功' % (money, to_account))
-
- def get(self, request):
- # 渲染转换页面
- response = render(request, 'transfer.html')
-
- return response
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>登录</title>
- </head>
- <body>
-
- <h1>我是网站A,登录页面</h1>
-
- <form method="post">
- <label>用户名:</label><input type="text" name="username" placeholder="请输入用户名"><br/>
- <label>密码:</label><input type="password" name="password" placeholder="请输入密码"><br/>
- <input type="submit" value="登录">
- </form>
-
- </body>
- </html>
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>转账</title>
- </head>
- <body>
- <h1>我是网站A,转账页面</h1>
-
- <form method="post">
- <label>账户:</label><input type="text" name="to_account" placeholder="请输入要转账的账户"><br/>
- <label>金额:</label><input type="number" name="money" placeholder="请输入转账金额"><br/>
- <input type="submit" value="转账">
- </form>
-
- </body>
- </html>
运行测试,如果在未登录的情况下,不能直接进入转账页面,测试转账是成功的
- #定义路由
- from django.conf.urls import url
- from ads import views
-
- urlpatterns = [
- url(r'^$',views.AdsView.as_view()),
- ]
-
- #定义视图
- class AdsView(View):
-
- def get(self,request):
-
- return render(request,'index.html')
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
-
- <h1>我是网站B</h1>
-
- <form method="post" action="http://127.0.0.1:9000/transfer/">
- <input type="hidden" name="to_account" value="黑客">
- <input type="hidden" name="money" value="190000" hidden>
- <input type="submit" value="点击领取优惠券">
- </form>
-
- </body>
- </html>
运行测试,在用户登录网站A的情况下,点击网站B的按钮,可以实现伪造访问
- from django.middleware.csrf import get_token
- csrf_token = get_token(request)
在渲染转账页面的,做以下几件事情:
- def get(self, request):
- # 生成csrf_token
- from django.middleware.csrf import get_token
- csrf_token = get_token(request)
-
- # 渲染转换页面,传入 csrf_token 到模板中
- response = render(request, 'transfer.html',context={'csrf_token':csrf_token})
-
- # 设置csrf_token到cookie中,用于提交校验
- response.set_cookie('csrf_token', csrf_token)
-
- return response
在转账模板表单中添加 csrf_token 隐藏字段
- <head>
- <meta charset="UTF-8">
- <title>转账</title>
- </head>
- <body>
- <h1>我是网站A,转账页面</h1>
-
- <form method="post">
- <input type="hidden" name="csrftoken" value="{{ csrf_token }}">
- <label>账户:</label><input type="text" name="to_account" placeholder="请输入对方账户"><br/>
- <label>金额:</label><input type="number" name="money" placeholder="请输入转账金额"><br/>
- <input type="submit" value="转账">
- </form>
-
- </body>
- </html>
运行测试,进入到转账页面之后,查看 cookie 和 html 源代码
- # 取出表单中的 csrf_token
- form_csrf_token = request.POST.get("csrftoken")
- # 取出 cookie 中的 csrf_token
- cookie_csrf_token = request.COOKIES.get('csrf_token')
- # 进行对比
- if cookie_csrf_token != form_csrf_token:
- return HttpResponse('token校验失败,可能是非法操作')
运行测试,用户直接在网站 A 操作没有问题,再去网站B进行操作,发现转账不成功,因为网站 B 获取不到表单中的 csrf_token 的隐藏字段,而且浏览器有同源策略,网站B是获取不到网站A的 cookie 的,所以就解决了跨站请求伪造的问题
Django默认是开启CSRF的
模板中设置 CSRF 令牌
- {% csrf_token %}
- 或者
- <input type="hidden" value="{{ csrf_token }}">
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。