请求处理流程
客户端(浏览器发送请求)—》路由Route(根据请求进行转发给视图)—》View(接受请求、处理请求)—》Model(数据库交互、查询修改等)
—》View(根据数据库返回的数据进行反馈,给Template)—-》Template渲染相应的内容—-》通过View将显示内容返回给客户端
配置ALLOWED_HOSTS
要想本机被其他主机访问,需要配置允许访问的域名
默认127.0.0.1
如果要局域网内访问,就要加入局域网IP,公网访问,则公网IPALLOWED_HOSTS = [“192.168.1.4”,]
路由重定向
在路由urls中,无论是path还是re_path都有name参数,用来动态获取path中urlpattern的具体内容
比如在子应用的urls中,使用了name为home,我们可以通过home获取到管理页面视图的urlpatternurlpatterns = [
re_path(r’^index/$’,index,name=”index”)
]
当我们在其他视图需要获取管理页面视图的url时,直接通过获取,而不需要把url写死from django.urls import reverse
def index(req):
path = reverse('home')
print(path)
return render(req,'index.html')
使用Postwoman发送请求
打印的路径如下
使用reverse就可以直接获取到对应name视图urlpattern,而不需要访问urls中的index/
当urls中的urlpattern改变为home
时,只要name不变,仍然是index,就一样可以获取到管理页面视图的路由urlpattern
命名空间
为了防止不同应用中的name重复,通常以子应用名作为include的第二个参数(命名空间)
include的第一个参数是一个元组include((‘子应用名.urls’))
工程urls如下urlpatterns = [
path(‘admin/’,admin.site.urls),
path(”,include((‘login.urls’,’login’))),
path(”,include((‘myapp.urls’,’myapp’))),
]
login.urlsurlpatterns = [
re_path(r’^index/$’,index,name=”index”)
]
myapp.urlsurlpatterns=[
path(‘index/’,index,name=’index’)
]
这个时候reverse需要使用namespace:name来获取视图对应的urlpatternpath = reverse(‘login:index’)
HttpRequest对象
HTTP请求数据传递参数的四种方式
- URL的特定部分,如/weather/bejing/2018,可以在服务器端的路由中用正则表达式截取;
- 查询字符串(query string):形如key=1&value=2
- 请求体:表单、json、XML
- 请求头
正则提取请求URL的参数
- 进行正则分组的参数会传递给视图
- 需要在view中定义变量来接收参数
urlpatterns = [
re_path(r'(1)/(xiaozhi)’,detail)
]
比如当前url表示,id为1,用户为xiaozhi的用户
在view中定义如下,用变量来接收参数def detail(request,acc_id,acc_name):
return HttpResponse(“Hello” + acc_name)
路由将会接收到参数,进行转发给View处理
如果需要匹配所有整数,就在urls中添加正则urlpatterns = [
path(‘admin/’, admin.site.urls,name=’home’),
re_path(r'(\d+)/(\s+)$’,detail)
]
关键字参数
利用正则来实现关键字参数urlpatterns = [
path(‘admin/’, admin.site.urls,name=’home’),
re_path(r'(?P<acc_id>\d+)/(?P<acc_name>[a-zA-Z]+)/$’,detail)
]
可以看到路由中定义acc_id在前,acc_name在后
所以请求的时候要注意顺序,这样定义的好处是,即使view中参数顺序与路由不一致,也不影响接收处理请求def detail(request,acc_name,acc_id):
return HttpResponse(acc_name + acc_id)
查询字符串
在url中传递参数,格式如下http;//hostname:port/路由名/?key=value&key=value&…
使用查询字符串传递参数http://127.0.0.1:8000/index/?acc_id=1&acc_name=xiaozhi
View通过GET接收参数def index(req):
query_params = req.GET
print(query_params)
得到的是一个字典,进一步获取内容def index(req):
query_params = req.GET
print(query_params.get(‘acc_id’))
print(query_params.get(‘acc_name’))
但是在传递一个键多个值的时候,只能获取到最后一个http://127.0.0.1:8000/index/?acc_id=1&acc_name=xiaozhi&acc_id=233def index(req):
query_params = req.GET
print(query_params)
print(query_params.get(‘acc_id’))
print(query_params.get(‘acc_name’))
return render(req,’index.html’)
所以要获取一个键,多个值,要使用getlistdef index(req):
query_params = req.GET
print(query_params)
print(query_params.getlist(‘acc_id’))
print(query_params.get(‘acc_name’))
return render(req,’index.html’)
请求体
POST表单
首先在seetings里关闭CSRF跨域MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware’,
‘django.contrib.sessions.middleware.SessionMiddleware’,
‘django.middleware.common.CommonMiddleware’,
# ‘django.middleware.csrf.CsrfViewMiddleware’,
‘django.contrib.auth.middleware.AuthenticationMiddleware’,
‘django.contrib.messages.middleware.MessageMiddleware’,
‘django.middleware.clickjacking.XFrameOptionsMiddleware’,
]
注释掉就可以接收表单请求了
在视图中获得请求数据def index(req):
data = req.POST
print(data)
return render(req,’index.html’)
JSON
在body中添加数据,POST一下{
“acc_id”:”123″,
“acc_name”:”xiaozhi”
}
由此可见,数据不在POST中,而在body中def index(req):
data = req.body
print(data)
return render(req,’index.html’)
原始数据是byte字节类型,为了便于查看,解码一下def index(req):
data = req.body.decode()
print(data)
return render(req,’index.html’)
字符串格式化为JSON数据def index(req):
data = json.loads(req.body.decode())
print(data)
return render(req,’index.html’)
请求头
获取数据def index(req):
data = req.META
返回字典
请求数据
def index(req):
data = req.META
print(data)
return HttpResponse(req,”index.html”)
HttpResponse对象
包含参数
- content:传递字符串,不可以是对象、字典等
- status:状态码
- content_type
- 语法规则:大类/小类
- text/html
- text/css
- text/javascript
- application/json
- image/png
- …
- 是一个MIME类型(告诉客户端是什么类型数据)
- 语法规则:大类/小类
return HttpResponse(“Hello”,status=200)
JsonResponse
from django.http import JsonResponse
def index(req):
dict = {
“name”:”xiaozhi”
}
return JsonResponse(dict)
页面跳转
from django.shortcuts import render, redirect
def index(req):
dict = {
“name”:”xiaozhi”
}
return redirect(“https://www.baidu.com/”)
跳转至对应路由中def index(req):
dict = {
“name”:”xiaozhi”
}
return redirect(“/admin/”)
状态保持
- 浏览器请求服务器是无状态的。
- 无状态:指一次用户请求时↑浏览器、服务器无法知道之前这个用户做过什么,每次请求都是- 次新的 请求。
- 无状态原因:浏览器与服务器是使用Socket套接字进行通信的,服务器将请求结果返回给浏览器之后, 会免闭当前的Socket连接,而且服务器也会在处理页面完毕之后销毁页面对象。
- 有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
- 实现状态保持主要有两种方式:
- 在客户端存储信息使用Cookie
- 在服务器端存储信息使用Session
cookie、session运作流程
- 浏览器首次访问站点,不携带cookie信息,浏览器会在响应中创建一个Cookie,包含SessionID返回给客户端浏览器,并保存到本地
- 第二次访问同样的站点,会自动带上SessionID、cookie信息,服务器检测到后会验证用户的身份,验证成功后调用Session存在内存中的用户信息
实现
设置Cookie
- 判断请求中有无Cookie
- 获取用户信息
如果没有,则
- 创建Cookie
- 返回Cookie
def index(req):
# 判断有无Cookie
# 获取用户信息
username = req.GET.get(‘acc_name’)
# 创建Cookie
response = HttpResponse(“set_cookie”)
response.set_cookie(“username”,username)
# 返回Cookie
return responsehttp://127.0.0.1:8000/index/?acc_id=233&acc_name=xiaozhi
获取Cookie
def get_cookie(req):
cookies = req.COOKIES
username = cookies.get(‘username’)
print(username)
return HttpResponse(“get_cookie”)http://127.0.0.1:8000/get_cookie/
- Cookie被包含在在请求头中
- 第一次服务器会在响应头中返回Cookie,之后就不再返回Cookie了
- Cookie是基于IP分配的
过期时间
def set_cookie(req):
# 判断有无Cookie
# 获取用户信息
username = req.GET.get(‘acc_name’)
# 创建Cookie
response = HttpResponse(“set_cookie”)
response.set_cookie(“username”,username,max_age=3600) # 单位是s
# 返回Cookie
return response
使用max_age,从浏览器请求服务器开始计时
删除Cookie
两种方式 response.delete_cookie(‘username’) #参数是key
response.set_cookie(“username”,username,max_age=0)
- Session需要依赖于Session
- 如果浏览器禁用cookie,则Session不能实现
实现
session的信息存在数据库表django_session中mysql> show tables;
+—————————-+
| Tables_in_test_django |
+—————————-+ | |
| django_session |
+—————————-+mysql> desc django_session
-> ;
+————–+————-+——+—–+———+——-+
| Field | Type | Null | Key | Default | Extra |
+————–+————-+——+—–+———+——-+
| session_key | varchar(40) | NO | PRI | NULL | |
| session_data | longtext | NO | | NULL | |
| expire_date | datetime(6) | NO | MUL | NULL | |
+————–+————-+——+—–+———+——-+
可以看到里面没有数据mysql> select * from django_session;
Empty set (0.00 sec)
创建session
def set_session(req):
print(req.COOKIES)
# 假设信息验证成功
username=’xiaozhi’
# 设置session
req.session[‘username’]=username
return HttpResponse(“set_session”)http://127.0.0.1:8000/set_session/?username=xiaozhi
可以看到请求后多了sessionid
数据库中多了记录mysql> select * from django_session;
+———————————-+———————————————————————————–+—————————-+
| session_key | session_data | expire_date |
+———————————-+———————————————————————————–+—————————-+
| q3ir5zkuukin23t5e3nn5cb7ijs0lxjm | eyJ1c2VybmFtZSI6InhpYW96aGkifQ:1ncnh6:maEMAkqj-BolYvCEKov1YyjJ26giv1mhZek3_ew0cCY | 2022-04-22 12:26:28.403729 |
+———————————-+———————————————————————————–+—————————-+
1 row in set (0.00 sec)
获取session
def get_session(req):
# 从浏览器的Cookie中获取sessionid
cookies = req.COOKIES
# 获取session对应的信息
username = req.session[‘username’]
return HttpResponse(“get_session”)
清除所有session
删除值部分req.session.clear()
清除session数据
在存储中删除session的整条数据。req.session.flush()
删除session中的指定键及值
在存储中只删除某个键及对应的值。del req.session[‘key’]
设置session的有效期
request.session.set_expiry(value) # 单位为s
- 如果value是一个整数1 session将在value秒没有活动后过期。
- 如果value为0 ,那么用户session的Cookie将在用户的浏览器免闭时过期。
- 如果value为None,那么session有效期将采用系统默认值,
- 默认为两周,可以通过在settings .py中设置SESSION_ COOKIE_ AGE来设置全局默认值。
Session保存
在settings中,可以设置session数据的存储方式,可以保存在数据库、本地缓存中
数据库保存
Django默认将session数据保存在数据库中,setting中可以写配置,也可以不写
配置SESSION_ENGINESESSION_ENGINE= ‘django.contrib.sessions.backends.db’
如果存储在数据库中,需要在INSTALLED_APPS中安装Session应用(默认配置)’django.contrib.sessions’,
本地缓存
存储在本机内存中,如果丢失则不能找回,比数据库的方式读写更快。SESSION_ENGINE = ‘django.contrib.sessions.backends.cache’
混合存储
优先从本机内存中存取,如果没有则从数据库中存取。SESSION_ENGINE = ‘django.contrib.sessions.backends.cached.db’
Redis
在Redis中保存session,需要引入第三方拓展,可以使用django-redis解决
django-redis 中文文档 — Django-Redis 4.7.0 文档 (django-redis-chs.readthedocs.io)
安装拓展pip install django-redis
配置settings的cachesCACHES = {
“default”: {
“BACKEND”: “django_redis.cache.RedisCache”,
“LOCATION”: “redis://127.0.0.1:6379/1”,
“OPTIONS”: {
“CLIENT_CLASS”: “django_redis.client.DefaultClient”,
}
}
}
作为session backend使用SESSION_ENGINE = “django.contrib.sessions.backends.cache”
SESSION_CACHE_ALIAS = “default”
Redis获取Session与正常session一样request.session.get(‘name’)
删除sessiondel request.session[‘name’]
删除redis中session的所有value数据(不包括key)request.session.clear()
删除所有数据(包括key)r
类视图
面向对象的登录视图设计
GET请求渲染页面
POST请求验证登录并跳转def login(req):
if req.method==”GET”:
return render(req)
else:
# POST请求验证登录
return redirect(“首页”)
- 类视图采用面向对象的设计方式
- 继承自View
- 包含get、post、put、delete等方法
- 类试图的方法的第二个参数必须是请求实例对象
- 类试图的方法必须有返回值返回值是HttpResopnse及其子类
class LoginView(View):
# 重写方法
def get(self,req):
return HttpResponse(“GET”)
def post(self,req):
return HttpResponse(“POST”)
在urls中需要as_view引入urlpatterns = [
re_path(r’^login/$’,LoginView.as_view())
]
类视图原理
首先在urls中调用类的as_view()方法def as_view(cls, **initkwargs):
“””Main entry point for a request-response process.”””
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
‘The method name %s is not accepted as a keyword argument ‘
‘to %s().’ % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError(“%s() received an invalid keyword %r. as_view “
“only accepts arguments that are already “
“attributes of the class.” % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, ‘request’):
raise AttributeError(
“%s instance has no ‘request’ attribute. Did you override “
“setup() and forget to call super()?” % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# __name__ and __qualname__ are intentionally left unchanged as
# view_class should be used to robustly determine the name of the view
# instead.
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.__annotations__ = cls.dispatch.__annotations__
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
# the dispatch method.
view.__dict__.update(cls.dispatch.__dict__)
return view
会返回一个view,第22行会调用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)
dispatch方法会对请求方法进行小写,在列表中进行验证http_method_names = [‘get’, ‘post’, ‘put’, ‘patch’, ‘delete’, ‘head’, ‘options’, ‘trace’]
如果不在列表中就会报http_method_not_allowed
错误
最后handler会将请求转发至对应的重构函数
MRO的继承顺序
登陆验证
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin # 验证是否登录
class LoginView(LoginRequiredMixin,View):
# 重写方法
def get(self,req):
return HttpResponse(“GET”)
def post(self,req):
return HttpResponse(“POST”)
- 首先会在LoginView中找dispatch
- 没有的话逐一找其父类:LoginRequiredMixin、View
- 在LoginRequiredMixin中class LoginRequiredMixin(AccessMixin):
“””Verify that the current user is authenticated.”””
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)从源码中可以看出,如果没有登陆,会返回:handle_no_permission()不允许访问,继续在View类中找dispatch,返回视图 - 如果反过来,先继承View,会直接返回视图,不会进行登录认证
中间件
Django中的中间件是一个轻量级、底层的插件系统,可以窗人Django的请求和响应处理过程,修改Django的输入或输出。
中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性。
我们可以使用中间件,在Django处理视图的不同阶段对输入或输出进行干预。
通俗来说,就是将一个请求进行一次或多次处理,而不是像视图那样,处理后就返回。只能做一件事
且所有的请求都会经过中间件处理MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware’,
‘django.contrib.sessions.middleware.SessionMiddleware’,
‘django.middleware.common.CommonMiddleware’,
# ‘django.middleware.csrf.CsrfViewMiddleware’,
‘django.contrib.auth.middleware.AuthenticationMiddleware’,
‘django.contrib.messages.middleware.MessageMiddleware’,
‘django.middleware.clickjacking.XFrameOptionsMiddleware’,
]
中间件定义
就是在视图函数的外面再定义一个函数,且这个函数必须要有一个参数def simple_middleware(get_response):
# One-time configuration and initialization.
def middleware(request):
# Code to be executed for each request before
# the view (and later middleware) are called.
print(“before request”)
response = get_response(request)
print(“after request”)
# Code to be executed for each request/response after
# the view is called.
return response
return middleware
get_response是一个形参,也是一个函数,下面可以调用
中间件注册加载
MIDDLEWARE = [
‘django.middleware.security.SecurityMiddleware’,
‘django.contrib.sessions.middleware.SessionMiddleware’,
‘django.middleware.common.CommonMiddleware’,
# ‘django.middleware.csrf.CsrfViewMiddleware’,
‘django.contrib.auth.middleware.AuthenticationMiddleware’,
‘django.contrib.messages.middleware.MessageMiddleware’,
‘django.middleware.clickjacking.XFrameOptionsMiddleware’,
# 注册中间件
‘login.middleware.simple_middleware’,
]
所有的请求,包括静态资源的请求,都会经过中间件
使用案例
判断每个请求中是否带有Cookie的一些信息def SimpleMiddleware(get_response):
def middleware(request):
username = request.COOKIES.get(“username”)
if username is None:
print(“username is NONE”)
else:
print(“username为:” + username )
response = get_response(request)
print(“after request”)
return response
return middleware
中间件初始化
def simple_middleware(get_response):
# One-time configuration and initialization.
def middleware(request):
return response
return middleware
在中间件函数前面,这个部分就是中间件初始化的代码位置。
- Debug=True时默认初始化两次,False则一次
- 中间件的初始化只会在项目重新运行时进行一次,刷新页面不会导致中间件重新初始化
注意点
响应前,请求获取的顺序是按照中间件顺序,从上到下
而响应的顺序是按中间件顺序,从下到上。
先获取请求的后响应,后获取请求的先响应
Django分页
后端
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
一些属性和方法paginator = Paginator(book_list,3) # 先拿到分页器对象,第一个参数:对象列表,第二个参数:每页显示的条数
paginator.count # 总条数
paginator.num_pages # 总页数
paginator.page_range # 页码数列表
Paginator.page_range:这是从第一页到最后一页组成的一个列表,比如分页对象总共有10页,那么Paginator.page_range = [1,2,3,4,5,6,7,8,9,10]
current_page = paginator.page(5) # 取某一页,返回一个对象
current_page.object_list # 某一页里所有数据,例如:这是第5也所有数据
current_page.has_next() # 是否有下一页
current_page.has_previous() # 是否有上一页
current_page.next_page_number() # 下一页的页码数
current_page,previous_page_number() # 上一页的页码数from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
class getAllUserInfo(View):
def get(self,request):
”’
分页获取用户数据
:param request:
:return: 一个JSON对象{}
”’
allUser = account.objects.all()
# 创建分页
page = request.GET.get(“page”,default=1)
# 创建分页对象,每页10个数据
paginator = Paginator(allUser,10)
try:
content = paginator.page(page) # 获取对应页的数据
except EmptyPage:
content = paginator.page(paginator.num_pages) # 返回最后一页的数据
# content = content.values
content = serializers.serialize(“json”,content)
res_json = {
“status”:200,
“data”:content,# 实际数据
“count”:paginator.count ,# 数据总条数
“num_pages”:paginator.num_pages # 数据总页数
}
print(res_json)
# res_json = json.loads(str(res_json))
return JsonResponse(res_json,safe=False)
- 从数据库中获取数据
- 从前端获取需要查询的页码
- 创建分类器,传入数据,并设置每页的数据个数
- 获取对应页的数据(该页不存在则返回最后一页数据)
- 对数据进行序列化,并返回相关数据
- 该页的数据
- 数据总条数
- 数据总页数
前端
创建分页组件<div class=”example-pagination-block”>
<el-pagination
layout=”prev, pager, next”
@current-change=”handleCurrentChange”
@size-change=”handleSizeChange”
:current-page=”currentPage”
:page-size=”pageSize”
:total=”num_pages”
/>
</div>
传入相关参数
- @current-change:当前页面发生变化(切换页面)时触发的函数
- @size-change:当前页面数据条数发生变化时触发的函数
- :current-page:当前页码
- :page-size:当前页面数据条数
- :total:所有数据的总页数
在data中定义相关变量 data() {
return {
userList: [],
search: ”,
currentPage: 1, //当前数据页数
pageSize:10, //每页数据条数
total:0 ,//总数据条数
num_pages:1
}
},
定义触发事件时的函数 handleSizeChange(val) {
console.log(`每页 ${val} 条`)
this.pageSize = val
},
handleCurrentChange(val) {
console.log(`当前页: ${val}`)
this.currentPage=val
this.getUserData()
},
请求数据的同时,给相关变量赋值getUserData() {
this.axios.get(“http://127.0.0.1:8000/getalluser/”, {
params: {
“page”:this.currentPage
}
}).then((res) => {
let datas = JSON.parse(res.data.data)
for (var i = 0; i < datas.length; i++) {
this.userList.push(datas[i].fields)
}
this.total = res.data.count // 数据总条数
this.num_pages = res.data.num_pages //总页面数
})
分析过程
第一次获取数据,会将所有的页数返回给前端变量
当点击页面切换时,调用事件函数,更新当前页码变量,并将页码作为参数,重新获取数据