Django之路由层和视图层详解
路由层
首先我们来看一下,路由层都有哪些东西,其实你看django很人性化,将所有的介绍都放在了简介里面,不信,你看👇👇👇
"""BMS URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.11/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), ]
那我们就来具体的学习一下这些东西吧
路由层我们来学习以下这些东西
- 无名分组
- 有名分组
- 反向解析
- 路由分发
- 名称空间
无名分组
就是将加括号的正则表达式匹配到的内容当作位置参数自动传递给对应的视图函数
url(r'^test/(\d+)/',views.test), # 匹配一个或多个数字
你想,你给这个路径传了一个随机的数字,当作位置参数传递给了视图函数,视图函数不接受会怎么样?👇👇👇
那么login接收到的参数都是怎么来的?其实是路由层正则表达式将路径匹配到以后自动给后面的视图函数加上括号,在括号里面给这个函数传递参数,什么都没有匹配的话,就会只传一个request过去,,像上面这样就会将这个随机匹配的数字当作第二个位置参数传过去,看👇👇👇
这也证实了我们上面说的login接收的参数是怎么来的一说,所以可以简单理解为无名分组就是不指名道姓的传参过去,按照顺序来接收,是不是简单多了
那可以稍微猜想一下有名分组是不是就是指名道姓的传参过去,binggo,你猜对了,那在python中这两种分别叫做位置传参和关键字传参,好的,明白的差不多了那开始学习怎么用吧!
有名分组
将加括号的正则表达式匹配到的内容当作关键字参数自动传递给对应的视图函数👈👈👈
url(r'^test/(?P<year>\d+)/',views.test), # (?P<year>)正则中给一个匹配的正则表达式起名字,这里就是给匹配到的\d+起了叫year的名字
接着上面的套路来,先看示例
哎呀,快去看一下自己的视图函数是接收什么参数的,这个不是关键字传参么,怎么还说没有year关键字呢?
是自己没有将视图函数中的参数改成人家指定的关键字,改完之后再看看效果
所以说有名分组就是在路由中将加括号匹配到的内容指定一个名字传给视图函数,视图函数必须在参数中定义这个名字,不然就不可以🙅🙅🙅🙅🙅
既然有名和无名就是关键字传参和位置传参,那我们就试试看他们到底能不能混用(python中位置参数和关键字参数可以混用)
虽然不支持这两种混用,但是它可以接收多个无名分组和多个有名分组
反向解析
根据名字动态获取到对应的路径
到这里我们先补充一个知识点,就是路由的第一参数是一个正则表达式,匹配规则按照从上往下依次匹配,匹配到一个之后立即匹配,直接执行对应函数
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$',views.home), url(r'^test/$',views.test), # 加$结尾是严格匹配 url(r'^testadd/$',views.testadd), url(r'',views.error) ]
网站首页路由
url(r'^$',views.home)
网站不存在路由
url(r'',views.error)
知道了这个以后,我们来看看反向解析,假设你这个网站还未上线,临时变动,要求修改路径,但是这个路径你已经在后端和前端都用了,而且超过了1000个,你要基于这种情况如何修改?
那我们就需要用到反向解析,动态的获取到对应函数的路径,给这个路由对应的函数对应关系起一个名字,后面直接用这个名字就可以了,具体做法如下👇👇👇
url(r'^index6668888/$',views.index,name='index') # 后面再用到这个路径不管前面的路径如何变化,里面的具体使用只用管这个name对应的index就可以了
在后端使用的时候,需要导入reverse
from django.shortcuts import reverse def index(request): reverse('你给路由与视图函数对应关系起的别名') #动态找到对应的路径
reverse('index')
前端使用
<a href={% url'index'%}> {%url'你给路由与视图函数对应关系起的别名'%}
无名分组反向解析
url(r'^test/(\d+)/',views.test,name='list')
后端使用:
from django.shortcuts import reverse def test(request,xxx): reverse('list',args=(xxx,))
return HttpResponse('ok')
前端使用:
{% url'你给路由与视图函数对应关系起的别名' edit_id %}
{% url 'list' xxx %}
有名分组反向解析
url(r'^test/(?P<year>\d+)',views.test,name='test')
后端使用:
# 后端有名分组和无名分组都可以用这种形式 print(reverse('list',args=(10,))) # 下面这个了解即可 print(reverse('list',kwargs={'year':10}))
前端使用:
# 前端有名分组和无名分组都可以用这种形式
{% url 'list' 10 %}
# 下面这个了解即可
{% url 'list' year=10 %}
学习完了反向解析,以后我们想获取前端传过来的数据可以使用反向解析了
url(r'^edit/(\d+)/',views.edit,name='edit') 前端模板语法 {%for user_obj in user_list%} <a href='edit/{{ user_obj.pk }}/'></a> {% endfor %} 视图函数 from django.shortcuts import reverse def edit(request,edit_id): url = reverse('edit',args=(edit_id,)) 模板 {% url 'edit' edit_id %}
上述👆内容总结
总结:针对有名分组与无名分组的反向解析统一采用一种格式即可 后端 reverse('list',args=(10,)) # 这里的数字通常都是数据的主键值 前端 {% url 'list' 10 %} 反向解析的本质:就是获取到一个能够访问名字所对应的视图函数
路由分发
django每一个app下面都可以有自己的urls.py路由层,templates文件夹,static文件夹。
项目名下的/urls.py(总路由)不再做路由与视图函数的匹配关系,而是做路由的分发
具体做法:
url(r'^app01/',) #如何分发? from django.conf.urls import url,include #第一种起别名 from app01 import urls as app01_urls #名字重复起别名 from app02 import urls as app02_urls #名字重复起别名 url(r'^app01/',include(app01_urls)) #直接include(app01_urls) 就可以达到分发的效果 url(r'^app02/',include(app02_urls)) #第二种 url(r'^app01/',include('app01.urls')) #??? url(r'^app02/',include('app02.urls'))
难道不好奇为什么app01.urls就可以到具体的应用里面的路由中呢?
各个应用下面的urls.py里面的书写
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^index/',views.index) ]
名称空间
反向解析发现不能识别app01,app02,这就引入名称空间的概念
做法一:
url(r'^app01/',include(app01_urls,namespace='app01') ) url(r'^app02/',include(app02_urls,namespace='app02') )
app01.urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^index/',views.index,name='index') ]
app01.views.py
reverse('app01:index')
app02.urls.py
from django.conf.urls import url from app02 import views urlpatterns = [ url(r'^index/',views.index,name='index') ]
app02.views.py
reverse('app02:index')
做法二
app01.urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^index/',views.index,name='app01_index') ]
app01.views.py
reverse('app01_index')
伪静态网页
seo搜索优化 将动态的网页做成一个好像是静态网页,提高百度的搜索,就是提高seo搜索 url(r'^index.html',views.index,name='app01_index')
虚拟环境
一个项目对应一个环境,用什么模块下什么模块,提高加载速度 相当于重新下载了一个python解释器
如果你的项目中有venv--说明你用的是虚拟环境,venv是虚拟环境的标志
django1.0与django2.0之间的区别 django2.0里面的path第一个参数不支持正则,你写什么就匹配,100%精准匹配 django2.0里面的re_path对应着django1.0里面的url 虽然django2.0里面的path不支持正则表达式,但是它提供五个默认的转换器 str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式 int,匹配正整数,包含0。 slug,匹配字母、数字以及横杠、下划线组成的字符串。 uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。 path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
自定义转换器
- 正则表达式
- 类
- 注册
class FourDigitYearConverter: regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value # 占四位,不够用0填满,超了则就按超了的位数来! register_converter(FourDigitYearConverter, 'yyyy') PS:路由匹配到的数据默认都是字符串形式
视图层
FBV与CBV
FBV:基于函数的视图
CBV:基于类的视图
CBV: url(r'^mycls/',views.MyCls.as_view()) class MyCls(View): def get(self,request): return render(request,'index.html') def post(self,request): return HttpResponse('post')
无论是FBV还是CBV路由层都是路由对应视图函数内存地址
urlpatterns = [ # url(r'^mycls/',views.view) url(r'^mycls/',views.MyCls.as_view()) ] class MyCls(View): def get(self,request): return render(request,'index.html') def post(self,request): return HttpResponse('post')
JsonRespponse
from django.http import JsonResponse def index(request): # res = {'name':'Jason大帅比','password':18} # return HttpResponse(json.dumps(res)) return JsonResponse({'name':'Jason大帅比','password':'1888888'},json_dumps_params={'ensure_ascii':False})
Request对象和Response对象
request对象
当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象。
请求相关的常用值
- path_info 返回用户访问url,不包括域名
- method 请求中使用的HTTP方法的字符串表示,全大写表示。
- GET 包含所有HTTP GET参数的类字典对象
- POST 包含所有HTTP POST参数的类字典对象
- body 请求体,byte类型 request.POST的数据就是从body里面提取到的
属性
所有的属性应该被认为是只读的,除非另有说明。
属性: django将请求报文中的请求行、头部信息、内容主体封装成 HttpRequest 类中的属性。 除了特殊说明的之外,其他均为只读的。 0.HttpRequest.scheme 表示请求方案的字符串(通常为http或https) 1.HttpRequest.body 一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。 但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。 另外,我们还可以用 python 的类文件方法去操作它,详情参考 HttpRequest.read() 。 2.HttpRequest.path 一个字符串,表示请求的路径组件(不含域名)。 例如:"/music/bands/the_beatles/" 3.HttpRequest.method 一个字符串,表示请求使用的HTTP 方法。必须使用大写。 例如:"GET"、"POST" 4.HttpRequest.encoding 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。 这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。 接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。 如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。 5.HttpRequest.GET 一个类似于字典的对象,包含 HTTP GET 的所有参数。详情请参考 QueryDict 对象。 6.HttpRequest.POST 一个类似于字典的对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。 POST 请求可以带有空的 POST 字典 —— 如果通过 HTTP POST 方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。 因此,不应该使用 if request.POST 来检查使用的是否是POST 方法;应该使用 if request.method == "POST" 另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。 7.HttpRequest.COOKIES 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。 8.HttpRequest.FILES 一个类似于字典的对象,包含所有的上传文件信息。 FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。 注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会 包含数据。否则,FILES 将为一个空的类似于字典的对象。 9.HttpRequest.META 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例: CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。 CONTENT_TYPE —— 请求的正文的MIME 类型。 HTTP_ACCEPT —— 响应可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 HTTP_HOST —— 客服端发送的HTTP Host 头部。 HTTP_REFERER —— Referring 页面。 HTTP_USER_AGENT —— 客户端的user-agent 字符串。 QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。 REMOTE_ADDR —— 客户端的IP 地址。 REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的用户。 REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 SERVER_PORT —— 服务器的端口(是一个字符串)。 从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时, 都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。 所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。 10.HttpRequest.user 一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。 如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们。 例如: if request.user.is_authenticated(): # Do something for logged-in users. else: # Do something for anonymous users. user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。 ------------------------------------------------------------------------------------- 匿名用户 class models.AnonymousUser django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点: id 永远为None。 username 永远为空字符串。 get_username() 永远返回空字符串。 is_staff 和 is_superuser 永远为False。 is_active 永远为 False。 groups 和 user_permissions 永远为空。 is_anonymous() 返回True 而不是False。 is_authenticated() 返回False 而不是True。 set_password()、check_password()、save() 和delete() 引发 NotImplementedError。 New in Django 1.8: 新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。 11.HttpRequest.session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。 完整的细节参见会话的文档。 request属性相关
文件上传
前端需要注意的点:
1.method需要指定成post
2.enctype需要改为formdata格式
后端需要注意的点:
1.配置文件中注释掉csrfmiddleware中间件
2.通过request.FILES获取用户上传的post文件数据
def upload(request): """ 保存上传文件前,数据需要存放在某个位置。默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。从内存读取一次,写磁盘一次。 但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。 :param request: :return: """ if request.method == "POST": # 从请求的FILES中获取上传文件的文件名,file为页面上type=files类型input的name属性值 filename = request.FILES["file"].name # 在项目目录下新建一个文件 with open(filename, "wb") as f: # 从上传的文件对象中一点一点读 for chunk in request.FILES["file"].chunks(): # 写入本地文件 f.write(chunk) return HttpResponse("上传OK") 上传文件示例代码
方法
1.HttpRequest.get_host() 根据从HTTP_X_FORWARDED_HOST(如果打开 USE_X_FORWARDED_HOST,默认为False)和 HTTP_HOST 头部信息返回请求的原始主机。 如果这两个头部没有提供相应的值,则使用SERVER_NAME 和SERVER_PORT,在PEP 3333 中有详细描述。 USE_X_FORWARDED_HOST:一个布尔值,用于指定是否优先使用 X-Forwarded-Host 首部,仅在代理设置了该首部的情况下,才可以被使用。 例如:"127.0.0.1:8000" 注意:当主机位于多个代理后面时,get_host() 方法将会失败。除非使用中间件重写代理的首部。 2.HttpRequest.get_full_path() 返回 path,如果可以将加上查询字符串。 例如:"/music/bands/the_beatles/?print=true" 3.HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 返回签名过的Cookie 对应的值,如果签名不再合法则返回django.core.signing.BadSignature。 如果提供 default 参数,将不会引发异常并返回 default 的值。 可选参数salt 可以用来对安全密钥强力攻击提供额外的保护。max_age 参数用于检查Cookie 对应的时间戳以确保Cookie 的时间不会超过max_age 秒。 复制代码 >>> request.get_signed_cookie('name') 'Tony' >>> request.get_signed_cookie('name', salt='name-salt') 'Tony' # 假设在设置cookie的时候使用的是相同的salt >>> request.get_signed_cookie('non-existing-cookie') ... KeyError: 'non-existing-cookie' # 没有相应的键时触发异常 >>> request.get_signed_cookie('non-existing-cookie', False) False >>> request.get_signed_cookie('cookie-that-was-tampered-with') ... BadSignature: ... >>> request.get_signed_cookie('name', max_age=60) ... SignatureExpired: Signature age 1677.3839159 > 60 seconds >>> request.get_signed_cookie('name', False, max_age=60) False 复制代码 4.HttpRequest.is_secure() 如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。 5.HttpRequest.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。 大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。 如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware, 你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。 请求相关方法
今天就暂时到这里了,更多详情,请看下回分享🤭🤭🤭