URL
Django奉行DRY主义,提倡使用简洁、优雅的URL:
.html
、.php
或.cgi
之类后缀;URL路由在Django项目中的体现就是urls.py文件,这个文件可以有很多个,但绝对不会在同一目录下。
实际上Django提倡项目有个根urls.py,各app下分别有自己的一个urls.py,既集中又分治,是一种解耦的模式。
新建一个Django项目,默认会自动会在项目根目录下创建一个urls.py
文件,就是项目的根URL:
"""BooksManage URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
默认导入了url和admin,有一条指向admin后台的url路径。
建议注释掉后台路由或改掉默认后台url路径。
urls.py中默认就有urlpatterns,可以把它看作一个存放了映射关系的列表。
django2.0中常用的是path()方法,还可以使用re_path()方法来兼容1.x版本中的url()方法。
用法大致都是一样的,这些方法主要接收4个参数:
regext
和view
kwargs
和name
regex(正则表达式):
view(视图函数):
kwargs:
name(别名):
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path('articles/2018/', views.special_case_2018),
path('articles//' , views.year_archive),
path('articles///' , views.month_archive),
path('articles////' , views.article_detail),
]
注:
匹配例子:
* /articles/2017/06/ 将匹配第三条,并调用views.month_archive(request, year=2017, month=6);
* /articles/2018/匹配第一条,并调用views.special_case_2018(request);
* /articles/2018将一条都匹配不上,因为它最后少了一个斜杠,而列表中的所有模式中都以斜杠结尾;
* /articles/2016/05/building-a-django-site/ 将匹配最后一个,并调用views.article_detail(request, year=2016, month=5, slug="building-a-django-site"。
默认情况下,Django内置下面的路径转换器:
0863561d3-9527-633c-b9b6-8a032e1565f0
。返回一个UUID对象;/
。 写一个类,并包含下面的成员和属性:
例如,新建一个converters.py文件,与urlconf同目录,写个下面的类:
class FourDigitYearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
写完类后,在URLconf 中注册,并使用它,如下所示,注册了一个yyyy:
from django.urls import register_converter, path
from . import converters, views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('articles/2018/', views.special_case_2018),
path('articles//' , views.year_archive),
...
]
Django2.0的url虽然改‘配置’了,但它依然向老版本兼容。
而这个兼容的办法,就是用re_path()方法代替path()方法。
re_path()方法在骨子里,根本就是以前的url()方法,只不过导入的位置变了。
下面是一个例子,对比一下Django1.11时代的语法,有什么太大的差别?
from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'^articles/(?P[0-9]{4})/$' , views.year_archive),
re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/$' , views.month_archive),
re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/(?P[\w-]+)/$' , views.article_detail),
]
如果需要获取URL中的一些片段,作为参数,传递给处理请求的视图函数,那么该怎么做呢?
答案是可以使用命名的正则表达式组来捕获URL中的值并以关键字参数传递给视图。
(?Ppattern)
, 使用有名分组,还可以解决因为视图函数中参数位置变动而导致页面显示混乱的情况。
无命名分组的时候,视图函数的形参名,可以随便定义。但是有命名分组,名字必须一一对应。
关键字参数在于,先赋值,再传参。所以视图函数,必须一一对应才行。
例:
from django.urls import path,re_path
from app01 import views
urlpatterns = [
re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/(?P[0-9]{4})/$' , views.year_archive),
re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/$' , views.month_archive),
re_path(r'^articles/(?P[0-9]{4})/(?P[0-9]{2})/(?P[0-9]{2})/$' , views.article_detail),
]
以上URL捕获的值作为关键字参数而不是位置参数传递给视图函数。
/articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数,而不是views.month_archive(request, '2005', '03')。
/articles/2003/03/03/ 请求将调用函数views.article_detail(request, year='2003', month='03', day='03')
在实际应用中,这意味你的URLconf 会更加明晰且不容易产生参数顺序问题的错误 —— 你可以在你的视图函数定义中重新安排参数的顺序。当然,这些好处是以简洁为代价;
请求的URL被看做是一个普通的Python字符串,URLconf将对其查找并进行匹配。
注:进行匹配时将不包括GET或POST请求方式的参数以及域名。
捕获的参数作为字符串类型传递给视图。
例:
在https://www.example.com/app/的请求中,URLconf匹配查找的是app/。
在https://www.example.com/app/?page=5的请求中,URLconf也将查找app/。
URLconf不会检查使用何种HTTP请求方法,所有请求方法POST、GET、HEAD等都将路由到同一个URL的同一个视图。
那么不同的请求在哪里进行处理呢?答案就是在URL路由的下一阶段,视图函数中才进行具体处理。
例:djiango 1.x版本的re_path
urlpatterns = [
re_path('articles/(?P[0-9]{4})/' , year_archive),
re_path('article/(?P[a-zA-Z0-9]+)/detail/' , detail_view),
re_path('articles/(?P[a-zA-Z0-9]+)/edit/' , edit_view),
re_path('articles/(?P[a-zA-Z0-9]+)/delete/' , delete_view),
]
思考:
第一个问题,函数 year_archive 中year参数是字符串类型的,因此需要先转化为整数类型的变量值,当然year=int(year) 不会有诸如如TypeError或者ValueError的异常。那么有没有一种方法,在url中,使得这一转化步骤可以由Django自动完成?
第二个问题,三个路由中article_id都是同样的正则表达式,但是你需要写三遍,当之后article_id规则改变后,需要同时修改三处代码,那么有没有一种方法,只需修改一处即可?
在Django2.0中,可以使用 path 解决以上的两个问题
这是一个简单的例子:
from django.urls import path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles//' , views.year_archive),
path('articles///' , views.month_archive),
path('articles////' , views.article_detail),
]
<>
从url中捕获值。
捕获一个整数变量。 /
字符。举例:
app01_urls.py
第一条规则使用path。urlpatterns = [
path('articles//' , views.article_year), # article_year(request,year=value1)
re_path(r'^articles/2003/$', views.special_year), # special_year(request)
re_path(r'^articles/(\d{4})/(\d{2})/$', views.article_month), # article_month(request,value1,value2)
# article_month(request,year=value1,month=value2,day=value3)
re_path(r'^articles/(?P\d{4})/(?P\d{2})/(?P\d{2})/$' , views.article_day),
path('login.html/', views.login, name="login_in"),
]
它和有命名分组类似,也是关键字传参。
不同的是,它做了类型转换,比如以上是转换成了int。
小技巧来指定视图参数的默认值。
其实就是和python函数中的传参道理是一样的。
示例:
URLconf(urls.py)
如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P[0-9]+)/$' , views.page),
]
View (views.py)
如下:
def page(request, num="2"):
# Output the appropriate page of blog entries, according to num.
...
上面的例子中,两个URL模式指向同一个视图views.page。
但是第一个模式不会从URL中捕获任何值。
如果第一个模式匹配,page()
函数将使用num参数的默认值”2”。
如果第二个模式匹配,page()
将使用捕获的num值。
在1个Django项目里面有多个APP目录,大家共有一个url容易造成混淆。
这时可以使用路由分发让每个APP的拥有自己单独的url,方便以后的维护管理。
做解藕,不将所有的url放到一个py文件里面,根据应用名来进行拆分。
也就是在每个app里,各自创建一个urls.py
路由模块,然后从根路由出发,将app所属的url请求,全部转发到相应的urls.py
模块中。
urls.py
中的官方说明:Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
路由转发使用的是include()
方法,所以需要导入,它的参数是转发目的地路径的字符串,路径以圆点分割。
每当Django 遇到include()
(来自django.conf.urls.include()
)时,它会去掉URL中匹配的部分并将剩下的字符串发送给include的URLconf做进一步处理,也就是转发到二级路由去。
from django.urls import include
例:
'''
At any point, your urlpatterns can “include” other URLconf modules. This
essentially “roots” a set of URLs below other ones.
'''
from django.urls import path,re_path,include
from app01 import views
urlpatterns = [
re_path(r'^admin/', admin.site.urls),
re_path(r'^blog/', include('blog.urls')),
]
from django.urls import path,re_path,include
from app01 import views
urlpatterns = [
re_path(r'^articles/(\d{4})/$', views.article_year), # article_year(request,分组匹配的值)
re_path(r'^articles/2003/$', views.special_year), # special_year(request)
re_path(r'^articles/(\d{4})/(\d{2})/$', views.article_month), # article_month(request,value1,value2)
# article_month(request,year=value1,month=value2,day=value3)
re_path(r'^articles/(?P\d{4})/(?P\d{2})/(?P\d{2})/$' , views.article_day),
]
from django.contrib import admin
from django.urls import path,re_path,include
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index),
re_path('^$', views.index),
re_path('^index/$', views.index), # index(request)
path('app01/', include('app01.app01_urls')),
# 注意:app01后面,必须有斜杠,否则页面无法访问。
]
访问原来的url: http://127.0.0.1:8000/articles/2003/
,提示404。
因为路由分发了,所以访问时,必须加上应用名。
正常访问url:http://127.0.0.1:8000/app01/articles/2003/
页面访问正常。
在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。
* 就好比像是拿到一块拼图碎片,在嵌入到拼图板中。
做为开发者,你也不会希望使要硬编码这些URL(费力、不可扩展且容易产生错误),因为这样容易导致一定程度上产生过期的URL。你会希望设计一种与URLconf 毫不相关的专门的URL 生成机制或者直接不用硬编码的方式。
urls.py
中起一个别名name(name=自定义的别名
)templates
模板中:使用url '变量名(别名)'
语法。views.py
中导入reverse
方法:from django.urls import reverse()
get_absolute_url()
方法。(也就是在模型model中)例:以登陆页面为例
* 原urls.py
配置
from django.contrib import admin
from django.urls import path,re_path
from app import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', views.login),
]
views.py
中相关配置def login(request):
return render(request, 'login.html')
login.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录title>
head>
<body>
<form action="/login/" method="post">
<label>用户名label>
<input type="text" name="user"/>
<label>密码label>
<input type="password" name="pwd" />
<input type="submit" value="登录">
form>
body>
html>
现发生需求变更,业务线的URL发生了更改:
要求从原http://127.0.0.1:8000/login/
改为http://127.0.0.1:8000/login.html/
。
我们当然可以手动将相关的login改为login.html,
在这个简单例子里大至就只需要改动urls中的login\
和html中的form表单action中的\login\
两处为login.html\
就完成了。
但如果是真实生产复杂的环境,某些大量重复出现的URL就不便了。
从这一个简单例子可看出之前硬编码(写死)的一个URL,后期发生需求变更URL要改变的时候,特别不利于维护。
由鉴于此,可以使用反向解析的方法去解决。
以上为例:
* 修改后的urls.py
部分
urlpatterns = [
path('admin/', admin.site.urls),
path('login.html/', views.login, name="login"),
# 为login.html添加了一个别名叫login
]
上述修改为login.html
url填加了一个别名叫login
。
别名去代指url。此时login
对应的值是路径login.html/
。
login.html
文件部分,用到一个语法,来引用url
变量。 <form action={% url 'login' %} method="post">
{% csrf_token %}
<label>用户名label>
<input type="text" name="user"/>
<label>密码label>
<input type="password" name="pwd" />
<input type="submit" value="登录">
form>
更改部分。
它表示从url文件(urls.py)中,调用变量login。利用render将页面渲染,返回给浏览器。
重新访问url:http://127.0.0.1:8000/login.html/
使用控制台查看html代码,发现action的属性,就是login.html。说明已经被后端给渲染出来了。
这就是反向解析,路径会变,但是别名不会变。别名是随着路径的变动而变动的。
如此以后再有改动url,只需在urls.py
中改动一处就可以了,方便维护。
推荐以后写页面,使用别名(反向解析)。
html文件中使用反向解析我们知道了,那么视图函数中又如何使用反向解析呢?
还是以上述为例,只不过这次我们以增加登录验证函数和成功跳转到首页为例。
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index, name="index"),
path('login/', views.login, name="login"),
]
给index/
同样设置一个别名,后面视图函数会使用到。
from django.shortcuts import render,HttpResponse,redirect
from django.urls import reverse
# 导入HttpResponse方法,这可以直接发送字符串给浏览器,简单测试模拟页面用
# 导入redirect方法,它用于控制跳转页面
# 导入reverse方法,用于反向解析(传入别名变量作为参数)
def index(request):
return HttpResponse('首页欢迎你,简单模拟首页页面用'
)
def login(request):
if request.method == "POST":
# 当请求类型为post时
username = request.POST.get("user")
# request.post.get可以拿到form表单中以关键字(name=)命名的变量值,
# 也就是拿到用户输入的用户名和密码
password = request.POST.get("pwd")
# 简单模拟数据库验证,通过验证时跳转到首页
if username == 'tielemao' and password == '123':
# 硬编码方式: return redirect('/index/')
# 使用反向解析如下,
# reverse方法用于将index别名解析回/index/
return redirect(reverse("index"))
return render(request, 'login.html')
效果如图:
之后不论在urls.py
中index/
怎么改变,只要别名指向它,通过反向解析的视图函数永远拿到的是最新的。
命名空间(英语:Namespace)是表示标识符的可见范围。
为什么引入命名空间?
URL命名空间可以保证反查到唯一的URL,即使不同的app使用相同的URL名称。
类似地,它还允许你在一个应用有多个实例部署的情况下反查URL。
换句话讲,因为一个应用的多个实例共享相同的命名URL,命名空间提供了一种区分这些命名URL 的方法。
实现命名空间的做法很简单,在urlconf文件中添加app_name = 'tielemao'
和namespace='author-login'
这种类似的定义。
例:有2个应用,app01和app02。
如果url控制层定义的name值一致,那么就会出现冲突。
为了解决这个问题,需要用到名称空间。
修改urls.py里面的路由分发,使用关键字namespace
声明命名空间
urlpatterns = [
path('admin/', admin.site.urls),
path('index/', views.index, name="index"),
re_path('^$', views.index),
re_path('^', include('app01.app01_urls',namespace="app01")),
]
app01_urls.py
,注册自定义转化器。并应用在article_year视图函数上。 #!/usr/bin/env python
# -*- coding: utf-8 -*-
#导入url_converter模块中的FourDigitYearConverter类
from app01.url_converter import FourDigitYearConverter
#必须导入register_converter方法
from django.urls import path,re_path,include,register_converter
from app01 import views
#注册方法FourDigitYearConverter,定义别名为yyyy
register_converter(FourDigitYearConverter,"yyyy")
#注意:如果有命名空间,必须要声明app_name,否则报错
app_name = 'app01'
urlpatterns = [
#使用自定义转换器
path('articles//' , views.article_year),
# article_year(request,year=value1)
re_path(r'^articles/2003/$', views.special_year),
# special_year(request)
re_path(r'^articles/(\d{4})/(\d{2})/$', views.article_month),
# article_month(request,value1,value2)
# article_month(request,year=value1,month=value2,day=value3)
re_path(r'^articles/(?P\d{4})/(?P\d{2})/(?P\d{2})/$' , views.article_day),
path('login/', views.login, name="login_in"),
]
urls.py
文件:from django.conf.urls import include, url
urlpatterns = [
url(r'^app01/', include('appo1.urls')),
]
此时,appo1.urls
中定义的URL将具有应用名称空间app01。
注意:如果有命名空间,必须要声明app_name,否则报错。
在include的URLconf模块中设置与urlpatterns属性相同级别的app_name属性。
必须将实际模块或模块的字符串引用传递到include()
,而不是urlpatterns本身的列表。
urls.py1
声明命名空间的语法为 app_name = 命名空间名
reverse("命名空间名:url别名")
{% url '命名空间名:url别名' %}
当Django找不到与请求匹配的URL时,或者当抛出一个异常时,将调用一个错误处理视图。
错误视图包括400、403、404和500,分别表示请求错误、拒绝服务、页面不存在和服务器错误。
它们分别位于:
handler400 —— django.conf.urls.handler400。
handler403 —— django.conf.urls.handler403。
handler404 —— django.conf.urls.handler404。
handler500 —— django.conf.urls.handler500。
这些值在根URLconf中设置,在其它app中的二级URLconf中设置这些变量无效。
Django有内置的HTML模版,用于返回错误页面给用户,但正常都会使用自己定义的友好错误页面。
步骤如下:
URLconf(urls.py)
如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P[0-9]+)/$' , views.page),
]
# 增加的条目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.page_error
def page_not_found(request):
return render(request, '404.html')
def page_error(request):
return render(request, '500.html')
def permission_denied(request):
return render(request, '403.html')
def bad_request(request):
return render(request, '400.html')
【end】
2018-7-2
参考引用:刘江的博客及教程部分
http://www.liujiangblog.com/blog/17/
http://www.liujiangblog.com/course/django/134