软件框架
定义
一个软件是由其中各个软件模块组成的,每一个模块都有特定的功能,模块与模块之间通过相互配合来完成软件的开发。
软件框架是针对某一类软件设计问题而产生的
Django遵循的是MVC思想
MVC框架
MVC的产生理念:分工,让专门的人去做专门的事情(比如:输入、处理、输出)
MVC的核心思想:解耦
MVC是三个模块的简称:
- M:model,模型,和数据库进行交互
- V:View,视图,产生html页面
- C:Controller,控制器,接收请求,进行处理,与M和V进行交互,返回应答
以通过浏览器注册用户信息为例:
Django简介
Django,发音为[`dʒæŋɡəʊ],是用python语言写的开源web开发框架,并遵循MVC设计。
MVT
Django是遵循MVC的一个Web框架,但是他有自己的一个名字,叫做MVT
快速开发
和DRY
原则。Do not repeat yourself。不要自己去重复一些工作
M:Model,模型,和数据库进行交互,与MVC中的M功能相同
V:View,视图,接收请求,进行处理,与M和T交互,返回应答,与MVC中的C功能相同
T:template,模板,产生html页面,和MVC终的V功能相同
MVT的各部分功能:
虚拟环境
在Linux上,如果用pip3安装的包,会安装在/user/local/lib/python3.xx/dis-packages下面。当安装同一个包的不同版本的时候,后安装的版本会把原来安装的版本覆盖掉。
如何解决这个问题?
使用虚拟环境,所谓虚拟环境,就是真实python环境的一个复制版本。在虚拟环境中使用的python是复制的python,安装python包也是安装在复制的python中
ps:如果是在pycharm里的设置里面安装的,而不是用pip命令安装的,会默认安装在一个虚拟环境里面
Linux下安装虚拟环境的命令:
sudo pip install virtualenv 安装虚拟环境
sudo pip install virtualenvwrapper 安装虚拟环境扩展包
-
编辑家目录下面的.bashrc文件,添加如下两行内容
- export WORKON_HOME=$HOME/.virtualenvs
- source /user/local/bin/virtualenvwrapper.sh
使用source .bashrc使其生效
创建python2的虚拟环境:mkvirtualenv 虚拟环境名称
创建python3的虚拟环境:mkvirtualenv -p python3 虚拟环境名
进入虚拟环境工作:workon 虚拟环境名
查看机器上有多少个虚拟环境:workon 空格 + 两个tab键
退出虚拟环境:deactivate
删除虚拟环境:rmvirtualenv 虚拟环境名
在虚拟环境中的一些命令:
- pip list 查看安装了的包,pip freeze也可以
- pip install 包的名字(如果要指定版本号,再紧跟==版本号),如果使用sudo安装,仍然是安全到全局
Windows环境安装虚拟环境:
- pip list查看是否已经安装virtualenv
- 如果没有安装过:pip install virtualenv,安装成功后,会提示:Success fully installed virtualenv-xxx
- 在一个目录下创建虚拟环境:virtualenv 虚拟环境的名字
- 进入虚拟环境所在目录的scripts,查看scripts目录结构,可以看到激活虚拟环境的命令:activate.bat
- 执行activate命令
项目创建
django-admin startproject 项目名字(注意:需要先进入虚拟环境,Linux和Windows相同)
创建成功后,目录结构如下:
其中:
-
__init__.py
说明其所在路径是一个python的包 - settings.py是项目的配置文件,比如项目要使用哪个数据库,就在里面配置
- urls.py url:在访问网站时,我们输入的网站就是一个url的地址。urls.py文件是进行url路由的配置(当我们输入一个地址后,他怎么给我们找到对应的处理函数是谁)
- wsgi.py Django是遵循wsgi协议的框架,wsgi.py就是web服务器和django交互的一个入口
- manage.py 是我们项目的管理文件,是一个可执行文件,通过该文件,我们可以管理整个项目
创建django应用
一个项目由很多个应用组成,每一个应用完成一个功能模块
创建应用的命令如下:python manage.py startapp 应用名
每创建一个应用,里面就会有如下文件:
其中:
-
__init__.py
说明该应用是一个python模块 - models.py中写和数据库相关的内容
- views.py 接收请求,进行处理,与M和T进行交互,返回应答。定义处理函数(在django中称视图函数)
- tests.py写测试代码的文件,不怎么需要关心
- admin.py跟网站的后台管理相关的文件
- migrations文件夹:存放由模型类生成的迁移文件
在我们建立应用之后,必须建立应用和项目之间的联系,即:对应用进行注册:在settings.py中修改installed_apps配置项,eg:注册booktest应用
运行项目
运行一个web项目,就需要一个web服务器,而django本身给我们提供了一个web服务器,使用python manage.py runsevser运行该服务器,然后即可根据提示用浏览器访问
ORM
O:object,对象
R:relations,关系
M:mapping,映射
ORM的作用:建立类和表的对应关系
ORM让我们能够通过对类和对象的操作实现对表的操作,就不需要再去写sql语句。这是元类的一种最经典的应用
在创建的项目文件夹下的models.py文件中,我们设计和表对应的类,这个类被称做模型类
ORM的另一个作用:根据设计的类生成数据库中的表
模型(M)
模型类设计和表生成
模型类设计
在项目文件夹下的models.py文件中,比如我们定义一个图书类
from django.db import models
# Create your models here.
class BookInfo(models.Model):
# 必须继承了Model类之后,他才是一个模型类
# 模型类的名字就对应的表的名字为:应用名_小写的模型类名
# 模型类中的类属性就对应于表中的一个字段(列)
btitle = models.CharField(max_length=20) #书名
#通过models.CharField体现btitle的类型是一个字符串,max_length制定字符串的最大长度
bpub_date = models.DateField() #出版日期
#models.DateField说明其是一个日期类型
#在django中,id不需要我们定义,他会帮我们自动生成
模型类的字段属性和选项
模型类属性命名限制
- 不能是python关键字
- 不允许使用连续的下划线,不是由django的查询方式决定的
- 定义属性时需要指定字段类型,通过字段类型的参数指定选择,语法如下: 属性名 = models.字段类型(选项)
字段类型
使用时需要引入django.db.models包
AutoField 自动增长的IntegerField,通常不用指定,不指定时django会自动创建属性名为id的自动增长属性,在迁移文件中可以查看到
BooleanField 布尔字段,值为True或False
NullBooleanField 支持Null,True,False三种值
CharField(max_length=最大长度) 字符串,参数max_length指定最大字符个数,必须指定,不然报错
TextField 大文本字段,一般超过4k个字
IntegerField 整数
DecimalField(max_digits=None, decimal_places=None) 十进制浮点数,第一个参数表示总位,第二个参数表示小数位数
FloatField 浮点数,参数同上。注意:float的精确度比decimal小
-
DateField([auto_now=False, auto_now_add=False]) 日期,有两个可选参数:
- auto_now 表示每次保存对象时,自动设置该字段为当前时间,用于“最后一次修改”的时间戳,它总是使用当前日期,默认为false
- auto_now_add 表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认false
- 两个参数是相互排斥的,不能同时使用
TimeField 时间参数同DateField
DateTimeField 日期时间,参数同上
FileField 上传文件字段
ImageField 继承于FileField,对上传的内容进行校验,确保是有效的图片
选项
作为Field()中的参数
通过选项实现的字段的约束,选项如下:
- default 默认值。设置默认值
- primary_key 若为True,则该字段会成为模型的主键字段,默认值是false,一般作为AutoField的选项使用
- unique 唯一性约束,如果为True,这个字段在表中必须有唯一值,默认值是False
- db_index 若值为True,则在表中会为这个字段创建索引,默认值为False
- db_column 字段的名称,如果未指定,则使用属性的名称
- null 如果为True,表示允许为空,默认值是False
- blank 如果为True,则该字段允许为空白,默认值是False
更多的可以见官方文档
对比:null是数据库的概念,blank是后台管理页面表单验证范畴的
注意:当修改模型类之后,如果添加的选项不影响表的结构,则不需要重新做迁移,eg:blank和default
模型类生成表
一、 生成迁移文件
迁移文件是根据模型类生成的,生成的迁移文件存放在migrations文件夹下
命令:python manage.py makemigrations
二、执行迁移生成表
命令:python manage.py migrate
根据迁移文件生成表
注意:django默认使用的数据库是sqlite3,在项目文件夹下,与项目同名的文件夹下的settings.py文件中DATABASES字段处可以看到
当文件迁移后,我们就可以在项目文件夹下看到一个名为db.sqlite3的文件,即我们的数据库文件,可以直接使用相应软件打开
通过模型类操作数据表
- 使用python manage.py shell进入项目终端
- from booktest.models import BookInfo (from 应用名.models import 自己定义的类的名字)
- 创建类的对象:b = BookInfo()
- 增加实例属性(注意:这个实例属性的名字必须和类属性的名字一样):b.btitle = "天龙八部"
- from datetime import date
- b.bpub_date = date(1990,1,1)
- 然后就把这个实例属性里面保存的数据添加到数据库:b.save(),注意:这个方法是继承自models.Model
- 用 模型类名.objects查看对应表里面的数据(注意:是模型类名,而不是上面的变量名b),eg:查看id=1的记录:b2 = BookInfo.objects.get(id=1),b2是一个BookInfo类的对象。即:把查出来的数据是保存在一个对象里面,然后可以用类似:b2.btitle查看btitle对应的数据
- 更改数据:b2.bpub_date = date(1990,10,10),即:直接更改查询的对象的属性值即可,然后用b2.save()将保存同步到数据库
- 如何删除一条记录:b2.delete()
建立两张表之间的关系:外键
from django.db import models
# Create your models here.
class BookInfo(models.Model):
# 必须继承了Model类之后,他才是一个模型类
# 模型类中的类属性就对应于表中的一个字段(列)
btitle = models.CharField(max_length=20) #书名
#通过models.CharField体现btitle的类型是一个字符串,max_length制定字符串的最大长度
bpub_date = models.DateField() #出版日期
#models.DateField说明其是一个日期类型
#在django中,id不需要我们定义,他会帮我们自动生成
class HeroInfo(models.Model):
hname = models.CharField(max_length=20) #名字
hgender = models.BooleanField(default=False) #性别,布尔类型,default制定默认值
hcomment = models.CharField(max_length=128) #备注
# 在有一对多的关系的两个类中,需要在“多”的类中定义外键,建立“一”和“多”的关系
# 关系属性对应的表的字段名格式:关系属性名_id
hbook = models.ForeignKey('BookInfo',on_delete=models.CASCADE) # 建立了图书类和英雄人物类之间的关系,注意:如果两个类不在一个应用里面,则需要写成 应用.类名,eg:booktest.BookInfo
注意:往“多”类的属性里面赋值的时候,相关联属性其对应值必须是与其关联的类的对象,eg:
对该属性进行查看,发现其是一对象:
查看与“一”相关联的“多”的信息(eg:此处与图书相关联的英雄的信息)
注意:这里的heroinfo是小写
查询表中的所有内容:类名.objects.all(),返回的是一个列表,其中的元素为每一条数据的对象
后台管理
在所创建应用的对应文件夹下,有一个admin.py,(eg:本案例中为:/myProject/booktest/admin.py)通过它,我们实现后台的管理
步骤:
-
本地化
-
语言和时区的本地化:修改settings.py文件
-
登录管理页面需要有一个管理员的账户:创建管理员:python manage.py createsuperuser
-
使用:python manage.py runserver启动服务器,然后就可以通过浏览器进行管理,127.0.0.1:8000/admin,然后就可以帮助我们管理数据库里面的数据。注意:服务器的默认端口是8000,但是我们也可以在运行时直接指定:python manage.py runserver 127.0.0.1:8001
-
注册模型类
在应用下的admin.py中注册模型类,告诉django框架根据注册的模型类来生成对应表管理页面
admin文件:
注册后的管理界面:
- 自定义管理页面:自定义模型管理类。模型管理类就是告诉django在生成的管理页面上显示哪些内容(比如,我们让其在浏览 器中显示图书名称的同时显示出版时间)
class BookInfoAdmin(admin.ModelAdmin):
# 自定义模型管理类,名字可以随便取,但是通常是:表的名字+Admin
list_display = ['id','btitle','bpub_date'] # 固定写法,列表中写要在页面中显示的内容
# 同时,在注册的时候,我们需要让该模型类知道自己的管理类是谁
admin.site.register(BookInfo, BookInfoAdmin) # 注意,该语句不能分两次写,否则会报错
视图(V)
在django中,通过浏览器去请求一个页面的时候,是使用视图函数来处理这个请求的,视图函数处理后,要给浏览器返回页面内容
视图函数的使用
一、定义视图函数
在应用下的views.py中,eg:定义一个名为index的视图函数(注意:视图函数必须有一个参数:request)
from django.http import HttpResponse
def index(request):
# 视图函数必须有一个request参数,它是一个HttpRequest对象
# 当用户输入http://127.0.0.1:8000/index的时候,要给浏览器返回一个内容
# 视图的作用,就是在里面会进行处理,如果要用到数据库,就与M交互,如果要产生页面,就与T交互
# 视图函数必须返回一个HttpResponse对象,需要从django.http中导入
# 所谓要产生页面就与T(template)进行交互:template就是一段html代码,当V拿到tempalte后,
# 将里面的部分数据用从M(数据库)拿来的数据进行替换,然后返回,浏览器就可以直接解析这段html代码从而在浏览器上显示对应内容
return HttpResponse("<h1>hello django!</h1>")
# 我们知道,一个地址对应一个处理函数,那么如何让django知道哪个地址是对应哪个处理函数?
# 答:进行url路由的配置
二、进行url的配置
首先我们要在应用对应的目录下建立urls.py文件,然后在其中创建一个列表,如下:
from django.conf.urls import url
from booktest import views
urlpatterns = [
# 通过url函数设置url路由的配置项
url(r'^index', views.index) #建立/index和视图index之间的关系
]
同时,我们也需要在项目的urls里面添加配置项
from django.contrib import admin
from django.urls import path,include
from django.conf.urls import url
urlpatterns = [
path('admin/', admin.site.urls), # 配置项
url(r'^', include('booktest.urls')) # 将booktest应用中的urls文件包含进来
# url函数第一个参数是一个正则表达式
]
效果图如下:
流程分析:
当我们在浏览器中输入127.0 .0.1:8000/index的时候,django拿到的是/index
-
然后就会拿这一部分去查找对应的视图
- 先到项目的urls文件里面去查找,拿该字符串与urlpatterns中每一项的第一个参数(正则表达式)进行匹配
- 如果匹配上了(由于我们在第二项只匹配了开头,那么无论匹配什么都能成功),就会执行后面的动作,于是django就拿着字符串到应用中去与应用中的urls进行匹配,但是注意:在整个匹配过程中,/index中,最前面的/不参与正则的匹配,匹配成功。(
在进行匹配时,会去掉匹配成功的部分,比如此处,我们在项目的urls中进行匹配时,由于只匹配了个开头,没有具体的数据,所以django还是拿着index去应用的urls中匹配,如果我们假设在项目的urls中匹配到了i,那么django就只会拿着ndex去应用中匹配
) - 然后一看对应的是一个视图,django就会去调用这个视图,然后获得返回的数据(
"<h1>hello django!</h1>"
) - 于是浏览器就可以进行显示
注意:在应用的urls文件中进行url匹配的时候,一定要严格匹配开头结尾,避免出现意外
模板(T)
django中的模板不仅仅是个html文件,还可以在其中定义变量,也可以写类似于编程语言的语句
模板文件的使用
创建模板文件夹(通常是在项目文件夹下创建templates)
-
配置模板目录(在项目的settings.py文件下),如下:
-
使用模板文件
- 加载模板文件:去模板目录下面获取html文件的内容,得到一个模板对象(通常,不同的应用会用到不同的模板文件,所以我们会再在templates下面建立与应用名同名的文件夹,用以存储该应用用到的模板文件)
- 定义模板上下文:向模板文件传递数据,即:如果模板中使用了变量,需要将变量的数据给他
- 模板渲染,读出变量后,将变量的位置替换掉,即得到一个标准的html文件
eg:
模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<h1>这是一个模板文件</h1>
<p>我在这里使用了模板变量,在模板中使用变量,如果不是在代码段内,就要使用两个花括号括起来:{{content}}</p>
<ul>模板中的代码段要写在两个%中间:{% for i in list %}
<li>{{i}}</li>
{% endfor %}
<!-- 在模板中,如果使用代码,有开始就一定有结束,比如endfor,比如endif-->
</ul>
</body>
</html>
应用程序中的views文件
# -*- coding:utf-8 -*-
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse
from django.template import loader,RequestContext
def index(request):
# # 1. 加载模板文件
# temp = loader.get_template('booktest/index.html') #注意:这里的路径是相对与templates的
# # 返回值是一个模板对象
#
# # 2. 定义模板上下文:给模板文件传数据。所谓定义模板上下文,就是创建一个RequestContext对象
# context = RequestContext(request, {}) # RequestContext的第一个对象需要是一个request
# context.push(locals())
# # 要给模板文件传递的数据就放在第二个参数里面,其是一个字典,通过键-值对的方式传递
# # 由于我们这里没有用到变量,所以传空字典即可
#
# # 3. 模板渲染:产生标准的html内容
# # 模板对象temp里面有一个render方法,能够把RequestContext对象中对应的位置替换成对应的值
# # 然后返回替换后的内容:一个标准的html文件
# res_html = temp.render(request=request,context=locals())
# # 4. 返回给浏览器
#
# return HttpResponse(res_html)
return render(request,'booktest/index.html',{"content":"hello world", "list": list(range(10))})
# 注意:实际上以上几个过程都已经被进一步封装到了render中,我们可以直接写成:render(request, '/booktest/index.html',{})
# 第三个参数是我们要传递给模板文件的数据,但是建议自己手动替换。如果不传的话也可以直接不写
效果图:
知识补充
- 在配置url的时候,只要对正则表达式分组,django就会将该分组作为参数传递给后面的参数,eg:
from django.conf.urls import url
from booktest import views
urlpatterns = [
url(r'^index', views.index),
url(r'^show_books$', views.show_books),
url(r'^show_books/(\d+)$', views.detail),
# 在配置url的时候,只要对正则表达式分组,django就会将该分组作为参数传递给后面的参数,这里就是将匹配到的数字传递给views.detail函数
# ps:新版django可以使用path来代替url
]
- 模板文件中 for循环里面的empty项,当要遍历的东西为空的时候,就执行empy
<h1>{{ book.btitle }}</h1>
英雄信息如下:<br>
<ul>
{% for hero in heros %}
<li>{{hero.hname}}---{{hero.hcomment}}</li>
{% empty %}
<!-- empty:用在for循环中,如果遍历东西为空,就会执行这里的东西 -->
<li>没有英雄信息</li>
{% endfor %}
</ul>
Django中数据库的配置
在django中,通过方便的配置就可以进行数据库的切换,直接更改项目的settings文件
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
"""使用mysql数据库"""
"""注意:在使用mysql数据库的时候需要使用一个叫MySQLdb的模块,我们直接安装pymysql即可"""
"""然后在项目文件夹下的settings文件中加上如下代码:
import pymysql
pymysql.install_as_MySQLdb()
"""
'ENGINE': 'django.db.backends.mysql', #说明使用mysql数据库
'NAME': 'bj18', #说明要使用的数据库的名字,注意,数据库必须事先手动创建
# 配置用户名和密码
'USER': 'root',
'PASSWORD': "",
'HOST': 'localhost', #指定数据库所在的ip地址,因为通常我们用的数据库并不是在本地,如果是连本机,可以直接使用localhost
'PORT': 3306, #配置数据库的端口
}
}
关于报错问题
:
-
raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__) django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.
- 解决办法:找到Python安装路劲下的Python36-32\Lib\site-packages\django\db\backends\mysql\base.py文件,将文件中的如下代码注释`
if version < (1, 3, 3):
raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
-
注释后,笔者又遇到了一个报错:
query = query.decode(errors='replace')
- 解决办法:更进一步修改 operations.py。把其中的
query = query.decode(errors='replace')
修改为query = query.encode(errors='replace')
- 解决办法:更进一步修改 operations.py。把其中的
-
其他报错:
-
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xca in position 2: invalid conti nuation byte
- 解决办法:在报错的文件第一行添加:
# -*- coding:utf-8 -*-
- 解决办法:在报错的文件第一行添加:
-
重定向
这里说的重定向,就是当一个视图函数处理完请求后,继续返回到某一页面
代码示例:
from datetime import date
from django.http import HttpResponseRedirect
def create(request):
"""新增一本图书"""
b = BookInfo()
b.btitle = "流行蝴蝶剑"
b.bpub_date = date(2000,11,12)
b.save()
# return HttpResponse('ok')
# 返回应答:让浏览器再访问/index/,这就需要用到:from django.http import HttpResponseRedirect
return HttpResponseRedirect(redirect_to='/index')
# HttpResponseRedirect也有简单的写法:
# from django.shortcuts import redirect
# return redirect('index')
相应html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图书信息</title>
</head>
<body>
<h1>图书信息如下:</h1>
<a href="/create">新增</a>
<!-- 注意:这里的/create相当于127.0.0.1:8000/create-->
<!-- 但是如果不加/,就是和之前的地址进行拼接,比如,如果之前的地址是127.0.0.1:8000/index,那么此时就是127.0.0.1:8000/index/create -->
<ul>
{% for book in books %}
<li>{{ book.btitle }}---{{ book.bpub_date }}</li>
{% empty %}
<li>信息为空</li>
{% endfor %}
</ul>
</body>
</html>
当我们点击添加的时候,本来会跳转到127.0.0.1:8000/create,但是由于其视图函数处理后进行了重定向,所以我们在浏览器就看不到这个跳转的过程
通过模型类查询表中的数据
更改配置使其产生日志文件
在Linux里面让mysql产生日志文件mysql.log
- 使用
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
命令打开mysql的配置文件,去除68,69行的注释 -
sudo service mysql restart
)重启mysql服务,就会产生mysql日志文件 - 打开MySQL的日志文件。/var/log/mysql/mysql.log 是mysql日志文件所在的位置。
- 如果使用
sudo tail -f /var/log/mysql/mysql.log
可以实时查看其中的日志文件
查询函数
通过 模型类.objects属性可以调用如下函数,实现对模型类对应的数据表的查询,注意,要是模型类,而不是它的实例
类名就相当于(注意,不是)表的名字,模型类.xxx就是在表中查找数据
以下格式:(函数名:功能;返回值;说明)
- get:返回表中满足条件的一条且仅能有一条数据;返回值是一个模型类对象;参数中写查询条件,如果查询到多条语句,抛出异常MultipleObjectsReturned,查询不到语句,抛出DoesNotExist
- all:返回模型类对应表格中的所有数据;返回值是QuerySet类型;查询集
- filter:返回满足条件的数据;返回值是QuerySet类型;参数写查询条件
- exclude:返回不满足条件的数据;返回值是QuerySet类型;参数写查询条件
- order_by:对查询结果进行排序;返回值是QuerSet类型;参数中写根据哪些字段进行排序,eg:以id升序排列,参数就为'id',如果以id降序,'-id'
查询时使用的条件
条件的格式:模型类的属性名__条件名=值\
在查询时,可以传多个条件,他们之间是且的关系
# 判等:exact
BookInfo.objects.get(id=1) #完全的写法为:BookInfo.objects.get(id__exact=1)
# 模糊查询
# 包含:contains
BookInfo.objects.filter(btitle__contains='传') #查询名字中含有“传”字的书
# 以xxx结尾:endswith
# 以xxx开始:starswith
# 空查询:isnull
BookInfo.objects.filter(btitle__isnull=False) #查询书名不为空的数据
# 范围查询:in
# eg:查询id为1或3或5的图书
BookInfo.objects.filter(id__in=[1,3,5])
# 比较查询
# gt:great than,大于 lt:less than,小于 gte:great euqal,大于等于 lte:less than equal,小于等于
# eg:查询编号大于3的图书
BookInf.objects.filter(id__gt=3)
# 日期查询
BookInfo.objects.filter(bpub_date__year=19990) #date有一个属性为year
from datetime import date
BookInfo.objects.filter(bpub_date__gt=date(1980,1,1))
对于QuerySet,可以直接继续调用上面的方法
eg:把id大于3的图书信息按阅读量从大到小排序显示:BookInfo.objects.filter(id__gt=3).order_by('-bread'),因为filter返回的是一个QuerySet,所以可以直接继续调用order_by
Q对象
作用:用于查询时条件之间的逻辑关系:not and or ,可以对Q对象进行& | -操作,~取反
使用之前需要先导入:from django.db.models import Q
eg:
BookInfo.objects.fitler(Q(id=1) | Q(id=2))
BookInfo.objects.fitler(Q(id=1) | Q(id=2))
F对象
作用:用于类属性之间的比较
使用之前需要先导入:from django.db.models import F
eg:查询图书阅读量大于评论量
BookInfo.objects.filter(bread__gt=F('bcomment'))
# F对象还可以进行算数的运算
# 查询图书阅读量大于两倍评论量的信息
BookInfo.objects.filter(bread__gt=2F('bcomment')*2)
聚合函数
作用:对查询结果进行聚合操作
sum count avg max min
aggregate:调用这个函数来使用聚合。返回值是一个字典
使用之间需要先导入聚合类:from django.db.models import Sum,Count,Max,Min,Avg
案例:
# 查询所有图书的数目
from django.db.models import Count,Sum
BookInfo.objects.all().aggregate(Count('id'))
# 注意:count的参数不能为*
# 查询所有图书阅读量的总和
BookInfo.objects.aggregate(Sum('bread'))
count函数,注意,这里说的不是上面的Count
返回值是一个数
eg:查询所有图书的数目
BookInfo.objects.all().count()
# 同样的,如果是对一个表中的所有数据进行查询,.all()都可以省略
# 即:可以写成:BookInfo.objects.count()
查询集
all filter exclude order_by 调用这些函数会产生一个查询集
查询集特性
- 惰性查询:只有在实际使用查询集中的数据的时候才会发生对数据库的真正查询,eg:当book = BookInfo.objects.all()的时候,不会产生数据的查询
- 缓存:当使用的是同一个查询集时,第一次的时候会发生数据库的查询,然后把结果缓存起来,之后再使用这个查询集时,使用的是缓存中的结果
限制查询集
可以对一个查询集进行 取下标或者切片 操作来限制查询集的结果
对一个查询集进行切片操作会产生一个新的查询集,下标不允许为负
eg:
books = BookInfo.objects.all()
bk2 = books[0:3]
取出查询集第一条数据的两种方式:
- b[0]:如果不存在,会抛出IndexError异常
- b[0:1].get():如果不存在,会抛出DoesNotExist异常
exists:判断一个查询集中是否有数据,返回结果为True或False
用法:查询集.exists()
模型类之间的关系
和表一样,模型类之间也有三种关系
一对多关系
例:图书类-英雄类
models.ForeignKey() 定义在多的类中
多对多关系
例:新闻类-新闻类型类,一篇新闻可能是体育新闻,还可能是国际新闻,同样,体育新闻下也有多篇新闻
models.ManyToManyField() 定义在哪个类中都可以
一对一关系
例:员工基本信息类-员工详细信息类
models.OneToOneField 定义在哪个类中都可以
关联查询(一对多)
我们把在“多”的类中定义的属性叫做关联属性
案例:
# 查询图书id为1的图书关联的所有英雄的信息
b = BookInfo.objects.get(id=1)
b.heroinfo_set.all()
# 查询id为1的英雄关联的图书信息
h = HeroInfo.objects.get(id=1)
h.hbook()
通过模型类实现关联查询
例:查询图书信息,要求图书管理的英雄的描述包含“八”
Book.Info.objects.filter(heroinfo__hcoment__contains=8)
heroinfo是对应的“多”的类名的小写,hcomment是“多”的属性(相当于表中的字段),contains是条件表达式
最终要拿到哪个表中的数据,就通过哪个表来查
例:查询图书信息,要求图书中的英雄的id大于3
BookInfo.objects.filter(heroinfo__id__gt=3)
例:查询书名为“天龙八部”的所有英雄
Hero.objects.filter(hbook__bititle="天龙八部“)
注意:如果前面要查询的类中没有该关系属性,后面的参数中就必须写类名;如果前面类中有该关系属性,写的应该是属性名
插入、更新和删除
调用一个模型类对象的save方法的时候就可以实现对模型类对应数据表的插入和更新
调用一个模型类对象的delete方法的时候,就可以实现对模型类对应数据的删除
自关联
自关联是一种特殊的一对多的关系,只是这些一对多的关系都在一张表里面
示例:
class AreaInfo(models.Model):
"""地区模型类"""
atitle = models.CharField(max_length=20)
# 关系属性,代表当前地区的父级地区
aParent = models.ForeignKey('self', null=True, blank=True,on_delete=models.CASCADE) #代表这个类与他自身有了这种关联
管理器
BookInfo.objects.all() --> objects到底是一个什么东西?
答:objects是django帮助我们自动生成的管理器对象,通过这个管理器可以实现对数据的查询
objects是models.Manager类的一个对象。自定义管理器后Django不再帮助我们生成默认的objects管理器
示例:
class BookInfo(models.Model):
"""图书模型类"""
btitle = models.CharField(max_length=20)
bpub_date = models.DateField()
bread = models.IntegerField(default=0)
bcomment = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False) #删除的时候,执行逻辑删除,而非真正的删除,默认不删除
book = models.Manager() #自定义一个管理器对象
此时,我们就不能够通过BookInfo.objects.xxx来查询数据,而是使用:BookInfo.book.xxx
通常,我们是自己定义一个类,继承models.Manager类,然后让其作为管理器类
自定义管理器类的作用:
- 通过对方法的重写,改变查询的结果集
代码示例:不返回逻辑上被删除了的数据
class BookInfoManager(models.Manager):
def all(self):
# 1. 调用父类的all方法获取所有数据
books = super().all()
books.filter(isDelete=False)
return books
- 添加额外的方法以简化操作
代码示例:
class BookInfoManager(models.Manager):
def all(self):
# 1. 调用父类的all方法获取所有数据
books = super().all()
books.filter(isDelete=False)
return books
# 2. 封装函数:操作模型类对应的数据表(增删查改)
def create_book(self, btitle, bpub_date):
obj = BookInfo()
obj.btitle = btitle
obj.bpub_date = bpub_date
obj.save()
return obj
效果:
注意:在models.Manager里面默认给我们封装好了一个create方法,不过必须要通过关键字传参
以上代码还有 一个问题:即,一旦模型类的名字发生改变,我们就必须手动更改管理器内的代码,我们可以通过self.model来获得模型类的名称
代码示例:
class BookInfoManager(models.Manager):
def all(self):
# 1. 调用父类的all方法获取所有数据
books = super().all()
books.filter(isDelete=False)
return books
# 2. 封装函数:操作模型类对应的数据表(增删查改)
def create_book(self, btitle, bpub_date):
model_class = sel.model #获得model所在的模型类
obj = model_class() #创建对象
obj.btitle = btitle
obj.bpub_date = bpub_date
obj.save()
return obj
模型管理器类和模型类的关系
元选项
情景:由于我们生成的表的名字为”应用名_小写的模型类名",那么当我们的应用名一旦发生改变,该模型类对应的表名就跟着改变,但是这个类对应的表名已经生成了,这就会导致无法访问原表,会报错
如何解决:让模型类对应的表名不依赖于应用的名字:通过元选项指定表名,在模型类里面定义一个元类,通过该类里面的db_table属性来定义表名
代码示例:
class BookInfo(models.Model):
"""图书模型类"""
btitle = models.CharField(max_length=20)
bpub_date = models.DateField()
bread = models.IntegerField(default=0)
bcomment = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False) #删除的时候,执行逻辑删除,而非真正的删除,默认不删除
objects = BookInfoManager() #自定义一个管理器对象
class Meta:
db_table = "bookinfo" # 指定模型类对应的表名为bookinfo
如果仅仅是要建立与表的对应关系(模型类中的名字与鲜美 存表中的名字不一致),直接这样即可;如果我们是要用此法更改名字,需要我们再重新做一下迁移
视图(V)
视图的功能:接收请求,进行处理,与M和T进行交互,返回应答
返回html内容HttpResponse,一可能重定向redirect
视图函数的使用
使用
使用
-
定义视图函数
- request参数必须有。是一个HttpRequest对象,参数名可以变化,但不要更改
配置url:建立url与视图函数之间的对应关系
url配置过程
- 在项目的urls文件中包含具体应用的urls文件,在具体应用的urls文件中写url和视图的对应关系
- url配置项是定义在一个叫做urlpatterns的列表中,其中的每一个元素就是一个配置项,每一个配置项都调用url函数(现在已经是调用path函数了,不过url函数仍然可以)
匹配过程
- 去除域名和后面的参数,剩下/aindex,再把前面的/去掉,剩下aindex
- 拿aindex先到项目的url.py文件中进行从上到下的匹配,匹配成功之后执行后面对应的处理动作,就是把匹配成功的部分a字符去除,然后拿剩下的部分index到应用的urls.py文件中再进行从上到下的匹配。
- 如果匹配成功则调用相应的视图产生内容返回给客户端。如果匹配失败则产生404错误。
错误视图
当我们访问一个不存在的页面时,会看到一个如下的错误信息
这通常是为了调试方便,会将我们网站的地址配置显示出来,小实际应用中是不可取的,根据红色框内的提示信息,我们可以通过更改配置来显示标准的404页面
- 找到项目的settings文件
- 更改DEBUG=FALSE # 关闭debug模式
- 更改ALLOWED_HOSTS=['*'] #表示允许所有ip访问
- 如果要显示自定义的页面,则需要的templates目录下面自定义一个404.html文件。注意,这个文件不需要我们自己配置,django会自动调用,同时会向该文件传一个名为request_path的模板变量,表示用户请求的页面
404错误通常源于:
- url没有配置
- url配置错误
如果是自己的视图里面出错,在浏览器端会提示500错误,如果我们不想使用默认的,也可以自己在templates下面自定义一个名为500的html
通常,我们在开发过程中,要打开DEBUG模式
捕获url参数
进行url匹配时,把所需要的捕获的部分设置成一个正则表达式组,这样django框架就会自动把匹配成功后相应组的内容作为参数传递给视图函数。一旦进行了捕获,在视图函数中就必须声明。
- 位置参数,直接在正则表达式中分组即可,参数名可以随便指定
- 关键字参数:在位置参数的基础上给正则表达式组命名即可。
(?P<组名>正则表达式)
,关键字参数中,视图中参数名必须和正则表达式组名一致
视图函数中的request参数
request就是HttpRequest类型的对象,其中包含着浏览器请求的一些信息,就是把WSGI模型中的application函数中的env进行了一层包装
案例:模拟登录
先在settings文件中注释掉'django.middleware.csrf.CsrfViewMiddleware',
,否则会出现403错误。也可以from django.views.decorators.csrf import csrf_exempt,然后用csrf_exempt装饰对应的视图函数
应用urls配置文件:
from booktest import views
from django.urls import path
urlpatterns = [
path(r'index', views.index),
path(r'login', views.login),
path(r'login_check',views.login_check)
]
将应用urls包含到项目urls文件中的代码:略
views文件代码:
# -*- coding:utf-8 -*-
from django.shortcuts import render,redirect
# Create your views here.
from datetime import date
from django.http import HttpResponse, HttpResponseRedirect
def index(request):
return HttpResponse("hello world")
def login(request):
return render(request,'booktest/login.html',{})
def login_check(request):
# 1. 获取提交的用户名和密码
# 2. 进行登录的校验
# 3. 返回应答
"""
request对象有两个属性:他们的是QueryDict类型的对象,和字典很相似,可以通过键取出值
request.POST:保存的是post的提交参数
request.GET:保存的是get方式提交的参数
QueryDict的用法:
from django.http.request import QueryDict
q = QueryDict('a=1&b=2&c=3') #实例化了一个QueryDict对象,他里面存储了abc三个属性
q['a'] #就能够获得a的值1,还可以通过q.get('a")获得
# 同字典一样,如果是用中括号取,如果键不存在,会抛异常,如果是get方法取,键不存在会返回none,不会报错
# get方法也可以默认值
#QueryDict和字典的本质区别
#在字典中,一个键只能对应一个值,而QueryDict一个键可以对应多个值
#eg:
q1 = QueryDict('a=1&a=2&a=3&b=4')
q1['a'] #返回3
q1.get('a') #返回3
q1.getlist('a') #返回一个列表,其中元素为1、2、3
"""
username = request.POST.get('username') #post提交的数据中,键就是表单的name值
password = request.POST.get('password')
#实际情况下,用户名和密码应该是到数据库中去查找
#模拟:用户名为smart,密码为123
if username=="smart" and password == "123":
#正常情况下,我们登录成功后,会跳转到一个页面,这里我们跳转到首页
return redirect('/index')
else:
#用户和密码错误就还是跳转到登录页面
return redirect('/login')
login.html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<!--表单的两种提交方式:-->
<!--1. POST:提交的参数在请求头里面,如果数据比较重要,采用POST-->
<!--2.GET:提交的参数在url中-->
<form method="post" action="/login_check">
<!-- 表单中的action就指定我们提交的地址-->
<!-- method指定表单提交的方式-->
用户名:<input type="text", name="username"><br>
密码:<input type="password", name="password"><br>
<input type="submit", value="登录">
</form>
</body>
</html>
注意:此种方式中,我们采用的是全局刷新如果登录页面内容过多,产生的体验相当不好,此问题可以结合ajax采用局部刷新
ajax实现局部刷新的流程:
注意:后台返回的数据就在function的data里面
ps:css、js、images等静态文件,在django中,都要放在项目下的static文件夹中,然后到settings中进行配置,如下:
#在最后一行:
STATICFILES_DIRS = [os.path.join(BASE_DIR,'static')] #设置静态文件的保存目录
通常,每种静态文件都放在一个文件夹下面,eg:js就放在js文件夹下面,css、images类似
ajax请求
在项目文件夹下的static/js下,有jquery文件
ajax就是异步的javascript,其异步体现在,当发起了ajax请求之后,不等待回调函数的执行,而是继续往下执行代码
当然,也可以发起同步的ajax请求,只需添加:'async':false即可
注意:ajax的请求都是在后台
test_ajax.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax</title>
<script src="/static/js/jquery-3.4.1.js"></script>
<script>
$(function(){
$('#btnAjax').click(function(){ //当点击该按钮的时候,发起ajax请求
$.ajax({
'url':'/ajax_handle',
'type':'get',
// type不写的话默认就是get
'dataType':'json'
}).done(function(data){
//进行处理
alert(data.res)
})
})
})
</script>
</head>
<body>
<input type="button" value="ajax请求" id="btnAjax">
<!--一点击按钮的时候,就发起一个ajax的请求-->
</body>
</html>
应用下的urls文件
from booktest import views
from django.urls import path
urlpatterns = [
path(r'index', views.index),
# path(r'create', views.create), #新增一本图书
# path(r'areas',views.areas),
path(r'login', views.login),
path(r'login_check',views.login_check),
path(r'test_ajax',views.ajax_test), #显示ajax页面
path(r'ajax_handle',views.ajax_handle),
]
应用下的views.py
from django.shortcuts import render,redirect
def ajax_test(request):
return render(request,'booktest/test_ajax.html')
from django.http import HttpResponse, JsonResponse
def ajax_handle(request):
# 注意:是return 一个JsonResponse的对象
# 假设返回的是{'res':1}
return JsonResponse({'res':1})
效果图:
同时,如果我们用浏览器,查看“网络”,会发现它并没有请求整个页面,即:实现了局部刷新
在使用ajax实现局部刷新时,我们首先要分析出访问地址时需要携带的参数以及视图函数处理完成之后,所返回的json格式
HttpRequest对象讲解
属性
- 下面除非特别说明,属性都是只读的。
- path:一个字符串,表示请求的页面的完整路径,不包含域名和参数部分。
- method:一个字符串,表示请求使用的HTTP方法,常用值包括:'GET'、'POST'。
- 在浏览器中给出地址发出请求采用get方式,如超链接。
- 在浏览器中点击表单的提交按钮发起请求,如果表单的method设置为post则为post请求。
- encoding:一个字符串,表示提交的数据的编码方式。如果为None则表示使用浏览器的默认设置,一般为utf-8。这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值。
- GET:QueryDict类型对象,类似于字典,包含get请求方式的所有参数。
- POST:QueryDict类型对象,类似于字典,包含post请求方式的所有参数。
- FILES:一个类似于字典的对象,包含所有的上传文件。
- COOKIES:一个标准的Python字典,包含所有的cookie,键和值都为字符串。
- session:一个既可读又可写的类似于字典的对象,表示当前的会话,只有当Django 启用会话的支持时才可用,详细内容见"状态保持"。
状态保持
http协议是无状态的,下一次去访问一个页面时,并不知道上一次对这个页面做了什么
cookie
cookie是由服务器生成,保存在浏览器端的一小段文本信息
cookie的特点:
- 以键值对方式进行存储。
- 通过浏览器访问一个网站时,会将浏览器存储的跟网站相关的所有cookie信息发送给该网站的服务器。request.COOKIES
- cookie是基于域名安全的。www.baidu.com www.tudou.com
- cookie是有过期时间的,如果不指定,默认关闭浏览器之后cookie就会过期。
设置cookie:需要一个HttpRespose类的对象,或者是它的子类对象,通过它的set_cookie方法,我们可以设置cookie
浏览器发给服务器的cookie保存在request对象的COOKIES里面,它是一个标准的字典
HttpResponse有哪些子类?答:HttpResponseRedirect以及JsonResponse都是
代码示例:
from datetime import datetime,timedelta
from django.http import HttpResponse, HttpResponseRedirect
def set_cookie(request):
response = HttpResponse('set_cookie')
# 设置一个cookie信息
response.set_cookie('num',1, expires=datetime.now()+timedelta(days=14))
#第一个参数就是"键",第二个参数就是"值";后面是指定过期时间为14天
#指定过期时间也可以使用max_age,单位为s,但是可以直接写计算式
response.set_cookie('num2',2, expires=datetime.now()+timedelta(days=14)) #可以设置多个cookie
return response
# 当我们return一个response的时候,这个cookie会交给浏览器保存
def get_cookie(request):
num = request.COOKIES['num']
return HttpResponse(num)
除了以上代码外,只需要配置urls文件即可
session
session与cookie最大的区别是cookie保存在浏览器端而session保存在服务器端
对于敏感、重要的信息,建议要储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息。
session的特点
- session是以键值对进行存储的。
- session依赖于cookie。唯一的标识码保存在sessionid cookie中。
- session也是有过期时间,如果不指定,默认两周就会过期。
在用django创建的mysql数据库中,有一张django_session表,专门用来存储session
代码示例:
def get_cookie(request):
num = request.COOKIES['num']
return HttpResponse(num)
from django.http import HttpResponse
def set_session(request):
request.session['username'] = 'smart'
request.session['password'] = '123'
return HttpResponse('设置session')
def get_session(request):
username = request.session['username']
password = request.session['password']
return HttpResponse(username+":"+password)
除此之外,只需要设置urls即可
效果图:数据是经过base64编码的
session的其他方法
- 取值:request.session.get('key', defaultValue)
- 清楚所有session:request.session.clear(),在存储中删除值的部分,但是该条记录还在
- 删除整条数据:request.session.flush,删除后,该条记录都已经不存在
- 删除session中的指定键及值,在存储中只删除某个键及对应值:del request.session.['key']
- 设置会话的超时时间,如果没有指定过期时间,则默认是两个星期:request.session.set_expiry(value),是设置sessionid这个cookie的,整数,单位为s,即:删除后,浏览器端的sessionid将过期,就无法通过sessionid去服务器端获取其对应值
- 判断session中是否存在某key:request.session.has_key['key']
模板(T)
模板的功能
产生html页面,控制页面上展示的内容,模板文件不仅仅是一个html文件。
模板文件包含两个部分的内容:
- 静态内容:css、js、html
- 动态内容:用于动态去产生一些网页内容,通过模板语言来产生
模板文件的加载顺序
- 首先去配置的模板目录下面去找模板文件,由于该配置项是一个列表,所以我们也可以添加多个,查找的时候从前往后查找。
- 去INSTALLED_APPS下面的每个应用的templates去找模板文件,前提是应用中必须有templates文件夹。
模板语言
模板文件中的动态内容即由模板语言产生
模板变量
模板变量名是由数字,字母,下划线和点组成的,不能以下划线开头。
使用模板变量:{{模板变量名}},如果是在{%%}的代码区,可以直接使用
模板变量的解析顺序:例如:{{ book.btitle }}
- 首先把book当成一个字典,把btitle当成键名,进行取值book['btitle']
- 把book当成一个对象,把btitle当成属性,进行取值book.btitle
- 把book当成一个对象,把btitle当成对象的方法,进行取值book.btitle
例如:{{book.数字}}
- 首先把book当成一个字典,把0当成键名,进行取值book[0]
- 把book当成一个列表,把0当成下标,进行取值book[0]
如果解析失败,不会报错,而是用空字符串进行替换
使用模板变量时,.前面的可能是一个字典,可能是一个对象,还可能是一个列表。
模板标签
{% 代码段 %}
for循环:
{% for x in 列表 %}
# 列表不为空时执行
{% empty %}
# 列表为空时执行
{% endfor %}
可以通过{{ forloop.counter }}得到for循环遍历到了第几次。
{% if 条件 %}
{% elif 条件 %}
{% else %}
{% endif %}
关系比较操作符:> < >= <= == !=
注意:进行比较操作时,比较操作符两边必须有空格。
逻辑运算:not and or
过滤器
过滤器用于对模板变量进行操作。
过滤器的本质是一个函数,可能需要参数
使用格式:模板变量|过滤器:参数
常用过滤器:
- date:改变日期的显示格式。
- length:求长度。字符串,列表.
- default:如果前面的计算结果为False,则使用给定的默认值,否则,使用原来的
代码示例:
{% for book in books %}
{% if book.id <= 2 %}
<li class = 'red'>{{ book.btitle }}---{{ book.bpub_date | date:'Y年-m月-d日' }}</li>
<!-- Ymd是date函数的参数,Y表示以4位显示年,m表示以两位数字显示月,d... -->
{% endif %}
{% endfor %}
个人对过滤器的理解,用过滤器过滤后的返回值替换原来的变量
更多相关内容见官网
自定义过滤器
- 在应用下新建一个包,名为:templatetags,一个字母都不能错
- 然后在其里面新建一个py文件,文件名随意,我们的过滤器就写在这里面
过滤器的本质就是python的函数
自定义过滤器的参数至少一个(前面的模板变量),最多两个
代码示例:
from django.template import Library
register = Library() #创建一个Library对象
@register.filter #用该对象中的filter装饰自己定义的函数,使其成为一个过滤器
def mod(num): #在使用过滤器的时候,前面的部分就会作为第一个参数自动传递给过滤器,如果是需要多个参数,则使用方法如下:表达式或变量 | 过滤器:第二个参数(过滤器最多两个参数)
return num % 2 == 0 #过滤掉奇数
- 在要使用该过滤器的模板文件中的开始位置,对该过滤器进行加载:{% load 自定义的过滤器的名字 %}
模板注释
注意:模板注释在浏览器上查看网页源代码的时候是无法看到的,但是html注释的内容是可以看到的
单行注释:{# 注释内容 #}
-
多行注释:
{% comment %}
注释内容
{% endcomment %}模板继承
模板继承也是为了重用html页面内容
我们把每个页面都一样的内容写在父模板文件中
代码示例:
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Base{% endblock title %}</title>
</head>
<body>
<h1>导航栏</h1>
{#页面不同的地方,需要在父模板中预留位置,这个位置我们称为预留快#}
{#子模板就可以重写父模板中预留块的内容#}
{% block block_name %}
父模板的预留块中可以写内容,也可以不写,这里我们直接令预留块的名字为block_name
{% endblock block_name %}
<h1>版权信息</h1>
</body>
</html>
child.html
{% extends 'booktest/base.html' %}
{#注意:路径是相对于templates的#}
{#继承了之后,里面就不能再写其他内容#}
{#重写父模板中的预留块#}
{#如果不重写预留块中的内容,就直接按照父模板中的内容显示#}
{% block block_name %}
{# 在子模板中,可以使用{{ block.super }}获取父模板中预留块的内容#}
{{ block.super }}<br>
这是子模板中的内容
{% endblock block_name %}
html转义
在视图函数中对模板传参时(eg:render中),会对以下字符自动转义
小于号< 转换为 <
大于号> 转换为 >
单引号' 转换为 '
双引号" 转换为 "
与符号& 转换为 &
如何关闭转义:
法一:使用safe过滤器(不需要第二个参数)
法二:使用autoescape标签,off表示关闭转义,on表示打开转义。用法如下:
{% autoescape off %}
模板语言代码
{% endautoescape %}
safe和autoescape的区别:safe是对一个变量起作用,而autoescape是对其内部代码段中的所有变量都关闭转义
模板硬编码中的字符串默认不会经过转义
何为模板硬编码中的字符串?即,写“死”了的部分,示例如下:
{{ test | default:'<h1>hello</h1>' }}
其中,默认的参数就不会被转义
要对硬编码进行转义,必须手动执行,eg:将<
手动写成<
scrf攻击
即:跨站请求的伪造
登录装饰器
情景示例:
在进行网站开发的时候,有些页面是用户登录后才能访问的,假如用户访问了这个地址,需要进行登录的判断,如果用户登录的话,可以进行后续的操作,如果没有登录,跳转到登录页。
我们就可以把这个登录验证加在对应页面的视图函数中
但是如果页面过多,每个都要自己写,就很麻烦,我们可以将其放在一个装饰器里面
csrf伪造
上图说明:
当我们访问正常网站时,一切正常,在我们的电脑上保存有sessionid,假设我们更改了在该网站的密码(假设之后没有退出,即浏览器一直保存有该sessionid);而当我们访问另一个网站时,假设我们点击了其上面的某按钮或图片时,该网站向第一个网站发送了一个请求,该网站就能够伪造我们的身份更改我们在第一个网站的密码
csrf伪造成功的两个关键点:
- 登录正常网站之后,你的浏览器保存有sessionid,且你没有退出
- 你不小心访问了另外一个网站,并且你点击了页面上的按钮
这即所谓的跨站请求伪造
Django默认启用了csfr防护,即settings中的MIDDLEWARE_CLASSES中的django.middleware.csfr.CsrfViewMiddleware'项。该防护只针对post提交。所以重要的数据用post提交
但是我们会发现打开之后,当我们直接访问/change_pwd后,访问/change_pwd_action也会失败(403)。此时,就需要我们在post提交数据时加上{% csrf_token %}标签,eg,在表单提交post数据时,在表单内加上这个标签:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form method="post" action="/login_check">
{% csrf_token %}
用户名:<input type="text", name="username" value={{username}}><br>
密码:<input type="password", name="password" value={{password}}><br>
<input type="checkbox" name="remember">记住用户名<br>
<input type="submit", value="登录">
</form>
</body>
</html>
防护的原理
- 渲染模板文件时我们在模板中写的{% csrf_token %}标签会被替换成一个名字叫做csrfmiddlewaretoken的隐藏域。
- 服务器交给浏览器保存一个名字为csrftoken的cookie信息。
- 提交表单时,两个值都会发给服务器,服务器进行比对,如果一样,则csrf验证通过,否则失败。
- 这种情况下,当我们上方所说的另一个网站想访问第一个时,由于它没有该csrfmiddlewaretoken的隐藏域(因为“另一个网站”是无法查看我们电脑本地的网页源代码的,也就拿不到该隐藏域),就会请求失败,报403
验证码
在用户注册、登录页面,为了防止暴力请求(不断尝试密码),可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。(只是因为程序识别验证码中的图片的难度比较大,所以降低了暴力请求的概率,而不是绝对防止)
可以用Pillow这个包产生验证码
from PIL import Image, ImageDraw, ImageFont
from django.utils.six import BytesIO
...
def verify_code(request):
#引入随机函数模块
import random
#定义变量,用于画面的背景色、宽、高
bgcolor = (random.randrange(20, 100), random.randrange(
20, 100), 255) #用rgb方式定义颜色
width = 100
height = 25
#创建画面对象,并设置宽高
im = Image.new('RGB', (width, height), bgcolor)
#创建画笔对象
draw = ImageDraw.Draw(im)
#调用画笔的point()函数绘制噪点
for i in range(0, 100): #循环遍历100次,在画面上添加噪点
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill) #指定在哪个点画,画的颜色是什么
#定义验证码的备选值
str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
#随机选取4个值作为验证码
rand_str = ''
for i in range(0, 4):
rand_str += str1[random.randrange(0, len(str1))]
#构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
font = ImageFont.truetype('FreeMono.ttf', 23)
#构造字体颜色
fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
#绘制4个字
draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
#释放画笔
del draw
#存入session,用于做进一步验证(对比用户输入的验证码对不对)
request.session['verifycode'] = rand_str
#内存文件操作
buf = BytesIO()
#将图片(im)保存在内存中,文件类型为png
im.save(buf, 'png')
#将内存中的图片数据返回给客户端,MIME类型为图片png
return HttpResponse(buf.getvalue(), 'image/png')
效果图:
注意:如果报
OSError: cannot open resource
错误,通常是view中使用的字体不存在,如果是windows,直接采用Windows/Fonts下面存在的字体即可
校验用户输入的验证码时,从session中取出正确值,再和用户的输入值相对比即可
url反向解析
比如,当我们使用超链接在多个页面间跳转的时候,需要指呆跳转到何处,如果我们直接写“死”了,当页面的地址发生变化时,我们就必须手动更改,非常麻烦。
这种情况下,我们没有必要把它写“死”,可以在链接处动态获取。
用法:
-
在项目的urls文件中进行include时,指定namespace
- 在应用的urls文件中配置url时,指定name
- 在模板文件中使用该地址时,格式如下:
- {% url 'namespace名字:name' %} 例如{% url 'booktest:fan2'%}
- 带位置参数(通过url提交的参数):
{% url 'namespace名字:name' 参数1 参数2 %} 例如{% url 'booktest:fan2' 1%},这里的参数在通过urls查找视图函数的时候,会自动拼接成(/xxx)的形式,然后传递给对应的视图函数 - 带关键字参数(通过url提交的参数,即,给组指定了名字):
{% url 'namespace名字:name' 关键字参数 %} 例如{% url 'booktest:fan2' id=1 %}
eg:
<a href="{{ url 'booktest:index'}}">首页</a>
<!-- 其中,booktest是在项目urls文件中include时指定的namespace的名字,index是在应用url中指定的name -->
在重定向的时候使用反向解析
from django.core.urlresolvers import reverse
重定向示例:
from django.core.urlresolvers import reverse
def test_redirect(request):
url = reverse("booktest:index")
# namespace:name
# 如果有位置参数:url = reverse('booktest:show_args",args=(1,2)) #将参数以元组的形式传递过去
# 关键字参数:url = reverse('booktest:show_kwargs',kwargs={c':3,'d':4}),则在调用对应的视图函数的时候,会以关键字的形式进行传参,传递给对应的视图函数
return redirect(url) #导入redirect的语句省略
其他技术
静态文件
使用
在网页使用的css文件,js文件和图片叫做静态文件
- 在项目中新建文件夹static,通常,每一个类别的文件都新建一个文件夹来保存
'/abc/images/xxx'
,而我们的所有静态文件还是在项目的static文件夹下存储
<img src = '/static/images/mm.jpg'
如何在模板文件(html)中动态获取STATIC_URL?
示例:
<!DOCTYPE html>
{% load staticfiles %} }
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<img src="{% static 'images/mm.jpg' %}">
{# 完成staticurl和路径的拼接#}
{# static就对应这staticurl的配置#}
</body>
</html>
中间件
中间件函数是django框架给我们预留的函数接口,让我们可以干预请求和应答的过程。这个函数的名字和参数都已经固定好了,必须按照规定的格式写
eg:阻止某ip访问自己的网站
获取浏览器端的ip地址
使用request对象的META属性:request.META.['REMOTE_ADDR']
阻止某ip访问本网站的所有页面的方法一
利用装饰器,当发现请求的ip是某个我们想阻止的ip时,进行另外的处理
阻止某ip访问本网站的所有页面的方法二
在应用下新建一个py文件,名字可以变,但是我们通常叫做middleware
-
在里面定义process_view函数,注意,名字是固定的,在视图函数调用之前,会先调用此函数。其参数为request, view_func,*views_args, **view_kwargs)。这个函数需要放到一个类里面
- request:和视图的request一样
- view_func:要调用的视图函数
- view_args、*view_kwargs:视图函数的位置参数和关键字参数
代码示例(middleware.py):
from django.http import HttpResponse
class BolckedIPSMiddleware(object):
# 中间件类
EXCLUDE_IPS = ['192.168.14.74']
def process_view(self,request, view_func, *view_args, **view_kwargs):
# 中间件函数
user_ip = request.META['REMOTE_ADDR']
if user_ip in BolckedIPSMiddleware.EXCLUDE_IPS:
return HttpResponse('forbidden')
-
在项目的settings下 的MIDDLEWARE中对该我们自己定义的中间件类进行注册,eg:
详解
中间件类的名字可以自己取(常以MIDDLECLASS)结尾,但是里面的中间件函数名称是固定的
常用中间件预留函数: 名字和参数都是固定的
-
__init__
:服务器重启之后,响应第一个请求的时候调用。 - process_request:是在产生request对象,进行url匹配之前调用。必须有一个request参数,即图中的request对象
- process_view:是url匹配之后,调用视图函数之前。参数为request, view_func, *view_args, **view_kwargs。分别为request对象、接下来要调用的视图函数的 名字 、视图函数所需要用到的位置参数和关键字参数
- process_response:视图函数调用之后,内容返回给浏览器之前。必须有request和response参数,分别为request对象是视图函数的返回值
- process_exception:视图函数出现异常,会调用这个函数。参数为request和exception。分别为request对象和异常对象
- 如果注册的多个中间件类中包含process_exception函数的时候,调用的顺序跟注册的顺序是相反的。
在类中,只定义需要用到的即可,不必全部定义
中间件函数的执行流程: 其中的红色箭头部分表示人为的干预,让其提前返回
以下部分待改
上传图片
配置上传文件保存目录
需要先创建一个目录,配置说明上传文件就保存在该目录下方
示例:
新建目录:(目录的位置和名字不固定)
在项目settings中配置目录:
通过后台管理页面上传
以图片为例:
- 设计模型类
class PicClass(models.Model):
gpic = models.ImageField(upload_to = "booktest") #upload_to指定上传目录。注意:是上传到我们配置了的目录下的哪个目录(我们这里配置的是static/media目录)
-
迁移。
迁移的注意事项:1. 每有一次迁移成功,就会在diango_migrations表下有一条记录,在后面继续迁移时,如果发现该表中已经有该记录,则不再迁移;2. 如果报xxx表已经存在,就到migrations目录下的对应文件中删除已经存在的表即可
在应用下的admin里面注册模型类
假设我们通过浏览器上传了图片,就会发现该表中多了一跳纪律,它里面存储的是相对路径
用户自定义上传图片
- 定义用户上传图片的页面并显示,就是自己定义一个表单。只有定义 了method和enctype,才能上传文件
在模板文件show_upload.html中包含如下代码:
<form method='post' actioon='/upload_handle' enctype='multipart/form-data'>
<!-- 在/upload_handle这个地址对应的视图函数里面保存文件 -->
{% csrf_token %}
<input type='file' name='pic'><br>
<input type='submit' value='上传'>
</form>
视图函数:
from django.conf import settings #获取配置的上传目录
def show_upload(request):
""""显示上传图片的页面"""
return render(request,'booktest/show_upload.html')
def upload_handle(request):
"""上传图片处理"""
url的配置以及部分模块的导入省略
admin站点
控制管理页展示
类ModelAdmin可以控制模型在Admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页的展示方式。
在应用下的admin.py中,注册模型类前定义管理类。eg:AreaAdmin
管理类有两种使用方式:
- 注册参数
- 装饰类
注册参数:在应用下的admin.py文件中,注册模型类: