之前有在用Django写一些小网站,现在暑假想说再来複习一下之前买的这本书
于是我就把它写成一系列的文章,也方便查语法
而且因为这本书大概是2014年出的,如今Django也已经出到2.多版
有些内容也变得不再支援或适用,而且语法或许也改变了
所以我会以最新版的Python和Django来修正这本书的内容跟程式码
目录:django系列文章-Django学习纪录
16. URL配置与视图进阶技巧
16.1 主题1-汇入模组v.s.汇入函式
来看一下我们的urls.py
from django.contrib import adminfrom django.urls import path, re_pathfrom restaurants.views import menu, welcome, list_restaurants, comment, register, indexfrom django.contrib.auth.views import LoginView, LogoutViewurlpatterns = [ path('admin/', admin.site.urls), re_path(r'menu/(\d{1,5})', menu), path('welcome/', welcome), path('restaurants_list/', list_restaurants), re_path(r'comment/(\d{1,5})', comment), path('index/', index), path('accounts/login/', LoginView.as_view(template_name='login.html'), name='login'), path('accounts/logout/', LogoutView.as_view(), name='logout'), path('accounts/register/', register),]
这种写法如果当专案越来越複杂时,撰写的URL对应越来越多
这时就会产生出几个问题:
1.过多的视图函式要汇入导致汇入句太长
2.每一个使用到的视图函式都必须确定有被汇入
3.不同应用中的视图函式名称可能发生冲突
解决方式
汇入整个模组,而不是汇入个别的函式
from django.contrib import adminfrom django.urls import path, re_pathimport django.contrib.auth.viewsimport restaurants.viewsurlpatterns = [ path('admin/', admin.site.urls), re_path(r'menu/(\d{1,5})', restaurants.views.menu), path('welcome/', restaurants.views.welcome), path('restaurants_list/', restaurants.views.list_restaurants), re_path(r'comment/(\d{1,5})', restaurants.views.comment), path('index/', restaurants.views.index), path('accounts/login/', django.contrib.auth.views.LoginView.as_view(template_name='login.html'), name='login'), path('accounts/logout/', django.contrib.auth.views.LogoutView.as_view(), name='logout'), path('accounts/register/', restaurants.views.register),]
如此一来汇入句变得清爽,也比较不会有忘记汇入的问题
而且名称冲突的问题透过完整汇入路径而被解决
16.2 主题2-使用字串进行配置
16.2.1 用字串取代函式
新版的Django已不再支援使用字串进行配置
16.3 主题3-除错模式的URL配置
在开发阶段我们经常需要做一些测试
这时就会多出一些页面与URL pattern
不过我们不希望它们在正式上线时出现
那么可以这样做:
...from django.conf import settingsimport restaurants.views...if settings.DEBUG: urlpatterns += [ path('test/', restaurants.views.test), ]
这样就可以使得/test/
只有在除错模式时才有效
16.4 主题4-URL配置与视图函式的参数
16.4.1 从URL中取出参数
使用位置取出
这就是之前讲过的部分
要注意的是当超过一个以上的参数要被传递时
Django只会依序地将值赋予
因此我们要介绍一个更弹性的作法
使用关键字取出
就像python的函式允许使用关键字参数
URL传递参数时也允许使用关键字参数
urlpatterns = [ ... re_path(r'test/(?P<p1>\d{1,5})/(?P<p2>\d{1,5})', restaurants.views.test),]
def test(request, p1, p2): ...
透过(?P<关键字>要撷取的参数)
可以从URL中将要撷取的参数以关键字的方式取出
例如/test/1/2
会将'1'给p1,'2'给p2
这完全取决于名称而非位置
我们也可以改写一下comment
re_path(r'comment/(?P<id>\d{1,5})', restaurants.views.comment)
使用这个方法就不需要考虑参数的顺序了,有些时候会方便许多
而以上这两个方法是允许混用的,只是容易造成错误,并不推荐
16.4.2 传递额外的参数
如果今天我们想要让/firstmenu/
对应到/menu/1/
这个页面
但是/firstmenu/
却不能提供参数给视图函式
这时可以这样做
def menu(request, id=1): ...
urlpatterns = [ ... re_path(r'menu/(\d{1,5})', restaurants.views.menu), path('firstmenu/', restaurants.views.menu), ...]
但是如果又多出几个,譬如/secondmenu/
的话那就没办法了
使用另外一种方法
urlpatterns = [ ... re_path(r'menu/(\d{1,5})', restaurants.views.menu), path('firstmenu/', restaurants.views.menu, {'id':'1'}), path('secondmenu/', restaurants.views.menu, {'id':'2'}), ...]
当不同的URL要使用同一个视图函式时,这会是一个很好的办法
而且如果要同时从URL中抽取参数,又提供额外的字典参数,也是可以的
不过当两者混用产生冲突时,会以字典参数为主,这是比较需要注意的
通用视图
假如现在有两个视图函式,长的差不多,程式码也很多重複
那其实可以写成一个通用视图
mysite/templates/users_list.html
{% extends 'base.html' %}{% block title %} 使用者列表 {% endblock %}{% block content %} <table> <tr> <th>使用者帐号</th> <th>使用者姓名</th> </tr> {% for u in users %} <tr> <td>{{u.username}}</td> <td>{{u.last_name}}{{u.first_name}}</td> </tr> {% endfor %} <table>{% endblock %}
比如这两个视图函式
from django.contrib import authdef list_users(request): users = auth.models.User.objects.all() return render(request, 'users_list.html', locals())def list_restaurants(request): restaurants = Restaurant.objects.all() return render(request, 'restaurants_list.html', locals())
path('restaurants_list/', restaurants.views.list_restaurants),path('users_list/', restaurants.views.list_users),
这两个视图有着大量重複的逻辑,不如将共有的部分写成一个通用视图
def list(request, model): objs = model.objects.all() return render(request, '{0}s_list.html'.format(model.__name__.lower()), locals())
path('restaurants_list/', restaurants.views.list, {'model': restaurants.models.Restaurant}),path('users_list/', restaurants.views.list, {'model': auth.models.User}),
记得要将模板中的users
和restaurants
变量改为objs
资料库模型的属性__name__
可以取出模型的名称
再透过lower
函式转为小写
这样就解决了~
16.5 主题5-分层的URL配置
就如同模板可以隶属于不同的应用里一样
Django也允许我们将URL的配置分散到各个应用
再透过全站等级的根URL配置分层下去负责
settings.py中的
ROOT_URLCONF = 'mysite.urls'
...from django.contrib import admin...urlpatterns = [ path('admin/', admin.site.urls), ...]
当有人向网站发出URL请求时,Django会由ROOT_URLCONF
所指定的根URL配置来找寻对应的视图,接着如果发现include
关键字,则会进入下一层,到指定的配置档中继续试着配对URL
举个例子
mysite/restaurants/urls.py
from django.urls import path, re_pathimport restaurants.viewsurlpatterns = [ re_path(r'menu/(\d{1,5})', restaurants.views.menu, name='menu'), re_path(r'comment/(?P<id>\d{1,5})', restaurants.views.comment), path('restaurants_list/', restaurants.views.list, {'model': restaurants.models.Restaurant}),]
mysite/mysite/urls.py
from django.urls import path, re_path, includeurlpatterns = [ ... path('restaurants', include(restaurants.urls)),]
当网站接收到URL请求: /resraurants/menu/1/
,它会先配对/resraurants/
成功,因为还没到结尾所以配对未结束,所以任何以/resraurants/
开头的URL都会算做配对成功,无论是/resraurants/
或是/resraurants/menu/1/
或是/resraurants/comment/2/
接着会寻找include中指定的配置档再去配对,同时截掉目前已经配对成功的部份,因此在次层需要配对的URL从/resraurants/menu/1/
变为/menu/1/
,最后配对re_path(r'menu/(\d{1,5})', restaurants.views.menu, name='menu')
成功
在搬移配置的时候要注意:
1.有关的页面请求都要加上新的URL前缀(如上例为/resraurants/
)
2.必须自己修正在其他页面中重导到那些页面所使用的URL(因为原本可能没有前缀)
16.5.1 视图函式参数的传递
假设
...import app1...urlpatterns = [ re_path(r'foo/(?P<p1>\w+)/', include(app1.urls), {'p2': 'hello'}),]
app1/urls.py
...import app1.views...urlpatterns = [ path('bar1/', app1.views.bar1), path('bar2/', app1.views.bar2),]
p1和p2都会往下传递给所有在app1.urls
的视图函式,即bar1
和bar2
要注意的是,如果bar1
或bar2
并不需要这个参数,则会产生错误