之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼
來看一下我們的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),
]
如此一來匯入句變得清爽,也比較不會有忘記匯入的問題
而且名稱衝突的問題透過完整匯入路徑而被解決
新版的Django已不再支援使用字串進行配置
在開發階段我們經常需要做一些測試
這時就會多出一些頁面與URL pattern
不過我們不希望它們在正式上線時出現
那麼可以這樣做:
...
from django.conf import settings
import restaurants.views
...
if settings.DEBUG:
urlpatterns += [
path('test/', restaurants.views.test),
]
這樣就可以使得/test/
只有在除錯模式時才有效
這就是之前講過的部分
要注意的是當超過一個以上的參數要被傳遞時
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)
使用這個方法就不需要考慮參數的順序了,有些時候會方便許多
而以上這兩個方法是允許混用的,只是容易造成錯誤,並不推薦
如果今天我們想要讓/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}),
記得要將模板中的users
和restaurants
變量改為objs
資料庫模型的屬性__name__
可以取出模型的名稱
再透過lower
函式轉為小寫
這樣就解決了~
就如同模板可以隸屬於不同的應用裡一樣
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(因為原本可能沒有前綴)
假設
...
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
並不需要這個參數,則會產生錯誤