No.017-Python-学习之路-Django
一. WEB框架简介
web框架的本质即一个socket服务端,所有的web框架本质就是下面一个socketServer;
import socket
def hand_request(client):
buf = client.recv(1024)
print(buf)
client.send(bytes("HTTP/1.1 200 OK\r\n\r\n", encoding="utf-8"))
client.send(bytes("<h1 style='background-color:red;'>Hello World", encoding="utf-8"))
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8000))
sock.listen(5)
while True:
connection, address = sock.accept()
hand_request(connection)
connection.close()
if __name__ == '__main__':
main()
1.1 一个简单的web框架
如下代码中,取自environ中的一写用户反过来信息,然后根据信息进行处理;
from wsgiref.simple_server import make_server
def handle_index():
return ['<h1>Hello, index!</h1>'.encode("utf-8"), ]
def handle_date():
return ['<h1>Hello, date!</h1>'.encode("utf-8"), ]
def RunServer(environ, start_response):
# environ封装所有的客户端发来的所有数据
# start_response 封装要返回给用户的数据,响应头状态
start_response('200 OK', [('Content-Type', 'text/html')])
# 返回给用户的数据
##return ['<h1>Hello, web!</h1>'.encode("utf-8"), ]
current_url = environ['PATH_INFO']
if current_url == '/index':
return handle_index()
elif current_url == "/date":
return handle_date()
else:
return ['<h1>404</h1>'.encode("utf-8"), ]
if __name__ == "__main__":
# 根据ip,端口创建一个socketServer,并设置回调函数RunServer
httpd = make_server('', 8000, RunServer)
print("Serving HTTP on port 8000...")
# 开始监听端口,当有链接进来时,触发回调函数
httpd.serve_forever()
wsgi-Web Session Gateway Interface一种协议,
1.1.2 使key与func代替上面的判断
这样比上面好处在于,如果有新的url,只需修改dict即可;
在真正的WEB框架中,确实是这么来处理url的,唯一改进的是使用re将一类url进行分类处理;
from wsgiref.simple_server import make_server
# 若要返回相关的html页面,理论上就使用这一种方式来处理的
def handle_index():
with open("s2.html", "rb") as f:
data = f.read()
return [data, ]
def handle_date():
with open("s3.html", "rb") as f:
data = f.read()
return [data, ]
# 如果有新的请求在这里面添加参数即可,无需修改下面的东西
ULR_DICT = {
"/index": handle_index,
"/date": handle_date
}
# 此函数内定义框架内主要的处理逻辑
def RunServer(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
current_url = environ['PATH_INFO']
func = None
if current_url in ULR_DICT:
func = ULR_DICT[current_url]
if func:
return func()
else:
return ['<h1>404</h1>'.encode("utf-8"), ]
if __name__ == "__main__":
# 根据ip,端口创建一个socketServer,并设置回调函数RunServer
httpd = make_server('', 8000, RunServer)
print("Serving HTTP on port 8000...")
# 开始监听端口,当有链接进来时,触发回调函数
httpd.serve_forever()
1.1.3 MVC与MTV
MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),具有耦合性低、重用性高、生命周期成本低等优点;
所有的文件包括py文件,html文件,数据文件等都放置在同一目录下会显得非常的乱,所以一般我们会将他们进行分类存放,即新建文件夹进行存放,方式大约有2种:
- MVC类型:Model里放置数据库文件,View里放置模板文件,Controller放置业务处理;
- MTV类型:Model里放置数据库文件,Template存放模板文件,View放置业务处理;
Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和View(视图),也就是MTV框架。
1.2 HTTP协议
超文本传输协议(Hyper Text Transfer Protocol, HTTP)
- 工作在应用层,最著名的是用于web浏览器与web服务器之间的双向通信;
版本迭代:
- 最广泛使用版本为HTTP1.1 由IETF 1999年6月发布,文档RFC 2616。
- HTTP2.0在2015年5月已RFC 7540的方式发布;
如果需要详细了解推荐:<<HTTP权威指南>>
1.2.1 HTTP请求的方法
在
HTTP1.0
中定义了三种请求方法:GET
,POST
和HEAD
方法,在HTTP1.1
中新增了6种。
No. | 方法 | 描述 |
---|---|---|
01 | GET |
请求指定的页面信息,并返回实体主体。 |
02 | HEAD |
类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头。 |
03 | POST |
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 |
04 | PUT |
从客户端向服务器传送的数据取代指定的文档的内容。 |
05 | DELETE |
请求服务器删除指定的页面。 |
06 | CONNECT |
HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
07 | OPTIONS |
允许客户端查看服务器的性能。 |
08 | TRACE |
回显服务器收到的请求,主要用于测试或诊断。 |
09 | PATCH |
是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
1.2.2 HTTP状态吗
所有HTTP响应第一行都是状态行,依次为当前HTTP版本号,3为数字的状态吗,描述状态码的短语(可以自定义),彼此由空格分隔;
- 1xx消息:请求已被服务器接收,还需要等等,在处理;
- 2xx成功:请求已被服务器接收,理解,并接受;
- 3xx重定向:需要后续操作才能完成这一请求;
- 4xx请求错误:请求含有此法错误或者无法被执行;
- 5xx服务器错误:服务器在处理某个正确请求时发生错误;
1.2.3 URL
HTTP统一资源定位符,使用5个基本元素唯一标识Internet中的某一资源;
5个基本元素分别是:
- 传输协议;
- [可选]访问资源需要的凭证信息;
- 服务器,通常为域名及IP;
- 端口,不加使用默认端口;
- 路径
URL后的查询方式有两种:
- GET传参方式在以上中添加?n=1&n=2
- #...,在HTML中的锚点跳转;
1.2.4 请求格式
请求具体格式如下:
GET /user/home/ HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
请求数据...
1.2.5 响应格式
响应具体格式如下:
HTTP/1.1 200 OK
Date: Sat, 19 Sep 2020 13:13:38 GMT
Server: WSGIServer/0.2 CPython/3.8.0
Content-Type: text/html; charset=utf-8
X-Frame-Options: DENY
Content-Length: 434
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
响应正文
二. Django
2.1 简单使用
2.1.1 安装django
# 安装django
pip install django
2.1.2 建立project[工程]
安装完成后,会在Python/scripts目录生成两个程序
用来生成相关的project文件
# 生成project文件mysite
django-admin.exe startproject mysite
# 提交相关的表结构到自带的sqlite数据库->默认会生成一些
python manage.py migrate
# 测试运行下project,默认为8000端口
python manage.py runserver
python manage.py runserver 127.0.0.1:8001
# 访问
生成project的目录结构
firstDjango
- firstDjango ##整个程序的配置
- init
- asgi.py # Django3.0新出的异步功能
- settings.py # Django需要的全局配置文件
- urls.py # 设置url入口<url对应关系>
- wsgi.py # django不负责socket,这里配置遵循wsgi规范socketserver,推荐uwsgi+nginx
- db.sqlite3 # 自带的sqlite数据库
- manage.py # 但凡需要管理Django程序,都是通过manage.py来做的
- python manage.py
- python manage.py startapp xx
- python manage.py makemigrations
- python manage.py migrate
通过pycharm 来创建django project
只需要在file/new project/django即可
如果cmd下建的project,可手动配置相关项目,注意点在与需要这两个环境变量设置
PYTHONUNBUFFERED=1;DJANGO_SETTINGS_MODULE=firstDjango.settings
第一个自定义响应
根目录下新建一个文件夹myFirstApp,新建s1.py
from django.shortcuts import HttpResponse
def say_hello(request):
return HttpResponse("hello web, Hello Django")
在firstDjango/firstDjango下的urls.py添加映射
from django.contrib import admin
from django.urls import path
from myFirstApp import s1
urlpatterns = [
path('admin/', admin.site.urls),
path("index/", s1.say_hello)
]
2.2.3 工程下面的app[ 应用]
一个正常的网站往往包含很多完全不同的功能,比如任意一个正常的网址最起码包含这几项:
myProject
- myProject
- 配置
- 主站app
- 后台管理app
使用manage.py来建立app/在pycharm中
python manage.py startapp cmdb
python manage.py startapp openstack
这样会在工程的目录下生成响应的APP目录,目录结构如下
cmdb
- migrations # 数据库表结构操作记录
- admin.py # Django提供的后台管理
- apps.py # 配置当前app
- models.py # ORM,写指定的雷,通过命令创建数据库结构
- tests.py # 单元测试
- views.py # 业务代码
需要在django中注册app
#方式共两种:
INSTALLED_APPS = [
# 方式一,直接写名字即可,这种是能让django知道有这个app
'app01',
# 方式二,调用apps中的类,推荐这种,某些时候可以复写这个类
'app01.apps.App01Config'
]
如下,新增cmdb应用
# firstDjango/firstDjango/urls.py
from django.contrib import admin
from django.urls import path
from cmdb import views as cmdbv
from openstack import views as openstackv
urlpatterns = [
path('admin/', admin.site.urls),
path("cmdb/", cmdbv.say_hello),
path("openstack/", openstackv.say_hello)
cmdb/view.py
from django.shortcuts import HttpResponse
def say_hello(request):
return HttpResponse("CMDB")
2.2.4 整体django工程流程
-
通过pycharm或者django-admin新建工程;
-
在工程根目录下新建templates目录用于存放html相关文件;
# 新建templates后,需要在setting中进行设置: TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', # 将相关目录添加到该位置 'DIRS': [BASE_DIR / 'templates'] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
-
在工程根目录下新建 static文件夹用于存放js,css等html中的静态文件;
# 工程根目录下新建static目录 STATIC_URL = '/static/' # 配置静态文件存放目录[css,js文件所在目录] STATICFILES_DIRS = ( [BASE_DIR / 'static'] )
-
新建app,并配置业务代码;
python manage.py startapp BruceApp # 配置views from django.shortcuts import render # Create your views here. def login(request): return render(request, 'login.html')
-
将相关业务程序导入urls并配置映射
from django.contrib import admin from django.urls import path from BruceApp import views urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), ]
2.2.5 报错处理
-
CSRF 异常告警,中间件的一个东西,暂时放放...
处理方法:
将settings.py中的django.middleware.csrf.CsrfViewMiddleware注释掉<暂时这么处理>
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middlewared.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
-
RuntimeError at /login
按照提示里面的操作进行处理即可,主要原因是在form中action中是/login/,则在urls必须为login/,form中 比urls中多的前面那个/,其代指本地域名<完整的意思就是跳转到本地的login/这个url地址中>
还有注意排错的时候,需要将程序手动关闭并重启;
2.2.6 静态文件引入
所有的图片,css,js,plugins等html中会用到的文件均建议放置在此文件夹。
django中setting.py中配置static目录
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/' # 调用static文件夹们时使用的url,并不是static的文件夹的名字
STATICFILES_DIRS = [ # 指定static文件夹的路径
BASE_DIR / 'static', # 因为时列表当然可以指定多个
BASE_DIR / 'static1',
BASE_DIR / 'static2',
]
# 注意在调用的时候只能使用/static/而非使用/static1-2/等,使用/static/时,程序会依次在列表中找资源
建议目录分类
static
- css
- js
- img
- plugins
2.2.7 简单使用django总结
# 新建django工程
django-admin startproject mydjango
# 新建app
cd mydjango
python manage.py startapp cmdb
# 添加static静态文件目录
## 新增mydjango.static目录
## mydjango.setting中添加如下配置
STATICFILES_DIRS = (
[BASE_DIR / 'static']
)
# 添加templates目录
## 新增mydjango.templates目录
## mydjango.setting中添加如下配置
TEMPLATES = [
{
# 将相关目录添加到该位置
'DIRS': [BASE_DIR / 'templates']
},
]
# 定义路由规则
## 在mydjango.urls.py在中新建规则
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.login),
]
# 定义视图函数
## 在app.views.py下定义,并导入mydjango.urls.py中
request.method # 获取用户请求的类型
## 简单获取数据
request.POST.get('keyname', None) # POST过来的数据,类似与一个字典,使用get获取;
request.GET.get('keyname', None) # GET过来的数据,类似与一个字典,使用get获取;
## 简单回应
return HttpResponse("str")
return render("request", "HTML template lujing ")
return redicrt('url') #这个跳转是301告诉客户端需要跳转,所以天template是不合理也不行的
# 模板渲染
## 在django中存在特殊的模板语言
### 在视图函数中给模板传数据
def func(request):
return render(request, "index.html", { "error": "error_infunc", 'error_list': [1, 2, 3]})
###变量替换:
{{ error }}
###对list取值:
{{ list.0 }}
###对dict取值:
{{ dict.key1 }}
###循环语句1####
{% for row in error_list%}
<li>{{row}}</li>
{% endfor %}
###判断语句1####
{%if age %}
{%if ange > 16%}
...
{% endif%}
{%else%}
...
{%endif%}
2.2 Django请求生命周期
在一个请求发送到django页面时,会经过一个基本的处理过程然后返回结果,这个过程即是生命周期;
具体的请求的流程:
- 请求发送到wsgi,wsgi封装请求的相关数据(request);
- django去匹配路径,根据路径判断要执行哪个函数;
- 执行函数,函数处理具体的业务逻辑;
- 函数返回响应,django按照HTTP协议的相依的格式进行返回;
发送请求的途径:
- 在浏览器的地址栏种输入地址,回车发出get请求;
- a标签,发出的是get请求;
- form表单提交,默认get,一般使用post方式;
2.3 获取数据
2.3.1 从HTML标签获取
在html的form表单中有多种方式提交数据,form表单中提交的数据由在http协议发向提交的url:
- input text标签
- input radio标签
- input checkbox标签
- input file标签
- select options标签
有些标签传递是单个值,有些则传递是多个值:
- get("key", "default") 方式获取单个值,若获取单个值,则只获取最后一个值;
- getlist("key", "default") 方式获取多个值,返回一个列表;
- 上传文件通过FILES来获取对象,通过name获取的是名字,接收需要使用file.chunks,具体看例中;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传信息至后端</title>
</head>
<body>
<div>
<!-- action指定url为本域名的 upload/ 使用http.post方法,支持传文件-->
<!-- 文件的上传不能使用默认的编码方式,需要使用mutipart/form-data编码方式才行-->
<form action="/upload/" method="post" enctype="multipart/form-data">
<p>
<!-- 在视图函数中用name来取值 -->
<input type="text" name="name" placeholder="name">
</p>
<p>
<label for="#s1">男:</label><input id="#s1" type="radio" name="sex" value="男">
<label for="#s2">女:</label><input id="#s2" type="radio" name="sex" value="女">
</p>
<p>
ball:<input type="checkbox" name="favor" value="ball">
run:<input type="checkbox" name="favor" value="run">
cpu:<input type="checkbox" name="favor" value="cpu">
</p>
<p>
<select name="city" size="3" multiple="multiple">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
</p>
<p>
照片上传:<input type="file" name="photo">
</p>
<p>
<input type="submit" value="提交">
<input type="reset" value="重置">
</p>
</form>
</div>
</body>
</html>
2.3.2 从http请求中获取
在视图函数中,所有从client处获取的数据均由request这个形参来接收:
- http.post 使用request.POST来接收;
- http.get 使用request.GET来接收;
- 文件通过request.FILES来接收;
- 可以使用request.path_info获取当前在用的url,在template和视图中均可使用;
def upload(request):
if request.method == "GET":
return render(request, "upload.html")
elif request.method == "POST":
print("name", request.POST.get("name", "无名"))
print("sex", request.POST.get("sex", "无性别"))
print("get_favor", request.POST.get("favor", "无爱好"))
print("getlist_favor", request.POST.getlist("favor", "无爱好们"))
print("get_city", request.POST.get("city", "无城市"))
print("getlist_city", request.POST.getlist("city", "无城市们"))
# 文件提交
file = request.FILES.get('photo')
# file是文件对象,存在内存中;
print("file", file.name)
path = os.path.join("BruceApp", "receive")
# 建立文件句柄,然后将文件一点点写入句柄;
with open(os.path.join(path, file.name), "wb") as f:
# 获取二进制方式一
for chunk in file.chunks():
f.write(chunk)
# 直接通过文件对象获取
for i in file():
f.write(i)
# 注,可以看到上传的文件类型判断其实是通过文件名来判断的,二进制流无法判断的;
return render(request, "upload.html")
[17/Sep/2020 13:01:50] "POST /upload/ HTTP/1.1" 200 1240
name alvin
sex 女
get_favor run
getlist_favor ['ball', 'run']
get_city 广州
getlist_city ['上海', '广州']
file 仓库库存.xlsx
2.4 视图
2.4.1 之FBV与CBV
Django同时支持FBV(function base view)与CBV(class base view)这两种对应关系,两种没有好坏之分,建议两种都用;
2.4.1.1 FBV
在此种方式中view中的url对应的是function
视图函数:
def home(request):
if request.method == "GET":
pass
elif request.method == "POST":
USER_LIST.append({'user': request.POST.get('user1'),
'pwd': request.POST.get('pwd'),
'mail': request.POST.get('mail')})
# 通过dict将USER_LIST映射给模板中的user_list
# 模板中的user_list接到数据后,会执行循环语句遍历并替换
return render(request, 'home.html', {"user_list": USER_LIST})
urls:
urlpatterns = [
path('home/', views.home),
]
2.4.1.2 CBV
在此种方式中view中的url对应的是一个类<此类事views类的子类>
支持的HTTP请求方法类型:
['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
用户发来请求前:
- 调用as-view(**kwargs)方法,返回view方法;
- 在view方法的处理方式:
- 使用获取的相关参数实例化cls;
- 使用的setup方法初始化一些属性;
- self.request = request
- 调用dispatch方法;
用户发来请求:
- 首选django会根据url来找到指定的function或者class;
- 然后在根据methon来选择具体的处理方式;
- views会使用dispatch来判断请求的类型,从而调用类中不同的函数:
view中的dispatch
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
视图类
from django.views import View
class Home(View):
def dispatch(self, request, *args, **kwargs):
print("在进请求前,可以做些事情")
result = super(Home, self).dispatch(request, *args, **kwargs)
print("当请求处理完毕,可以做些事情")
# 这里为什么要return呢?
# 原因一个请求过来,终端要拿到返回,还是需要通过dispatch拿到结果;
# 如get方法,返回值是返回给dispatch,dispatch再返回给用户;
return result
def get(self, request):
return HttpResponse("get..")
def post(self, request):
return HttpResponse("post...")
可以额外做的操作
# 在dispatch中有这个检测:
if request.method.lower() in self.http_method_names:...
# 所以可以修改http_method_names来限制可使用的http method
http_method_names = ['get'']
# 在dispatch中对调用前及调用后的数据进行处理...
2.4.1.3 装饰器
装饰器常用来在不改变函数代码的情况下,为其添加一定的功能;
装饰器
# 这是一个普通的装饰器
def time_cost(func):
def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print("spend time", time.time() - start)
return res
return inner
# 但是这个装饰器有个问题,就是其函数相关的所有信息会从func变为inner的信息,比如fun.__name__,fun.__info__等;
# 使用wraps可以将func的相关信息保留并在被装饰完成后重新交给func
from functools import wraps
def time_cost(func):
@wraps(func)
def inner(*args, **kwargs):
'''from inner'''
start = time.time()
res = func(*args, **kwargs)
print("spend time", time.time() - start)
return res
return inner
在装饰视图时,我们一般使用这种装饰器结构,让request独立出来,方便调用;
from functools import wraps
def time_cost(func):
@wraps(func)
def inner(request, *args, **kwargs):
start = time.time()
res = func(reuqest, *args, **kwargs)
print("spend time", time.time() - start)
return res
return inner
@time_cost # FBV方式直接装饰即可
def get(request):
return HttpResponse("hello decorator...")
装饰fbv如上,装饰cbv如下:
from django.views import View
# 需要导入method_decorator
# 作用是让传入的第一个参数为request,与FBV方式相同,而非self即当前对象;
from django.utils.decorators import method_decorator
@method_decorator(time_cost, name='post') # 可以写在类上面,并指定被装饰方法
@method_decorator(time_cost, name='dispatch') # 可以写在类上面,并指定被装饰方法
class Home(View):
@method_decorator(time_cost) # 为所有的方法添加装饰器
def dispatch(self, request, *args, **kwargs):
result = super(Home, self).dispatch(request, *args, **kwargs)dispatch
return result
@method_decorator(time_cost) # 为单独的某个方法添加装饰器
def get(self, request):
return HttpResponse("get..")
@method_decorator(time_cost) # 为单独的某个方法添加装饰器
def post(self, request):
return HttpResponse("post...")
2.4.2 request对象
HTTP协议发送过来的内容由wsgi封装成一个request对象;
属性:
request.method # HTTP的请求方法,get,post,put,delete等
request.GET #URL携带的的参数,不仅仅是get请求,只要是url中?k1=v1&k2=v2都会放到GET中
request.POST #post请求提交的数据
request.path_info #路径信息,不包含ip,port,?参数
request.body # 获取发过来请求体,原始数据经过urlcode编码,发送给后台,后台可通过这个解析出post的相关数据;
request.META # 获取请求头里面的所有信息,返回的是一个字典,变更(小写->大写,添加HTTP_开头)
request.COOKIES # 返回字典,cookies信息
request.session # 可读可写的类字典对象,表示当前的会话,只有当django启用会话支持时才使用;
request.FILES # 长传的文件
# 其他一些不常用的
request.scheme # 获取协议类型,如http or https等
request.user # 返回当前的用户,这个是jango中的一套认证系统,大部分情况下不适用;
方法:
request.get_host() # 根据META里的信息获取请求主机名
request.get_full_path() # 相对于path_info的不同点在于会带上?参数
request.get_signed_cookie() # 获取一个加密的cookie
request.is_secure() # 是否是安全的,即请求是不是通过HTTPS发起的
request.is_ajax() # 判断请求是否是ajax请求;
2.4.3 response对象
from django.shortcuts import HttpResponse, render, redirect, JsonResponse
HttpResponse('str') # 返回字符串,最根本的返回对象;
render(request, 'template_namae', {参数字典}) # 渲染HTML,生成相关字符串;
redirect('url') # 重定向, 注意重定向环路
JsonResponse({'foo': 'bar'}) # 用于前后端分离,发送的是json响应数据;
关于render
def render(request, template_name, context=None, content_type=None, status=None, using=None):
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
# 可以看到是使用loader.render_to_string的方法来对渲染html文件的,这里面可以替换使用jinja2的方式来渲染;
# 可以看到传递的除了我自定义的context,同时也传递了request对象,所以默认情况下template就可以理解request对象,即支持{{ request }}这个方法;
关于redirect
# 返回的是3xx状态 + 在head中添加Location:url 即完成跳转;
# 如下用HttpResponse来实现
ret = HttpResponse('', status=301)
ret['Location'] = '/publisher_list/' # 使用setattr来赋值属性;
关于jsonResponse
# 返回时json序列后的str,同时返回content_type设置为json类型
# 如下用HttpResponse来实现
ret = HttpResponse(json.dump({'k1':'v1'})) # 返回字符串,最根本的返回对象;
ret['Content_type'] = "application/json" # 返回类型为json类型;
# 以上等同于以下方式:
JsonResponse({'k1':'v1'}) # 用于前后端分离,发送的是json响应数据;
2.5 模板语言
在django种有两种特殊符号{{}}和{% %},前者表示变量,后者表示逻辑相关的操作;
2.5.1 数据
2.5.1.1 变量
使用{{ var }}来定义,var为变量名,var会在渲染的时候被替换成对应的值;
- 值的处理相当于python种print对数据的处理;
- 可以使用“.”来取列表种值,取属性等,如{{ list.0 }},{{ obj.name }};
- list不支持-n取值,不如{{ list.-1 }};
- 因为方法后不加括号,所以一般不可以加参数;
- 取值优先级:.key > .属性 .方法 > .索引;
2.5.1.2 字典的传递与遍历
与在python中的遍历相似,默认循环取key,可以指定只去value,items返回key:value元组;
字典具有方法,可以使用诸如:
- dict.keys
- dict.values
- dict.items
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>dict</title>
</head>
<body>
<div>
<ul>
{% for key in my_dict %}
<li>{{ key }}</li>
{% endfor %}
</ul>
<ul>
{% for value in my_dict.values %}
<li>{{ value }}</li>
{% endfor %}
</ul>
<ul>
{% for key, value in my_dict.items %}
<li>{{ key }}-{{ value }}</li>
{% endfor %}
</ul>
</div>
</body>
</html>
2.5.1.3 过滤器
有时候传入的值,我们还想做些修改,就会用到过滤器,即filter,过滤器最多只能有1个参数;
#通用语法
{{}}
{{ baby|default: "Alvin"}} # 如果不传或者传空值则使用这个默认值,":"左右不可有空行注意
{{ filesize|filesizeformat }} # 在数字后边添单位,byte
{{ 4|add:2 }} # 一个加法作用,支持数字加法,字符串即列表拼接
{{ Bruce|lower }} # 将字符变为小写;
{{ Bruce|upper }} # 将字符变为大写;
{{ Bruce|title }} # 将首字母大写;
{{ list|length }} # 返回变量长度
{{ list|slice:'-1:-3:-1' }} # 列表切片,注意如果是反向取值的话,步长也要指定,否则取不到;
{{ list|first }} # 取第一个元素
{{ list|last }} # 取第二个元素
{{ list|join:'--' }} # 即list的拼接str方法
{{ longStr|truncatechars:'n'}} # 取n-3个字符+...构成返回用来处理长字符串
{{ longStr|truncatewords:'n'}} # 取n个单词+...构成返回;
{{ now|date:'Y-m-d H:i:s' }} # 格式化datetime对象,不一样地方在于i表示分,s表示s;
{{ a|safe }} # 默认情况下模板传递的html标签会被转义为字符串,如果想使用需要safe关闭转义
格式化时间可以在工程中的setting种做调整
USE_L10N = False
# datetime类型
DATETIME_FORMAT = 'Y-m-d H:i:s'
# date类型
DATE_FORMAT = 'Y-m-d'
# time类型
TIME_FORMAT = 'H:i:s'
2.5.2 语句
2.5.2.1 for循环
{% for item in list %}
...
{% endfor}
for中的一些特殊值
Variable | Description |
---|---|
forloop.counter |
当前循环的索引值(从1开始) |
forloop.counter0 |
当前循环的索引值(从0开始) |
forloop.revcounter |
当前循环的倒序索引值(到1结束) |
forloop.revcounter0 |
当前循环的倒序索引值(到0结束) |
forloop.first |
当前循环是不是第一次循环(布尔值) |
forloop.last |
当前循环是不是最后一次循环(布尔值) |
forloop.parentloop |
本层循环的外层循环 |
如果循环的内容是空,加一些显示
{% for item in kong %}
如果不空这个生效
{% empty %}
如果时空循环显示这个
{% endfor %}
2.5.2.2 条件语句
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断;
{% if age > 73 %}
老年人
{% elif age > 40 %}
中年人
{% else %}
年轻人
{% endif %}
注意:本身判断里面时不支持加减乘除的,但是可以使用filter来实现,比如:
{% if age|add:3 > 73 %}
3年后就成为老年人了
{% endif %}
注意:不支持连续判断,比如 7 > 5 > 1
- 在python中表示一个and运算,即7 > 5 and 5 > 1 所以返回true;
- 而在js和模板语言中时按照顺序来判断的,7>5为true(1),而true(1) > 1为false,返回false;
2.5.2.3 with定义中间变量
#写法1:为某个值取个别名,在with的范围内都可以使用
{% with business.employees.count as total %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
#写法2:为某个值取个别名,在with的范围内都可以使用
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
2.5.2.4 csrf_token
这个标签用于跨站请求伪造保护,添加在form标签内;
<form method="post">
{% csrf_token %}
<input type="text" name="name">
<input type="submit" value="submit">
</form>
这个是将form进行了相关的渲染,生成认证用的字符串;
2.5.3 页面组建
对于一个Project中的不同页面,有一部分页面是相同的,可以使用母版与继承来处理;
2.5.3.1 母版
一个包含多个页面的公共部分,定义多个block块,让子页面进行覆盖;
# 在母版中指定
{% block main %} #可以添加名称,用来定义多个block块
....替换的内容
{% endblock %}
{% block css %}
各子类在里面定义自己独有的样式
{% endblock %}
{% block js %}
各子类在里面定义自己独有的 js
{% endblock %}
2.5.3.2 继承
其他业务html可以继承母版,并重新复写block块;
注意点:
- 在引用时,加不加引号时两种不同的结果;
- 要显示的内容需要放置block块中,如果不在block中则无法替换;
- 子html中 有时需要自己的样式,这些可以在css处及js处定义css block块及js block块;
# 继承母版内容的方式
# 直接写母版的名称,要加引号;
{% extends ‘mother.html’ %}
# 不加引号实际为变量,需要在render时传参;
{% extends mother %}
render(request, "template.name", {'name':'mother.html'})
{% block main %}
子页面html
{% endblock %}
2.5.3.3 组件
有时候有些内容在很多的页面中都有调用,此时可以使用组件的方式进行操作;
# 把一小段公用的HTML文本写入一个HTML文件,如nav.html中;
# 在需要该组件的模块中:
{% include 'nav.html' %}
2.5.3.4 静态文件
静态文件的引入方式有两种一种是url直接引入,另外一种使用load来引入:
<!--引入方式一:存在问题在变更url名称时,更改的地方过多-->
<link href="/static/css/dashboard.css" rel="stylesheet">
<!--引入方式二:通过语法去setting中获取url并跟相对路径进行拼接-->
{% load static %}
<link href="{% static 'css/dashboard.css' %}" rel="stylesheet">
<!--补充:获取静态文件的url,即setting中的配置的url-->
<!--补充:也可以使用这种方式拼接静态文件的引用-->
<link href="{% get_static_prefixe %}css/dashboard.css" rel="stylesheet">
2.5.3.5 django内默认寻找顺序
django内部有一个逻辑的顺序来寻找static及templates文件夹;
在django内部,如果使用html模板/或者static文件<{%static 'dir'}导入>时, 先查找最外层目录有没有static或templates文件夹,如果没有则会按照app注册顺序,顺序的查找每个app直到找到为止;
所以, 如果默认配置名称为static及templates时,可以不再setting中配置TEMPLATES的DIRS及STATICFILES_DIRS.
注: 基于这样的规则,有时候我们找到的并不是我们想要的, 比如我找的是rbac/templates/login.html, 但是找到的却是它前面一个app-web下的login.html; 可以在templates下面新建文件夹用于区分, 比如在rbac/templates/中新建rbac目录存放login.html, 在调用的时候使用, 如下:
@register.inclusion_tag('rbac/menu.html') # 加rbac的原因, 模板寻找的顺序
def create_menu(request):
menu = request.session.get(settings.SESSION_MENU, None)
current_url = request.path_info
print(menu)
for row in menu:
if re.match("^%s$" % row['menu__url'], current_url):
row['class'] = "active"
return {'menu_info': menu}
2.5.4 tag自定义
有时候模板中处理的东西并不满足我们的需求,所以需要自定义,总共有三种自定义
2.5.4.1 filter自定义
filter是唯一可以放置在if中的标签,其他两个会报错;
-
在app下创建一个名为templatetags的python包,固定写法;
-
创建一个python文件,文件名自定义;
-
文件内固定写法:
from django import template register = template.Library() # register名字不能错
-
写函数 + 装饰器
@register.filter # 固定装饰器 def has_permission(request, palias): # request是装饰的变量, palias是参数 permissions = request.session.get(settings.SESSION_PER, {}) if palias in permissions: return True else: return False
-
调用
# 在template中调用tag # 注意如果报错,记得重启django进程 {% load rbac %} # filter_tag可以在if调用 # 同系统filter_tag一样的调用方式 {% if request|has_permission:'customer_import' %} <a class="btn btn-default" href="{% url 'customer_import' %}"> <i class="fa fa-file-excel-o" aria-hidden="true"></i> 批量导入 </a> {% endif %}
2.5.4.2 simple tag自定义
filter的自定义可以接受的参数太少了,所以也可以使用simple tag,更像一个函数;
前面步骤同filter,装饰器及变量灵活
@register.simple_tag
def plus(*args, **kwargs):
return "{}_{}".format('_'.join(args), "*".join(kwargs.values()))
调用方式
# 导入
{% load app01_demo %}
# 使用与传参
{% plus "1" "2" "3" k1="d" k2="e" k3="f" %}
但是simple tag时无法在判断等语句种使用的,而filter时可以的;
2.5.4.3 inclusion_tag自定义
simple tag相当于函数,但是在生成html代码段的时候不太方便,这时候就用到inclusion_tag来处理;
前面步骤同filter,
# templatetags/my_inclusion.py种内容
from django import template
register = template.Library()
@register.inclusion_tag('result.html')
def show_results(n):
n = 1 if n < 1 else int(n)
data = ["第{}项".format(i) for i in range(1, n+1)]
return {"data": data}
inclusion_tag中需要传入模板templates/result.html
<ul>
{% for choice in data %}
<li>{{ choice }}</li>
{% endfor %}
</ul>
然后再模板中调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>inclusion_tag test</title>
</head>
<body>
{% load my_inclusion %}
{% show_results 10 %}
</body>
</html>
2.5.4.4 三种自定义总结
- filter只能有一个变量,但是优点时可以写在判断内,而其他两个不行;
- simple tag支持不限量变量数,返回的是一个具体的值,但是再渲染html时不太灵便;
- inclusion_tag用来生成灵活调整的组件时非常有用,比如分页的页标;
2.6 路由系统
URL配置(URLconf)就像Django所支撑网站的目录,它的吧恩智是URL与要为URL调用的视图函数之间的映射表;我们已这种方式告诉DJangon遇到那个URL时,要执行哪个函数;
-
匹配顺序是从上到下的;
-
默认情况下url最后加不加“/”是由django来自动添加的,如果不想自动添加,可以settinging中关闭:
APPEND_SLASH=True
2.6.1 对应关系类型
如果确定项目的urls.py位置呢,其实是在setting中设置的:
ROOT_URLCONF = 'FcNetWeb.urls'
2.6.1.1 一对一对应
一个url对应一个function/class
在处理详细显示这个需求时,可以使用get方法进行传参
- 在urls中添加两个url,dict和details
urlpatterns = [
path('dict/', views.Dict.as_view()),
path('details/', views.Details.as_view()),
]
- 在views新建两个视图类
MY_DICT = {
"1": {"name": "bruce", "age": 18, "favor": "ball"},
"2": {"name": "alvin", "age": 18, "favor": "run"},
"3": {"name": "hebburn", "age": 18, "favor": "sing"},
"4": {"name": "diana", "age": 18, "favor": "sleep"},
}
class Dict(View):
def get(self, request):
return render(request, "mydict.html", {"my_dict": MY_DICT})
class Details(View):
def get(self, request):
person_id = request.GET.get("id", None)
# 获取get传过来的id,根据这个在dict中取值并用details.html渲染传给用户
if person_id in MY_DICT:
return render(request, "details.html", {"person": MY_DICT[person_id]})
else:
return HttpResponse("<div style='color:red'>无此人数据<div>")
- 在templates中新建两个模板
mydict.html
使用每条数据的key+url为其构建一条请求;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>dict</title>
</head>
<body>
<div>
<ul>
{% for key, value in my_dict.items %}
<li><a href="/details/?id={{ key }}">{{ value.name }}</a></li>
{% endfor %}
</ul>
</div>
</body>
</html>
details.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ person.name }}的详细信息</title>
</head>
<body>
{{ person.name }}
<div>
<ul>
{% for key, value in person.items %}
<li>{{ key }}:{{ value }}</li>
{% endfor %}
</ul>
</div>
</body>
</html>
2.6.1.2 正则一对多-分组位置传参
使用正则可以实现一个function/class对应多个url;
在处理详细显示这个需求时,使用正则的实现方式如下:
- 在urls中添加两个url,dict和details
from django.contrib import admin
from django.urls import path
from BruceApp import views
from django.urls import re_path
urlpatterns = [
path('dict/', views.Dict.as_view()),
# re_path在3.0中用于正则匹配
re_path(r'^details-(\d+).html/', views.Details.as_view()),
]
- 在views中修改Details
class Details(View):
def get(self, request, person_id):
# person_id = request.GET.get("id", None)
print(person_id)
if person_id in MY_DICT:
return render(request, "details.html", {"person": MY_DICT[person_id]})
else:
return HttpResponse("<div style='color:red'>无此人数据<div>")
- 在templates中修改mydict.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>dict</title>
</head>
<body>
<div>
<ul>
{% for key, value in my_dict.items %}
<li><a href="/details-{{ key }}.html">{{ value.name }}</a></li>
{% endfor %}
</ul>
</div>
</body>
</html>
2.6.1.3 正则一对多-分组键值对传参
键值对既可以使用**kwargs来接收成一个dict,也可以当做对形参的直接赋值;
以上一对多url中一对多关系根据正则的分组返回一个或多个正则的值,然后由视图函数中的参数获取,还有一种可以在正则中为分组添加key,从而获取键值对;
注:命名分组与顺序分组是不能混用的….
- urls中在正常正则中添加?P
;
urlpatterns = [
re_path(r'^details-(?P<person_id>\d+).html/', views.Details.as_view()),
]
- 在视图类中的方法中添加获取
class Details(View):
def get(self, request, **kwargs):
print(kwargs)
if kwargs.get('person_id') in MY_DICT:
return render(request, "details.html", {"person": MY_DICT[kwargs.get('person_id')]})
else:
return HttpResponse("<div style='color:red'>无此人数据<div>")
2.6.1.4 默认参数
在一对多的正则匹配规则中,如果分组未传递参数,则视图函数可以通过默认值的方式给予形参一个值;
# 应用场景中,比如对分页进行操作;
# urls.py中
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# views.py中,可以为num指定默认值
def page(request, num="1"):
pass
2.6.1.5 在url中额外的参数传递
除了在url中的分组捕获以外, 也可以通过使用字典的方式,额外向视图函数传些参数;
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]
2.6.1.6 一些对应技巧
-
可以使用一对多正则将多个比较类似的功能合并到一个url中,并利用反射完成取类;
# url中 urlpatterns = [ # 也可以写成(\w+)(\d+).html,这样需要在里加写判断 url(r'(publisher|book|author)(\d+).html', views.del, name = "del"), ] # view中 def del(request, single_url_name, nid): # 反射取类 cls = getattr(models, single_url_name.capitalize()) # 通过nid差值删除 cls.object.fiter(id=nid).delete() # single_url_name对应我们显示的各个模块的url,可以使用反射 return redirect(reverse(single_url_name)) # 这边我们手动反射,其实在redirect中有针对single_url_name的检测及反射 # 所以下面这么写,也是OK的,也可以使用model来做 return redirect(single_url_name) # template中 <a hrep="{% url del 'publisher' nid %}">删除</a>
2.6.2 命名name与反向解析
在django中可以在定义url中为其命名(即创建url同时添加参数name="xxx");
作用:可以根据此名称生成自己想要的URL,通过字符串拼接也可以实现,这种更优雅,更灵活些;
<!--templates中:-->
<ul>
{% for key, value in my_dict.items %}
<li><a href="/details-{{ key }}.html">{{ value.name }}</a></li>
<li><a href="{% url "dt" person_id=key %}">{{ value.name }} + 1</a></li>
{% endfor %}
</ul>
<!-- 两种都可以实现跳转,但是第二种方式在url变化时,不需要修改template -->
本质: 本质上看是{name:{url:function}}这种关系,取得三个中的任意一个值即可找到另外两个的值;
2.6.2.1 urls,py中定义name
path('dict/', views.Dict.as_view(), name="n1")
re_path(r'^details-(\d+).html/', views.Details.as_view(), name="n2")
re_path(r'^details-(?P<person_id>\d+).html/', views.Details.as_view(), name="n2")
2.6.2.2 在视图函数反向解析
from django.urls import reverse
# 注在django.shortcuts.py中也导入了reverse所以可以从django.shortcuts导入
from django.shortcuts import reverse
url1 = reverse('n1')
url2 = reverse('n2', args=("1314",))
url3 = reverse('n3', kwargs={"person_id": "1316"})
2.6.2.3 在templates中反向解析
<a href="{% url "n1" %}">{{ value.name }}</a>
<a href="{% url "n1" "1314" %}">{{ value.name }}</a>
<a href="{% url "n1" person_id='1316' %}">{{ value.name }}</a>
2.6.3 路由分发
比如我一个程序中有很多的app,所有的app都导入项目中的urls.py中,会让整个urls.py的修改过于频繁,这种是完完全全不行的,所以我们使用路由分发;
路由分发,即根据一定的规则将一类url全部转到app中的某个urls.py中;
2.6.3.1 一级url分发举例
工程名:AlvinDjanjo
APP01:BruceApp
APP02:DianaAPp
Alvin.Djanjo.urls.py中需要引入include
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('bruce/', include("BruceApp.urls")),
path('diana/', include("DianaApp.urls")),
]
BruceApp.urls.py中写具体的路由规则,也可以构建二级路由分发;
from django.urls import path
from BruceApp import views
from django.urls import re_path
urlpatterns = [
path('login/', views.login),
path('home/', views.Home.as_view()),
path('upload/', views.upload),
path('dict/', views.Dict.as_view()),
re_path(r'^details-(?P<person_id>\d+).html/', views.Details.as_view(), name='dt'),
]
DianaApp.urls.py中写具体的路由规则,也可以构建二级路由分发;
from django.urls import path
from DianaApp import views
from django.urls import re_path
urlpatterns = [
path('home/', views.Home.as_view()),
]
访问方式变成:
http://127.0.0.1:8004/bruce/home/
http://127.0.0.1:8004/diana/home/
2.6.4 命名空间
用来区分不同APP中的相同URL的name,URL的命名空间模式也可以让你唯一反转命名的URL;
在project.urls中添加namespace
from django.contrib import admin
from django.urls import path
from django.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
# namespace需要为不同的app添加名称两种方式,一种在app的url中加,如下
path('app01/', include('app01.urls', namespace="app1")),
# 一种是在project中添加,如下
path('app02/', include(('app02.urls', 'app02'), namespace="app2")),
]
在app01.urls中
from django.urls import path
from app01 import views
# 第一种方式,为app添加一个名称
app_name = "app01"
urlpatterns = [
path('index/', views.index, name="index"),
]
在app02.urls中,不用添加
from django.urls import path
from app02 import views
urlpatterns = [
path('', views.index, name="home"),
path('index/', views.index, name="index"),
]
在app01.views及app02.views中reverse时,额外添加命名空间指定
from django.shortcuts import render, reverse
def index(request):
u1 = reverse('app02:index')
str2 = "from app02\n" + u1
return render(request, 'app02.html', {"str2": str2})
在tempalate.app01.html中reverse时,额外添加命名空间指定
<body>
<div>{{ str1 }}</div>
<div>{% url 'app01:index' %}</div>
<div>{% url 'app02:index' %}</div>
</body>
2.7 Django的ORM
ORM分为DBfirst及CODEfirst两种;DBfirst指的是需要现在mysql中创建相关的数据库及表,再在code中创建相关的代码;而CODEfirst则相反,大多数ORM都是这类,写好相关的类自动给你完成相关的创建;
ORM与数据库本质上分为两大方面:
- 根据类自动创建数据库表;
- 根据类对数据库表中的数据进行各种操作;
2.7.1 简单使用
2.7.1.1 setting中的设置
# 在这里注册的自己的APP,不添加makemigrations不会去找app下的modules文件来生成记录
INSTALLED_APPS = [
...,
'DianaApp'
]
# 这里用来修改使用的数据库类型
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
若使用mysql数据库,需要下面几步
-
因为django的orm默认使用Mysqldb连接数据库,而我们使用pymysql通过下面方式转换:
# 在工程.工程名.\_\_init\_\_.py中添加 import pymysql pymysql.version_info = (1, 4, 1, "final", 0) # django3.0后有版本要求 pymysql.install_as_MySQLdb()
-
在setting中修改DATABASES
DATABASES = { 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': BASE_DIR / 'db.sqlite3', 'ENGINE': 'django.db.backends.mysql', 'NAME': 'djangouse', "USER": "django", "PASSWORD": "xxx", "HOST": "127.0.0.1", "PORT": '3306', 'OPTIONS': { 'isolation_level': "repeatable read", }, } }
注:若不加OPTIONS会导致django.db.utils.OperationalError: 1665的报错,解决方法有两种,另一种是修改binlog format
mysql> SET GLOBAL binlog_format = 'ROW'; # 查询修改 mysql> show variables like 'binlog_format';
-
生成表结构
python manage.py makemigrations DianaApp python manage.py migrate
2.7.1.2 models中创建类
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
2.7.1.3 在数据库内创建表
python ./manage.py makemigrations
python ./manage.py migrate
2.7.1.4 简单的增删改查
from DianaApp import models
class Orm(View):
def get(self, request):
# 新建数据方式一
models.UserInfo.objects.create(username="Bruce", password="123")
# 新建数据方式一->变种
dic = {"username": "Alvin", "password": "123"}
models.UserInfo.objects.create(**dic)
# 新建数据方式二
obj = models.UserInfo(username="Hebburn", password="123")
obj.save()
# 查询
result01 = models.UserInfo.objects.all()
result02 = models.UserInfo.objects.filter(username="Bruce")
# 返回的是一个django对象,可迭代,内部放置的是UserInfo对象;
print(result01, result02)
# 更新
# 这是批量的修改
# 只改特定的字段
models.UserInfo.objects.filter(username="Alvin").update(password="123.com")
result03 = models.UserInfo.objects.filter(username="Alvin")
# 值的修改,save后存入数据库
# 这种方式,会对所有的字段更新
result03.username = "hebburn" # 只会在内存中修改
resule03.save() # 这一步才会修改数据库
for row in result03: print(row.password)
# 删除
models.UserInfo.objects.filter(id=2).delete()
return HttpResponse("orm操作测试")
2.7.2 字段
2.7.2.1 常用字段
字段名 | 字段描述 |
---|---|
AutoField | 自增整型字段,必填primary_key=True,则成为数据库主键,无django自动创建。 |
IntegerField | 一个整数类型范围 -2147483648 ~ 2147483647。 |
CharField | 字符类型,必须提供max_length参数,max_length表示字符的长度。 |
DateField/timeField | 日期类型,日期格式为YYYY-MM-DD,详单与Python中的datetime.date的实例。 auto_now=True每此条目更新都会跟随更新为最新时间; auto_now_add=True新增条目时会自动保存当前的时间 |
DatetimeField | 日期时间字段,格式为]DD HH:MM[:ss[.uuuuuu]][TZ],对应Python的datetime.datetime实例。 |
BooleanField | bool类型 |
TextField | 文本类型 |
FloatField | 浮点型 |
DecimalField | 10进制小时,参数max_digits总长度,参数decimal_places小数总长度 |
2.7.2.2 自定义字段
class MyCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
class Test(models.Model):
phone = MyCharField(max_length=11)
2.7.2.3 字段参数
参数名 | 说明 |
---|---|
null | 数据库中字段是否可以为空 |
db_column | 指定数据库中字段的列表 |
default | 设置默认值 |
primary_key | 数据库中字段是否为主键 |
db_index | 数据库中字段是否可以建议索引 |
unique | 数据库中字段是否可以建立唯一索引 |
unique_for_date | 数据库中字段日期部分是否可以 |
verbose_name | Admin中显示的字段名称 |
blank | Admin中是否允许用户输入为空,和null连用,否则在admin中会歧义 |
editable | Admin中是否可以编辑 |
choices | Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 render =models.IntegerField(choices=[(0,'男'),(1,'女'),],default=1) |
2.7.3 外键
使用外键建立表与表之间的关系,主要一对一,一对多及多对多关系;
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, to_field="id")
# 在外键中创建时,数据库内存储字段会自动为其添加上Class_id;
2.7.3.1 主要参数说明
- to:指定关联的类,可以是类名,也可是直接是字符串,不过字符串时,Publisher必须先于当前类;
- on_delete:指定当foregin关联的类中数据删除时,本表数据的操作方式 2.0版本必填:
- models.CASCADE:当关联删除时,当前表中条目跟随删除<默认>;
- models.PROTECT:当关联删除时,禁止关联删除,报错;
- models.SET系列:当关联删除时,相关条目从新设置关联<某个值,默认值,NULL>;
2.7.3.2 一对多映射
-
从ForeginKey字段拿到的是关联类的对象,如果要拿id,可以直接使用Publisher_id;
def get(self, request, **kwargs): books = models.Book.objects.all() print(books) for book in books: print(book.publisher) # 拿到的是对象 print(book.publisher_id) # 拿到是ID return HttpResponse("BookList", books)
-
对ForeginKey的赋值方式也有两种,一种是使用对象赋值,一种是使用ID赋值;
# 直接使用ID进行赋值 models.Book.objects.create( name=name, publisher_id=publisher_id ) # 获取响应的对象进行赋值 models.Book.objects.create( name=name, publisher=models.Publisher.objects.get(publisher_id=publisher_id) )
2.7.3.3 多对多映射
在python种使用ManyToManyField建议表与表之间建立多对多映射;
class Book(models.Model):
name = models.CharField(max_length=64)
publisher = models.ForeignKey(to=Publisher, on_delete=models.SET('1'), to_field="id")
class Author(models.Model):
name = models.CharField(max_length=16)
book = models.ManyToManyField("Book")
以上会生成三张表,第三张表不可直接使用python维护,除非手动生成第三张表;
多对多映射有些特点如下:
- 从多对多的字段拿到的值不是对象,而是一个中间商
related_descriptors
app01.Book.None
# 如下的中间商->关系管理对象;
<class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
# 可以使用相关方法获取对象,比如获取所有对象的.all()方法;
<QuerySet [<Book: Book object (1)>, <Book: Book object (2)>]>
对多对对象进行增删改查
author_obj.books.set([1,2]) # 设置多对多关系
author_obj.delete() # 删除时会直接删除条目+第三张表中的多对多关系
2.7.4 Model Meta参数
class UserInfo(models.Model):
nid = models.AutoField(primary_key=True)
username = models.CharField(max_length=32)
class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"
# admin中显示的表名称
verbose_name = '个人信息'
# verbose_name加s
verbose_name_plural = '所有用户信息'
# 联合索引
index_together = [
("pub_date", "deadline"), # 应为两个存在的字段
]
# 联合唯一索引
unique_together = (("driver", "restaurant"),) # 应为两个存在的字段
2.7.5 脚本执行ORM及13条
import os
# 初始化相关的环境变量,并取得setting的相关值
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'FcNetWeb.settings')
# 导入django模块
import django
# 执行django模块
django.setup()
# 以上初始化步骤必须按照顺序来,否则报错
from app01 import models
# 1:all查询所有的数据,返回QuerySet-对象列表 [对象,对象]
ret01 = models.Publisher.objects.all()
# 2:获取一个有且唯一的数据,返回对象,没有或者多个就报错;
ret02 = models.Publisher.objects.get(id=1)
# 3:filter获取满足条件的数据
ret03 = models.Publisher.objects.filter(id__gt=1)
# 4: 获取不满足条件的所有条目数
ret04 = models.Publisher.objects.exclude(id=1)
# 5: order_by排序,默认升序,-field即为降序
# 支持排序完成后再对相同的值根据第二个字段再次排序
ret05 = models.Publisher.objects.order_by("name", "pid")
# 6: reverse反转,对排序完成QuerySet反转;
ret06 = ret04.reverse()
# 7: values 返回QuerySet内部非对象而是对象属性<指定属性>组成的字典
# all可以省略
ret07 = models.Publisher.objects.all().values("id", "name")
# 8: values_list 返回QuerySet内部非对象而是对象属性<指定属性>组成的元祖
# all可以省略
ret08 = models.Publisher.objects.all().value_list("id", "name")
# 9: distinct 去重,根据字段及字段去重
ret09 = models.Publisher.objects.values("name").distinct("name")
# 10: count获取条目的数量
# all可以省略
# 可以用len同样获取,len效果更高
ret10 = models.Publisher.objects.all().count()
len(models.Publisher.objects.all())
# 11:返回第一个对象
ret11 = models.Publisher.objects.all().first()
# 12:返回最后一个对象
ret12 = models.Publisher.objects.all().last()
# 13:判断是否存在
ret13 = models.Publisher.objects.filter(id=1).exists()
2.7.6 ORM中的双下划线
2.7.6.1 单表
# 值比较
ret14 = models.Publisher.objects.filter(pk__lt=2) # less than
ret15 = models.Publisher.objects.filter(pk__lte=2) # less equal than
ret16 = models.Publisher.objects.filter(pk__gt=1) # greate than
ret17 = models.Publisher.objects.filter(pk__gte=1) # grate equal tha
ret18 = models.Publisher.objects.filter(pk__range=[1, 2]) # 1< ok < 2
ret19 = models.Publisher.objects.filter(pk__in=[1, 2, 3]) # 包含1,2,3
# 模糊查找
ret20 = models.Publisher.objects.filter(name__contains='123') # 相当于like
ret21 = models.Publisher.objects.filter(name__icontains='123') # 忽略大小写
ret22 = models.Publisher.objects.filter(name__startswith='123') # 以什么什么开始
ret23 = models.Publisher.objects.filter(name__istartswith='123') # 忽略大小写
ret24 = models.Publisher.objects.filter(name__endswith='123') # 以什么什么结束
ret25 = models.Publisher.objects.filter(name__iendswith='123') # 忽略大小写
# 时间查询
ret26 = models.Publisher.objects.filter(age__year='2019') #
ret27 = models.Publisher.objects.filter(age__contains='2019-01')
# null查询
ret28 = models.Publisher.objects.filter(age__isnull=True)
2.7.6.2 外键与反向
# Book中有外键->连接Publisher
# 正向查-我想查询书,根据出版社的名字的查
ret29 = models.Book.objects.filter(publisher__name='人民邮电出版社')
# 可以再加单表操作查询
ret30 = models.Book.objects.filter(publisher__name__contains='人民邮电出版社')
# 反向查-我想查出版社, 根据书籍obj或者书籍字段
# 查Publisher根据book_obj来查
ret31 = models.Publisher.objects.get(pk=1)
ret31.book_set # 约定格式,即类名小写并加_set,
# 查Publisher根据book中的字段,约定也是类名小写加__field
ret32 = models.Publisher.objects.filter(book__name__contains="图解")
# 如下语句中在Book中设置了别名即查询别名,用来反向查找, 共两种,
#一种为related_name, 影响xxx_set及双下划线连表查询;
#一种为related_query_name, 影响耍下划线连表查询, 优于related_name;
publisher = models.ForeignKey(to=Publisher, on_delete=models.SET('1'), to_field="id", related_query_name='mybook')
# 外键中设置别名
# related_name='books' 反向查询别名,代替book_set
ret31.books
# 根据外键字段查询时,代替表表名
ret32 = models.Publisher.objects.filter(books__name__contains="图解")
# 外键中设置查询别名
# related_query_name='mybook'
# 代替表名,外键别名,作用于字段查询中
ret32 = models.Publisher.objects.filter(mybook__name__contains="图解")
2.7.6.3 多对多与增删改查
author = models.Author.objects.get(pk=1)
book = models.Book.objects.get(pk=1)
# 多对多正向-对象
author.book # 关系对象
author.book.all() # 从关系对象中获取book对象
# 多对多反向-对象
book.author_set # 反向默认方式获取,返回关系对象
book.author_set.all() # 从关系对象中获取author对象
# 多对多正向-字段
alvin_book = models.Book.objects.filter(author__name="Alvin")
# 多对多反向-字段
tupu_author = models.Author.objects.filter(book__name="图解网络硬件")
# 关系设置-包括多对多双方的,及一对多中,从一与多的关系
##################################################
# 多对多-设置关系
# set设置多对多关系,需要引用对象:
# 作者的书设置为书-pk;
author.book.set([1, 2, 3])
# 作者的书设置为书-对象
author.book.set(models.Book.objects.filter(id__in=[2, 3]))
# add/remove/clear添加/删除/清空多对多关系
# 多对多中支持id及obj
author.book.add(1, 2)
author.book.remove(*models.Book.objects.filter(id__in=[2, 3]))
author.book.clear()
# 新增一个和当前对象有关系的对象,关系自动生成
author.book.create(name="兔姐自传")
#######################################################
# 一对多-设置关系
pub01 = models.Publisher.objects.get(pk=1)
# 支持上面所有方法,区别
# set/add/remove仅支持obj及*Queryset,不支持pk列表
pub01.book_set.add(*models.Book.objects.filter(pk__in=[1, 2, 3]))
# clear及/remove这类会造成book中的pubulisher_id字段为空,需要在数据库中设置为空才可使用
pub01.book_set.remove(*models.Book.objects.filter(pk__in=[1, 2]))
2.7.6 ORM高级操作
2.7.6.1 聚合
##############################
# 需要使用aagregate加聚合不同的方法
# aggregate终止字句,后面没法再跟其他放发
from django.db.models import Max, Min, Count, Sum, Avg
books = models.Book.objects.all()
print(books)
print(books.aggregate(Max('price'), Min('price'), Count('price'), sum=Sum('price'), avg=Avg('price')))
####返回结果是一个字典,默认生成的key是field__funName,如果想改变,可以指定形参生成如sum及avg;
# {'sum': Decimal('1163.00'),
# 'avg': Decimal('96.916667'),
# 'price__max': Decimal('100.00'),
# 'price__min': Decimal('77.00'),
# 'price__count': 12}
2.7.6.2 分组
分组(group)一般和聚合(aggregate)联合使用,单独的分组是没有意义的,当涉及多个表时,可以使用join进行连表;
这个是一个查询+连表+分组+聚合的sql
select app01_publisher.name, avg(app01_book.price)
from app01_book
inner join app01_publisher
on (app01_book.pub_id = app01_publisher.id)
group by app01_publisher;
查询一本书有多少个作者
sql写法:
select app01_book.name as book_name, count(app01_author.name) as count
from app01_book
inner join app01_author_book on (app01_book.id=app01_author_book.book_id)
inner join app01_author on (app01_author_book.author_id=app01_author .id)
group by app01_book.name;
orm写法:
# 通过book对象查询方法
models.Book.objects.annotate(count=Count('author__id')).values('id', 'name', 'count')
# 通过author对象查询方法
models.Author.objects.values('book__id', 'book__name').annotate(count=Count('id'))
查询每个出版社最便宜的一本书
sql写法:
select app01_publisher.id, app01_publisher.name, min(app01_book.price)
from app01_publisher
left join app01_book on (app01_publisher.id = app01_book.pub_id)
group by app01_publisher.id;
orm写法:
models.Book.objects.values('pub_id', 'pub__name').annotate(Min('price'))
models.Publisher.objects.annotate(m=Min("book__price")).values('id', 'name', 'm')
# 注意两种写法对空数据的处理方式不同,一个为left,一个为right
查询不止一个作者的书
# 方法1
models.Book.objects.annotate(count=Count('author__id')).values('id', 'count').filter(count__gt=1)
# 方法2
models.Author.objects.values('book').annotate(count=Count('id')).filter(count__gt=1)
根据一本书的作者数量排序
models.Book.objects.annotate(count=Count('author__id')).values('id', 'count').order_by('-count')
一个作者他所有书的价格之和
models.Author.objects.annotate(Sum("book__price")).values()
models.Book.objects.values('author', 'author__name').annotate(Sum("price"))
2.7.6.3 F查询
在ORM中,我要想比较一个字段与另一个字段,或者赋值时根据另外一个字段赋值,需要F函数;
F字段支持与常数的加减乘除和取模操作
from django.db.models import F
# 查询所有库存大于销量的书籍
models.Book.objects.filter(score__gt=F('soild'))
# 对所有书本打8折销售
models.Book.objects.all().update(price=F('price')*0.8)
2.7.6.4 Q查询
在filter中多个条件是与的关系,当我们需要用到或,非等更复杂的查询条件时,需要Q函数;
支持条件:
- | 或
- & 与
- ~ 非
# 多条件合并查询,id<3或者id>10,切名字不是以‘图解;开头的书
models.Book.objects.filter(Q(Q(id__lt=3) | Q(id__gt=10)) & ~Q(name__startswith="图解"))
2.7.6.5 事物
对应mysql中的事物操作,在ORM中使用固定的格式;
格式如下:
from django.db import transaction
with transaction.atomic(): # 原子型操作
... # 要写得到操作内容
但是正常情况下,我不希望报错影响程序,正常写法应该如下:
try: # try必须在外边,如果在with里面,会使原子操作无效
from django.db import transaction
with transaction.atomic():
... # 要写得到操作内容
except Exception as e:
print(e)
2.8 cookie与session
2.8.1 cookies
因HTTP是无状态的协议,即每次请求都是独立的。但是有时候我们需要保存一个状态<比如登录状态>,这个时候就需要用到cookies;
cookies是保存在浏览器中,用来保存一些状态的键值对,可以用来验证:
- 由服务器让浏览器进行设置的;
- cookies信息保存在浏览器本地的,浏览器有权不保存;
- 浏览器再次访问时自动携带应对的cookie;
2.8.1.1 django中操作cookie
# 设置cookies:
# 需要为Response类的对象
# 本质上是在http的响应头中添加set_cookie字段,添加键值对
response.set_cookie('is_login', 'value')
response.set_signed_cookie('is_login', "cookies_bruce", salt="s14", max_age=100, path="/", secure=False, httponly=True)
#参数说明
## max_age 超时时间
## path cookie生效的路径,默认为/,可以设置比如/home/
## secure=True 是否可以进行HTTPS传输
## httponly=True 只能传输,限制js获取,比如document.cookies时,就无法获取了;
# 获取cookies:
# 本质是通过请求头中的set_cookie字段获取
request.COOKIES.get('key') # 非加密获取方式,加密这种方式获取的是密文
request.get_signed_cookie('is_login', salt="s14", default="") # 加密方式获取
# 删除cookies:
# 本质上仍然是设置cookie,只是将内容变为“”,将超时时间变为0
reponse.delete_cookie(key)
2.8.1.2 简单举例
from django.shortcuts import render, redirect, reverse
# Create your views here.
from app01 import models
from functools import wraps
# 新建一个装饰器,用于验证登录cookie
def isLogin(fun):
@wraps(fun)
def wp(request, *args, **kwargs):
b_url = request.get_full_path()
## 获取明文的cookie
# login_status = request.COOKIES.get('is_login')
# 获取密文的cookie
login_status = request.get_signed_cookie('is_login', salt="s14", default="")
if login_status != 'cookies_bruce':
return redirect(reverse('login') + "?url={}".format(b_url))
else:
ret = fun(request, *args, **kwargs)
return ret
return wp
def login(request):
md = request.method
# 跳转会原来页面-login页面接受传递的参数
url = request.GET.get('url')
if url:
to_url = url
else:
to_url = reverse('home')
if md == "GET":
return render(request, 'log.html')
elif md == "POST":
name = request.POST.get("user")
pwd = request.POST.get("pwd")
ret = redirect(to_url)
if models.Muser.objects.filter(name=name, pwd=pwd).first():
##设置cookie的两种方法
# ret.set_cookie('is_login', '1')
ret.set_signed_cookie('is_login', "cookies_bruce", salt="s14", max_age=100, path="/", secure=False, httponly=True)
return ret
@isLogin
def home(request):
print(request.COOKIES.get("is_login"))
return render(request, 'home.html')
@isLogin
def main(request):
return render(request, 'main.html')
@isLogin
def delcookie(request):
rt = redirect('home')
rt.delete_cookie('is_login')
return rt
2.8.2 session
cookie是保存在浏览器中的键值对,而session是保存在服务器上的一组组键值对并依赖cookie;
2.8.2.1 session存在的意义
cookie存在的问题
- cookie是信息存在浏览器本地,不太安全;
- 浏览器会对cookie的大小和个数有一定限制的,一般不大于4K;
而session存储Server上相对安全,同时也没有大小额的限制;
2.8.2.2 django操作session
前提:
# setting中
## app里要有session的注册
INSTALLED_APPS = [
'django.contrib.sessions',
]
## 中间件里要有session的设置
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
]
# 数据库中要有django_session相关的表
操作:
# 获取、设置、删除Session中的数据-操作方法类似于列表
## 设置
request.session['k1']
request.session.setdefault('k1',123) # 存在则不设置
## 获取
request.session.get('k1',None)
request.session['k1'] = 123
## 删除
del request.session['k1']
request.pop('k1')
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
# 会话session的key
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
## 默认情况下,即使session超时,数据也不会删除的
request.session.clear_expired()
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
# 删除当前会话的所有Session数据,但不会删除浏览器cookie
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie
##这用于确保前面的会话数据不可以再次被用户的浏览器访问
##例如,django.contrib.auth.logout() 函数中就会调用它。
request.session.flush()
# 设置会话Session和Cookie的超时时间
## 如果value是个整数,session会在些秒数后失效。
## 如果value是个datatime或timedelta,session就会在这个时间后失效。
## 如果value是0,用户关闭浏览器session就会失效。
## 如果value是None,session会依赖全局session失效策略。
request.session.set_expiry(value)
设置
# 在django的global_setting中可以看到session的默认配置
##查看方法,在任意文件中
from django.conf import global_settings
## 然后,ctrl + 点击global_settings即可看到
# Cache to store session data if using the cache session backend. #跟缓存相关
SESSION_CACHE_ALIAS = 'default'
# Cookie name. This can be whatever you want.# 在cookie中的名称
SESSION_COOKIE_NAME = 'sessionid'
# Age of cookie, in seconds (default: 2 weeks). # 默认超时时间
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
# A string like "example.com", or None for standard domain cookie.# cookie的几个参数
SESSION_COOKIE_DOMAIN = None
# Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_SECURE = False
# The path of the session cookie.
SESSION_COOKIE_PATH = '/'
# Whether to use the HttpOnly flag.
SESSION_COOKIE_HTTPONLY = True
# Whether to set the flag restricting cookie leaks on cross-site requests.
# This can be 'Lax', 'Strict', 'None', or False to disable the flag.
SESSION_COOKIE_SAMESITE = 'Lax'
# Whether to save the session data on every request. # 每次请求来后,都会更新超时时间
SESSION_SAVE_EVERY_REQUEST = False
# Whether a user's session cookie expires when the Web browser is closed. #浏览器关闭即超时
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
# The module to store session data # 设置存储 session的存储方式,有缓存,数据库等
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# Directory to store session files if using the file session module. If None,
# the backend will use a sensible default.
SESSION_FILE_PATH = None
# class to serialize session data
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'
2.9 中间件
中间件是一个用来处理Django的请求和响应的框架级别的钩子。
它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。
每个中间件组件都负责做一些特定的功能。
本质是一个自定义类,类中定义了几个方法,Django框架会在处理请求的特定的时间去执行这些方法。
2.9.1 配置步骤
-
新建文件夹,一般放置在app下,eq:middlewares
-
新建py文件,名字任意,eq:mid01.py
-
文件中新建类
from django.utils.deprecation import MiddlewareMixin class MID01(MiddlewareMixin): ...
-
在project下的setting中注册middlewares;
MIDDLEWARE = [ 'app.middlewares.mid01.MID01', ]
2.9.2 分类介绍
中间件共5个方法:
- process_request(self,request) : 处理request请求
- process_view(self, request, view_func, view_args, view_kwargs):修改视图行为
- process_template_response(self,request,response):修改模板渲染
- process_exception(self, request, exception):异常时处理
- process_response(self, request, response):处理response
2.9.2.1 process_request
process_request(self, request)用来处理request请求
执行时间:路由之前
参数:
- request:请求的对象,和视图函数时同一个对象;
执行顺序:按照注册的顺序,顺序执行
返回值:
- None:正常流程
- HttpResponse后跳转至同类下的response方法,向上执行;
2.9.2.2 process_response
process_response(self, request, response):处理response
执行时间:视图及所有的中间件之后
参数:
- request:请求的对象,和视图函数时同一个对象;
- response:返回的对象,和视图函数返回的对象是同一个;
执行顺序:倒序执行
返回值:
- 必须返回HttpResponse
2.9.2.3 process_view
process_view(self, request, view_func, view_args, view_kwargs):修改视图行为
执行时间:路由之后,视图之前
参数:
- request:请求的对象,和视图函数时同一个对象;
- view_func:视图函数
- view_args:视图函数的位置参数
- view_kwargs:视图函数的关键字参数
执行顺序:顺序执行
返回值:
- None:正常流程
- HttpResponse后跳过之后的view及视图函数直接转至最后一级response函数;
2.9.2.4 process_exception
process_exception(self, request, exception):异常时处理
执行时间:视图函数中,当视图函数异常时执行
参数:
- request:请求的对象,和视图函数时同一个对象;
- exception:异常的对象;
执行顺序:倒序执行
返回值:
- None:当前中间件没有处理异常,交给下一个中间件处理异常,直至最后django处理异常;
- HttpResponse当前中间件处理了异常,跳过之后的exception直接转至最后一级response函数;
2.9.2.5 process_template_response
process_template_response(self,request,response):修改模板渲染
执行时间:视图中返回的对象时TemplateResponse对象时触发;
参数:
- request:请求的对象,和视图函数时同一个对象;
- response:返回的对象,和视图函数返回的对象是同一个,为TemplateResponse对象;
执行顺序:倒序执行
返回值:
- 再次处理过的TemplateResponse对象
- TemplateResponse相对于Render对象时,此时的TR还没有完成渲染;
- 修改模板的名字:response.template.name
- 修改内容数据:response.context.data
2.9.3 整体执行顺序
2.9.3.1 非触发式流程如下
2.9.3.2 如果包含触发式流程如下:
2.9.4 csrf中间件
2.9.4.1 csrf攻击简介
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF
如图中所示,B利用了A在浏览器中的cookie信息,模拟合法用户在A上完成了一个用户完全不知得到操作,从而达成一定的目的;
2.9.4.2 django中csrf的防御流程
在django中使用csrf-token的方式来防御csrf攻击;
-
当用户访问有post等需求的页面时,django会在用户get请求时设置两个东西:
-
django会在response中设置浏览器cookies<csrftoken:64位字符串>;
-
django会在渲染的页面中放置一个input框;
<input type="hidden" name="csrfmiddlewaretoken" value=64位字符串>
-
-
当用户post类操作提交相关数据时,会同时提交input框里的内容至django,当然cookie也会带;
-
django收到后,会在csrf中间件中完成input提交的token与cookie携带的cookie的比较,详细:
-
在process_request中会从session/cookie中获取token,并赋值给META.CSRF_COOKIE;
-
在process_views中完成整体的验证,步骤详细:
-
首先判断是否豁免验证,一般有csrf_processing_done,csrf_exempt及非重点method;
-
然后会尝试获取submit的cookie,共有两种:
-
从post中获取
request.POST.get('csrfmiddlewaretoken', '')
-
从META中的settings.CSRF_HEADER_NAME('HTTP_X_CSRFTOKEN')属性获取
equest.META.get(settings.CSRF_HEADER_NAME, '')
-
-
对比两个token,判断其解密后的secret是否一致,详细:
- 一个token长64,其中前32为为salt,后32为加密后的secret;
- 分别使用salt对密文解密得到secret,并对比是否相同;
-
-
在process_response中根据request中的cookie异常属性值来维护csrf_cookie;
-
2.9.4.3 csrf装饰器
在django中,除了使用中间件统一验证csrf,还可以使用装饰器为某些页面设置豁免,或者在关闭中间件的的情况下,为某些页面加上csrf验证;
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt # 设置豁免,本质是为视图函数index添加 csrf_exempt=True的属性
def index(request):
pass
@csrf_protect # 设置csrf验证-方式1
def index(request):
pass
urlpatterns = [ # 设置csrf验证-方式2
url(r'^index/',csrf_protect(views.index)),
]
2.9.5 配置举例
2.9.4.1 登录认证及所有中间方法配置
class MID01(MiddlewareMixin):
# 执行在路由之前
def process_request(self, request):
ip = request.META.get('REMOTE_ADDR')
history = TIME.get(ip, [])
TIME[ip] = history
now = time.time()
times = 0
for item in history[:]:
if now - item < 5:
times += 1
else:
TIME[ip].remove(item)
TIME[ip].append(now)
print("次数", times)
if times > 2:
return HttpResponse("访问过于频繁")
status = request.session.get('is_log')
login_url = reverse('login')
if request.path_info != login_url:
t_url = request.path_info
if not status:
return redirect(login_url + "?url={}".format(t_url))
# 执行在路由之后,视图函数之前
def process_view(self, request, view_fun, view_args, view_kwargs):
print("process_view_MID01")
# 执行在视图函数之后
def process_response(self, request, response):
print("process_response_MID01")
return response
# 视图函数之后且视图中有异常时执行
def process_exception(self, request, exception):
print("process_exception_MID01")
# 执行条件时必须返回TemplateResponse
def process_template_response(self, request, response):
print("process_template_response_MID01")
2.10 AJAX
Asynchronous Javascript And Xml (异步的js与xml):使用js语言与服务器进行异步交互,传输XML数据,现在也可以传输json格式;
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内;
- AJAX使用JavaScript技术向服务器发送异步请求;
- AJAX请求无须刷新整个页面;
- 因为服务器响应内容不再是整个页面,而是页面中的部分内容,所以AJAX性能高;
2.10.1 json
在python中及js中普遍使用json进行,不同模块间的数据交互,如下是一个序列化与反序列化图;
2.10.2 ajax的两种使用方式
2.10.2.1 使用jquery实现 ajax方法
这里面使用get请求来传递参数,实际是因为未传递csrf-token,所以避免认证环节;
template中:
<body>
{% csrf_token %}
<input id="i1" type="text" name="i1">+
<input id="i2" type="text" name="i2">=
<input id="i3" type="text" name="i3">
<button>提交</button>
<script src="/static/jquery-3.5.1.js"></script>
<script>
$("button").on('click', function (){
$.ajax(
{
url:"/hide/", 提交向的URL
type:"GET", 提交的方法
data: { 提交的数据
i1:$("#i1").val(),
i2:$("#i2").val()},
success: function (data){ 回调函数,参数data接收视图函数的返回
$("#i3").val(data)
}
}
)
})
</script>
</body>
view中:
def hide(request):
i1 = request.GET.get('i1') # 从提交的数据中获取值
i2 = request.GET.get('i2') # 从提交的数据中获取值
i3 = int(i1) + int(i2) # 处理
return HttpResponse(i3) # 返回值到回调函数的data形参中,一般返回json格式;
2.10.2.2 使用原生js实现
var b2 = document.getElementById("b2");
b2.onclick = function () {
// 原生JS
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", "/ajax_test/", true);
xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlHttp.send("username=q1mi&password=123456");
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
alert(xmlHttp.responseText);
}
};
};
2.10.3 csrf-token上传
csrf-token在client中能找到的位置有两个:cookie中及隐藏的input;
csrf-token在request的携带位置总共有两个:POST.csrfmiddlewaretoken及头部的X_CSRFTOKEN;
2.10.3.1 从隐藏input到POST.csrfmiddlewaretoken
$.ajax({
url: "/cookie_ajax/",
type: "POST",
data: {
"username": "Q1mi",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})
2.10.3.2 从cookie到头部的X_CSRFTOKEN
$.ajax({
url: "/cookie_ajax/",
type: "POST",
headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 从Cookie取csrftoken,并设置到请求头中
data: {"username": "Q1mi", "password": 123456},
success: function (data) {
console.log(data);
}
})
2.10.2.3 上传文件
$("#b3").click(function () {
var formData = new FormData();
formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
formData.append("f1", $("#f1")[0].files[0]);
$.ajax({
url: "/upload/",
type: "POST",
processData: false, // 告诉jQuery不要去处理发送的数据
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
data: formData,
success:function (data) {
console.log(data)
}
})
})
三. django的组件
四. 其他操作
4.1 前端模板的COPY
大部分情况下,我们并不写HTML网页,我们只需要copy并修改即可;
4.1.1 常用的几个COPY的网址
4.1.2 Copy的一般步骤
前面页面的复制设计HTML及相关的静态文件,根据复杂度可以使用两种方式;
4.1.2.1 较简单的页面
一般较简单的页面,比如bootstrap中的简约登录框,复制步骤如下:
- 浏览器-检查-将页面HTML中的body复制下来替换自己的HTML中的body;
- 在head中查看需要哪些静态文件,然后下载或者新建引入;
4.1.2.2 复杂的页面
针对复杂的页面,使用上述方法就太慢并且有时候会有异常,那么我们可以使用下面的步骤:
-
Ctrl+S另存为:因为涉及的静态文件太多一个个下载过慢,另存一次下载;
- 复制前将相关的广告之类删除
-
Ctrl+U复制源码贴到本地:为防止页面代码在显示中被JS等改变,复制源码是最准确的;
- 复制前将相关的广告之类删除
-
将下载的相关静态文件,按类复制到static文件夹;
-
Pycharm中使用Ctril+R替换,因为一个个改太慢了,Pycharm支持正则替换哦;
#查找 'css/(.*?.css)' #替换为 "/static/css/loginin/$1"
-
查漏补缺,比如以上操作结束后,仍然未加载背景图片,去源网址取,然后找到要修改的位置:
- 缺少的文件位置不一定只在HTML中,也可以在css及js中;
4.2 上线时的操作
4.2.1 关闭debug
在setting.py中设置
DEBUG=False #原来为True
ALLOWED_HOST = ['*'] # 设置允许所有的主机访问;
4.3 使用django.admin
4.3.1 步骤
-
创建一个超级用户
python manage.py createsuperuser
-
注册model
# 在app下面的admin.py中 from django.contrib import admin from app01 import models admin.site.register(models.Person)
-
url登录及obj显示
# 使用http://127.0.0.1/admin/ # 可以为类添加__str__(),这个可以用来在admin显示obj的细节
4.3.2 调整
整体通过定义admin.ModelAdmin的子类来对显示及修改作修改;
调整的内容:
- 修改显示的字段
- 修改显示中可修改的字段
- 隐藏非关键字段,显示部分字段;
- 将forgin-key放在关联目标的下面显示等;
- 显示多对多关系;
model.py
from django.db import models
# Create your models here.
# 用户表,存放所有的所有用户
## 关联role,分配角色信息
class User(models.Model):
name = models.CharField(max_length=32, verbose_name="用户名")
password = models.CharField(max_length=32, verbose_name="密码")
role = models.ManyToManyField(to="Role", verbose_name="角色")
class Meta:
verbose_name = "用户"
verbose_name_plural = "用户表"
# 角色表
## 关联permission,基于角色分配权限
class Role(models.Model):
name = models.CharField(max_length=32, verbose_name="角色名")
permission = models.ManyToManyField(to="Permission", verbose_name="权限")
class Meta:
verbose_name = "角色"
verbose_name_plural = "角色表"
def __str__(self):
return self.name
# 权限表
## 存储所有可使用的权限信息
class Permission(models.Model):
url = models.CharField(max_length=64, verbose_name="url")
name = models.CharField(max_length=32, verbose_name="权限")
is_menu = models.BooleanField(verbose_name="是否菜单", default=False)
menu = models.OneToOneField(to="Menu", null=True, blank=True, on_delete=models.CASCADE)
class Meta:
verbose_name = "权限"
verbose_name_plural = "权限信息表"
def __str__(self):
return self.name
class Menu(models.Model):
title = models.CharField(max_length=32, verbose_name="标题", default="default")
icon = models.CharField(max_length=64, verbose_name="图标")
class Meta:
verbose_name = "菜单"
verbose_name_plural = "菜单信息表"
def __str__(self):
return self.title
rbac/admin.py
from django.contrib import admin
from rbac import models
# 根据permission到menu的外键关系,建立table放置在menu下面;
class MenuInPermision(admin.TabularInline):
model = models.Permission
# 建立useradmin
class UserAdmin(admin.ModelAdmin):
list_display = ['name', 'password', '角色字段'] # 显示展示的字段,默认为obj
list_editable = ['password',] # 显示以上字段中可直接编辑的字段,默认无
def 角色字段(self, obj): # 转换下多对多字段,让其可以在display中显示
return [role for role in obj.role.all()]
class MenuAdmin(admin.ModelAdmin):
inlines = [MenuInPermision] # 关联上面建立的table,并放置在同一页面
class PermissionAdmin(admin.ModelAdmin):
list_display = ['name', 'url', 'is_menu', "menu"]
# fields = ['name', 'url', 'is_menu', "menu"] # 可编辑的字段,默认所有
search_fields = ('name', 'url') # 建立搜索框,并制定可搜索的字段
fieldsets = ( # 与fields冲突,将所有的字段进行分类,并可额外指定样式
['主要', {
'fields': ('name', 'url'),
}
],
[
'高级', {
'classes': ('collapse',), #css样式 # 可隐藏及展示的样式
'fields': ('is_menu', 'menu'),
}
]
)
admin.site.register(models.User, UserAdmin) # 注册同时使用UserAdmin自定义内容
admin.site.register(models.Role)
admin.site.register(models.Permission, PermissionAdmin)
admin.site.register(models.Menu, MenuAdmin)