django
Django
HTTP协议
超文本传输协议(英文:Hyper Text Transfer Protocol,HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信基础.HTTP有很多应用,最著名的是用于Web浏览器服务器之间的双工通信.
HTTP是一个客户端(用户)和服务器端(网站)请求和应答的标准(TCP)..
一次请求一次响应就断开连接,无状态,短链接,格式包括头和体,请求头和请求体,响应头和响应体,请求头与请求头之间用一个\r\n,请求头与请求体用\r\n\r\n
HTTP请求/响应的步骤:
1. 客户端连接到Web服务器
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接.
2. 发送HTTP请求
通过TCP协议套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行,请求头部,空行和请求数据4部分组成.
3. 服务器接收请求并返回HTTP响应
Web服务器解析请求,定位请求资源.服务器将资源副本写到TCP套接字,由客户端读取.一个响应由状态行,响应头部,空行和响应数据4部分组成
4. 释放连接TCP连接
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
5. 客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
在浏览器地址栏键入URL,按下回车之后经历的流程:
1. 浏览器向DNS服务器请求解析该URL中的域名所对应的的IP地址;
2. 解析出IP地址后,根据IP地址和默认端口80,和服务器建立TCP连接
3. 浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器.
4. 服务器对浏览器请求作出响应,并把对应的HTML文本发送给浏览器
5. 释放TCP连接
6. 浏览器将该HTML文本并显示内容
HTTP请求方法
1. GET
向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
2. HEAD
与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
3. POST
向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
4. PUT
向指定资源位置上传其最新内容。
5. DELETE
请求服务器删除Request-URI所标识的资源。
6. TRACE
回显服务器收到的请求,主要用于测试或诊断。
7. OPTIONS
这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作
8. CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
HTTP状态码
所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此由空格分隔。
状态代码的第一个数字代表当前响应的类型:
+ 1xx消息——请求已被服务器接收,继续处理
+ 2xx成功——请求已成功被服务器接收、理解、并接受 ***
+ 3xx重定向——需要后续操作才能完成这一请求
+ 4xx请求错误——请求含有词法错误或者无法被执行
404: NOT FIND(服务器上没有) 403 服务器上有,但是没有权限 ***
+ 5xx服务器错误——服务器在处理某个正确请求时发生错误
URL
超文本传输协议(HTTP)的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中:
+ 传送协议。
+ 层级URL标记符号(为[//],固定不变)
+ 访问资源需要的凭证信息(可省略)
+ 服务器 -- (通常为域名,有时为IP地址)
+ 端口号 -- (以数字方式表示,若为HTTP的默认值":80",https 端口默认":443"可省略)
+ 路径 -- (以“/”字符区别路径中的每一个目录名称)
+ 查询 -- (GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
+ 片段 -- 以“#”字符为起点
`https://www.sogou.com:443/web?query=海水两边颜色不同&_asf=www.sogou.com&_ast=&w=01015002&p=40040108&ie=utf8&from=index-nologin&s_from=index&oq=&ri=0&sourceid=sugg&suguuid=&sut=0&sst0=1566374533005&lkt=0%2C0%2C0&sugsuv=0003ECF37B7010FB5D174856999B7817&sugtime=1566374533005`
+ http,是协议;
+ www.sogou.com是域名,服务器
+ 443是服务器上的网络端口号
+ /web 是路径
+ ?query=海水两边颜色不同 : 问号之后是查询参数
web框架
web框架的本质 : socket服务端
web框架的功能
1. 使用socket收发消息
2. 根据不同的路径返回不同的内容
3. 返回动态的数据(字符串的替换 -- 模板的渲染(使用jinja2) )
分类:
+ Django实现 2 3 功能
+ flask 实现 2 功能
+ tornado 实现 1 2 3功能
服务器程序和应用程序
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
**WSGI**(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。( **跨站请求伪造** )
常用的WSGI服务器有uWSGI、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
Django处理请求的一个流程
1. 在浏览器上输入地址,回车,发送一个get请求
2. wsgi模块接受请求,把请求相关的内容封装成request对象
3. 根据url地址,找到对应的函数
4. 执行函数,得到返回值.wsgi模块将httpresponse对象按照http响应格式发送给浏览器
Django的所有命令
下载安装
-
命令行
pip3 install django==1.11.23 -i https://pypi.tuna.tsinghua.edu.cn/simple -- (源地址)
-
pycharm
创建项目
-
命令行
django-admin startproject 项目名称
-
pycharm
flie _ new_project _ django _ 项目路径 选解释器
启动项目
首先切换到项目的根目录
-
命令行
- python manage.py runserver # 127.0.0.1:8000
- python manage.py runserver 80 # 127.0.0.1:80 修改端口号
- python manage.py runserver 0.0.0.0:80 # 0.0.0.0:80 修改IP和端口号
-
pycharm
点绿三角 dj --> 不要右键运行文件
创建APP
python manage.py startapp app名称
数据库迁移
python manage.py makemigrations # 记录索引APP下的models的变更记录
python manage.py migrate # 同步迁移记录
配置
url.py 文件
urls.py
写 url
与函数的对应关系,写函数
from django.shortcuts import HttpResponse,render
def func(request):
return HttpResponse('返回的字符串') # 返回字符串
def home(request):
return render(request,'home.html') # 返回html页面
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^func/', func), # 路径和函数的对应关系
url(r'^home/', home),
]
静态文件的配置
STATIC_URL = '/static/' # 别名
STATICFILES_DIRS = [ # 按照列表的顺序进行查找
os.path.join(BASE_DIR, 'x1'),
os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'x2')
]
登录的实例
form表单
- action -- 提交的地址 , method -- 请求方法 ( 默认是get , 设置post )
- input 需要有name属性 , required表示必须填写(可以去掉)
- submit 提交的按钮或者input
- form表单 novalidate不需要校验
目前提交post请求,要注释一个csrf中间件 ( setting.py文件)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', # csrf中间件
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
request 和 response( 初识 )
request
# 发送get请求的方式:
- form表单,不指定method
- 在地址栏中直接输入地址,回车
- a标签
request.GET # url上携带的参数,获取到的数据是字典形式 ?k1=v1&k2=v2
request.GET.get('k1','xxx') # 获取某一个参数对应的值,没有就返回xxx
request.method # 请求的方法(一般用来做判断) -- GET POST PUT
#发送post请求:
form表单 : method = 'post'
request.POST # 获取所有form表单提交的数据
request.POST.get('k1','xxx') # 获取提交数据的某一个值,没有就返回xxx
response
HttpResponse('字符串') # 返回字符串
render(request,'模板文件名',{'k1':v1,'k2':v2} # 返回一个完整的页面
redirect('/URL路径/') # 重定向,相当于响应头 Location:url
app
新建app
python36 manage.py startapp app名字
注册app
在setting中
INSTALLED_APPS = [
# 两种方法
'app01',
'app01.apps.App01Config', # 推荐写法
]
目录:
admin.py django提供一个 admin工具
apps.py app的信息
models.py 模型 model 跟数据库有关
views.py 写函数
HTTPRequest对象就是咱们的视图函数的参数request
# print(request) #<WSGIRequest: GET '/home/'>
# # print(dir(request))
#
# print(request.path) #/home/ 纯路径
# print(request.path_info) #/home/ 纯路径
# print(request.get_full_path()) #/home/?a=1&b=2 全路径(不包含ip地址和端口)
# print(request.META) #请求头相关数据,是一个字典
# print(request.method) #GET
# print(request.GET)
# print(request.POST)
# print(request.body) 能够拿到请求数据部分的数据(post,get没有)
HTTPResponse对象
HTTPResponse('字符串')
render(request,'xx.html')
redirect 重定向#用法 redirect(路径) 示例:redirect('/index/')
FBV和CBV 视图(视图函数和视图类)
类视图 CBV
views.py
from django.views import View
class LoginView(View):
# def dispatch(self, request, *args, **kwargs):
# print('xx请求来啦!!!!')
# ret = super().dispatch(request, *args, **kwargs)
# print('请求处理的逻辑已经结束啦!!!')
# return ret
def get(self,request): #处理get请求直接定义get方法,不需要自己判断请求方法了,源码中用dispatch方法中使用了反射来处理的
print('小小小小')
return render(request,'login.html')
def post(self,request):
print(request.POST)
return HttpResponse('登录成功')
urls.py路由写法
url(r'^login/', views.LoginView.as_view()),
视图加装饰器
视图函数FBV
def wrapper(func):
def inner(*args, **kwargs):
print(11111)
ret = func(*args, **kwargs)
print(22222)
return ret
return inner
@wrapper
def index(request):
print('xxxxx')
return HttpResponse('indexxxxxxxxx')
视图类CBV
from django.utils.decorator import method_decorator
@method_decorator(wrapper,name='get') # 方式3
class LoginView(View):
@method_decorator(wrapper) #方式2
def dispatch(self, request, *args, **kwargs):
print('xx请求来啦!!!!')
ret = super().dispatch(request, *args, **kwargs)
print('请求处理的逻辑已经结束啦!!!')
return ret
@method_decorator(wrapper) #方式1
def get(self,request):
print('小小小小')
return render(request,'login.html')
def post(self,request):
print(request.POST)
return HttpResponse('登录成功')
模板渲染
{{ 变量 }} {% 逻辑 %} -- 标签
万能的点
<h1>91李业网</h1>
<h2>{{ name }}</h2>
<h2>{{ d1.items }}</h2>
<h2>我是"{{ l1.1 }}"</h2>
<h2>{{ num }}</h2>
<h2>{{ obj.p }}</h2> #如果调用的方法需要传参,sorry用不了
过滤器(内置)
参考博客:https://www.cnblogs.com/clschao/articles/10414811.html
1.default
如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。
{{ value|default:"nothing"}}
如果value没有传值或者值为空的话就显示nothing
2.length
返回值的长度,作用于字符串和列表。
{{ value|length }}
返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4.
3.filesizeformat
将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:
{{ value|filesizeformat }}
如果 value 是 123456789,输出将会是 117.7 MB。
4.slice
切片,如果 value="hello world",还有其他可切片的数据类型
{{value|slice:"2:-1"}}
5.date
格式化,如果 value=datetime.datetime.now()
{{ value|date:"Y-m-d H:i:s"}}
关于时间日期的可用的参数(除了Y,m,d等等)还有很多,有兴趣的可以去查查看看。
6.safe
Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全,django担心这是用户添加的数据,比如如果有人给你评论的时候写了一段js代码,这个评论一提交,js代码就执行啦,这样你是不是可以搞一些坏事儿了,写个弹窗的死循环,那浏览器还能用吗,是不是会一直弹窗啊,这叫做xss攻击,所以浏览器不让你这么搞,给你转义了。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
我们去network那个地方看看,浏览器看到的都是渲染之后的结果,通过network的response的那个部分可以看到,这个a标签全部是特殊符号包裹起来的,并不是一个标签,这都是django搞得事情。
比如:
value = "<a href='#'>点我</a>" 和 value="<script>alert('123')</script>"
{{ value|safe}}
很多网站,都会对你提交的内容进行过滤,一些敏感词汇、特殊字符、标签、黄赌毒词汇等等,你一提交内容,人家就会检测你提交的内容,如果包含这些词汇,就不让你提交,其实这也是解决xss攻击的根本途径,例如博客园:
7.truncatechars
如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。
参数:截断的字符数
{{ value|truncatechars:9}} #注意:最后那三个省略号也是9个字符里面的,也就是这个9截断出来的是6个字符+3个省略号,有人会说,怎么展开啊,配合前端的点击事件就行啦
8.truncatewords
在一定数量的字后截断字符串,是截多少个单词。
例如:‘hello girl hi baby yue ma’,
{{ value|truncatewords:3}} #上面例子得到的结果是 'hello girl h1...'
9.cut
移除value中所有的与给出的变量相同的字符串
{{ value|cut:' ' }}
如果value为'i love you',那么将输出'iloveyou'.
11.join
使用字符串连接列表,{{ list|join:', ' }},就像Python的str.join(list)
12.timesince(了解)
将日期格式设为自该日期起的时间(例如,“4天,6小时”)。
采用一个可选参数,它是一个包含用作比较点的日期的变量(不带参数,比较点为现在)。 例如,如果blog_date是表示2006年6月1日午夜的日期实例,并且comment_date是2006年6月1日08:00的日期实例,则以下将返回“8小时”:
{{ blog_date|timesince:comment_date }}
分钟是所使用的最小单位,对于相对于比较点的未来的任何日期,将返回“0分钟”。
13.timeuntil(了解)
似于timesince,除了它测量从现在开始直到给定日期或日期时间的时间。 例如,如果今天是2006年6月1日,而conference_date是保留2006年6月29日的日期实例,则{{ conference_date | timeuntil }}将返回“4周”。
使用可选参数,它是一个包含用作比较点的日期(而不是现在)的变量。 如果from_date包含2006年6月22日,则以下内容将返回“1周”:
{{ conference_date|timeuntil:from_date }}
标签
for循环标签
循环列表等
{% for person in person_list %}
<p>{{ person.name }}</p> <!--凡是变量都要用两个大括号括起来-->
{% endfor %}
循环字典
{% for key,val in dic.items %}
<p>{{ key }}:{{ val }}</p>
{% endfor %}
empty
{% for person in person_list %}
<p>{{ person.name }}</p> <!--凡是变量都要用两个大括号括起来-->
{% empty %}
<p>没有找到东西!</p>
{% endfor %}
forloop.counter 当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等
示例:
{% for i in d2 %}
{% for k,v in d1.items %}
<li>{{ forloop.counter }}-- {{ forloop.parentloop.counter }} === {{ k }} -- {{ v }}</li>
{% endfor %}
{% endfor %}
if判断标签
{% if num > 100 or num < 0 %}
<p>无效</p> <!--不满足条件,不会生成这个标签-->
{% elif num > 80 and num < 100 %}
<p>优秀</p>
{% else %} <!--也是在if标签结构里面的-->
<p>凑活吧</p>
{% endif %}
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。
with
方法1
{% with total=business.employees.count %} #注意等号两边不能有空格
{{ total }} <!--只能在with语句体内用-->
{% endwith %}
方法2
{% with business.employees.count as total %}
{{ total }}
{% endwith %}
自定义过滤器(@register.filter )
无参数的自定义过滤器
1.先在app文件夹下建立templatetags的文件夹
2.在改文件夹下建立任意名称的py文件,例如:myfilter.py
from django import template 导入该文件
from django.shortcuts import render
register = template.Library() 注册器
@register.filter 装饰器,写了这个就会把下面的函数定义成一个自定义过滤器
def addstr(v1): 要写参数
return v1+'oo' 对参数的内容进行加工
3.在urls写路由对应关系
4.在app文件夹下的view文件下写对应函数
def son(request):
return render(request,'son.html',{'name':'八戒'})
要把需要加工的内容,传递给需要展示出来的HTML文件,son.html
5.(1)在对应的HTML文件里先导入自定义过滤器的py文件:{% load myfilter %}
(2){{ name|addstr }},这里的name是view文件下的son函数return传过来的内容
addstr是myfilter文件里的addrstr的函数返回值 return v1+'oo' 就是在name的基础上加了oo,返回给HTML
有参数的自定义过滤器
1.先在app文件夹下建立templatetags的文件夹
2.在改文件夹下建立任意名称的py文件,例如:myfilter.py
from django import template 导入该文件
from django.shortcuts import render
register = template.Library() 注册器
@register.filter 装饰器,写了这个就会把下面的函数定义成一个自定义过滤器
def addstr(v1,v2): 要写参数,最多两个
return v1+v2 对参数的内容进行加工
3.在urls写路由对应关系
4.在app文件夹下的view文件下写对应函数
def son(request):
return render(request,'son.html',{'name':'八戒'})
要把需要加工的内容,传递给需要展示出来的HTML文件,son.html
5.(1)在对应的HTML文件里先导入自定义过滤器的py文件:{% load myfilter %}
(2){{ name|addstr:'oo' }},这里的name是view文件下的son函数return传过来的内容,oo为第二个参数
addstr是myfilter文件里的addrstr的函数返回值 return v1+v2 ,v1是name对应的内容,v2是第二个参数oo,
addstr对两个参数进行加工,即:八戒oo,返回给HTML
自定义标签( @register.simple_tag)
1.在myfilter中写自定义标签的函数
from django import template
from django.shortcuts import render
register = template.Library()
@register.simple_tag 自定义标签
def mytag(n1,n2): 参数没有限制
return n1+n2 把参数进行进行加工
2.先导入这个文件 -->{% load myfilter %}
使用这个自定义标签 {% mytag name '真好' %}
mytag是自定义标签的函数
name是view函数中的return返回给HTML的值
'真好'是另一个参数,并对原来数据进行加工
return n1+n2 加工
返回html片段的标签(@inclusion_tag )
在不加url的情况下,想用son.html文件,但是想把result.html作为一个组件引入到son.html
过程:
1.建立路由关系
2.views函数
def res(request):
return render(request,'son.html')
3.首先在son.html使用了 inclusion_tag :{% res 's1' 's2' %},会先找到res函数,执行这个函数,得到一个返回值
4.这个返回值不会交给son.html的res,而是先交给@register.inclusion_tag('result.html')的result.html,
5.在result.html进行渲染,渲染完成之后会生产一个标签,才会作为一个组件生成到son.html中,
6.所以在son.html有了result.html东西.
静态文件配置
1 项目目录下创建一个文件夹,例如名为jingtaiwenjianjia,将所有静态文件放到这个文件夹中
2 settings配置文件中进行下面的配置
# 静态文件相关配置
STATIC_URL = '/abc/' #静态文件路径别名
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'jingtaiwenjianjia'),
]
3 引入<link rel="stylesheet" href="/abc/css/index.css">
url别名和反向解析
写法
url(r'^index2/', views.index,name='index'),
反向解析
后端: from django.urls import reverse
reverse('别名') 例如:reverse('index') -- /index2/
html: {% url '别名' %} -- 例如:{% url 'index' %} -- /index2/
url命名空间
路由分发 include
1 在每个app下创建urls.py文件,写上自己app的路径
2 在项目目录下的urls.py文件中做一下路径分发,看下面内容
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls')),#app01/home/
url(r'^app02/', include('app02.urls')),
]
命名空间namespace
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls',namespace='app01')),#app01/home/
url(r'^app02/', include('app02.urls',namespace='app02')),
]
新建app01和app02
在app01新建urls和app02新建urls
在app01的views写对应函数
在app02的views写对应函数
使用:
后端:reverse('命名空间名称:别名') -- reverse('app01:home')
hmtl:{% url '命名空间名称:别名' %} -- {% url 'app01:home' %}
orm单表操作
orm简介
对象关系映射(Object Relational Mapping,简称ORM)它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。
ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。
ORM在业务逻辑层和数据库层之间充当了桥梁的作用
使用mysql
数据库的流程
-
创建一个
mysql
数据库注意 : setting.py文件中的 TEMPLATES 的 DIR 路径
-
在settings中配置数据库
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 引擎 'NAME': 'day54', # 数据库名称 'HOST': '127.0.0.1', # IP 'PORT': 3306, # 端口号 'USER': 'root', # 用户名 'PASSWORD': '123' # 密码 } } 会在控制台上打印原生sql语句 LOGGING = { 'version':1, 'disable_existing_loggers':False, 'handlers':{ 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers':{ 'django.db.backends':{ 'handlers':['console'], 'propagate':True, 'level':'DEBUG', } } }
-
使用
pymysql
模块连接数据库# 写在与setting同级目录下的init.py中 import pymysql pymysql.install_as_MySQLdb()
-
写对应关系,在
app
下的models.py
中写类class User(models.Model): username = models.CharField(max_length=32) # varchar类型(长度32) password = models.CharField(max_length=32) # varchar类型(长度32) # 外键的创建 pub = models.ForgienKey('Publisher',on_delete=models.CASCADE) # on_delete 2.0版本以上必填
-
执行数据库迁移的命令
python36 manage.py makemigrations # 记录下models.py的变更,创建迁移文件 python36 manage.py migrate # 将变更记录同步到数据库中,迁移
django操作原生sql语句
ret = models.Book.objects.raw('select * from app where id = 2')
print(ret)
输出的是queryset对象
可以for循环取值
from django.db import connection
cursor=connection.cursor()
# 插入操作
cursor.execute("insert into hello_author(name) values('钱钟书')")
# 更新操作
cursor.execute("update hello_author set name='abc' where name='bcd'")
# 删除操作
cursor.execute("delete from hello_author where name='abc'")
# 查询操作
cursor.execute("select * from hello_author")
raw=cursor.fetchone() # 返回结果行游标直读向前,读取一条
cursor.fetchall() # 读取所有
orm对数据库的增删改查
1.先要通过mysql的sql语句在终端中创建库
创建一个表 = 在app下的models写个类
class UserInfo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
bday = models.DateField()
checked = models.BooleanField()
2.在manage.py下的Terminal写数据库同步指令(方法一)
python manage.py makemigrations
python manage.py migrate
在pycharm中的Tools下的Run manage Taskd点击,在下面写makemigrations和migrate两条指令(方法二)
*********增**********
方式1:
new_obj = models.UserInfo(
id=2,
name='八戒',
bday='2019-09-27',
checked=1,
)
new_obj.save() # 翻译成sql语句,然后调用pymysql,发送给服务端
方式2:
# ret 是创建的新的记录的model对象(重点)
print(ret) #UserInfo object 莎莎
print(ret.name) #UserInfo object
print(ret.bday) #UserInfo object
批量增加:
obj_list = []
for i in range(10):
obj = models.Book(
title='xx'
)
obj_list.append(obj)
models.Book.objects.bulk_create(obj_list)
********删********
简单查询:filter() -- 结果是queryset类型的数据里面是一个个的model对象,类似于列表
models.UserInfo.objects.filter(id=7).delete() #queryset对象调用
models.UserInfo.objects.filter(id=7)[0].delete() #model对象调用
********改********
方式1:update
models.UserInfo.objects.filter(id=2).update(
name='篮子',
checked = 0, checked可以看出一个判断条件
)
错误示例,model对象不能调用update方法
models.UserInfo.objects.filter(id=2)[0].update(
name='加篮子+2',
)
方式2
ret = models.UserInfo.objects.filter(id=2)[0]
ret.name = '加篮子+2'
ret.save()
print(models.ttt.objects.filter(pid=2))----><QuerySet [<ttt: ttt object>]>对象集合
print(models.ttt.objects.filter(pid=2)[0]) --->ttt object对象,可以进行操作
更新时的auto_now参数
# 更新记录时,自动更新时间,创建新纪录时也会帮你自动添加创建时的时间,但是在更新时只有使用save方法的方式2的形式更新才能自动更新时间,有缺陷,放弃
now2 = models.DateTimeField(auto_now=True,null=True)
********查*********
all() queryset
filter(id=1,name='xx') and queryset
get() model对象
customer.get_sex_display #sex_type = (('male', '男'), ('female', '女')) 取男女
count()
first()
last()
exclude() exclude(id=1)
exists() False True
order_by('id','-price')
reverse() 反转,先排序
values() -- queryset([{},{}])
values_list() queryset([(),()])
distinct()
filter 双下划线查询
filter(price__gt=30)
filter(price__gte=30)
filter(price__lt=30)
filter(price__lte=30)
filter(price__range=[30,40])
filter(price__in=[30,40,50..])
filter(title_contains='py')
filter(title_icontains='py')
filter(title_istartswith='py')
filter(title_iendswith='py')
filter(pub_date__year__gt='2018',pub_date__month='09',pub_date__day='09')
增加
# 一对一
# au_obj = models.AuthorDetail.objects.get(id=4)
models.Author.objects.create(
name='海狗',
age=59,
# 两种方式
au_id=4
# au=au_obj
)
# 一对多
# pub_obj = models.Publish.objects.get(id=3)
#
# models.Book.objects.create(
# title='xx2',
# price=13,
#
# publishDate='2011-11-12',
# # publishs=pub_obj , #类属性作为关键字时,值为model对象
# publishs_id=3 # 如果关键字为数据库字段名称,那么值为关联数据的值
# )
# 多对多 -- 多对多关系表记录的增加
# ziwen = models.Author.objects.get(id=3)
# haigou = models.Author.objects.get(id=5)
new_obj = models.Book.objects.create(
title='海狗产后护理第二部',
price=0.5,
publishDate='2019-09-29',
publishs_id=2,
)
new_obj.authors.add(3,5) # #*args **kwargs
new_obj.authors.add(*[3,5]) # 用的最多,
new_obj.authors.add(ziwen, haigou)
删除
# 一对一
# models.AuthorDetail.objects.filter(id=3).delete()
# models.Author.objects.filter(id=3).delete()
# 一对多
# models.Publish.objects.filter(id=3).delete()
# models.Book.objects.filter(id=4).delete()
# 多对多
book_obj = models.Book.objects.get(id=2)
# book_obj.authors.add() # 添加
# book_obj.authors.remove(1) #删除
# book_obj.authors.clear() # 清除
# book_obj.authors.set(['1','5']) # 先清除再添加,相当于修改
改
# 改
# ret = models.Publish.objects.get(id=2)
# models.Book.objects.filter(id=5).update(
# # title='华丽丽',
# publishs=ret,
# # publishs_id=1,
# )
基于对象的跨表查询
# 查询
# 一对一
# 关系属性写在表1,关联到表2,那么通过表1的数据去找表2的数据,叫做正向查询,返过来就是反向查询
# 查询一下王洋的电话号码
# 正向查询 对象.属性
# obj = models.Author.objects.filter(name='王洋').first()
# ph = obj.au.telephone
# print(ph)
# 查一下电话号码为120的作者姓名
# 反向查询 对象.小写的表名
# obj = models.AuthorDetail.objects.filter(telephone=120).first()
# ret = obj.author.name #陈硕
# print(ret)
# 一对多
# 查询一下 海狗的怂逼人生这本书是哪个出版社出版的 正向查询
# obj = models.Book.objects.filter(title='海狗的怂逼人生').first()
# ret = obj.publishs.name
# print(ret) #24期出版社
# 查询一下 24期出版社出版过哪些书
# obj = models.Publish.objects.filter(name='24期出版社').first()
#
# ret = obj.book_set.all() #<QuerySet [<Book: 母猪的产后护理>, <Book: 海狗的怂逼人生>]>
# for i in ret:
# print(i.title)
# 多对多
# 海狗的怂逼人生 是哪些作者写的 -- 正向查询
# obj = models.Book.objects.filter(title='海狗的怂逼人生').first()
# ret = obj.authors.all()
#
# print(ret) #<QuerySet [<Author: 王洋>, <Author: 海狗>]>
# for i in ret:
# print(i.name)
# 查询一下海狗写了哪些书 -- 反向查询
# obj = models.Author.objects.filter(name='海狗').first()
# ret = obj.book_set.all()
# print(ret)
# for i in ret:
# print(i.publishs.name)
# print(i.title)
# return HttpResponse('ok')
用admin操作数据库
admin添加用户
执行run manage.py Task和数据库迁移指令一样
输入:python manage.py createsuperuser创建admin用户
输入用户名:tangjian
邮箱不用输 直接回车
输入密码:必须超过8位,并且别太简单
admin注册
admin.py把数据库中的表注册到admin
from django.contrib import admin
# Register your models here.
from app01 import models
admin.site.register(models.Author)
admin.site.register(models.AuthorDetail)
admin.site.register(models.Publish)
admin.site.register(models.Book)
注册成功后启动项目,在url中输入.../admin/, 登录
admin解决忘记密码
在manage.py的Terminal输入以下命令:前提记得用户名
python3 manage.py shell
from django.contrib.auth.models import User
user = User.objects.get(username='soog')
user.set_password('4560000')
user.save()
聚合查询和分组查询
聚合查询
写之前先导入聚合函数 from django.db.models import Max,Min,Count,Avg,Sum
print(models.book.objects.aggregate(Max('price'))) #{'price__max': Decimal('89.00')}
print(models.book.objects.aggregate(Min('price'))) #{'price__min': Decimal('23.00')}
print(models.book.objects.aggregate(Sum('price'))) #{'price__sum': Decimal('237.00')}
print(models.book.objects.filter(pk__range=[1,3]).aggregate(Max('price'),Min('price'))) #pk__range=[1,3]范围查询,在pk值为1-3中的最大值,能取到3
print(models.book.objects.aggregate(Count('price'))) #{'price__count': 5}
print(models.book.objects.aggregate(Avg('price'))) #{'price__avg': 48.4}
分组查询
sql语句中用group by分组
统计每本书的作者的个数
ret = models.book.objects.annotate(Count('book_author')).values() #annotate是给book添加新增字段,count,但只是在查询的时候会显示(values()查询)
统计每个出版社的最便宜的书
方法一
ret = models.publish.objects.annotate(Min('book__price')).values()
方法二
#也可以用for循环代替values()取值
#values('book_author')指定分组,分组条件,values放前面是指定分组条件,放后边是取值(大多数情况)
ret = models.book.objects.values('book_publish__p_name').annotate(Min('price'))
for i in ret:
print(i)
统计不止一个的作者图书
ret = models.book.objects.annotate(count=Count('book_author')).filter(count__gt=1)
print(ret)
查询各个作者的书的总价格
方法一:
ret = models.author.objects.annotate(Sum('book__price')).values()
print(ret)
方法二:
ret = models.book.objects.values('book_author').annotate(Sum('price'))
print(ret)
F和Q查询
# F查询,比较两个字段之间的内容.sale__gt=F('num') sale>num
ret = models.book.objects.filter(num__lt=F('sale'))
print(ret)
# 对sale数据进行修改,例如:全部销量+10
# 这种方法比较复杂
ret= models.book.objects.all()
for i in ret:
i.sale = i.sale+10
i.save()
# F对某一个字段的每一个值进行操作
models.book.objects.update(sale=F('sale')+10)
# Q查询
# 查询pk值大于5或小于2的对象,Q表示 或
ret = models.book.objects.filter(Q(pk__gt=2)|Q(pk__lt=5))
print(ret)
事务
from django.db import transaction
try:
with transaction.atomic():
# orm操作
book1 = models.Book.objects.get(pk=1)
book2 = models.Book.objects.get(pk=2)
book1.num -= 20
book1.save()
int('sss') #转账过程中模拟发生错误
book2.num += 20
book2.save()
except Exception as e:
print(e)
cookie
Cookie和Session其实是共通性的东西,不限于语言和框架
定义: 保存在浏览器上的一组组键值对. k1=v1
Cookie是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息(键值对),用于服务器记录客户端的状态
Cookie无论是服务器发给浏览器还是浏览器发给服务器,都是放在请求头中的,记录着用户的登录状态
为什么要使用cookie?
http协议是无状态的,每次的请求相对独立,之间没有关系. 没有办法保存状态
特性:
cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
- 服务器让浏览器进行设置,浏览器也有权不设置
- 保存在浏览器本地
- 下次访问时自动携带对应的cookie
应用:
- 登录
- 投票
- 记录网页浏览习惯
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等
应用示例:
from django.shortcuts import render, redirect, HttpResponse
import functools
def login_required(func):
@functools.wraps(func) #装饰器需要加的
def inner(request, *args, **kwargs):
# 获取cookie值
# is_login = request.COOKIES.get('is_login')
# 获取session
is_login = request.session.get('is_login')
print(is_login)
print(request.session.items())
print(request.session.session_key)
if is_login != '1000':
return redirect(f'/login/?returnurl={request.path_info}')
ret = func(request, *args, **kwargs)
return ret
return inner
def login(request):
request.session.clear_expired() # 清除失效数据
if request.method == 'POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if user == 'Agoni' and pwd == '123':
# 获取是否有跳转的地址
returnurl = request.GET.get('returnurl') # 获取url上的参数的值 '/url/'
if returnurl:
ret = redirect(returnurl)
else:
ret = redirect('/home/')
# 设置cookie,通过响应对象设置
# ret.set_cookie('is_login', '1000') # Set_Cookie: is_login=1000; Path=/ 设置响应头
# ret['Set_Cookie'] = 'is_login=1000; path=/'
# 设置session,用request,按照字典的方式设置
request.session['is_login'] = '1000'
request.session['user'] = 'coco'
return ret
return render(request, 'login.html', {'error': '输入有误,请重新输入'})
return render(request, 'login.html')
@login_required
def home(request):
# 读取cookie
# is_login = request.COOKIES.get('is_login')
# 判断是否登录
# if is_login != '1000':
# return redirect('/login/')
return HttpResponse('HOME OK')
@login_required
def index(request):
# is_login = request.COOKIES.get('is_login')
# if is_login != '1000':
# return redirect('/login/')
return HttpResponse('INDEX OK')
def loginout(request):
# 删除cookie
ret = redirect('/login/')
# ret.delete_cookie('is_login') # 通过key删除对应的cookie
# request.session.delete()
request.session.flush()
return ret
Django中操作cookie
-
设置
ret = rendicter('/home/') # 响应对象 ret.set_cookie('is_login', '1000') / ret['Set-Cookie'] = 'is_login=1000; path=/'
# 参数 max_age=None # 设置超时时间,不设置默认关闭浏览器就失效 path='/' # Cookie生效的路径,/ 表示根路径,可指定url domain=None # Cookie生效的域名 secure=False # 设置True只允许https传输携带cookie httponly=False # 设置为True 只能http协议传输,无法被JavaScript获取
-
获取
# 相当于字典来获取cookie值 request.COOKIE.get('is_login') request.COOKIE[key]
-
删除
ret = redirect('/login/') ret.delete_cookie('is_login')
# 设置加密cookie
ret.set_signed_cookie('is_login','1000',salt='s1')
# 获取加密cookie
request.get_sigined_cookie('is_login',default='',salt='s1')
session
定义: 保存在服务器上一组组键值对,必须依赖于cookie
session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果 浏览器禁用了 cookie ,同时 session 也会失效(当然也可以在 url 中传递)
为什么要用session?
-
cookie保存在浏览器上,不安全
浏览器对cookie的大小有限制,最大字节数4096
-
session支持更多的字节
保存在服务器
有较高的安全性
-
session实现机制
- 依赖于cookie
- session将数据保存在服务端
- 当用户发送请求时,在服务端会生产随机字符串,并开辟一块内存空间,存储个人数据,随机字符串会基于cookie返回给浏览器
session流程解析
Django中操作session
-
设置
request.session['is_login']='1000'
-
获取
request.session['is_login'] request.session.get('is_login') *** request.session.session_key 获取sessionid随机字符串
-
删除
request.session.delete() # 删除session数据,不删除cookie request.session.flush() # 删除session数据,删除cookie
Django中session相关方法
# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
request.session.pop(key)
# 获取所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
# 获取会话session的key,随机字符串
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired() ***
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
# 删除当前会话的所有Session数据
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
这用于确保前面的会话数据不可以再次被用户的浏览器访问
例如,django.contrib.auth.logout() 函数中就会调用它。
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value) ***
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
session配置
SESSION_COOKIE_NAME = 'sessionid' # 修改唯一表示cookie名字
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # 设置cookie超时时间
SESSION_SAVE_EVERY_REQUEST = True # 发送请求时(每次访问),更新超时时间
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 关闭浏览器时.cookie是否失效
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # session存储引擎 (cache-缓存,cache_db-缓存+数据库,db-数据库,file-文件,signed_cookies-加密cookie)
cookie和session的优缺点
使用cookie的缺点:
如果浏览器使用的是cookie,那么所有的数据都保存在浏览器端,
cookie可以被用户禁止
cookie不安全(对于敏感数据,需要加密)
cookie只能保存少量的数据(大约是4k),cookie的数量也有限制(大约是几百个),不同浏览器设置不一样,反正都不多
cookie只能保存字符串
对服务器压力小
使用session的缺点
一般是寄生在Cookie下的,当Cookie被禁止,Session也被禁止
当然可以通过url重写来摆脱cookie
当用户访问量很大时,对服务器压力大
我们现在知道session是将用户信息储存在服务器上面,如果访问服务器的用户越来越多,那么服务器上面的session也越来越多, session会对服务器造成压力,影响服务器的负载.如果Session内容过于复杂,当大量客户访问服务器时还可能会导致内存溢出。
用户信息丢失, 或者说用户访问的不是这台服务器的情况下,就会出现数据库丢失.
Cookie与Session问答
Cookie运行在客户端,Session运行在服务端,对吗?A:不完全正确。Cookie是运行在客户端,有客户端进行管理;Session虽然是运行在服务器端,但是sessionID作为一个Cookie是存储在客户端的。
浏览器禁止Cookie,Cookie就不能用了,但Session不会受浏览器影响,对吗?A:错。浏览器禁止Cookie,Cookie确实不能用了,Session会受浏览器端的影响。很简单的实验,在登录一个网站后,清空浏览器的Cookie和隐私数据,单机后台的连接,就会因为丢失Cookie而退出。当然,有办法通过URL传递Session。
浏览器关闭后,Cookie和Session都消失了,对吗?A:错。存储在内存中额Cookie确实会随着浏览器的关闭而消失,但存储在硬盘上的不会。更顽固的是Flash Cookie,不过现在很多系统优化软件和新版浏览器都已经支持删除Flash Cookie。百度采用了这样的技术记忆用户:Session在浏览器关闭后也不会消失,除非正常退出,代码中使用了显示的unset删除Session。否则Session可能被回收,也有可能永远残留在系统中。
Session 比 Cookie 更安全吗? 不应该大量使用Cookie吗?A:错误。Cookie确实可能存在一些不安全因素,但和JavaScript一样,即使突破前端验证,还有后端保障安全。一切都还要看设计,尤其是涉及提权的时候,特别需要注意。通常情况下,Cookie和Session是绑定的,获得Cookie就相当于获得了Session,客户端把劫持的Cookie原封不动地传给服务器,服务器收到后,原封不动地验证Session,若Session存在,就实现了Cookie和Session的绑定过程。因此,不存在Session比Cookie更安全这种说法。如果说不安全,也是由于代码不安全,错误地把用作身份验证的Cookie作为权限验证来使用。
Session是创建在服务器上的,应该少用Session而多用Cookie,对吗?A:错。Cookie可以提高用户体验,但会加大网络之间的数据传输量,应尽量在Cookie中仅保存必要的数据。
如果把别人机器上的Cookie文件复制到我的电脑上(假设使用相同的浏览器),是不是能够登录别人的帐号呢?如何防范?A:是的。这属于Cookie劫持的一种做法。要避免这种情况,需要在Cookie中针对IP、UA等加上特殊的校验信息,然后和服务器端进行比对。
django请求生命周期大致流程
情况一:没有model和HTML
浏览器发送请求到django的wsgi(wsgi接收浏览器发过来的请求,是一个外部服务器),wsgi接收到这个请求,然后封装成request对象,request对象走url路由匹配,路由匹配成功后走视图函数,执行视图函数,把http这个对象变成字符串(http相应格式的字符串),最后变成bytes类型发给浏览器
情况二:有model和HTML
浏览器发送请求到django的wsgi,wsgi接收到这个请求,然后封装成request对象,request对象走url路由匹配,路由匹配成功后走视图函数,执行视图函数,在执行模板(template),把模板读取出来,通过models在数据库中拿对应的数据,拿到数据后在返回给models,然后在给视图,拿到模板和数据进行渲染,然后变成http对象给wsgi
情况三:有model和HTML和中间件
浏览器发送请求到django的wsgi,wsgi接收到这个请求,然后封装成request对象,先经过中间件进行处理,request对象走url路由匹配,路由匹配成功后走视图函数,执行视图函数,在执行模板(template),把模板读取出来,通过models在数据库中拿对应的数据,拿到数据后在返回给models,然后在给视图,拿到模板和数据进行渲染,然后在经过中间件,封装成http对象给wsgi
中间件
中间件是Django请求/响应处理的钩子框架。这是一个轻量级的低级“插件”系统,用于在全球范围内改变Django的输入或输出。
每个中间件组件都负责完成一些特定的功能。
1. process_request(self,request)
执行时间: 在视图函数和路由匹配之前执行
参数: request ,request请求对象和视图函数是同一个
返回值:
None 正常流程
Httpresponse :按照app的注册顺序执行,当其中某一个中间件有Httpresponse返回值时,当前中间件之后的中间件的process_request方法 , 路由匹配 , 视图函数都不执行,直接执行当前中间件的process_response(响应)方法,倒序执行之前的response的方法,最终返回给浏览
# 示例
# view.py视图函数
from django.shortcuts import render,HttpResponse
def index(request):
print('request index')
return HttpResponse('ok')
# mymiddleware.py
都没有返回值的情况:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
结果: MD1 process_request
MD2 process_request
MD1有返回值的情况:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
return HttpResponse('MD1 MiddlewareMixin')
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
结果:MD1 process_request
都有返回值的情况:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
return HttpResponse('MD1 MiddlewareMixin')
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
return HttpResponse('MD2 MiddlewareMixin')
结果:
MD1 process_request
2.process_response(self,request,response)
执行时间: 在视图函数之后执行
参数:
request: 请求对象和视图函数是同一个
response: 视图函数或process_request方法返回的response对象
执行的顺序 :按照中间件的注册顺序 -- 倒序执行
返回值:
Httpresponse : 必须返回视图函数的response对象或者返回HttpResponse('内容') --> 内容会取代视图函数的返回内容
# 示例
# view.py视图函数
from django.shortcuts import render,HttpResponse
def index(request):
print('request index')
return HttpResponse('ok')
# mymiddleware.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
def process_response(self,request,response):
print('MD1 process_response')
return response
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
def process_response(self,request,response):
print('MD2 process_response')
return response
# return HttpResponse('ojbk') 当返回一个HttpResponse,这个内容会取代视图函数index中的return HttpResponse('ok') ,显示的页面内容为:ojbk
结果:
MD1 process_request
MD2 process_request
request index
MD2 process_response
MD1 process_response
#显示的页面内容为:ok
3.process_view(self, request, view_func, view_args, view_kwargs)
先执行process_request,在执行路由匹配,在执行process_view,在执行视图
执行时间: 在视图函数之前,路由匹配之后执行
参数
request : 请求对象和视图函数是同一个
view_func : 视图函数
view_args : 传递给视图函数的位置参数 -- 分组的参数
view_kwargs : 传递给视图函数的关键字参数 -- 命名分组的参数
执行顺序: 按照中间件的注册顺序,顺序执行
返回值
None : 正常流程
Httpresponse : 返回HttpResponse,当前中间件之后的中间件的process_view方法,视图函数都不执行,直接执行最后一个中间件的process_response方法,倒序执行之前的process_response方法. 最终返回给浏览器
# 示例
# view.py视图函数
from django.shortcuts import render,HttpResponse
def index(request):
print('request index')
return HttpResponse('ok')
# mymiddleware.py
process_view没有返回值的情况
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
def process_response(self,request,response):
print('MD1 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD1 process_view')
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
def process_response(self,request,response):
print('MD2 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD2 process_view')
结果:
MD1 process_request
MD2 process_request
MD1 process_view
MD2 process_view
request index
MD2 process_view
MD1 process_view
# mymiddleware.py
process_view有返回值的情况
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
def process_response(self,request,response):
print('MD1 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD1 process_view')
return HttpResponse('098K')
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
def process_response(self,request,response):
print('MD2 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD2 process_view')
结果:
MD1 process_request
MD2 process_request
MD1 process_view
MD2 process_response
MD1 process_response
#先按照中间件注册顺序执行process_request,在按照中间件注册顺序执行process_view,此时MD1的process_view有返回值,就不会执行视图函数,直接执行最后一个中间件的process_response,倒序执行process_response
4,process_exception(self,request,exception)
执行时间(触发条件)
视图函数层面有异常才执行
参数
request : 请求对象和视图函数是同一个
exception : 异常对象
执行顺序
按照中间件注册顺序 -- 倒序执行
返回值
None : 交给下一个中间件的process_exception方法处理异常,如果所有的中间件都没有处理,交给Django处理
Httpresponse : 返回HttpResponse , 当前中间件之前的中间件的process_exception不执行,直接执行最后一个中间件的process_response方法,倒序执行之前的process_response方法,最终返回给浏览器
# 示例
# view.py视图函数
from django.shortcuts import render,HttpResponse
def index(request):
print('request index')
return HttpResponse('ok')
# mymiddleware.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
def process_response(self,request,response):
print('MD1 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD1 process_view')
def process_exception(self,request,exception):
print("MD1 process_exception")
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
def process_response(self,request,response):
print('MD2 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD2 process_view')
def process_exception(self,request,exception):
print("MD2 process_exception")
return HttpResponse('NO')
结果:
MD2 process_request
MD1 process_view
MD2 process_view
request index
MD2 process_response
MD1 process_response
# 没有触发process_exception的方法条件
# 示例
# view.py视图函数
from django.shortcuts import render,HttpResponse
def index(request):
s = int('dsds') # 报错信息,触发条件
print(s) # 报错信息,触发条件
print('request index')
return HttpResponse('ok')
# mymiddleware.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,render
class MD1(MiddlewareMixin):
def process_request(self,request):
print('MD1 process_request')
def process_response(self,request,response):
print('MD1 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD1 process_view')
def process_exception(self,request,exception):
print("MD1 process_exception")
class MD2(MiddlewareMixin):
def process_request(self, request):
print('MD2 process_request')
def process_response(self,request,response):
print('MD2 process_response')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('MD2 process_view')
def process_exception(self,request,exception):
print("MD2 process_exception")
return HttpResponse('NO')
结果:
MD1 process_request
MD2 process_request
MD1 process_view
MD2 process_view
MD2 process_exception
MD2 process_response
MD1 process_response
# 没打印request index,因为错误在request index的上面
5. process_temp3late_response(self,request,response):
执行时间(触发条件)
from django.template.response import TemplateResponse
视图函数层面返回response对象是一个template_response(TemplateResponse)对象
参数
request:请求对象和视图函数是用一个
response : template_response 响应对象
执行顺序
按照中间件的注册顺序 -- 倒序执行
返回值
Httpresponse : 必须返回TemplateResponse 对象
写一个中间件
1.在项目的app下新建py文件,例如:mymiddleware.py
2.在mymiddleware.py写类,函数,逻辑,功能.(例如:加session登录认证功能)
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect,reverse
class LoginMiddleWare(MiddlewareMixin): #LoginMiddleWare类名,继承LoginMiddleWare
white_list = [reverse('login'),] #白名单,!!!!!
def process_request(self,request): #process_request方法
if request.path not in self.white_list: #判断浏览器访问路径是否在白名单里,如果不在就代表不是login,就继续执行
res = request.session.get('auth') #request是已经经过处理的,所以可以直接取session
if not res: #判断如果session不存在就重定向到login
return redirect(reverse('login'))
3.在settings中的MIDDLEWARE注册中间件,最好将自己定义的中间件注册到最后,不打破原来默认的执行顺序
# 如果不加白名单
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect,reverse
class LoginMiddleWare(MiddlewareMixin):
def process_request(self,request):
res = request.session.get('auth')
if not res:
return redirect(reverse('login'))
#这样当访问其他页面路径时,判断路径是否为login,如果不是,就重定向到login,然后login在接着执行LoginMiddleWare中间件的process_request,会在接着判断......导致访问次数过多
#错误类型:
该网页无法正常运作 127.0.0.1 将您重定向的次数过多。
尝试清除 Cookie.
完整的django请求生命周期
Ajax
ajax简介
AJAX (Asynchronous Javascript And XML)就是 " 异步的JavaScript和XML ".使用JavaScript语言与服务器进行异步交互,传输的数据为XML ( 传输的数据不只是XML )
AJAX最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据,并更新部分网页内容
简单说就是js技术,给服务器发送请求
特点: 异步交互 , 传输的数据量小 , 局部刷新
ajax请求示例(登录版本一)
#前端 login.HTML
{% load static %} #引入stctic
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
#用户名和密码输入框,之前用form,post方法提交数据
用户名:<input type="text" id="user">
密码:<input type="password" id="pwd">
<button id="btn">提交</button>
</body>
<script src="{% static 'js/jquery.js' %}"></script> #引入jQuery
<script>
$('#btn').click(function () { #给按钮添加点击事件
var user = $('#user').val(); #获取id为user的input框内容,定义变量
var pwd = $('#pwd').val(); #获取id为pwd的input框内容,定义变量
$.ajax({
url:'/login/', #发送到login,把数据提交到login.'{% url "login" %}'
type:'post', #提交数据的类型
data:{'username':user,'password':pwd}, #提交的数据
success:function (res) { #后端验证成功,res接收参数
if (res === '1'){ #判断后端发过来的数据,为1表示登录成功
location.href = '/upload/' #登录成功后访问/updata/,'{% url "updata" %}',可以说重定向
}else {
location.href = '/login/'
}
}
})
});
</script>
</html>
#后端 view login函数
from django.shortcuts import render,HttpResponse
import json
# Create your views here.
def login(request):
if request.method == 'POST': #判断前端的发送数据的类型
user = request.POST.get('username') #获取前端的数据,用户名
pwd = request.POST.get('password') #获取前端的数据,密码
if user == 'tj' and pwd == '123': #简单判断输入是否正确(连数据库)
return HttpResponse('1') #验证正确给前端的login返回信息为 '1',
else:
return HttpResponse('2') #验证失败给前端的login返回信息为 '2'
return render(request,'login.html') #GET请求
#!!!前提注释掉或删除settings中的中间件: 'django.middleware.csrf.CsrfViewMiddleware',否则访问不了,403
ajax请求示例(登录版本二)
# html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
用户名: <input type="text" id="user">
密码: <input type="password" id="pwd">
<button id="btn">登录</button>
</body>
<script src="{% static 'js_plugin/jquery.js' %}"></script>
<script src="{% static 'js_plugin/csrf_verify.js' %}"></script>
<script>
$('#btn').click(function () {
var user = $('#user').val(); #变量名最好不用username
var pwd = $('#pwd').val(); #变量名最好不用password
$.ajax({
url:'{% url "login" %}',
type:'post',
data:{user:user,pwd:pwd}, #变量名最好不用username 变量名最好不用password
success:function (res) {
if (res.status === 1){
console.log(res.status); #简单查看打印结果
location.href = '{% url "home" %}';
alert(res.msg)
}else{
console.log(res.status);
location.href = '{% url "login" %}';
alert(res.msg)
}
}
})
})
</script>
</html>
# view.py
def login(request):
if request.method == "POST":
username = request.POST.get('user')
password = request.POST.get('pwd')
ret_data = {'status': None, 'msg': None} #返回一个字典初始值为None
if username == 'tj' and password == '123':
print(ret_data)
ret_data['status'] = 1 #设置值
ret_data['msg'] = '登录成功'
else:
ret_data['status'] = 2
ret_data['msg'] = '登录失败'
return JsonResponse(ret_data) #直接返回一个字典.
#return JsonResponse(ret_data,safe=False) 返回一个非字典数据(假如ret_data是一个列表)
return render(request,'login.html')
Ajax通过Django的csrf校验
方式1
$.ajax({
url: "/cookie_ajax/",
type: "POST",
data: {
"username": "chao",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})
方式2
$.ajax({
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});
方式3
方式3
{% static 'jQuery操作cookie插件的路径' %}
$.ajax({
headers:{"X-CSRFToken":$.cookie('csrftoken')}, #其实在ajax里面还有一个参数是headers,自定制请求头,可以将csrf_token加在这里,我们发contenttype类型数据的时候,csrf_token就可以这样加
})
先下载jQuery操作cookie插件:https://plugins.jquery.com/cookie/,下载后导入到项目的static下
方式4
在static文件下新建js文件
把一下内容粘贴进去
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
之后再导入到HTML中{% static 'xxxx...' %}
上传文件
form上传文件
# html
<form action="" method="post" enctype="multipart/form-data"> 别忘了enctype
{% csrf_token %}
用户名: <input type="text" name="username">
密码: <input type="password" name="password">
头像: <input type="file" name="file"> #type=file,文件类型
<input type="submit">
</form>
# views.py
def upload(request):
if request.method == 'GET':
return render(request,'upload.html')
else:
print(request.POST)
print(request.FILES)
uname = request.POST.get('username')
pwd = request.POST.get('password')
file_obj = request.FILES.get('file') #文件对象
print(file_obj.name) #xxx.pptx,文件名称
with open(file_obj.name,'wb') as f:
# for i in file_obj: 方法1
# f.write(i)
for chunk in file_obj.chunks():#方法2
f.write(chunk)
return HttpResponse('ok')
Ajax上传文件
# html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>文件上传</h1>
文件上传:<input type="file" id="file" multiple>
<button id="btn">提交</button>
</body>
<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'js/csrf_verify.js' %}"></script> #通过CSRF验证的方式
<script>
$('#btn').click(function () {
var farmdata = new FormData(); #类似于form上传文件的multipart/form-data,上传文件写的
var file_obj = $('#file')[0].files[0];
farmdata.append('file_obj',file_obj); #把数据封装到farmdata, farmdata随意定义的变量
$.ajax({
url: '/upload/',
type: 'post',
data: farmdata, #发送数据直接发送farmdata
processData: false, #格式
contentType: false, #格式
success: function (res) {
location.href = '/home/'
}
})
})
</script>
</html>
file_obj = $('#file')[0].files[0]-->打印结果:
{name: "style.css", lastModified: 1569575520345, lastModifiedDate: Fri Sep 27 2019 17:12:00 GMT+0800 (中国标准时间), webkitRelativePath: "", size: 4362, …}
# view.py
def upload(request):
if request.method == 'POST':
print(request.POST)
print(request.FILES)
file_obj = request.FILES.get('file_obj') #获取文件对象
print(file_obj.name) #打印文件名
with open(file_obj.name,'wb') as f: #写文件
for chunk in file_obj.chunks():
f.write(chunk)
return render(request, 'upload.html')
ajax文件上传下载的processData,contentType参数
processData:处理数据
默认情况下,processData 的值是 true,其代表以对象的形式上传的数据都会被转换为字符串的形式上传。而当上传文件的时候,则不需要把其转换为字符串,因此要改成false
contentType:发送数据的格式
和 contentType 有个类似的属性是 dataType ,代表的是期望从后端收到的数据的格式,一般会有 json 、text……等
而contentType 则是与dataType 相对应的,其代表的是前端发送数据的格式
jsonresponse
json
json是一种跨平台的数据交换格式
# 1. json
+ json指的是JavaScript对象表示法
+ json是轻量级的文本数据交换格式 ***
+ JSON 独立于语言
+ JSON 具有自我描述性,更易理解
json支持的数据类型:
数字 , 字符串 , 列表 , 字典 , 布尔 , None
json方法:
+ 序列化: json.dumps( python的数据类型),是一个过程
+ 返序列化: json.loads( json的字符串)
# 2. 前端js
支持的数据类型
数字 , 字符串 , 数组 , 对象, 布尔 , None
方法:
序列化: JSON.stringify( js的数据类型)
返序列化: JSON.parse( json的字符串)
返回json的字符串:
JsonResponse({}) , JsonResponse([] , safe=False)
后端向前端传输数据
- python的json.dumps方法+js的JSON.parse方法
view.py
def login(request):
Users = models.User.objects.all()
username = []
for i in Users:
username.append(i.username)
return render(request, "login.html",{'username': json.dumps(username)})
其中 username是我User表中的一个字段,json.dumps()是python中json库的一个函数,将python对象编码成json字符串。python的json库还有一个函数是json.loads(),将已编码的json字符串解码为python对象
html
var usernames = "{{ username }}";
var usernames_r = usernames.replace(/\"/g, '\"');
var usernames_r_p = JSON.parse(usernames_r);
js中对json的处理也有两个函数。JSON.parse() 方法用于将一个 JSON 字符串转换为对象。另一个方法JSON.stringify() 用于将 JavaScript 值转换为 JSON 字符串。
前端向后端传输数据
- js的JSON.stringify()方法+python的json.loads()方法,上面也介绍过了,这边只放个object的例子
html
<script>
function sub()
{
var a = document.getElementById("aa").value;
var b = document.getElementById("bb").value;
var str = {"a": a, "b": b};
var str_json = JSON.stringify(str);
document.getElementById("cc").value = str_json;
return true;
}
</script>
<input type="text" id="aa"/>
<input type="text" id="bb"/>
<form method="POST" οnsubmit="return sub()">
{% csrf_token %}
<input type="hidden" name="data" id="cc"/>
<input type="submit" value="提交"/>
</form>
view.py
def login(request):
if request.method == 'POST':
print(request.POST['data'])
result = json.loads(request.POST['data'])
print(result['a'])
form
# 前端
from django.http import JsonResponse
username = request.POST.get('username')
pwd = request.POST.get('password')
ret_data = {'status':None,'msg':None}
print('>>>>>',request.POST)
#<QueryDict: {'{"username":"123","password":"123"}': ['']}>
if username == 'chao' and pwd == '123':
ret_data['status'] = 1000 # 状态码
ret_data['msg'] = '登录成功'
else:
ret_data['status'] = 1001 # 状态码
ret_data['msg'] = '登录失败'
return JsonResponse(ret_data)
# 后端的返回数据
ret_data_json = json.dumps(ret_data,ensure_ascii=False)
return HttpResponse(ret_data_json,content_type='application/json')
ajax
$.ajax({
url:'{% url "jsontest" %}',
type:'post',
data:{username:uname,password:pwd},
headers:{
// contentType:'application/json',
"X-CSRFToken":$.cookie('csrftoken'),
},
success:function (res) {
{#console.log(res,typeof res); // statusmsg {"status": 1001, "msg": "登录失败"}#}
{#var res = JSON.parse(res); //-- json.loads()#}
console.log(res,typeof res); //直接就是反序列化之后的了
# JSON.stringify() -- json.dumps
if (res.status === 1000){
// $('.error').text('登录成功');
location.href = '/home/'; // http://127.0.0.1:8000/home/
}else{
$('.error').text(res.msg);
}
}
})
# 后端
def jsontext(request):
if request.method == 'GET':
return render(request,'jsontext.html')
else:
username = request.POST.get('username')
pwd = request.POST.get('password')
ret_data = {'status':None,'msg':None} #!!!
print(request.POST)
if username == 'tj' and pwd == '123':
ret_data['status'] = 1000 #!!!数据类型
ret_data['msg'] = '登录成功'
else:
ret_data['status'] = 2000
ret_data['msg'] = '登录失败'
return JsonResponse(ret_data) #!!!
form组件
form组件的主要功能如下:
- 生成页面可用的HTML标签
- 对用户提交的数据进行校验
- 保留上次输入内容
1. 使用form组件
- 定义一个类和视图函数
from django.shortcuts import render,HttpResponse
from django import forms
class IndexForm(forms.Form):
user = forms.CharField(label='用户名',initial='joe') #initial='joe'设置默认值
password = forms.CharField(
label='密码',
widget=forms.PasswordInput,
# required=True/False,提交的数据是否可以为空
min_length=6,
max_length=10,#输入的最大长度为10位,超过10不允许输入
error_messages={'required':'内容不能为空',
'min_length':'密码长度最少为6位'
}
)
sex = forms.ChoiceField(label='性别',choices=((1,'男'),(2,'女')))
age = forms.IntegerField(label='年龄')
hobby = forms.MultipleChoiceField(label='爱好',choices=((1,'唱'),(2,'跳'),(3,'rep')),initial=[1,2])#initial=[1,2]多选的时候设置默认值
def index(request):
form_obj = IndexForm()
if request.method == 'POST':
form_obj = IndexForm(data=request.POST)
# print(form_obj.data) 前端提交的数据---><QueryDict: {'csrfmiddlewaretoken': ['JVIF0FIw8UKSLbFUuo88eQhz5tDvbB1fCdYiN5DkFk6cRdy68ddL5fs8HeiGwLRk'], 'user': ['tangjian'], 'password': ['ddd'], 'sex': ['2'], 'age': ['23'], 'hobby': ['3']}>
# print(form_obj.data.get('csrfmiddlewaretoken')) #获取csrfmiddlewaretoken
# print(form_obj.data.get('user')) #获取前端提交的值
if form_obj.is_valid(): #校验,结果为True/False
return HttpResponse('OK')
return render(request,'index.html',{'form_obj':form_obj})
2.html页面显示
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="" method="post" novalidate>
{% csrf_token %}
<p>
<label for="{{ form_obj.user.id_for_label }}">{{ form_obj.user.label }}:{{ form_obj.user }}</label>
<span>{{ form_obj.user.errors.0 }}</span> {# {{ form_obj.user.errors }}结果是一个列表,把全部错误拿出来 #}
</p> {# {{ form_obj.user.errors.0 }} 拿第一个错误#}
<p>
<label for="{{ form_obj.password.id_for_label }}">{{ form_obj.password.label }}:{{ form_obj.password }}</label>
<span>{{ form_obj.password.errors.0 }}</span>
</p>
<p>
{{ form_obj.sex.label }}:{{ form_obj.sex }}
</p>
<p>
{{ form_obj.age.label }}:{{ form_obj.age }}
</p>
<p>
{{ form_obj.hobby.label }}:{{ form_obj.hobby }}
</p>
<button>提交</button>
</form>
</body>
</html>
- 在模板中
# 写在form表单中
{{ form_obj.as_p }} # 显示所有字段
{{ form_obj.user }} # input框
{{ form_obj.user.label }} # label标签的中文提示
{{ form_obj.user.id_for_label }} # input框的id,找到字段对应的input框的id
{{ form_obj.errors }} # 所有字段的错误信息
{{ form_obj.user.errors }} # 一个字段的错误信息
{{ form_obj.user.errors.0 }} # 一个字段的第一个错误信息
- form和数据库
hobby = forms.MultipleChoiceField(label='爱好',choices=((1,'唱'),(2,'跳'),(3,'rep')),initial=[1,2])#initial=[1,2]多选的时候设置默认值,这个爱好是写死的,最后要显示数据库的数据
hobby=forms.MultipleChoiceField(label='好',choices=models.Hobby.objects.values_list('id','hobby'),
initial=[1,2])
#initial=[1,2]多选的时候设置默认值,这样写有一个问题,当在插入数据时,页面显示不出来新插入的数据,只能重启项目,因为类只会加载一次
hobby = forms.ModelMultipleChoiceField(label='爱好', queryset=models.Hobby.objects.all(), widget=forms.CheckboxSelectMultiple,initial=[1,2])
# 数据库中返回 def __str__(self):return self.hobby
2. 常用的字段
CharField # input框
ChoiceField # select框(单选)
MultipleChoiceField # select框(多选)
ModelMultipleChoiceField # 可选数据可以放在数据库
# 设置ModelMultipleChoiceField字段
hobby = forms.ModelMultipleChoiceField(
initial=[1, 2],
queryset=models.Hobby.objects.all(),
label='爱好',
widget=forms.CheckboxSelectMultiple
) # 多选框
3. 字段参数
initial # 设置初始值
label # 中文提示
widget # 设置插件
error_messages # 错误提示,以字典方式设置
required=False, # 设置false,字段不填,没有错误信息
min_length=6, # 最小长度
max_length=12, # 最大长度
choices # 可选择的值 -- 选择框必填 ((1,'值1'),(2,'值2'))
4. 常用插件
widget=forms.TextInput # 文本
widget=forms.PasswordInput # 密码
widget=forms.RadioSelect # 选择
widget=forms.CheckboxInput # 单选
widget=CheckboxSelectMultiple # 多选
5. 校验
required=False, # 设置false,字段不填,没有错误信息
min_length=6, # 最小长度
自定义校验
from django.core.exceptions import ValidationError
def check_user(value):
# 自定义规则,通过校验不做任何操作
if 'sb' in value:
# 不通过校验,抛出ValidationError异常
raise ValidationError('用户名有敏感字符')
# 校验哪个字段,要在某字典中设置validators=[check_user],并将自定义校验的函数名写入列表中
使用内置校验器
# 导入内置校验器
from django.core.validators import validate_email, RegexValidator
validators=[validate_email] # 在字段中设置
phone = forms.CharField(validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')]) # 类实例化
校验源码过程解析
is_valid() 校验入口 --->return self.is_bound and not self.errors判断语句,结果返回True/False,and两边的条件都为True时,返回True,否则返回False
先看is_bound是什么?
找is_bound--->self.is_bound = data is not None or files is not None,只要数据不为空或者文件不为空就返回True,
怎么知道数据或文件是否为空?-->def __init__(self, data=None, files=None,.....),
data是实例化的对象,例如:form_obj = RegisterForm(data=request.POST),什么时候执行这个__init__方法,他从 属于BaseForm类,在看自己定义的类class RegisterForm(forms.Form):
在看Form, class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):继承了BaseForm,实例化对象的时候就会执行BaseForm的init方法,所以self.is_bound就返回True,
再看self.errors
这个首先判断self.errors是什么,如果是个属性的话,前边的is_bound已经判断完,这两个在统一判断,结果就已经有了,源码也就结束了,所以self.errors肯定做了一些处理,@property def errors(self):加了@property也就成为了一个属性,
再进属性函数
def errors(self),判断self._errors is None:如果self._errors为空的话,无论是否为空都会执行自己函数的return self._errors,再看函数内的self.full_clean(),函数内self._errors = ErrorDict(),self._errors就是ErrorDict()的一个实例化对象,
在接着执行if not self.is_bound根据刚才的判断self.is_bound=TRUE,所以not self.is_bound=FALSE,
所以接着向下执行self.cleaned_data = {}类似于:username = self.cleaned_data.get('user')在字典里拿相应数据,
当看到一个判断只有一个单纯的return的话,没有什么意义,因为函数就结束了(return是函数的结束语句)
接着向下执行 self._clean_fields() self._clean_form() self._post_clean()这
这三个值校验功能,
在点开看 _clean_fields()---->for name, field in self.fields.items():这个self就是自己实例化的那个对象:form_obj(自己定义的form_obj = RegisterForm(data=request.POST)),用form_obj.fields可以查看这个对象的所有属性
for name, field in self.fields.items():就是在循环这个字典,
在接着进行下面代码的判断语句,if判断语句中有一个if field.disabled:代码里没有定义disabled所以不执行这个,接着向下执行,执行
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
这个就是字段内的属性校验
执行完以后执行self.cleaned_data[name] = value name是字典的键,values是提交过来的值
以下的就是局部钩子:
if hasattr(self, 'clean_%s' % name):判断当有clean_%s' % name的方法时 ,简单的格式化拼接
value = getattr(self, 'clean_%s' % name)() 执行这个方法,value就是函数执行的返回值
self.cleaned_data[name] = value 这句就是当局部钩子没有返回值时默认的这个方法就会返回一个None ,self.cleaned_data[name] = value,所以这个值就为空,所以执行局部钩子必须要有返回值,这个循环只是校验一个字段,当循环执行完后再执行其他字段的校验
-
is_valid() 中要执行 full_clean() 方法
- self._errors = { } 定义一个存放错误信息字典
- self.cleaned_data = {} 存放通过校验的有效数据
-
执行self._clean_fields()
- 先执行内置的校验和校验器的校验
- 有局部钩子,执行局部钩子
def clean_gender(self): # 局部钩子,clean_gender对gender字段进行校验, # 获取当前字段的值 v = self.cleaned_data.get('gender') # 不通过校验,抛出异常 # 通过校验,必须返回当前字段的值 return v 局部钩子可以对某个字段进行校验:clean_字段名,属性校验之后立马回执行局部钩子,cleaned_data就有了数据
-
执行self.clean() 全局钩子
from django.core.exceptions import ValidationError from django.core.validators import validate_email, RegexValidator def clean(self): # 全局钩子,能够实现对字段进行全局校验 # 不通过校验,抛出异常 # 通过校验,必须返回所有字段的值self.cleaned_data pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd != re_pwd: self.add_error('re_pwd', '两次密码不一致') # 将错误信息加到re_pwd中 raise ValidationError('两次密码不一致') return self.cleaned_data
Django-Form所有内置字段和参数
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀
CharField(Field)
max_length=None, 最大长度
min_length=add_consumer_obj, 最小长度
strip=True 是否移除用户输入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
BaseTemporalField(Field)
input_formats=None 时间格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允许空文件
ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选
ModelMultipleChoiceField(ModelChoiceField)
django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值
ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
SlugField(CharField) 数字,字母,下划线,减号(连字符)
...
UUIDField(CharField) uuid类型
Django Form内置字段
ModelForm
ModelForm,form与model的终极结合,会根据你model中的字段转换成对应的form字段,并且并你生成标签等操作。
modelform 类的写法
class AddBook(forms.ModelForm):
class Meta:
model = models.Book #更改model变量会报错
fields = '__all__' #更改fields变量会报错
labels={
'title': '书籍名称',
'price': '价格',
'publishdata': '出版日期',
'authors': '作者',
'publish': '出版社'
}
widgets = {'publishdata': forms.TextInput(attrs={"type": "date"}),
'authors':forms.SelectMultiple()}
error_messages = {
'title':{'required': '输入不能为空'},
'price':{'required':'输入不能为空'},
'publishdata':{'required':'输入不能为空'},
'authors':{'required':'选择不能为空'},
'publish':{'required':'选择不能为空'}
}
可以指定field_classes若要自定义由窗体实例化的字段类型,请执行以下操作。
例如,如果您想使用MySlugFormField为slug字段中,可以执行以下操作:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
field_classes = {
'slug': MySlugFormField,
}
class Meta下常用参数
model = models.Book # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
error_messages = {
'title':{'required':'不能为空',} #每个字段的所有的错误都可以写
}
save方法
from myapp.models import Book
from myapp.forms import BookForm
根据POST数据创建一个新的form对象
form_obj = BookForm(request.POST)
创建书籍对象
new_ book = form_obj.save()
基于一个书籍对象创建form对象
edit_obj = Book.objects.get(id=1)
使用POST提交的数据更新书籍对象
form_obj = BookForm(request.POST, instance=edit_obj)#instance编辑的时候要默认选中
form_obj.save()
例如:用modelform做图书管理系统的添加功能
views.py
from django.shortcuts import render,redirect
from django import forms
from app01 import models
# Create your views here.
# 添加书籍的modelform
class AddBook(forms.ModelForm): #继承ModelForm类
class Meta:
model = models.Book #对应到book表
fields = '__all__' #book表的全部数据
labels={ #最后胜出input框的显示
'title': '书籍名称',
'price': '价格',
'publishdata': '出版日期',
'authors': '作者',
'publish': '出版社'
}
widgets = {'publishdata': forms.TextInput(attrs={"type": "date"}), #出版日期的input框的格式
'authors':forms.SelectMultiple()} #设置authors为多选
error_messages = { #错误信息
'title':{'required': '输入不能为空'},
'price':{'required':'输入不能为空'},
'publishdata':{'required':'输入不能为空'},
'authors':{'required':'选择不能为空'},
'publish':{'required':'选择不能为空'}
}
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
for field_name,field in self.fields.items():
field.widget.attrs.update({'class':'form-control'}) #input框的样式
# 添加书籍
def add_book(request):
book_model_obj = AddBook() #实例化
if request.method == 'POST':
book_model_obj = AddBook(request.POST) #接收数据
if book_model_obj.is_valid(): #校验
book_model_obj.save() #!!!直接将获取的内容保存到数据库
return redirect('/show_book/')
return render(request,'add_book.html',{'book_model_obj':book_model_obj})
models.py
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=32)
def __str__(self): #!!!如果不写return self.name,在展示的时候只能显示对象
return self.name
class Publish(models.Model):
name = models.CharField(max_length=32)
def __str__(self): #!!!
return self.name
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5,decimal_places=2)
publishdata = models.DateField()
authors = models.ManyToManyField('Author')
publish = models.ForeignKey('Publish',on_delete=models.CASCADE)
add_book.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form action="" method="post" >
{% csrf_token %}
{% for book in book_model_obj %}
<div class="form-group">
{{ book.label }} 显示input框的名称
{{ book }} input框
<span>{{ book.errors.0}}</span> 错误信息
</div>
{% endfor %}
<button class="btn btn-success">提交</button>
</form>
</div>
</div>
</div>
</body>
</html>
用modelform做图书管理系统的编辑功能
view.py
# 编辑书籍modelform
class EditBook(forms.ModelForm):
class Meta:
model = models.Book
fields = '__all__'
labels = {
'title':'书籍名称',
'price':'价格',
'publishdata':'出版日期',
'authors':'作者',
'publish':'出版社'
}
widgets = {'authors': forms.SelectMultiple()}
error_messages = {
'title':{'required': '输入不能为空'},
'price':{'required':'输入不能为空'},
'publishdata':{'required':'输入不能为空'},
'authors':{'required':'选择不能为空'},
'publish':{'required':'选择不能为空'}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs.update({'class': 'form-control'})
# 编辑书籍
def edit_book(request):
edit_book_id = request.GET.get('pid') #获取到编辑对象的pk值
edit_book_obj = models.Book.objects.filter(pk=edit_book_id).first() #获取到编辑对象
edit_model_obj = EditBook(instance=edit_book_obj) #instance设置默认值
if request.method== 'POST':
pass
插入数据库......
return render(request,'edit_book.html',{'edit_model_obj':edit_model_obj})
edit_book.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<form action="" method="post" novalidate>
{% for book in edit_model_obj %}
<div class="form-group">
{{ book.label }}
{{ book }}
</div>
{% endfor %}
</form>
</div>
</div>
</div>
</body>
</html>
同源和跨域
同源机制:域名、协议、端口号相同的同源
简单请求
(1) 请求方法是以下三种方法之一:(也就是说如果你的请求方法是什么put、delete等肯定是非简单请求)
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:(如果比这些请求头多,那么一定是非简单请求)
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain,也就是说,如果你发送的application/json格式的数据,那么肯定是非简单请求,vue的axios默认的请求体信息格式是json的,ajax默认是urlencoded的。
#vue.js axios -- $.ajax
支持跨域,简单请求
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
支持跨域,复杂请求
由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
res['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001'
res['Access-Control-Allow-Headers'] = 'content-type'
# res['Access-Control-Allow-Methods'] = 'PUT'
# res['Access-Control-Allow-Origin'] = '*'
git
1 安装git
2 创建一个文件夹
3 在这个文件夹中执行下面的指令
git init 初始化一个仓库 (git管理的本地仓库)
git status 查看一下当前仓库中的文件或者文件夹的状态,新增的或者修改的都可以查看到
git add 文件名 例如:git add index.html 单纯的管理这个文件
git add . 管理所有的文件
git commit -m '描述' : git commit -m 'v1版本' #git进行了版本管理
CRM
分页
第一版:函数版
from django.shortcuts import render
# Create your views here.
def user_list(request):
user_list = [f"name:'tj--{i}',password:'xxx{i}'" for i in range(1, 200)]
try:
now_page = int(request.GET.get('page', 1))
if now_page <= 0:
now_page = 1
except Exception:
now_page = 1
all_data_count = len(user_list) #获取总数据数量
page_num = 10 #每页显示数据条数
show_page_num,more = divmod(all_data_count,page_num) # 获取分页栏的总页数长度:页码.more代表数据余数也要占一页
max_show_page_num = 9
if more:
show_page_num += 1
page_start = (now_page - 1) * page_num #切片的初始值
end_page = now_page * page_num #切片的终止值
half_show_page_num = max_show_page_num // 2 #页码的一半数,为了当前页码在中间
if show_page_num < max_show_page_num: #总数据/10之后显示的页码数和最大页码数的判断.
#如果总数据/10之后显示的页码数<最大页码数,就显示总数据/10之后显示的页码数
start_page_num = 1
end_page_num = show_page_num
else:
# 页码左边出现的极值情况,出现负数的页码数
if now_page - half_show_page_num <=0:
start_page_num = 1
end_page_num = max_show_page_num
# 页码右边出现的极值情况,页码数过大
elif now_page + half_show_page_num >= show_page_num: #当前页码数+总数据产生的页码数的一半大于总页码数时,有就是右边出现极值大的情况
end_page_num = show_page_num #符合条件就,显示页码的最大值
start_page_num = show_page_num - max_show_page_num + 1 # 符合条件就设置页码的初始值为显示页码的最大值-最大页码数:9
#不加1页码的数量不对
else:
start_page_num = now_page - half_show_page_num # 当前页码 - 页码的一半数,为页码的起始值
end_page_num = now_page + half_show_page_num # 当前页码 + 页码的一半数,为页码的终止值
return render(request,'user_list.html',{"user_list":user_list[page_start:end_page],'page_num':range(start_page_num,end_page_num+1)})
第二版:封装成类
paging.py
from django.utils.safestring import mark_safe
class DQPage:
def __init__(self,current_page_number,total_count,per_page_count=10,page_number_show=7):
"""
:param current_page_number: 当前页码
:param total_count: 总数据量
:param per_page_count: 每页显示多少条
:param page_number_show: 总共显示多少个页码
start_page_number:起始页码
end_page_number:结束页码
"""
try:
current_page_number = int(current_page_number)
except Exception:
current_page_number = 1
half_number = page_number_show // 2
a, b = divmod(total_count, per_page_count)
# 如果余数不为0,页码总数为商+1
if b:
total_page_count = a + 1
else:
total_page_count = a
# total_page_count 最大页数
# 当当前页码小于等于0的时候,默认显示第一页
if current_page_number <= 0:
current_page_number = 1
if current_page_number >= total_page_count:
current_page_number = total_page_count
# current_page_number 2
# print(total_page_count) # 2
start_page_number = current_page_number - half_number #
end_page_number = current_page_number + half_number + 1 # 6
if start_page_number <= 0:
start_page_number = 1
end_page_number = page_number_show + 1 # 7
if end_page_number >= total_page_count: # 6 > 2
start_page_number = total_page_count - page_number_show + 1 # -4
end_page_number = total_page_count + 1 # 3
if total_page_count < page_number_show:
start_page_number = 1
end_page_number = total_page_count + 1
self.current_page_number = current_page_number
self.per_page_count = per_page_count
self.total_page_count = total_page_count
self.start_page_number = start_page_number
self.end_page_number = end_page_number
@property
def start_data_number(self):
if not current_page_number:
current_page_number = 1 #如果不进行判断,如意出现不支持负索引的报错信息,防止出现负索引
return (self.current_page_number - 1) * self.per_page_count
@property
def end_data_number(self):
if not current_page_number:
current_page_number = 1
return self.current_page_number*self.per_page_count
def page_html_func(self):
page_html = """
<nav aria-label="Page navigation">
<ul class="pagination">
"""
first_page = f"""
<li>
<a href="?page={1}" aria-label="Previous">
<span aria-hidden="true">首页</span>
</a>
</li>"""
page_html += first_page
previous_page = f"""
<li>
<a href="?page={self.current_page_number - 1}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>"""
page_html += previous_page
for i in range(self.start_page_number,self.end_page_number):
if i == self.current_page_number:
page_html += f'<li class="active"><a href="?page={i}">{i}</a></li>'
else:
page_html += f'<li><a href="?page={i}">{i}</a></li>'
next_page = f"""
<li>
<a href="?page={self.current_page_number + 1}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
"""
page_html += next_page
last_page = f"""
<li>
<a href="?page={self.total_page_count}" aria-label="Previous">
<span aria-hidden="true">尾页</span>
</a>
</li>"""
page_html += last_page
page_html += """
</ul>
</nav>
"""
return mark_safe(page_html)
把前端代码在后端生成
将后端代码加载成前端代码,在前端直接调用就能生成相应的HTML标签
举个栗子:
1.在前端中写{{page_html}},标签就自动生成,page_html是后端定义的名
2.后端进行标签拼接
from django.utils.safestring import mark_safe
def page_html_func(self):
page_html = """
<nav aria-label="Page navigation">
<ul class="pagination">
"""
first_page = f"""
<li>
<a href="?page={1}" aria-label="Previous">
<span aria-hidden="true">首页</span>
</a>
</li>"""
page_html += first_page
previous_page = f"""
<li>
<a href="?page={self.current_page_number - 1}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>"""
page_html += previous_page
for i in range(self.start_page_number,self.end_page_number):
if i == self.current_page_number:
page_html += f'<li class="active"><a href="?page={i}">{i}</a></li>'
else:
page_html += f'<li><a href="?page={i}">{i}</a></li>'
next_page = f"""
<li>
<a href="?page={self.current_page_number + 1}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
"""
page_html += next_page
last_page = f"""
<li>
<a href="?page={self.total_page_count}" aria-label="Previous">
<span aria-hidden="true">尾页</span>
</a>
</li>"""
page_html += last_page
page_html += """
</ul>
</nav>
"""
return mark_safe(page_html) 将字符串生成标签
查询条件筛选
前端:
<form action="" class="form-inline pull-right" method="get">
<select name="search_field" id="" class="form-control">
<option value="qq__contains">qq</option>
<option value="name__contains">姓名</option>
</select>
<input type="text" class="form-control" id="search_word" placeholder="请输入搜索内容" name="keyword">
<button type="submit" class="btn btn-default">提交</button>
</form>
在form表单中作为提交搜索关键字和数据,请求方法为get,会在url拼路径,后端用GET方法取出搜索关键字和数据,前提先对搜索数据进行模糊匹配:字段名__contains方法
view.py
search_field = request.GET.get('search_field') # 搜索条件
keyword = request.GET.get('keyword') # 搜索数据
# 第一中方法 :models.Customer.objects.filter(**{search_field: keyword}) # (name='陈')
# models.Customer.objects.filter(Q(name__contains='陈'))
if keyword:
q = Q() # 实例化q对象 第二种方法
# q.connector = 'or' #指定q对象中的查询条件的连接符,或的关系,
q.children.append([search_field, keyword]) #等同于 Q(name__contains='陈')
# q.children.append(['name', 'xx']) # 等同于 filter(qq='11',name='xx')
all_customers = models.Customer.objects.filter(q)
# 等同于all_customers = models.Customer.objects.filter(**{search_field: keyword,'name':'xx'}) #filter(qq='11',name='xx')
else:
all_customers = models.Customer.objects.all()
保存搜索条件
前端:
<form action="" class="form-inline pull-right" method="get">
<select name="search_field" id="" class="form-control">
<option value="qq__contains">qq</option>
<option value="name__contains">姓名</option>
</select>
<input type="text" class="form-control" id="search_word" placeholder="请输入搜索内容" name="keyword">
<button type="submit" class="btn btn-default">提交</button>
</form>
view.py
print(request.GET.urlencode()) #search_field=qq__contains&keyword=123 搜索的关键字和搜索数据
request.GET.urlencode()把request.GET获取的querydict类型的的转化为url形式
如果在需要加其他参数,例如:加page = 1
querydict('search_field':"qq__contains",'keyword':123,'page':1)
request.GET.urlencode()--> search_field=qq__contains&keyword=123&page=1把这个作为一个参数传输给分页的组件,组件里设置接收参数,先设置为None,在分页的for循环page中(切片函数),将获得的保存的搜索条件拼接到循环中的a标签中.
!!! 会遇到这个对象(querydict)不可改变的错误信息(this querydict instance is immutable),
解决办法:
1.首先这个是一个querydict对象,查看它的类型type(request.GET)
会得到querydict的类型<class 'django.http.request.QueryDict'>
2.from django.http.request import QueryDict
再里面会执行一个__init__方法,有一个参数mutable=False,即为不可变的.
3.在下面会有一个__copy__的方法,可以使mutable=False改为mutable=True,也就是当执行__copy__就会执行.就能更改mutable
import copy
recv_data = copy.copy(request.GET)
公户和私户 的展示以及转换
公户就是谁都可以看见的信息,私户就是看见个人的客户信息
1.展示可以通过公户和私户的访问路径的不同判断是公户和私户,进行一些页面不同的展示
2.转化:
公户转私户:
def reverse_gs(self,request,customer_list): #reverse_gs公转私的方法
user_obj = models.UserInfo.objects.get(username=request.session.get('username')) #判断是哪个用户登录的
customer_list.update(consultant_id = user_obj.id) #把要公转私的数据客户的consultant_id改成对应销售的id
return redirect(request.path)
私户转公户:
def reverse_sg(self,request,customer_list):# reverse_sg私转公的方法
customer_list.update(consultant=None) #私专供就把选择的对象的consultant改为None
return redirect(request.path)
url编码
带着$,/,=就不是一个完整的值,一个键对应的一个值
url编码:一个完整的值,里面的特殊符号都会被替换成%和特殊符号对应的16进制的值
例如:
next=/customers/?search_field=qq__contains&keyword=1&page=4这个就不是一个完整的值,还有不安全的特殊符号,$导致不完整,
url编码就是把 /,=,$替换成%和特殊符号对应的16进制的值
from django.http.request import QueryDict
def reverse_url(url_name,id,request):
# /editcustomer/3/?next=/customers/?page=4
path = request.get_full_path()
query_dict_obj = QueryDict(mutable=True) #实例化对象,并把QueryDict改为可编辑模式(mutable=True)
query_dict_obj['next'] = path #给QueryDict添加一组键值对,{'next':'/customers/?search_field=qq__contains&keyword=1&page=4'}这个也不是一个完整的值,因为有$,浏览器在读取这个url时会直接读到/customers/?search_field=qq__contains会停止,后面的会读不到,所以说他是一个不完整的值,用url编码就会把这个不完整的值通过urlencode()方法转化为完整的值,完整的路径,读取时也就全部访问
encode_url = query_dict_obj.urlencode() #这步执行urlencode()方法,把 /,=,$替换成%和特殊符号对应的16进制的值:next=%2Fcustomer%2F%3Fsearch_field%3Dqq__contains%26keyword%3D1
事务
from django.db import transaction #事务公转私
def reverse_gs(self,request,cids): #reverse_gs共转私 的方法
with transaction.atomic():
customer_list = models.Customer.objects.select_for_update().filter(id__in=cids,consultant__isnull=True)
if customer_list.count() != len(cids):
return HttpResponse('选择失败了')
admin
class StudyRecordAdmin(admin.ModelAdmin):
list_display = ['id','attendance','score','student',] #可以再admin界面展示更多字段内容
list_editable = ['attendance','score','student',] #可编辑的子弹内容
admin.site.register(models.StudyRecord,StudyRecordAdmin)
modelformset_factory
from django.forms import modelformset_factory
from django import forms
# view.py
class StudyRecordModelForm(forms.ModelForm):
class Meta:
model = models.StudyRecord
fields = '__all__'
class StudyRecord(View):
def get(self,request,course_id):
formset_obj = modelformset_factory(model=models.StudyRecord,form=StudyRecordModelForm,extra=0)#extra=0是一个参数,如果为1,会多生成一条数据
formset = formset_obj(queryset=models.StudyRecord.objects.filter(course_record_id=course_id))
return render(request,'Customer/study_record.html',{'formset': formset})
def post(self,request,course_id):
formset_obj = modelformset_factory(model=models.StudyRecord, form=StudyRecordModelForm, extra=0)
formset = formset_obj(request.POST)
if formset.is_valid():
formset.save()
return redirect(request.path)
else:
return render(request,'Customer/study_record.html',{'formset':formset})
# html
{% extends 'starter.html' %}
{% block title %}
<h1>学习记录</h1>
{% endblock %}
{% block content %}
<form action="" method="post">
{{ formset.management_form }} #是一个标志格式,识别management_form!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{% csrf_token %}
<div class="container-fluid">
<div class="row">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>序号</th>
<th>学生</th>
<th>考勤</th>
<th>课程节次</th>
<th>本节成绩</th>
<th>作业批语</th>
</tr>
</thead>
<tbody>
{% for stu_obj in formset %}
<tr>
{{ stu_obj.id }} #固定格式,stu_objugu不会再前端中显示!!!!!!!!!!!!!!!!!!!!!!!
<td>{{ forloop.counter }}</td>
<td>{{ stu_obj.instance.student }}</td>
<td class="hidden">{{ stu_obj.student }}</td>
<td>{{ stu_obj.attendance }}</td>
<td>{{ stu_obj.instance.course_record }}</td>
<td class="hidden">{{ stu_obj.course_record }}</td>
<td>{{ stu_obj.score }}</td>
<td>{{ stu_obj.homework_note }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<button class="btn btn-success">保存</button>
</div>
</div>
</form>
{% endblock %}
权限
权限机制能够约束用户行为,控制页面的显示内容,也能使API更加安全和灵活;用好权限机制,能让系统更加强大和健壮。因此,基于Django的开发,理清Django权限机制是非常必要的。
举例
通过登录的用户名的角色的不同拥有不同的权限,通过对url的限制,对每个角色分配不同的url
表关系设计
# model.py
from django.db import models
# 用户信息
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
roles = models.ManyToManyField('Role') #与角色为多对多的关系
def __str__(self):
return self.username
# 角色信息
class Role(models.Model):
name = models.CharField(max_length=64)
permissions = models.ManyToManyField('Permission')
def __str__(self):
return self.name
# 权限信息
class Permission(models.Model):
title = models.CharField(max_length=64) #权限名(给哪些内容设置权限)
url = models.CharField(max_length=64) #通过url来设置权限
menu = models.BooleanField(default=False) #一级菜单动态生成权限
icon = models.CharField(max_length=64,null=True,blank=True) #一级菜单的内容图标
def __str__(self):
return self.title
# url.py
from django.conf.urls import url
from web.views import customer
from web.views import payment
from web.views import auth
urlpatterns = [
#登录,主页面
url(r'^login', auth.login,name='login'),
url(r'^home', auth.home,name='home'),
#客户管理
url(r'^customer/list/$', customer.customer_list),
url(r'^customer/add/$', customer.customer_add),
url(r'^customer/edit/(?P<cid>\d+)/$', customer.customer_edit),
url(r'^customer/del/(?P<cid>\d+)/$', customer.customer_del),
#账单管理
url(r'^payment/list/$', payment.payment_list),
url(r'^payment/add/$', payment.payment_add),
url(r'^payment/edit/(?P<pid>\d+)/$', payment.payment_edit),
url(r'^payment/del/(?P<pid>\d+)/$', payment.payment_del),
]
# auth.py
from django.shortcuts import redirect,render,HttpResponse
from django.urls import reverse
# from web import models
from rbac import models
def login(request):
username = request.POST.get('user')
password = request.POST.get('pwd')
ret = models.UserInfo.objects.filter(username=username,password=password).first()
if ret:
print(ret.username)
# 登录认证成功后设置的session认证标识
request.session['is_login'] = 1
# 登录成功后将该用户的全部权限(url)注入到session中
# 1.先找到该用户的角色,在通过角色找到该角色的所有权限
permission_list = models.Role.objects.filter(userinfo__username=username).values('permissions__url','permissions__menu','permissions__icon','permissions__title').distinct()
#print(permission_list)#<QuerySet [{'permissions__url': '/customer/list/'}]>
request.session['permission_list'] = list(permission_list) #将该用户的权限添加注入到session
return render(request,'login.html')
def home(request):
return render(request,'home.html')
# middleware 中间件,设置登录认证和权限
from django.utils.deprecation import MiddlewareMixin
from django.urls import reverse
from django.shortcuts import HttpResponse,render,redirect
import re
class Auth(MiddlewareMixin):
def process_request(self,request):
# 登录认证白名单
white_list = [reverse('login'), ]
# 权限认证白名单
permission_white_list = ['/login/','/admin/*','/home/']
path = request.path #访问的路径
if path not in white_list: #判断访问的路径在不在登录认证的白名单中,如果不在,判断有没有携带session的is_login
is_login = request.session.get('is_login') #获取session值
if not is_login: #如果session不存在表示没有设置session
return redirect(reverse('login')) #跳转到登录界面
#登录成功后
permission_list = request.session.get('permission_list') # 每个用户都有各自的权限,之前登录成功后已经将该用户的所有权限添加到session
for i in permission_white_list: #判断访问的路径在不在权限的白名单中,如果不在就需要权限认证
if re.match(i, path):
break
#权限认证
else:
for permission in permission_list: #循环这个permission_list,[{},{},{}],permission为字典的键,他的url为值
reg = r"^%s$"% permission['permissions__url'] #匹配路径,因为路径是不完全一样的,[{'permissions__url': '/customer/list/'}]>
print(reg)
if re.match(reg,path): #判断访问的路径在不在循环后的权限里
break #如果匹配成功就跳出循环
else:#
return HttpResponse('你不配')#
动态生成一级菜单
每个角色有不同的权限,每个权限有不同的功能,每个功能会展示不同的菜单
中间件......
from django.shortcuts import redirect,render,HttpResponse
from django.urls import reverse
# from web import models
from rbac import models
def login(request):
username = request.POST.get('user')
password = request.POST.get('pwd')
ret = models.UserInfo.objects.filter(username=username,password=password).first()
if ret:
print(ret.username)
# 登录认证成功后设置的session认证标识
request.session['is_login'] = 1
# 登录成功后将该用户的全部权限(url)注入到session中
# 1.先找到该用户的角色,在通过角色找到该角色的所有权限
permission_list = models.Role.objects.filter(userinfo__username=username).values('permissions__url','permissions__menu','permissions__icon','permissions__title').distinct()
#print(permission_list)#<QuerySet [{'permissions__url': '/customer/list/'}]>
request.session['permission_list'] = list(permission_list) #将该用户的权限添加注入到session
# 菜单权限,并注入到session
menu_list =[]
for permission in permission_list:
if permission.get('permissions__menu'):
menu_list.append(permission)
request.session['menu_list'] = menu_list
return redirect('/home/')
return render(request,'login.html')
def home(request):
return render(request,'home.html')
# menu.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="static-menu">
{% for m in menu_data %}
<a href="{{ m.permissions__url }}" class="{{ m.class }}">
<span class="icon-wrap"><i class="{{ m.permissions__icon }}"></i></span>{{ m.permissions__title }}</a>
{% endfor %}
{# 标签是循环不了的,但循环过滤器的是可以的 #}
</div>
</body>
</html>
# mytag.py
from django import template
register = template.Library()
@register.inclusion_tag('menu.html')
def menu(request):
menu_list = request.session.get('menu_list')
for i in menu_list:
if i.get('permissions__url') == request.path:
i['class'] = 'active'
menu_data = {'menu_data': menu_list}
return menu_data
# layout.html
.......
<div class="left-menu">
<div class="menu-body">
{% load mytag %}
{% menu request %} #
</div>
</div>
.......
菜单栏排序
通过给表加一个权重的字段,对这个字段进行排序
class Menu(models.Model):
name = models.CharField(max_length=32)
icon = models.CharField(max_length=32,null=True,blank=True)
weight = models.IntegerField(default=100) # 加权重字段
def __str__(self):
return self.name
# mytag.py
from django import template
from collections import OrderedDict
register = template.Library()
import re
@register.inclusion_tag('menu.html')
def menu(request):
menus_dict = request.session.get('menus_dict')
menus_order_key = sorted(menus_dict,key=lambda x:menus_dict[x]['weight'],reverse=True)
menus_order_dict = OrderedDict()
for key in menus_order_key:
menus_order_dict[key] = menus_dict[key]
path = request.path
for k,v in menus_order_dict.items():
v['class'] = 'hidden'
for i in v['children']:
if re.match(i['url'],path):
v['class'] = ''
i['class'] = 'active'
menu_data = {'menus_order_dict': menus_order_dict}
return menu_data
动态生成二级菜单
一级菜单的表结构已经做不了二级菜单了,需要加表关系关联
例如:
menu表:
id name icon
1 财务系统 .....
2 业务系统 .....
权限表
id title url menu_id(与menu关联)
1 客户展示 /.../ 2 (在业务系统里)
2 添加客户 /.../ None (也在业务系统里,但不作为菜单展示,非菜单权限)
3 删除客户 /.../ None
4 缴费记录展示 /.../ 1 (在财务系统里)
5 纳税记录展示 /.../ 1
# 表结构:
from django.db import models
# from rbac import models
# 用户信息
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
roles = models.ManyToManyField('Role') #与角色为多对多的关系
def __str__(self):
return self.username
# 角色信息
class Role(models.Model):
name = models.CharField(max_length=64)
permissions = models.ManyToManyField('Permission')
def __str__(self):
return self.name
# 一级菜单一级对应关联二级菜单
class Menu(models.Model):
name = models.CharField(max_length=32)
icon = models.CharField(max_length=32,null=True,blank=True)
def __str__(self):
return self.name
# 权限信息
class Permission(models.Model):
title = models.CharField(max_length=64) #权限名(给哪些内容设置权限)
url = models.CharField(max_length=64) #通过url来设置权限
menus = models.ForeignKey('Menu',null=True,blank=True)
def __str__(self):
return self.title
# 生成一二级菜单的数据结构:
'''
{
# 1-- 一级菜单的id
1:{
'name':'业务系统', # 一级菜单的名字
'icon':'fa fa-xx',
'children':[
{'title':'客户管理','url':'/customer/list/',}, # 二级菜单
]
},
# 2-- 一级菜单的id
2:{
'name':'财务系统', # 一级菜单的名字
'icon':'fa fa-xx2',
'children':[
{'title':'缴费展示','url':'/payment/list/',}, # 二级菜单
]
}
}
'''
# auth.py
from django.shortcuts import redirect,render,HttpResponse
from django.urls import reverse
# from web import models
from rbac import models
def login(request):
username = request.POST.get('user')
password = request.POST.get('pwd')
ret = models.UserInfo.objects.filter(username=username,password=password).first()
if ret:
# 登录认证成功后设置的session认证标识
request.session['is_login'] = 1
# 登录成功后将该用户的全部权限(url)注入到session中
# 1.先找到该用户的角色,在通过角色找到该角色的所有权限
permission_list = models.Role.objects.filter(userinfo__username=username).values('permissions__url','permissions__title','permissions__menus__name','permissions__menus__icon','permissions__menus__pk').distinct()
#print(permission_list)#<QuerySet [{'permissions__url': '/customer/list/'}]>
request.session['permission_list'] = list(permission_list)
menus_dict = {}
for menus in permission_list: #获取该用户的所有权限
if menus.get('permissions__menus__pk'): #判断是不是菜单权限,有菜单权限的pk和None,如果拿到了说明有菜单权限
if menus.get('permissions__menus__pk') in menus_dict:#判断该一级菜单的权限是否存在,存在的话就直接添加二级菜单
menus_dict[menus.get('permissions__menus__pk')]['children'].append({
'title':menus.get('permissions__title'),
'url':menus.get('permissions__url')
})
else: #不存在就直接添加完整的字典结构
menus_dict[menus.get('permissions__menus__pk')] = {
'name':menus.get('permissions__menus__name'),
'icon':menus.get('permissions__menus__icon'),
'children':[{
'title':menus.get('permissions__title'),
'url':menus.get('permissions__url')
}]
}
# 1.判断是不是菜单权限,这个判断说明拿到的pk是菜单权限
# 2.判断该一级菜单的键是否存在,如果已经存在,就直接添加二级菜单,如果不存在,就添加一级菜单和对应二级菜单因为会存在有相同的一级菜单,例如财务系统里有缴费记录展示和纳税记录展示,这两个都同属于财务系统,他们的pk值都是相同的,则在循环的时候后边的会把前面的pk相同的值覆盖
request.session['menus_dict'] = menus_dict #注入到session中
return redirect('/home/')
return render(request,'login.html')
# mytag.py
from django import template
register = template.Library()
import re
@register.inclusion_tag('menu.html')
def menu(request):
menus_dict = request.session.get('menus_dict')
path = request.path #根据对路径的判断,加动态的隐藏效果
for k,v in menus_dict.items():
v['class'] = 'hidden' #添加类值,隐藏属性的类
for i in v['children']:
if re.match(i['url'],path): #对路径进行匹配判断,如果为当前url路径,就把隐藏属性改为空,就没有任何效果
v['class'] = '' #把类值改为空,没有任何效果
menu_data = {'menus_dict': menus_dict}
return menu_data
# 加选中状态
# menu.HTML
<div class="static-menu">
<div class="multi-menu">
{% for k,v in menus_dict.items %}
<div class="item">
<div class="title">{{ v.name }}</div>
<div class="body {{ v.class }}">
{% for child in v.children %}
<a href="{{ child.url }}">{{ child.title }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
# layout.HTML
<script>
$('.title').click(function () {
$(this).next().toggleClass('hidden')
})
</script>
生成二级菜单的默认展开效果
问题:当点击二级菜单里的按钮时,二级菜单合上,正常情况下,当点击二级菜单内的按钮功能时,二级菜单是展开的
#最终要判断的就是parent_id和id是否相等,如果相等就代表当前点击的按钮或当前的路径或功能从属于这个二级菜单,就把这个二级菜单加展开的效果
class Permission(models.Model):
title = models.CharField(max_length=64) #权限名(给哪些内容设置权限)
url = models.CharField(max_length=64) #通过url来设置权限
menus = models.ForeignKey('Menu',null=True,blank=True)
parent = models.ForeignKey('self',null=True,blank=True)
Permission表:
id title url menus_id parent_id
1 客户管理 /customer/list/ 1 None
2 添加客户 /customer/add/ None 1
3 编辑客户 /customer/edit/(?P<cid>\d+)/ None 1
4 删除客户 /customer/del/(?P<cid>\d+)/ None 1
5 账单管理 /payment/list/ 2 None
6 添加缴费记录 /payment/add/ None 5
7 编辑缴费记录 /payment/edit/(?P<pid>\d+)/ None 5
8 删除缴费记录 /payment/del/(?P<pid>\d+)/ None 5
9 纳税记录展示 /nashui/ 2 5
添加路径导航,需要添加上面的表结构.
menus_id:1代表客户管理的展示的二级菜单,它从属的一级菜单为业务系统,2代表账单管理和纳税记录,它从属于的一级菜单为财务系统
parent_id是从属于哪个二级菜单,1代表的相应功能是客户管理下的子权限,5代表账单管理的子权限,None代表是二级菜单权限
# auth.py:
from django.shortcuts import redirect,render,HttpResponse
from django.urls import reverse
from rbac import models
def login(request):
username = request.POST.get('user')
password = request.POST.get('pwd')
ret = models.UserInfo.objects.filter(username=username,password=password).first()
if ret:
# 登录认证成功后设置的session认证标识
request.session['is_login'] = 1
# 登录成功后将该用户的全部权限(url)注入到session中
# 1.先找到该用户的角色,在通过角色找到该角色的所有权限
permission_list = models.Role.objects.filter(userinfo__username=username).values('permissions__url',
'permissions__title',
'permissions__pk', #获取二级菜单的pk值
'permissions__menus__name',
'permissions__menus__icon',
'permissions__menus__pk',
'permissions__parent_id', #获取该用户拥有的这个子权限
'permissions__url_names',
'permissions__menus__weight').distinct()
menus_dict = {}
url_names = []
permission_dict = {}
for menus in permission_list:
permission_dict[menus.get('permissions__pk')] = menus
url_names.append(menus.get('permissions__url_names'))
if menus.get('permissions__menus__pk'):
if menus.get('permissions__menus__pk') in menus_dict:
menus_dict[menus.get('permissions__menus__pk')]['children'].append({
'title': menus.get('permissions__title'),
'url': menus.get('permissions__url'),
'second_menu_id': menus.get('permissions__pk') #添加到menus_list中,左后注册到session
})
else:
menus_dict[menus.get('permissions__menus__pk')] = {
'name': menus.get('permissions__menus__name'),
'icon': menus.get('permissions__menus__icon'),
'weight':menus.get('permissions__menus__weight'),
'children': [{
'title': menus.get('permissions__title'),
'url': menus.get('permissions__url'),
'second_menu_id':menus.get('permissions__pk') #添加到menus_list中,左后注册到session
}]
}
request.session['menus_dict'] = menus_dict #注入到session
request.session['url_names'] = url_names
request.session['permission_dict'] = permission_dict
return redirect('/home/')
return render(request,'login.html')
def home(request):
return render(request,'home.html')
# mymiddleware.py:
from django.utils.deprecation import MiddlewareMixin
from django.urls import reverse
from rbac import models
from django.shortcuts import HttpResponse,render,redirect
import re
class Auth(MiddlewareMixin):
def process_request(self,request):
# 登录认证白名单
white_list = [reverse('login'), '/admin/*']
# 权限认证白名单
permission_white_list = ['/login/','/admin/*','/home']
path = request.path #访问的路径
request.pid = None #添加类属性. 二级菜单的展开效果的代码
bread_crumb = [
{'url':reverse('home'),'title':'首页'}
]
request.bread_crumb = bread_crumb
if path not in white_list: #判断访问的路径在不在登录认证的白名单中,如果不在,判断有没有携带session的is_login
is_login = request.session.get('is_login') #获取session值
if not is_login: #如果session不存在表示没有设置session
return redirect(reverse('login')) #跳转到登录界面
#登录成功后
permission_dict = request.session.get('permission_dict') # 每个用户都有各自的权限,之前登录成功后已经将该用户的所有权限添加到session
for i in permission_white_list: #判断访问的路径在不在权限的白名单中,如果不在就需要权限认证
if re.match(i, path):
break
#权限认证
else:
for permission in permission_dict.values(): #循环这个permission_list,[{},{},{}],permission为字典的键,他的url为值,permission为该用户的所有权限
reg = r"^%s$"% permission['permissions__url'] #匹配路径,因为路径是不完全一样的,[{'permissions__url': '/customer/list/'}]>
if re.match(reg,path): #判断访问的路径在不在循环后的权限里
pid = permission.get('permissions__parent_id') #二级菜单的展开效果的代码
if pid: #说明是一个从属于二级菜单的权限
# parent_permission = models.Permission.objects.get(pk=pid) 二级菜单的展开效果的代码
# 父级路径
request.bread_crumb.append({
'url': permission_dict[str(pid)]['permissions__url'],
'title': permission_dict[str(pid)]['permissions__title']
})
# 子权限
request.bread_crumb.append({
'url':permission.get('permissions__url'),
'title':permission.get('permissions__title')
})
request.pid = pid #二级菜单的展开效果的代码
else: #说明现在点击就是二级菜单返回的permissions__parent_id=None,就把pid设置为当前二级菜单权限的pk,在mytag.py里循环会循环到与二级菜单相等的pk
request.bread_crumb.append({
'url':permission.get('permissions__url'),
'title':permission.get('permissions__title')
})
request.pid = permission.get('permissions__pk') #二级菜单的展开效果的代码
break#如果匹配成功就跳出循环
else:
return HttpResponse('你不配')
# mytag.py:
from django import template
from collections import OrderedDict
register = template.Library()
import re
@register.inclusion_tag('menu.html')
def menu(request):
menus_dict = request.session.get('menus_dict')
menus_order_key = sorted(menus_dict,key=lambda x:menus_dict[x]['weight'],reverse=True)
menus_order_dict = OrderedDict()
for key in menus_order_key:
menus_order_dict[key] = menus_dict[key]
for k,v in menus_order_dict.items():
v['class'] = 'hidden'
# print(k)
# print(v)
#{'name': '业务系统',
# 'icon': 'fas fa-address-book',
# 'weight': 100,
# 'children': [{'title': '客户管理',
# 'url': '/customer/list/',
# 'second_menu_id': 1}],
# 'class': 'hidden'}
for i in v['children']:
if request.pid == i['second_menu_id']: #最终要判断的就是parent_id和permission的id是否相等,如果相等就代表当前点击的按钮或当前的路径或功能从属于这个二级菜单,就把这个二级菜单加展开的效果
v['class'] = ''
i['class'] = 'active'
menu_data = {'menus_order_dict': menus_order_dict}
return menu_data
路径导航(面包屑)
当访问一个界面或界面中一个功能时,页面上要显示当前访问的具体的功能名,例如:首页/业务管理/添加客户,一个显示作用,显示当前用户具体操作
bread_crumb = [
{'url': reverse('home'), 'title': '首页'}
] #建立路径导航的数据结构
request.bread_crumb = bread_crumb #添加到类属性中
同过判断当前访问路径生成数据结构
# mymiddleware.py
from django.utils.deprecation import MiddlewareMixin
from django.urls import reverse
from rbac import models
from django.shortcuts import HttpResponse, render, redirect
import re
class Auth(MiddlewareMixin):
def process_request(self, request):
# 登录认证白名单
white_list = [reverse('login'), '/admin/*']
# 权限认证白名单
permission_white_list = ['/login/', '/admin/*', '/home']
path = request.path # 访问的路径
request.pid = None # 添加类属性
bread_crumb = [
{'url': reverse('home'), 'title': '首页'}
] #面包屑的数据结构,只要做一个这样的数据结构就可以,通过当前的访问路径以及对应title添加到数据结构中,当权限认证成功后就可以获得当前访问路径和title
request.bread_crumb = bread_crumb #封装成类属性
if path not in white_list: # 判断访问的路径在不在登录认证的白名单中,如果不在,判断有没有携带session的is_login
is_login = request.session.get('is_login') # 获取session值
if not is_login: # 如果session不存在表示没有设置session
return redirect(reverse('login')) # 跳转到登录界面
# 登录成功后
permission_dict = request.session.get('permission_dict') # 每个用户都有各自的权限,之前登录成功后已经将该用户的所有权限添加到session
for i in permission_white_list: # 判断访问的路径在不在权限的白名单中,如果不在就需要权限认证
if re.match(i, path):
break
# 权限认证
else:
for permission in permission_dict.values(): # 循环这个permission_list,[{},{},{}],permission为字典的键,他的url为值
# print(permission) #该用户的所有权限
reg = r"^%s$" % permission['permissions__url'] # 匹配路径,因为路径是不完全一样的,[{'permissions__url': '/customer/list/'}]>
if re.match(reg, path): # 判断访问的路径在不在循环后的权限里
pid = permission.get('permissions__parent_id')
if pid: # 说明是一个从属于二级菜单的权限
# 父级路径
request.bread_crumb.append({
'url': permission_dict[str(pid)]['permissions__url'],
'title': permission_dict[str(pid)]['permissions__title']
})
# 子权限
request.bread_crumb.append({
'url': permission.get('permissions__url'),
'title': permission.get('permissions__title')
})
request.pid = pid
else: # 说明现在点击就是二级菜单返回的permissions__parent_id=None
request.bread_crumb.append({
'url': permission.get('permissions__url'), #获取当前的访问路径
'title': permission.get('permissions__title')
})
request.pid = permission.get('permissions__pk')
break # 如果匹配成功就跳出循环
else: #
return HttpResponse('你不配') #
# layout.HTML
......
<div>
<ol class="breadcrumb no-radius no-margin" style="border-bottom: 1px solid #ddd;">
{% for crumb in request.bread_crumb %}
{% if forloop.last %} #判断如果是左后一次循环就不添加任何效果
<li>{{ crumb.title }}</li>
{% else %}
<li><a href="{{ crumb.url }}">{{ crumb.title }}</a></li>
{% endif %}
{% endfor %}
</ol>
</div>
......
精确权限
把权限精确到按钮,例如,当一个没有添加,编辑,删除客户的权限时,就不显示对应的按钮
通过判断添加,编辑,删除等按钮的权限在不在该用户的权限里,通过添加表的字段,把URL的别名添加进去,在前端进行判断按钮级别的权限在不在该用户的权限列表里
表:
class Permission(models.Model):
title = models.CharField(max_length=64) #权限名(给哪些内容设置权限)
url = models.CharField(max_length=64) #通过url来设置权限
menus = models.ForeignKey('Menu',null=True,blank=True)
parent = models.ForeignKey('self',null=True,blank=True)
url_names = models.CharField(max_length=32,null=True,blank=True) #每个路径的别名
def __str__(self):
return self.title
# auth.py
from django.shortcuts import redirect,render,HttpResponse
from django.urls import reverse
# from web import models
from rbac import models
def login(request):
username = request.POST.get('user')
password = request.POST.get('pwd')
ret = models.UserInfo.objects.filter(username=username,password=password).first()
if ret:
# 登录认证成功后设置的session认证标识
request.session['is_login'] = 1
# 登录成功后将该用户的全部权限(url)注入到session中
# 1.先找到该用户的角色,在通过角色找到该角色的所有权限
permission_list = models.Role.objects.filter(userinfo__username=username).values('permissions__url',
'permissions__title',
'permissions__pk',
'permissions__menus__name',
'permissions__menus__icon',
'permissions__menus__pk',
'permissions__parent_id',
'permissions__url_names', #获取到该用户的url_names
'permissions__menus__weight').distinct()
#print(permission_list)#<QuerySet [{'permissions__url': '/customer/list/'}]>
# request.session['permission_list'] = list(permission_list)
# 1.判断是不是菜单权限,这个判断说明拿到的pk是菜单权限
# 2.判断该二级菜单的键是否存在,如果已经存在,就直接添加二级菜单,如果不存在,就添加一级菜单和对应二级菜单
# 因为会存在有相同的一级菜单,例如财务系统里有缴费记录展示和纳税记录展示,这两个都同属于财务系统,他们的
# pk值都是相同的,则在循环的时候后边的会把前面的pk相同的值覆盖
menus_dict = {}
url_names = []
permission_dict = {}
for menus in permission_list:
permission_dict[menus.get('permissions__pk')] = menus
url_names.append(menus.get('permissions__url_names')) #把url_names添加到列表中
if menus.get('permissions__menus__pk'):
if menus.get('permissions__menus__pk') in menus_dict:
menus_dict[menus.get('permissions__menus__pk')]['children'].append({
'title': menus.get('permissions__title'),
'url': menus.get('permissions__url'),
'second_menu_id': menus.get('permissions__pk')
})
else:
menus_dict[menus.get('permissions__menus__pk')] = {
'name': menus.get('permissions__menus__name'),
'icon': menus.get('permissions__menus__icon'),
'weight':menus.get('permissions__menus__weight'),
'children': [{
'title': menus.get('permissions__title'),
'url': menus.get('permissions__url'),
'second_menu_id':menus.get('permissions__pk') #二级菜单
}]
}
request.session['menus_dict'] = menus_dict
request.session['url_names'] = url_names #注入到session中
request.session['permission_dict'] = permission_dict
return redirect('/home/')
return render(request,'login.html')
def home(request):
return render(request,'home.html')
# html里的按钮权限:
<div class="btn-group" style="margin: 5px 0">
{% if 'customer_add' in request.session.url_names %}
<a class="btn btn-default" href="/customer/add/">
<i class="fa fa-plus-square" aria-hidden="true"></i> 添加客户
</a>
{% endif %}
</div>
每个按钮都要进行判断
from django.utils.module_loading import import_string
import_string找到项目下的urls.py下的路径,获取文件对象