iT邦幫忙

0

Django學習紀錄 16.URL配置與視圖進階技巧

之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
https://ithelp.ithome.com.tw/upload/images/20190724/20118889bj9fH1vhuR.jpg
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼

目錄:django系列文章-Django學習紀錄

16. URL配置與視圖進階技巧

16.1 主題1-匯入模組v.s.匯入函式

來看一下我們的urls.py

from django.contrib import admin
from django.urls import path, re_path
from restaurants.views import menu, welcome, list_restaurants, comment, register, index
from django.contrib.auth.views import LoginView, LogoutView

urlpatterns = [
    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 admin
from django.urls import path, re_path
import django.contrib.auth.views
import restaurants.views

urlpatterns = [
    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 settings
import 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 auth

def 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}),

記得要將模板中的usersrestaurants變量改為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_path
import restaurants.views

urlpatterns = [
    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, include

urlpatterns = [
    ...
    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的視圖函式,即bar1bar2
要注意的是,如果bar1bar2並不需要這個參數,則會產生錯誤

上一篇:Django學習紀錄 15.模板進階技巧

下一篇:Django學習紀錄 17.視圖類別


尚未有邦友留言

立即登入留言