如何搭建工程程序
如何获取请求数据(操作request对象)
如何构造响应数据(构造response对象)
框架提供的其他功能组件的使用
Django,发音为[`dʒæŋɡəʊ],是用python语言写的开源web开发框架,并遵循MVC设计。劳伦斯出版集团为了开发以新闻内容为主的网站,而开发出来了这个框架,于2005年7月在BSD许可证下发布。这个名称来源于比利时的爵士音乐家DjangoReinhardt,他是一个吉普赛人,主要以演奏吉它为主,还演奏过小提琴等。由于Django在近年来的迅速发展,应用越来越广泛,被著名IT开发杂志SDTimes评选为2013SDTimes100,位列"API、库和框架"分类第6位,被认为是该领域的佼佼者。
Django的主要目的是简便、快速的开发数据库驱动的网站。它强调代码复用,多个组件可以很方便的以"插件"形式服务于整个框架,Django有许多功能强大的第三方插件,你甚至可以很方便的开发出自己的工具包。这使得Django具有很强的可扩展性。它还强调快速开发和DRY(DoNotRepeatYourself)原则。
1) 重量级框架
对比Flask框架,Django原生提供了众多的功能组件,让开发更简便快速。
2)MVT模式
有一种程序设计模式叫MVC,其核心思想是分工、解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容。
MVC模式说明 :
1 官网:https://www.djangoproject.com/
2 github源码: https://github.com/django/django
3 中文文档 :https://yiyibooks.cn/xx/Django_1.11.6/index.html
4 Django Book教程: http://djangobook.com/
5 Tange with Django 教程: http://www.tangowithdjango.com/book17/
具体搭建见之前的博客
这里只说明虚拟环境的作用和意义:
作用:虚拟环境可以搭建独立的python运行环境, 使得单个项目的运行环境与其它项目互不影响。
注:这里的虚拟环境只是python的虚拟环境,并且虚拟环境也只是python的一个pip包。也就是说,除了python解释器其他都是一样的。例如linux的apt-get得到的包等其他资源都是共享的。
官方文档参考: 文档链接.
步骤:
创建Django项目
django-admin startproject name
创建子应用
python manager.py startapp name
在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:8000”便可看到效果。
在django中,创建子应用模块目录仍然可以通过命令来操作,即:
python manage.py startapp 子应用名称
manage.py为上面创建工程时自动生成的管理文件。
例如,在刚才创建的bookmanager工程中,想要创建一个用户book子应用模块,可执行:
cd ~/Desktop/code/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’。
用vscode链接远程服务器的时候,要将vscode的Python解释器换成虚拟环境的python解释器。
使用Django进行数据库开发的提示 :
使用Django进行数据库开发的步骤 :
1)定义模型类
根据书籍表结构设计模型类:
模型类:BookInfo
书籍名称字段:name
根据人物表结构设计模型类:
模型类:PeopleInfo
人物姓名字段:name
人物性别字段:gender
外键约束:book
外键要指定所属的模型类book = models.ForeignKey(BookInfo)
说明 :
书籍-人物的关系为一对多. 一本书中可以有多个英雄.
不需要定义主键字段, 在生成表时会自动添加, 并且值为自增长.
根据数据库表的设计
在models.py中定义模型类,继承自models.Model
from django.db import models
# 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)
2) 模型迁移 (建表)
可以理解为数据库和模型之间的桥梁。
迁移由两步完成 :
python manage.py makemigrations
python manage.py migrate
提示:默认采用sqlite3数据库来存储数据
站点: 分为内容发布和公共访问两部分
内容发布的部分由网站的管理员负责查看、添加、修改、删除数据
Django能够根据定义的模型类自动地生成管理模块
使用Django的管理模块, 需要按照如下步骤操作 :
1.管理界面本地化
2.创建管理员
3.注册模型类
4.发布内容到数据库
创建管理员的命令 :
python manage.py createsuperuser
python manager.py changepassword 用户名
登陆站点 :http://127.0.0.1:8000/admin
登陆站点成功:
在应用的admin.py文件中注册模型类:
注册模型成功后, 就可以在站点管理界面方便快速的管理数据.
# 准备书籍列表信息的模型类
class BookInfo(models.Model):
# 创建字段,字段类型...
name = models.CharField(max_length=10)
def __str__(self):
"""将模型类以字符串的方式输出"""
return self.name
站点管理页面做好了, 接下来就要做公共访问的页面了.
对于Django的设计框架MVT.
使用视图时需要进行两步操作:
查找视图的过程 :
1.请求者在浏览器地址栏中输入URL, 请求到网站.
2.网站获取URL信息.
3.然后与编写好的URLconf逐条匹配.
4.如果匹配成功则调用对应的视图.
5.如果所有的URLconf都没有匹配成功.则返回404错误.
URLconf入口(工程文件内):
需要两步完成URLconf配置:
1.在项目中定义URLconf
2.在应用中定义URLconf
1)在项目中定义URLconf:
2)在应用中定义URLconf:
提示:一条URLconf包括URL规则、视图两部分
URL规则使用正则表达式定义.
视图就是在views.py中定义的视图函数.
url匹配过程:
注意:admin比较特殊,是自动添加的,也必须要有这个,否则进不了管理界面。其他的,想要什么页面,就在这里和应用上添加,然后在view上渲染即可。
***注:先进行拆解,后去匹配项目的url.py,然后去匹配应用的url.py,然后去执行view。***,这里的admin也是一个页面,所以要写上,只不过admin比较特殊,是系统自带的。当注册完管理界面后,这里会自动出现admin
视图处理过程如下图:
使用视图时需要进行两步操作,两步操作不分先后
配置URLconf
在应用/views.py中定义视图
总结View和URL匹配流程 :
注:先拆解出/test/后,后去项目的url.py文件去进行匹配,然后去应用的url.py文件中进行匹配,后去执行view.py的内容。
思考 : 网站如何向客户端返回一个漂亮的页面呢?
提示 :
问题 :
设想 :
解决问题 :模板
在Django中, 将前端的内容定义在模板中, 然后再把模板交给视图调用, 各种漂亮、炫酷的效果就出现了.
模板使用步骤 :
1.创建模板
2.设置模板查找路径
3.模板接收视图传入的数据
4.模板处理数据
实现步骤
1.创建视图
2.创建模板
3.配置URLconf
# 定义视图:提供书籍列表信息
def bookList(request):
# 查询数据库书籍列表数据
books = BookInfo.objects.all()
# 构造上下文
context = {'books':books}
# 数据交给模板处理,处理完成后通过视图响应给客户端
return render(request, 'Book/booklist.html', context)
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>书籍列表信息title>
head>
<body>
<ul>
ul>
body>
html>
进入应用中的urls.py文件
from django.conf.urls import url
from book.views import index,bookList
urlpatterns = [
url(r'^$',index),
# 匹配书籍列表信息的URL,调用对应的bookList视图
url(r'^booklist/$',bookList)
]
1)BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(file)))
当前工程的根目录,Django会依此来定位工程内的相关文件,我们也可以使用该参数来构造文件路径。
2)DEBUG
调试模式,创建工程后初始值为True,即默认工作在调试模式下。
作用:
注意:部署线上运行的Django不要运行在调式模式下,记得修改DEBUG=False和ALLOW_HOSTS。
3)本地语言与时区
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:8000/static/index.html来访问。
或者我们在static目录中添加了一个子目录和文件book/detail.html,在浏览器中就可以使用127.0.0.1:8000/static/book/detail.html来访问。
注:
目前应用文件夹里就apps还没有了解。
在每个应用目录中都包含了apps.py文件,用于保存该应用的相关信息。
在创建应用时,Django会向apps.py文件中写入一个该应用的配置类,如
from django.apps import AppConfig
class BookConfig(AppConfig):
name = 'book'
我们将此类添加到工程settings.py中的INSTALLED_APPS列表中,表明注册安装具备此配置属性的应用。添加的方法有两种(以book应用为例):
这个类有两个主要的参数:
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'book'
verbose_name = '图书管理'
1)创建项目
django-admin startproject bookmanager
2)创建应用
python manager.py startapp book
3)更换python解释器:按需选择
4)安装应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#添加子应用
'book.apps.BookConfig'
]
5 ) 本地化
#设置中文
LANGUAGE_CODE = 'zh-Hans'
#亚洲上海时区
TIME_ZONE = 'Asia/Shanghai'
6 )模板路径
在应用同级目录下,创建templates模板文件夹,并在工程的setting中设置模板路径。
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',
],
},
},
]
7) 项目中匹配urls
正则 : 路径只要不是admin/就算匹配成功。并包含到应用中的urls.py
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
#正则为:只要不是 admin/ 就算匹配成功
url(r'^',include('book.urls'))
]
8) 应用中匹配urls.py
应用中创建 urls.py
正则 : 路径中包含booklist/,就调用视图中对应的bookList函数
from django.conf.urls import url
from book.views import bookList
urlpatterns = [
# 匹配书籍列表信息的URL,调用对应的bookList视图
url(r'^booklist/$',bookList)
]
9) 准备视图
# 定义视图:提供书籍列表信息
def bookList(request):
return HttpResponse('OK!')
10) 开启服务器, 测试项目
# 进入项目文件中, 开启项目对应的服务器
python manage.py runserver
# 浏览器中输入网址
http://127.0.0.1:8000/booklist/
在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库。如下所示:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
1)使用MySQL数据库首先需要安装驱动程序
pip install PyMySQL
2)在Django的工程同名子目录的__init__.py文件中添加如下语句
import pymysql
pymysql.install_as_MySQLdb()
作用是让Django的ORM能以mysqldb的方式来调用PyMySQL。
3)在setting中修改DATABASES配置信息
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'root', # 数据库用户名
'PASSWORD': 'mysql', # 数据库用户密码
'NAME': 'book' # 数据库名字
}
}
这里的NAME,指的是用mysql里的哪个数据库,相当于use命令。
所以写NAME之前或之后,要在mwsql命令行中创建一个book数据库:
create database book charset=utf8;
4) 在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
下面对上面的模型类进行说明:
1) 数据库表名
models文件中一个类就是一个在数据库建立了一个表。
模型类如果未指明表名,Django默认以小写app应用名_小写模型类名为数据库表名。
可通过db_table指明数据库表名。如上面所示。
2) 关于主键
django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
3) 属性命名限制
4)字段类型
类型 | 说明 |
---|---|
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,对上传的内容进行校验,确保是有效的图片 |
5) 选项
选项 | 说明 |
---|---|
null | 如果为True,表示允许为空,默认值是False |
blank | 如果为True,则该字段允许为空白,默认值是False |
db_column | 字段的名称,如果未指定,则使用属性的名称 |
db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False |
default | 默认 |
primary_key | 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用 |
unique | 如果为True, 这个字段在表中必须有唯一值,默认值是False |
null是数据库范畴的概念,blank是表单验证范畴的
6) 外键
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:
CASCADE级联,删除主表数据时连通一起删除外键表中数据
PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
SET_NULL设置为NULL,仅在该字段null=True允许为null时可用
SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用
SET()设置为特定值或者调用特定方法
DO_NOTHING不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常
将模型类同步到数据库中。
1)生成迁移文件
python manage.py makemigrations
根据模型类生成创建表的语句,体现在migrations中。(不会在数据库中创建表)
2)同步到数据库中
python manage.py migrate
根据第一步生成的语句在数据库中创建表。(会在数据库中创建表)
这两步执行完后,我们进入book数据库进行确认:
book不再为空,出现了很多表,其中就有bookinfo和peopleinfo。
(注,迁移的时候一定要先确认数据库的编码集)
然后我们查看一下表book_bookinfo里面的信息:
和我们在BookInfo类中写入的是一样的!
(注:如果迁移失败,可以先将book数据库删除,再讲migrations里的迁移文件删除,找到原因后,再重新建立book库,再进行迁移)
在mysql命令行中,手动向book数据库添加测试数据:
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工具就相当于Ipython
通过如下命令进入shell:
python3 manage.py shell
在shell工具里导入两个模型类,以便后续使用:
from book.models import BookInfo,PeopleInfo
查看mysql数据库日志可以查看对数据库的操作记录。 mysql日志文件默认没有产生,需要做如下配置:
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
把68,69行前面的#去除,然后保存并使用如下命令重启mysql服务。
sudo service mysql restart
使用如下命令打开mysql日志文件。
tail -f /var/log/mysql/mysql.log # 可以实时查看数据库的日志内容
# 如提示需要sudo权限,执行
# sudo tail -f /var/log/mysql/mysql.log
为了简便,下面的操作都是在shell工具下执行的。本质就是通过操作模型中的类,来操作数据库。
增加数据有两种方法。
1)save
通过创建模型类对象,执行对象的save()方法保存到数据库中。
>>> from book.models import BookInfo,PeopleInfo
>>> book = BookInfo(
... name='python入门',
... pub_date='2010-1-1'
... )
>>> book.save()
>>> book
<BookInfo: python入门>
2)create
通过模型类.objects.create()保存。
>>> PeopleInfo.objects.create(
... name='itheima',
... book=book
... )
<PeopleInfo: itheima>
一般使用这个方法。
在mysql执行select * from peopleinfo可得:
修改更新有两种方法
本质上都是先查询后更新!
1)save
修改模型类对象的属性,然后执行save()方法。
>>> person = PeopleInfo.objects.get(name='itheima')
>>> person.name = 'itcast'
>>> person.save()
>>> person
<PeopleInfo: itcast>
2)update
使用模型类.objects.filter().update(),会返回受影响的行数
>>> PeopleInfo.objects.filter(name='itcast').update(name='传智播客')
1
删除有两种方法,本质上也是先查后删。
1)模型类对象delete
>>> person = PeopleInfo.objects.get(name='传智播客')
>>> person.delete()
(1, {'book.PeopleInfo': 1})
2)模型类.objects.filter().delete()
>>> BookInfo.objects.filter(name='python入门').delete()
(1, {'book.BookInfo': 1, 'book.PeopleInfo': 0})
一 基本查询
get查询单一结果,如果不存在会抛出模型类.DoesNotExist异常。
all查询多个结果。
count查询结果数量。
>>> BookInfo.objects.get(id=1)
<BookInfo: 射雕英雄传>
# 注:get返回的是一个对象!可以在对这个对象进行操作
>>> BookInfo.objects.get(pk=2)
<BookInfo: 天龙八部>
>>> BookInfo.objects.get(pk=20)
Traceback (most recent call last):
File "" , line 1, in <module>
File "/home/python/.virtualenvs/py3_django_1.11/lib/python3.5/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/python/.virtualenvs/py3_django_1.11/lib/python3.5/site-packages/django/db/models/query.py", line 380, in get
self.model._meta.object_name
book.models.DoesNotExist: BookInfo matching query does not exist.
>>> BookInfo.objects.all()
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>]>
# all返回的是一个对象列表
>>> BookInfo.objects.count()
4
二 过滤查询
实现SQL中的where功能,包括
filter过滤出多个结果
exclude排除掉符合条件剩下的结果
get过滤单一结果
对于过滤条件的使用,上述三个方法相同,故仅以filter进行讲解(所以都是返回对象列表)。
过滤条件的表达语法如下:
属性名称__比较运算符=值
1)相等
exact:表示判等。
例:查询编号为1的图书。
BookInfo.objects.filter(id__exact=1)
可简写为:
BookInfo.objects.filter(id=1)
2)模糊查询
contains:是否包含。
说明:如果要包含%无需转义,直接写即可。
例:查询书名包含’传’的图书。
BookInfo.objects.filter(name__contains='传')
<QuerySet [<BookInfo: 射雕英雄传>]>
startswith、endswith:以指定值开头或结尾。
例:查询书名以’部’结尾的图书
>>> BookInfo.objects.filter(name__endswith='部')
<QuerySet [<BookInfo: 天龙八部>]>
以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.
3) 空查询
isnull:是否为null。
例:查询书名为空的图书。
>>> BookInfo.objects.filter(name__isnull=True)
<QuerySet []>
4) 范围查询
in:是否包含在范围内。
例:查询编号为1或3或5的图书
>>> BookInfo.objects.filter(id__in=[1,3,5])
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 笑傲江湖>]>
5)比较查询
gt大于 (greater then)
gte大于等于 (greater then equal)
lt小于 (less than)
lte小于等于 (less than equal)
例:查询编号大于3的图书
BookInfo.objects.filter(id__gt=3)
不等于的运算符,使用exclude()过滤器。
例:查询编号不等于3的图书
>>> BookInfo.objects.exclude(id=3)
<QuerySet [<BookInfo: 雪山飞狐>]>
6)日期查询
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
例:查询1980年发表的图书。
>>> BookInfo.objects.filter(pub_date__year=1980)
<QuerySet [<BookInfo: 射雕英雄传>]>
例:查询1990年1月1日后发表的图书。
>>> BookInfo.objects.filter(pub_date__gt='1990-1-1')
<QuerySet [<BookInfo: 笑傲江湖>]>
一 、F对象
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢?
答:使用F对象,被定义在django.db.models中。
语法如下:
F(属性名)
例:查询阅读量大于等于评论量的图书。
>>> from django.db.models import F
>>> BookInfo.objects.filter(readcount__gt=F('commentcount'))
<QuerySet [<BookInfo: 雪山飞狐>]>
可以在F对象上使用算数运算。
例:查询阅读量大于2倍评论量的图书。
>>> BookInfo.objects.filter(readcount__gt=F('commentcount')*2)
<QuerySet [<BookInfo: 雪山飞狐>]>
二、Q对象
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询阅读量大于20,并且编号小于3的图书。
>>> BookInfo.objects.filter(readcount__gt=20,id__lt=3)
<QuerySet [<BookInfo: 天龙八部>]>
或者
>>> BookInfo.objects.filter(readcount__gt=20).filter(id__lt=3)
<QuerySet [<BookInfo: 天龙八部>]>
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值)
例:查询阅读量大于20的图书,改写为Q对象如下。
BookInfo.objects.filter(Q(readcount__gt=20))
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
>>> BookInfo.objects.filter(Q(readcount__gt=20)|Q(id__lt=3))
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>
Q对象前可以使用~操作符,表示非not。
例:查询编号不等于3的图书。
>>> BookInfo.objects.filter(~Q(id=3))
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>
一 聚合函数
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg平均,Count数量,Max最大,Min最小,Sum求和,被定义在django.db.models中。
例:查询图书的总阅读量。
>>> from django.db.models import Sum
>>> BookInfo.objects.aggregate(Sum('readcount'))
{'readcount__sum': 126}
注意aggregate的返回值是一个字典类型,格式如下:
{‘属性名__聚合类小写’:值}
如:{‘readcount__sum’: 126}
使用count时一般不使用aggregate()过滤器。
例:查询图书总数。
BookInfo.objects.count()
注意count函数的返回值是一个数字。
二 排序
使用order_by对结果进行排序
# 默认升序
>>> BookInfo.objects.all().order_by('readcount')
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 笑傲江湖>, <BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>
# 降序
>>> BookInfo.objects.all().order_by('-readcount')
<QuerySet [<BookInfo: 雪山飞狐>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 射雕英雄传>]>
即是如:
查询书籍为1的所有人物信息
查询人物为1的书籍信息
由一到多的访问语法:
一对应的模型类对象.多对应的模型类名小写_set
例:
>>> book = BookInfo.objects.get(id=1)
>>> book.peopleinfo_set.all()
<QuerySet [<PeopleInfo: 郭靖>, <PeopleInfo: 黄蓉>, <PeopleInfo: 黄药师>, <PeopleInfo: 欧阳锋>, <PeopleInfo: 梅超风>]>
由多到一的访问语法:
多对应的模型类对象.多对应的模型类中的关系类属性名
例:
person = PeopleInfo.objects.get(id=1)
person.book
<BookInfo: 射雕英雄传>
访问一对应的模型类关联对象的id语法:
多对应的模型类对象.关联类属性_id
例:
>>> person = PeopleInfo.objects.get(id=1)
>>> person.book_id
1
关联过滤查询
一 由多模型类条件查询一模型类数据:
语法如下:
关联模型类名小写__属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。
例:
查询图书,要求图书人物为"郭靖"
>>> book = BookInfo.objects.filter(peopleinfo__name='郭靖')
>>> book
<QuerySet [<BookInfo: 射雕英雄传>]>
查询图书,要求图书中人物的描述包含"八"
>>> book = BookInfo.objects.filter(peopleinfo__description__contains='八')
>>> book
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>]>
二 由一模型类条件查询多模型类数据:
语法如下:
一模型类关联属性名__一模型类属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。
例:
查询书名为“天龙八部”的所有人物。
>>> people = PeopleInfo.objects.filter(book__name='天龙八部')
>>> people
<QuerySet [<PeopleInfo: 乔峰>, <PeopleInfo: 段誉>, <PeopleInfo: 虚竹>, <PeopleInfo: 王语嫣>]>
查询图书阅读量大于30的所有人物
>>> people = PeopleInfo.objects.filter(book__readcount__gt=30)
>>> people
<QuerySet [<PeopleInfo: 乔峰>, <PeopleInfo: 段誉>, <PeopleInfo: 虚竹>, <PeopleInfo: 王语嫣>, <PeopleInfo: 胡斐>, <PeopleInfo: 苗若兰>, <PeopleInfo: 程灵素>, <PeopleInfo: 袁紫衣>]>
一 概念
Django的ORM中存在查询集的概念。
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
all():返回所有数据。
filter():返回满足条件的数据。
exclude():返回满足条件之外的数据。
order_by():对结果进行排序。
对查询集可以再次调用过滤器进行过滤,如
>>> books = BookInfo.objects.filter(readcount__gt=30).order_by('pub_date')
>>> books
<QuerySet [<BookInfo: 天龙八部>, <BookInfo: 雪山飞狐>]>
也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。
从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。
判断某一个查询集中是否有数据:
exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。
二 两大特性
1)惰性执行
创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用。
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集books
books = BookInfo.objects.all()
继续执行遍历迭代操作后,才真正的进行了数据库的查询
for book in books:
print(book.name)
2)缓存
使用同一个查询集,第一次使用时会发生数据库的查询,然后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
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>]>
四 分页
文档.
#查询数据
books = BookInfo.objects.all()
#导入分页类
from django.core.paginator import Paginator
#创建分页实例
paginator=Paginator(books,2)
#获取指定页码的数据
page_skus = paginator.page(1)
#获取分页数据
total_page=paginator.num_pages
视图就是应用中views.py文件中的函数
视图的第一个参数必须为HttpRequest对象,还可能包含下参数如
通过正则表达式组获取的位置参数
通过正则表达式组获得的关键字参数
视图必须返回一个HttpResponse对象或子对象作为响应
子对象: JsonResponse HttpResponseRedirect
视图负责接受Web请求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:
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
# 只要不是‘admin/’就匹配成功,包含到应用中的urls.py
url(r'^', include('book.urls')),
]
四、应用中urls.py:匹配testproject/成功就调用views中的testproject函数,测试项目逻辑
from django.conf.urls import url
import views
urlpatterns = [
# 匹配`testproject/`成功就调用`views`中的`testproject`函数
url(r'^testproject/$', views.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
1)生成迁移文件
python manage.py makemigrations
2 ) 同步到数据库中
python manage.py migrate
3 ) 添加测试数据
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开发的网站,由哪一个视图进行处理请求,是由url匹配找到的。
配置URLconf :
一、settings.py中,指定url配置:
ROOT_URLCONF = ‘项目.urls’
二、项目中urls.py
匹配成功后,包含到应用的urls.py:
url(正则, include(‘应用.urls’))
三、应用中urls.py
匹配成功后,调用views.py对应的函数:
url(正则, views.函数名)
四、提示
- 正则部分推荐使用 r,表示字符串不转义,这样在正则
表达式中使用 \ 只写一个就可以- 不能在开始加反斜杠,推荐在结束加反斜杠
正确:path/
正确:path
错误:/path
错误:/path/- 请求的url被看做是一个普通的python字符串,进行匹配时不包括域名、get或post参数
3.1 如请求地址如下: http://127.0.0.1:8000/18/?a=10
3.2 去掉域名和参数部分后,只剩下如下部分与正则匹配 18/
在定义路由的时候,可以为路由命名,方便查找特定视图的具体路径信息。
1 ) 在使用include函数定义路由时,可以使用namespace参数定义路由的命名空间,如
url(r’^’,include(‘book.urls’,namespace=‘book’))
命名空间表示,凡是book.urls中定义的路由,均属于namespace指明的book名下。(一般namesapce都以应用名字命名)
命名空间的作用:避免不同应用中的路由使用了相同的名字发生冲突,使用命名空间区别开。
2) 在定义普通路由时,可以使用name参数指明路由的名字,如
urlpatterns = [
url(r'^$',index),
# 匹配书籍列表信息的URL,调用对应的bookList视图
url(r'^booklist/$',bookList,name='index'),
url(r'^testproject/$',views.testproject,name='test'),
]
使用reverse函数,可以根据路由名称(name),动态返回具体的路径,如:
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')
命令行会返回:
/testproject/
对于未指明namespace的,reverse(路由name)
对于指明namespace的,reverse(命名空间namespace:路由name)
PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件,可以直接去对我们写出来的路由和视图函数进行调试,作为后端程序员是必须要知道的一个工具。
一、安装方式1:去 Chrome 商店直接搜索 PostMan 扩展程序进行安装
二、安装方式2:https://www.getpostman.com/官网下载桌面版
三、 安装方式3:将已下载好的 PostMan 插件文件夹拖入到浏览器
打开 Chrome 的扩展程序页面,打开开发者模式选项 将插件文件夹拖入到浏览器(或者点击加载已解压的扩展程序选择文件夹):
(reuqest就是 HttpRequest的一个对象!)
回想一下,客户端利用HTTP协议向服务器传参有几种途径?4种:
下面根据客户端发送请求方式的不同,我们在服务器端的解析请求的方法如下。
注:这里使用的模板index为:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<a href="#">{{v1}}{{v2}} a>
body>
html>
一、位置参数
url(r’^(\d+)/(\d+)/$’, views.index),
def index(request, value1, value2):
# 构造上下文
context = {'v1':value1, 'v2':value2}
return render(request, 'Book/index.html', context)
二、关键字参数
其中?P<value1>部分表示为这个参数定义的名称为value1
可以是其它名称,起名要做到见名知意
url(r'^(?P\d+)/(?P\d+)/$' , views.index),
def index(request, value2, value1):
# 构造上下文
context = {'v1':value1, 'v2':value2}
return render(request, 'Book/index.html', context)
HttpRequest对象的属性GET、POST都是QueryDict类型的对象。
以百度的搜索为例:
https://www.baidu.com/baidu?tn=monline_3_dg&ie=utf-8&wd=python
与python字典不同,QueryDict类型的对象用来处理同一个键带有多个值的情况:
get(‘键’,默认值)
getlist(‘键’,默认值)
获取请求路径中的查询字符串参数(形如?k1=v1&k2=v2),可以通过request.GET属性获取,返回QueryDict对象。
# /get/?a=1&b=2&a=3
def get(request):
g = request.GET
a = request.GET.get('a')
b = request.GET.get('b')
alist = request.GET.getlist('a')
print(g) #
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中间件,如:
一、表单类型 Form Data
前端发送的表单类型的请求体数据,可以通过request.POST属性获取,返回QueryDict对象。
def post(request):
g=request.POST
a = request.POST.get('name')
b = request.POST.get('password')
alist = request.POST.getlist('name')
print(g)
print(a)
print(b)
print(alist)
return HttpResponse('OK')
示例:
二、非表单类型 Non-Form Data
非表单类型的请求体数据,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')
示例:
可以通过request.META属性获取请求头headers中的数据,request.META为字典类型。
常见的请求头如:
具体使用如:
def get_headers(request):
print(request.META['CONTENT_TYPE'])
return HttpResponse('OK')
示例:
method:一个字符串,表示请求使用的HTTP方法,常用值包括:‘GET’、‘POST’。
user:请求的用户对象。
path:一个字符串,表示请求的页面的完整路径,不包含域名和参数部分。
encoding:一个字符串,表示提交的数据的编码方式。
如果为None则表示使用浏览器的默认设置,一般为utf-8。
这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值。
FILES:一个类似于字典的对象,包含所有的上传文件。
注意和小结
视图在接收请求并处理后,必须返回HttpResponse对象或子对象。HttpRequest对象由Django创建,HttpResponse对象由开发人员创建。
与 Django 自动创建的 HttpRequest 对象不同, HttpResponse 对象是你的责任。你写的每个视图都要负责实例化、填充和返回一个 HttpResponse 对象。
HttpResponse 类位于 django.http 模块中。
可以使用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的子类,可以快速设置状态码
HttpResponseRedirect 301
HttpResponsePermanentRedirect 302
HttpResponseNotModified 304
HttpResponseBadRequest 400
HttpResponseNotFound 404
HttpResponseForbidden 403
HttpResponseNotAllowed 405
HttpResponseGone 410
HttpResponseServerError 500
若要返回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,有时也用其复数形式Cookies,指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等。服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。Cookies最典型记住用户名。
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,浏览器退出就删除cookie
response.set_cookie('itcast2', 'python2', max_age=3600) # 有效期一小时
return response
示例二:
二 读取Cookie
可以通过HttpRequest对象的COOKIES属性来读取本次请求携带的cookie值。request.COOKIES为字典类型。
def cookie(request):
cookie1 = request.COOKIES.get('username')
print(cookie1)
return HttpResponse('OK')
三 删除Cookie
可以通过HttpResponse对象中的delete_cookie方法来删除。
response.delete_cookie('username')
注意点:
流程:
一 、启用Session
Django项目默认启用Session。
如需禁用session,将上图中的session中间件注释掉即可。
二、 存储方式
在settings.py文件中,可以设置session数据的存储方式,可以保存在数据库、本地缓存等。
1 )数据库
存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式。
SESSION_ENGINE=‘django.contrib.sessions.backends.db’
如果存储在数据库中,需要在项INSTALLED_APPS中安装Session应用。
数据库中的表如图所示:
表结构如下:
由表结构可知,操作Session包括三个数据:键,值,过期时间。
2) 本地缓存
存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE=‘django.contrib.sessions.backends.cache’
3) 混合存储
优先从本机内存中存取,如果没有则从数据库中存取。
SESSION_ENGINE=‘django.contrib.sessions.backends.cached_db’
4) Redis
在redis中保存session,需要引入第三方扩展,我们可以使用django-redis来解决。
文档.
a、 安装扩展
pip install django-redis
b、配置
在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'
注意:
如果redis的ip地址不是本地回环127.0.0.1,而是其他地址,访问Django时,可能出现Redis连接错误,如下:
解决方法:
修改redis的配置文件,添加特定ip地址。
打开redis的配置文件
sudo vim /etc/redis/redis.conf
在如下配置项进行修改(如要添加10.211.55.5地址)
重新启动redis服务
sudo service redis-server restart
三、Session操作
通过HttpRequest对象的session属性进行会话的读写操作。(request.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(requset)
#1cookie中没有任何信息
print(request.COOKIES)
#2对用户名和密码进行确认,假设认为用户名和密码正确
user_id=666
# 3 设置session信息
request.session['user_id'=user_id]
return HttpResponse('set_session')
第二次及其之后的请求示例不再展示。
思考:一个视图,是否可以处理两种逻辑?比如get和post请求逻辑。
(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中也可以使用类来定义一个视图,称为类视图。
(不同的请求方式有不同的业务逻辑,类视图的方法,就直接采用http的请求方式的名字作为我们的函数名,例如get,post等)
使用类视图可以将视图对应的不同http请求方式以类中的不同方法来区别定义。如下所示:
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('这里实现注册逻辑')
注意:
类视图的好处:
定义类视图需要继承自Django提供的父类View,可使用from django.views.generic import View或者from django.views.generic.base import View导入,定义方式如上所示。
配置路由时,使用类视图的as_view()方法来添加。
urlpatterns = [
# 视图函数:注册
# url(r'^register/$', views.register, name='register'),
# 类视图:注册
url(r'^register/$', views.RegisterView.as_view(), name='register'),
]
二、 类视图原理
查看as_view源码:
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
...省略代码...
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# 调用dispatch方法,按照不同请求方式调用不同请求方法
return self.dispatch(request, *args, **kwargs)
...省略代码...
# 返回真正的函数视图
return view
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
可知,as_view返回的是一个视图函数名,所以路由中的views.RegisterView.as_view()就会被根据情况转换为views.RegisterView.get或views.RegisterView.post。(而判断请求方式是由as_view中的 dispatch执行的)
三、类视图的多继承重写dispatch
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', # 添加
]
init2 被调用
init 被调用
before request 被调用
before request 2 被调用
view 视图被调用
after response 2 被调用
after response 被调用
一 、配置
在工程中创建模板目录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>Titletitle>
head>
<body>
{{ city }}
body>
html>
三、模板渲染
调用模板分为两步骤:
例如,定义一个视图
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)
四、模板语法
1) 模板变量
变量名必须由字母、数字、下划线(不能以下划线开头)和点组成。
语法如下:
{{变量}}
模板变量可以使python的内建类型,也可以是对象。
def index(request):
context = {
'city': '北京',
'adict': {
'name': '西游记',
'author': '吴承恩'
},
'alist': [1, 2, 3, 4, 5]
}
return render(request, 'index.html', context)
比较运算符如下:
==
!=
<
>
<=
>=
布尔运算符如下:
and
or
not
注意:运算符左右两侧不能紧挨变量或常量,必须有空格。
3)注释
单行注释语法如下:
语法如下:
变量|过滤器:参数
列举几个如下:
safe,禁用转义,告诉模板这个变量是安全的,可以解释执行
length,长度,返回字符串包含字符的个数,或列表、元组、字典的元素个数。
default,默认值,如果变量不存在时则返回默认值。
data|default:‘默认值’
date,日期,用于对日期类型的值进行字符串格式化,常用的格式化字符如下:
Y表示年,格式为4位,y表示两位的年。
m表示月,格式为01,02,12等。
d表示日, 格式为01,02等。
j表示日,格式为1,2等。
H表示时,24进制,h表示12进制的时。
i表示分,为0-59。
s表示秒,为0-59。
value|date:"Y年m月j日 H时i分s秒"
模板继承和类的继承含义是一样的,主要是为了提高代码重用,减轻开发人员的工作量。(父模板和子模板是不同的文件)。
父模板:
如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中。
标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。 为了更好的可读性,建议给endblock标签写上名字,这个名字与对应的block名字相同。父模板中也可以使用上下文中传递过来的数据。
子模板:
子模版不用填充父模版中的所有预留区域,如果子模版没有填充,则使用父模版定义的默认值。
官方文档
inja2介绍
安装jinja2模块 :
pip3 install jinja2
Django配置jinja2:
1)在项目文件中创建 jinja2_env.py 文件
from jinja2 import Environment
def environment(**options):
env = Environment(**options)
return env
2)在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',
],
},
},
]
jinja2模板的使用绝大多数和Django自带模板一样 :
jinja2自定义过滤器:
在jinja2_env.py文件中自定义过滤器
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 校验的 WebA
#定义路由
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>
运行测试,如果在未登录的情况下,不能直接进入转账页面,测试转账是成功的
二 、攻击网站B的代码
#定义路由
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>Titletitle>
head>
<body>
<h1>我是网站Bh1>
<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
<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 }}">