Django 如何处理一个请求
URL解释:
url的匹配规则:
- 普通用法
#1.普通用法,无参数情况,配置URL及其视图如下:
from django.conf.urls import url,include
from TestApp import views
urlpatterns = [
url('^test/2018/$',views.TestOne.as_view())
]
#views.py
# -*- coding: utf-8 -*-
from django.views import View
from django.http import HttpResponse
class TestOne(View):
def get(self,request):
return HttpResponse('TestOne:普通url的写法')
输入地址:http://127.0.0.1:8000/test/2018/
- 正则url写法
#urls.py写法:
from django.conf.urls import url,include
from TestApp import views
urlpatterns = [
url('^test/[0-9]{4}/$',views.TestTwo.as_view())
]
#views.py写法:
# -*- coding: utf-8 -*-
from django.views import View
from django.http import HttpResponse
class TestTwo(View):
def get(self,request):
return HttpResponse('TestTwo:正则url写法')
输入地址:http://127.0.0.1:8000/test/2017
#2.传递参数情况,URL中通过正则指定参数:
url('^test/([0-9]{4})/$',views.TestThree.as_view())
class TestThree(View):
def get(self,request,year):
return HttpResponse('TestThree:带单个非命名写法')
url('^test/([0-9]{4})/([0-9]{2})/([0-9]{2})/$',views.TestFour.as_view())
class TestFour(View):
def get(self,request,year,month,day):
return HttpResponse('TestFour:带多个非命名写法')
#因为当加上圆括号的时候,django就能从URL中捕获这一个值并传递给相对应的views函数,当然使用的是位置传参。
* 域名部分会被过滤掉;
* 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^test 而不是 ^/test;
* 每个正则表达式前面的'r' 是可选的但是建议加上,表示字符串中任何字符都不转义;
* 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号。
* 任何组匹配的变量,都会议字符串的形式传递给view, 例如通过([0-9]{4})匹配出了2019,但2019会被当做字符串传递给year。
从这里可以看出,视图的参数是根据URL的正则式,按顺序匹配并自动赋值的。虽然这样可以实现任意多个参数的传递,但是却不够灵活,URL看起来很混乱,而且
由于是正则匹配,有些情况下容易出错。
- 命名组
刚才我们在使用圆括号进行传参的时候是位置传参,那么如果我们希望使用关键字传参的时候该怎么办呢?
这时候我们就使用到了命名组,命名组的正则表达式语法是(?P<name>pattern),其中name是指传递参数的名字,pattern是指匹配模式。
url('^test/(?P<year>[0-9]{4})/$',views.TestFive.as_view())
class TestFive(View):
def get(self,request,year): #入参year一定要与url中的参数命一致
print(year) #获取的是传过来的值
return HttpResponse('TestFive:带单个命名参数的写法')
url('^test/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$',views.TestSix.as_view())
class TestSix(View):
def get(self,request,year,month,day):
print(year,month,day)
return HttpResponse('TestSix:带多个命名参数的写法')
- 指定视图参数的默认值
利用named group可以为view指定一个默认参数来匹配多条规则。
url(r'^test/$',views.TestFive.as_view()),
url(r'^test/(?P<year>[0-9]{4})/$',views.TestFive.as_view())
class TestFive(View):
def get(self, request, year = '2010'):
print('-->===>', self, request, year)
return HttpResponse('TestFive: 命名组1')
#地址栏输入http://127.0.0.1:8000/test,调用TestFive的话,则会取year默认值2010
- 错误处理
当Django 找不到一个匹配请求的URL 的正则表达式时,或者当抛出一个异常时,Django 将调用一个错误处理视图。
每一个请求,都会返回一个HTTP状态码
200 : 请求正常;
404:就是找不到页面,或者说匹配不到对应的path路径;
403:是指服务器拒绝, 一般出现这种情况,是用户被服务器拉进黑名单,或者安全拦截;
400:你的request异常,一般就是你的请求缺少内容;
500:服务器异常,这个一般就是代码出现了异常;
在URLconf中指定参数,这些参数分别是
handler404: 一个callable或一个字符串,表示如果没有URL模式匹配,应该调用的视图的完整Python导入路径。默认情况下,这是'django.views.defaults.page_not_found'。
handler500: 默认情况下,这是'django.views.defaults.page_not_found'。
handler403: 默认情况下,这是'django.views.defaults.permission_denied'。
handler400: 默认情况下,这是'django.views.defaults.bad_request'。
可以自定义报错信息:
handler404 = 'TestApp.views.erropage' #需要app名称.views.函数名, 这个指定的实际上是一个引入的路径。只能调用函数,不能用类方法
#views.py中
def erropage(request):
return HttpResponse('not found page')
#settings.py中调试模式关闭才能看到自定义的信息
DEBUG = False
- 包含其它的URLconfs
在任何时候,你的urlpatterns 都可以包含其它URLconf 模块。这实际上将一部分URL 放置于其它URL 下面。
#在应用app中新建一个urls.py放置具体的
url(r'^2018/$',views.TestOne.as_view()),
url(r'^(?P<year>[0-9]{4})/$',views.TestFive.as_view()),
url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.TestSix.as_view()),
url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.TestSeven.as_view())
#在项目的根 urls.py 配置文件改为:
import views
from django.conf.urls import url,include
url(r'^test/',include('TestApp.urls'))
#如果项目非常庞大,应用非常多,应用的 URL 都写在根 urls.py 配置文件中的话,会显的非常杂乱,
还会出现名称冲突之类的问题,这样对开发整个项目是非常不利的。可以这样解决,把每个应用的 URL
写在它们各自的 urls.py 配置文件里,然后在根 urls.py 里用 include() 函数引用
- URL反向解析
在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。
人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
换句话讲,需要的是一个DRY 机制。除了其它优点,它还允许设计的URL 可以自动更新而不用遍历项目的源代码来搜索并替换过期的URL。
要获取一个URL,最初拥有的信息是负责处理它的视图的标识(例如名字),与查找正确的URL 的其它必要的信息如视图参数的类型(位置参数、关键字参数)和值。
Django 提供了一个解决方案使得URL 映射是URL 设计唯一的储存库。你用你的URLconf填充它,然后可以双向使用它:
1)根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。
2) 根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。
第一种方式是我们在前面的章节中一直讨论的用法。第二种方式叫做反向解析URL、反向URL匹配、反向URL查询或者简单的URL反查。
在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:
1)在模板中:使用url 模板标签。
2)在Python 代码中:使用django.core.urlresolvers.reverse() 函数。
3)在更高层的与处理Django 模型实例相关的代码中:使用get_absolute_url() 方法。
- 重定向:
#利用redirect跳转
#主程序urls.py中
from django.conf.urls import url,include
url(r'^test/',include('TestApp.urls'))
#应用程序urls.py中
import views
url(r'^2018/$',views.TestOne.as_view()),
url(r'^visit/(?P<flag>[0-9]{1})$',views.Visit.as_view())
#views.py中
from django.shortcuts import render,redirect
class Visit(View):
def get(self,request,flag):
#这是一个重定向方法
#如果需要在这做一个跳转
if flag == '1':
return HttpResponse('hello success!')
else:
return redirect('/test/2018') #重定向跳一个地址, 因为url地址开始肯定是一个/, 如果你不带斜杠的话,它就会当成一个相对地址,它自会自动在当前的url中往后添加。
- URL 的反向解析
URL反向解析一般是通过[reverse函数]使用django.core.urlresolvers.reverse() 函数,以及模板中的url标记实现。
- 未带参数
url(r'^visit/(?P<flag>[0-9]{1})$',views.Visit.as_view())
url(r'^2018/$',views.TestOne.as_view(),name='login'),
#views.py
class TestOne(View):
def get(self,request):
print('-->===>',self,request)
return HttpResponse('TestOne:普通用法')
class TestOne(View):
def get(self,request):
print('-->===>',self,request)
return HttpResponse('TestOne:普通用法')
class Visit(View):
def get(self,request,flag):
#这是一个重定向方法
#如果需要在这做一个跳转
if flag == '1':
return HttpResponse('hello success!')
else:
# reverse未带参数
return redirect(reverse('login'))
- 带非命名参数的跳转
url(r'^visit/(?P<flag>[0-9]{1})$',views.Visit.as_view()),
url(r'^([0-9]{4})/([0-9]{2})/([0-9]{2})$', views.TestFour.as_view(),name='login3')
class Visit(View):
def get(self,request,flag):
#这是一个重定向方法
#如果需要在这做一个跳转
if flag == '1':
return HttpResponse('hello success!')
else:
# reverse
return redirect(reverse('login3',args=("2018", "03","21")))
class TestFour(View):
def get(self, request, year,month,day):
print('-->===>', self, request, year,month,day)
return HttpResponse('TestFour: 3个非命名参数')
- 命名参数的跳转
#主urls.py
url(r'^test/',include('TestApp.urls'))
#应用urls.py
url(r'^visit/(?P<flag>[0-9]{1})/$',views.Visit.as_view()),
url(r'^login/(?P<username>[a-z]{3})/$', views.Login.as_view(),name='regist')
#views.py
class Visit(View):
def get(self,request,flag):
if flag =='1':
return HttpResponse('登录成功')
else:
return redirect(reverse('regist',kwargs={'username':'llp'}))
class Login(View):
def get(self,request,username):
message = '{} please login'.format(username)
return HttpResponse(message)
总结:如果url里面带了无名或者有名参数,那么在重定向时,reverse需要带上args或者kwargs。
- URL 模式的命名
为了完成上面例子中的URL 反查,你将需要使用命名的URL 模式。URL 的名称使用的字符串可以包含任何你喜欢的字符。并不仅限于合法的Python 名称。
当命名你的URL 模式时,请确保使用的名称不会与其它应用中名称冲突。如果你的URL 模式叫做comment,而另外一个应用中也有一个同样的名称,当你在模板中使用这个名称的时候不能保证将插入哪个URL。
在URL 名称中加上一个前缀,比如应用的名称,将减少冲突的可能。我们建议使用myapp-comment 而不是comment。 - URL 命名空间
为什么需要命名空间呢?
在之前如果我们通过URL反查的话是通过URL模式中的name属性来进行反查标记的,但是name属性容易重复并且不利于复用,当我们要多次部署一个URL配置模块的时候,就无法通过简单的name属性来进行标记了。
一般来说,同一应用下的不同实例应该具有相同的应用命名空间,但是,这并不意味着不同应用可以使用相同的实例命名空间,因为实例命名空间在你所有项目中都是唯一的。 - 反查带命名空间的URL
当解析一个带命名空间的URL(例如'polls:index')时,Django 将切分名称为多个部分,然后按下面的步骤查找:
1)首先,Django 查找匹配的应用命名空间(在这个例子中为'polls')。这将得到该应用实例的一个列表。
2)如果有一个当前应用被定义,Django 将查找并返回那个实例的URL 解析器。当前应用可以通过请求上的一个属性指定。
3)当前应用还可以通过reverse() 函数的一个参数手工设定。
4)如果没有当前应用。Django 将查找一个默认的应用实例。默认的应用实例是实例命名空间 与应用命名空间 一致的那个实例(在这个例子中,polls 的一个叫做'polls' 的实例)。
5)如果没有默认的应用实例,Django 将挑选该应用最后部署的实例,不管实例的名称是什么。
6)如果提供的命名空间与第1步中的应用命名空间 不匹配,Django 将尝试直接将此命名空间作为一个实例命名空间查找。
7)如果有嵌套的命名空间,将为命名空间的每个部分重复调用这些步骤直至剩下视图的名称还未解析。然后该视图的名称将被解析到找到的这个命名空间中的一个URL。
如果name是唯一的时候,你可以直接只使用name,但是如果name在其它include中也存在相同name,为了区分,我们可以给inlude设置一个namespace
我们通过命名空间名称+名称访问。
为了程序的健壮性,我们尽量使用命名空间+名称访问
顶层其实有一个app名称,但是我们日常开发中,几乎不使用,该功能了解知道即可
#主urls.py
#namespace只有在有多个include时,才需要
#namespace 与name的设置规则一样,同一级别,名称需要保持唯一
url(r'^test/',include('TestApp.urls',namespace='test'))
#应用下的urls.py
url(r'^visit/(?P<flag>[0-9]{1})$',views.Visit.as_view()),
url(r'^2018/$',views.TestOne.as_view(),name='login')
#views.py
class Visit(View):
def get(self,request,flag):
#这是一个重定向方法
#如果需要在这做一个跳转
if flag == '1':
return HttpResponse('hello success!')
else:
return redirect(reverse('test:login'))
#如果url写了namespace,则这里需要namespace:name形式,否则报错;
class TestOne(View):
def get(self,request):
print('-->===>',self,request)
return HttpResponse('TestOne:普通用法')
地址栏输入:http://127.0.0.1:8000/test/visit/0会重定向到http://127.0.0.1:8000/test/2018/
url(r'^test/',include('TestApp.urls',namespace='visit'))
url(r'^visit/(?P<flag>[0-9]{1})/$',views.Visit.as_view()),
url(r'^login/(?P<username>[a-z]{3})/$', views.Login.as_view(),name='regist')
class Visit(View):
def get(self,request,flag):
if flag =='1':
return HttpResponse('登录成功')
else:
print(reverse('visit:regist',kwargs={'username':'llp'}))
return redirect(reverse('visit:regist',kwargs={'username':'llp'}))
class Login(View):
def get(self,request,username):
message = '{} please login'.format(username)
return HttpResponse(message)
#输入
http://127.0.0.1:8000/test/visit/0=>跳转到http://127.0.0.1:8000/test/login/llp/
打印出来的是:llp please login
要记住reverse这个函数其实就是返回一个字符串/test/login/llp/。