談談Python之Django搭建企業級官網(第三篇下)

轉載請註明來源地址和原做者(CFishHome)前端

前沿

上一篇文章咱們學習了URL與視圖函數的映射、傳遞參數的三種方式、轉換器的簡單使用和include函數分層映射管理。接下來這一篇文章着重介紹Path、re_path、include、reverse、redirect函數的使用和自定義URL轉換器。學完這些內容,相信咱們對URL和視圖都會有了必定的瞭解和認識。爲了讓每篇文章具備必定的獨立性,我決定每篇文章都從新新建一個項目,便於測試和調試。python

預備

首先,咱們在Pycharm從新新建一個名爲book_project的Django項目,而後打開CMD窗口進入虛擬環境,接着跳轉到該項目的目錄下(含有manage.py),再依次執行建立app命令,以下圖:
談談Python之Django搭建企業級官網(第三篇下)
建立完成後,該項目的樣子大概是醬紫的:
談談Python之Django搭建企業級官網(第三篇下)git

模擬前端和後端

咱們想實現:front前端擁有前端首頁和前端登錄頁面,而cms後端擁有後端首頁和後端登錄頁面,而後分別輸入配置好的URL對這四個頁面進行訪問。
首先,爲cms和front兩個app手動添加urls.py文件。而後分別按如下步驟添加或修改項目文件代碼:
(1)爲cms的urls.py文件添加如下代碼:正則表達式

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index),
    path("login/", views.login)
]

(2)爲front的urls.py文件添加如下代碼:django

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index),
    path("login/", views.login)
]

(3)爲cms的views.py文件添加如下代碼:後端

from django.http import HttpResponse

def index(request):
    return HttpResponse("後端首頁")

def login(request):
    return HttpResponse("後端登錄頁面")

(4)爲front的views.py文件添加如下代碼:app

from django.http import HttpResponse

def index(request):
    return HttpResponse("前端首頁")

def login(request):
    return HttpResponse("前端登錄頁面")

(5)爲book_project這個主包的urls.py文件添加如下代碼:ide

from django.urls import path,include

urlpatterns = [
    path('', include("front.urls")),
    path('cms/', include("cms.urls"))
]

若是學習了上一篇文章的內容,我相信這些代碼的編寫是徹底OK的。好,咱們運行一下項目(注意,咱們是新建的項目,記得在勾選單一實例運行。)。
運行結果以下(完美,成功運行~):
談談Python之Django搭建企業級官網(第三篇下)函數

實際需求

模擬自動跳轉新用戶登錄頁面

實際需求:要想實現一個相似於知乎主頁同樣,如果新用戶第一次登錄知乎,無論哪一個頁面都會跳轉到登錄帳號的頁面,對於這一需求,咱們能夠經過如下模擬實現(爲了更好觀察代碼,同樣的代碼部分我直接提示省略):
(1)修改front前臺的index視圖函數學習

#省略上面代碼
from django.shortcuts import redirect  # 導入重定向redirect函數,最好記住redirect函數源於哪一個模塊,由於要常常重定向。

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,當輸入http://127.0.0.1:8000/?username=1正常顯示前臺首頁。
    if username:
        return HttpResponse('前臺首頁')
    else:
        return redirect('/login/')  # 跳轉經過redirect函數來進行頁面重定向,從新訪問指定的url頁面
#省略下面代碼

修改完代碼後,按下Ctrl+S保存(自動從新編譯項目)。注意觀察,輸入127.0.0.1:8000會發現自動跳轉到127.0.0.1:8000/login/。

重定向代碼的缺陷

其實還存在這樣一種狀況,假如老闆或項目經理忽然腦殼秀逗了,要求你更改網頁的URL,將127.0.0.1:8000/login/更改成127.0.0.1:8000/signin/,假如這時還採用上面的代碼進行網頁重定向,那你會很是抓狂,舉咱們這個項目栗子來講,不單隻要修改成下面這樣:

urlpatterns = [
    path("", views.index),
    path("signin/", views.login)
]

還要修改重定向位置的代碼這樣:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,不然正常顯示前臺首頁。
    if username:
        return HttpResponse('前臺首頁')
    else:
        return redirect('/singin/')  # 跳轉經過redirect函數來進行頁面重定向,從新訪問指定的url頁面

若是公司網站項目十分龐大的話,那可能還有不少地方都要修改,這十分耗時也費力。因此,咱們推薦爲URL命名來解決這一問題,給URL取個名字,只要調用reverse反轉URL的名字而不是直接重定向寫死的URL,那麼不管URL怎麼修改也影響不到其餘地方。

Path函數

在學習URL命名以前,先詳細學習下Path函數的使用。
path 函數的定義爲:

path(route,view,name=None,kwargs=None)  。

如下對這幾個參數進行講解。

  1. route 參數: url 的匹配規則。這個參數中能夠指定 url 中須要傳遞的參數,上一篇文章已講解,這個參數再也不贅述。請移步到談談Python之Django搭建企業級官網(第三篇上部)
  2. view 參數:能夠爲一個視圖函數或者是 類視圖.as_view() 或者是 django.urls.include() 函數的返回值。
  3. name 參數:這個參數是給這個 url 取個名字的,這在項目比較大, url 比較多的時候用處很大。這個參數就是用於URL命名。
  4. kwargs 參數:有時候想給視圖函數傳遞一些額外的參數,就能夠經過 kwargs 參數進行傳遞。這個參數接收一個字典。會將foo=bar做爲關鍵字實參傳入視圖函數,因此視圖函數要有形參來接收實參。
    好比如下的 url 規則:
    from django.urls import path
    from . import views
    urlpatterns = [
        path('blog/<int:year>/', views.year_archive, kwargs={'foo':'bar'}),  
    ]

    那麼之後在訪問 blog/1991/ 這個url的時候,會將{'foo':'bar'}做爲關鍵字參數傳給 year_archive函數。year_archive視圖函數的形參中最後添加一個kwargs參數來接收這個額外的參數。

URL命名

學習完path函數各參數,相信都知道該函數的name參數就是用於URL命名的了。
接下來修改front包的urls.py文件代碼以下:

urlpatterns = [
    path("", views.index),
    path("login/", views.login, name='login')
]

再次修改front包的views.py文件代碼以下:

#省略上面代碼
from django.shortcuts import redirect, reverse  # 注意這裏添加了reverse函數,reverse函數用於將指定URL名字反轉成URL。

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,不然正常顯示前臺首頁。
    if username:
        return HttpResponse('前臺首頁')
    else:
        # reverse('login')函數返回‘/login/’
        return redirect(reverse('login'))  # 跳轉經過redirect函數來進行頁面重定向,從新訪問指定的url頁面(至關於從新訪問127.0.0.1:8000/login/)
#省略下面代碼

按下Ctrl+S保存,輸入127.0.0.1:8000成功跳轉到127.0.0.1:8000/login登錄頁面。

應用命名空間

在公司實際開發中,若是公司裏的多我的同時負責網站的開發,並且A同事負責開發front的app,B同事負責開發cms的app,那麼因爲兩個app都有首頁和登錄頁面,那麼有可能url的name屬性可能會相同衝突。在多個app之間,有可能產生同名的url,這時候爲了不反轉url的時候產生混淆,可使用應用命名空間來作區分。定義應用命名空間很是簡單,只要在「app」的「urls.py」中定義一個叫作「app_name」的變量,來指定這個應用的命名空間便可。
就比如咱們的項目,將front包裏的urls.py和views.py文件和cms包裏的urs.py和views.py文件分別爲URL映射命名爲index和login,以下圖所示:
談談Python之Django搭建企業級官網(第三篇下)
運行項目,輸入127.0.0.1:8000自動跳轉到127.0.0.1:8000/cms/login網頁,而不是以前的127.0.0.1:8000/login網頁,因爲多個app的URL擁有相同的名字,因此Django在執行reverse函數反轉URL時懵逼了。爲了解決這個問題,咱們將採用應用命名空間。
修改front包的urls.py文件的代碼以下(django在執行到app時,會自動讀取這個應用命名空間並將這個名字做爲app的名字):

from django.urls import path
from . import views

app_name = "front"  # 添加了應用命名空間

urlpatterns = [
    path("", views.index, name="index"),
    path("login/", views.login, name='login')
]

之後在作反轉的時候就可使用「應用命名空間:url名稱」的方式進行反轉。示例代碼以下修改front包的views.py文件的代碼以下:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,不然正常顯示前臺首頁。
    if username:
        return HttpResponse('前臺首頁')
    else:
        return redirect(reverse('front:login'))  # 這裏爲URL名字前面添加front應用命名空間名

按下Ctrl+S保存,輸入127.0.0.1:8000自動跳轉到127.0.0.1:8000/login網頁,成功了,Django不會再懵逼了。

include函數

在上一篇文章咱們知道,在項目不斷龐大之後,常常不會把全部的 url 匹配規則都放在項目的 urls.py 文件中,而是每一個 app 都有本身的 urls.py 文件,在這個文件中存儲的都是當前這個 app 的全部url 匹配規則。而後再統一include到項目的 urls.py 文件中。 include 函數有多種用法,這裏講下幾種經常使用的用法:
(1)include(pattern,namespace=None) :直接把其餘 app 的 urls 包含進來。
以前的include用法,舉個栗子(下面的代碼僅用於解釋用法,不是將代碼添加到項目):

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
        path('admin/', admin.site.urls),
        path('book/',include("book.urls"))
]

咱們能夠發現這一種用法其實該函數還有參數2指定實例命名空間,默認是None。可是在使用實例命名空間以前,必須先指定一個應用命名空間,不先指定應用命名空間會報錯。一個app能夠建立多個實例,也就是可使用多個url映射同一個app,因此這就產生一個問題。之後在作反轉的時候,若是使用應用命名空間,那麼就會發生混淆。
將book_project主包的urls.py文件代碼修改以下:

from django.urls import path,include

urlpatterns = [
    path('', include("front.urls")),
    path('cms1/', include("cms.urls")),  # 這兩行代碼表明輸入127.0.0.1:8000/cms1或127.0.0.1:8000/cms2能映射到cms.urls內部的url路徑。
    path('cms2/', include("cms.urls"))
]

爲cms包的urls.py文件添加應用命名空間:

app_name = "cms"

爲cms包的views.py文件修改代碼以下:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,不然正常顯示前臺首頁。
    if username:
        return HttpResponse('後臺首頁')
    else:
        return redirect(reverse('cms:login'))  # 這裏使用了應用命名空間反轉URL

通過上面代碼的修改,這時候按下Ctrl+S保存,輸入127.0.0.1:8000/cms1成功跳轉到127.0.0.1:8000/cms1/login,可是當輸入127.0.0.1:8000/cms2卻自動跳轉到了127.0.0.1:8000/cms1/login,what?個人127.0.0.1:8000/cms2/login哪裏去了?致使上面的緣由就是前面介紹的一個app能夠建立多個實例,也就是可使用多個url映射同一個app,因此這就產生一個問題。之後在作反轉的時候,若是使用應用命名空間,那麼就會發生混淆。爲了不這個問題,咱們可使用實例命名空間。實例命名空間也是很是簡單,只要在「include」函數中傳遞一個「namespace」變量便可。
在上面代碼的基礎上繼續修改代碼,修改cms包的views.py文件代碼以下:

def index(request):
    username = request.GET.get('username')
    if username:
        return HttpResponse("後端首頁")
    else:
        current_namespace = request.resolver_match.namespace  # 返回當前app對應的實例命名空間(cms1或cms2)
        return redirect(reverse('%s:login' % current_namespace))  # 至關於"cms1:login"或」cms2:login「

接着修改book_project主包的urls.py文件代碼以下:

from django.urls import path,include

urlpatterns = [
    path('', include("front.urls")),
    path('cms1/', include("cms.urls", namespace="cms1")),  # 使用了實例命名空間namespace
    path('cms2/', include("cms.urls", namespace="cms2"))  
]

這時候按下Ctrl+S保存,輸入127.0.0.1:8000/cms1成功跳轉到127.0.0.1:8000/cms1/login,而後輸入127.0.0.1:8000/cms2也成功跳轉到了127.0.0.1:8000/cms2/login頁面。以下圖:
談談Python之Django搭建企業級官網(第三篇下)
(2)include((pattern_list,app_namespace),namespace=None) :「include」函數的第一個參數既能夠做爲一個字符串,也能夠做爲一個元組,若是是元組,那麼元組的第一個參數是子「urls.py」模塊的字符串,元組的第二個參數是應用命名空間,也就是說應用命名空間既能夠在子「urls.py」種經過"app_name"指定,也能夠在「include」函數中指定。
將book_project主包的urls.py文件代碼修改以下:

from django.urls import path, include
from front import views

urlpatterns = [
    path('', include(([
                          path('', views.index, name="index"),
                          path("login/", views.login, name='login')
                      ], "front"))),  # 注意
    path('cms1/', include("cms.urls", namespace="cms1")),
    path('cms2/', include("cms.urls", namespace="cms2"))
]

上面的代碼至關於徹底忽略了front包的urls.py文件的做用,由於已經被include函數的第一個參數列表給替代了,因此urls.py文件的「app_name = "front"」指定的應用命名空間天然也失效了。這時運行代碼徹底OK,跟以前的一摸同樣,輸入127.0.0.1:8000自動跳轉到127.0.0.1:8000/login頁面。
(3)include(pattern_list) :能夠包含一個列表或者一個元組,這個元組或者列表中又包含的是 path 或者是 re_path 函數(該函數後面會講)。
這個函數跟上一個函數差很少,也是用含有path函數的列表或元組替代了以前的pattern。可是須要注意的是,由於這樣會忽略了front包的urls.py文件的做用,因此urls.py文件的「app_name = "front"」指定的應用命名空間天然也失效了。那麼若是你映射的視圖函數內部進行反轉URL時指定了應用命名空間,那麼將會報錯,會提示找不到front命名空間,以下:

談談Python之Django搭建企業級官網(第三篇下)

因此,綜上建議,若是你映射的視圖函數內部進行反轉URL時指定了應用命名空間,最好調用include((pattern_list,app_namespace),namespace=None) 函數,在指定列表或元組 的同時也指定應用命名空間。

reverse函數

以前咱們都是經過url來訪問視圖函數。有時候咱們定義了URL名字,而後經過調用reverse函數反轉回它的url。就好像前面的代碼調用那樣:

reverse("login")
> /login/

若是有應用命名空間或者有實例命名空間,那麼應該在反轉的時候加上命名空間。就好像前面的代碼調用那樣:

reverse('front:login')
> /login/  # 注意,反轉的是前端app的url

接着咱們就要考慮另一個問題,就是若是URL映射時須要傳遞參數,這時reverse函數該怎麼調用才能傳遞參數呢?咱們都知道傳遞參數有其中兩種方式:1.尖括號方式傳遞參數 2.查詢字符串傳遞參數。
下面分別講解這兩種傳遞參數方式在reverse函數中的使用:
(1)URL尖括號傳遞參數
若是這個url中須要傳遞參數,那麼能夠經過 kwargs 來傳遞參數。
首先,咱們修改front包的views.py文件的代碼以下:

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,不然正常顯示前臺首頁。
    if username:
        return HttpResponse('前臺首頁')
    else:
        return redirect(reverse('front:login', kwargs={'user_id': 1}))  # 跳轉經過redirect函數來進行頁面重定向,從新訪問指定的url頁面

def login(request, user_id):
    text = "當前用戶登錄的ID是:%s" % user_id
    return HttpResponse(text)

接着,因爲上節測試include函數,將front的url映射全放在book_project主包的urls.py文件裏,因此修改爲這樣:

urlpatterns = [
    path('', include(([
                          path('', views.index, name="index"),
                          path("login/<user_id>/", views.login, name='login')
                      ], "front"))),
    path('cms1/', include("cms.urls", namespace="cms1")),
    path('cms2/', include("cms.urls", namespace="cms2"))
]

按下Ctrl+S保存,運行結果以下(成功訪問,說明reverse傳遞尖括號參數是有效的):
談談Python之Django搭建企業級官網(第三篇下)
(2)查詢字符串傳遞參數
由於 django 中的 reverse 反轉 url 的時候不區分 GET 請求和 POST 請求,所以不能在反轉的時候添加查詢字符串的參數。若是想要添加查詢字符串的參數,只能手動的添加。
首先,咱們修改front包的views.py文件的代碼以下(注意代碼的細微調動):

def index(request):
    # ?username=xxx
    username = request.GET.get('username')  # 當用戶輸入127.0.0.1:8000,檢測到沒有帳戶則跳轉到註冊頁面,不然正常顯示前臺首頁。
    if username:
        return HttpResponse('前臺首頁')
    else:
        return redirect(reverse('front:login') + "?next=/")  # 跳轉經過redirect函數來進行頁面重定向,從新訪問指定的url頁面

def login(request):
    n = request.GET.get("next")
    text = "你輸入的next值爲:%s" % n
    return HttpResponse(text)

接着,咱們修改book_project主包的urls.py文件的代碼以下(注意代碼的細微調動):

urlpatterns = [
    path('', include(([
                          path('', views.index, name="index"),
                          path("login", views.login, name='login')
                      ], "front"))),
    path('cms1/', include("cms.urls", namespace="cms1")),
    path('cms2/', include("cms.urls", namespace="cms2"))
]

按下Ctrl+S保存,運行結果以下(成功訪問,說明reverse傳遞查詢字符串參數是有效的):
談談Python之Django搭建企業級官網(第三篇下)

re_path函數

有時候咱們在寫url匹配的時候,想要寫使用正則表達式來實現一些複雜的需求,那麼這時候咱們可使用 re_path 來實現。 re_path 的參數和 path 參數如出一轍,只不過第一個參數也就
是 route 參數能夠爲一個正則表達式。
一些使用 re_path 的示例代碼以下:

from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'articles/(?P<year>[0-9]{4})/', views.year_archive),
re_path(r'articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/', views.month_archiv
e),
re_path(r'articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-_]+)/',
views.article_detail),
]

以上例子中咱們能夠看到,全部的 route 字符串前面都加了一個 r ,表示這個字符串是一個原生字符串。在寫正則表達式中是推薦使用原生字符串的,這樣能夠避免在 python 這一層面進行轉義。
用法:(?P<參數變量>匹配參數量的正則表達式),這個用法顯而易見,這個參數的名字是經過尖括號 <year> 進行包裹,以後纔是寫正則表達式的語法,說明若是在傳遞這個參數變量的時候,這個參數變量必須知足後面所寫的正則表達式,不然就會報錯。

自定義URL轉換器

在學習前面的轉換器和reverse函數反轉時,確定會產生疑惑:爲何轉換器能實現轉換功能?還有reverse函數是怎麼反轉成一個URL的呢?
好的,這些轉換和反轉過程允許我一一道來。
咱們都看過converters模塊的實現,以下圖:
談談Python之Django搭建企業級官網(第三篇下)
能夠看出這個Int轉換器類除了定義了正則表達式,還定義了兩個函數to_Python和to_url。
轉換器和reverse相互轉化過程以下:

當給path函數的第一個參數使用尖括號參數,若是使用轉換器,那麼會將這個參數傳遞給to_python函數進行int轉換,而後將轉換後的int值傳遞給視圖函數的同名形參接收,因此視圖函數的形參變成int類型的了。與此同時,若是視圖函數內調用reverse函數進行反轉的話,那麼reverse函數的kwargs參數的int類型值(即視圖函數的形參)傳遞給to_url轉換爲str類型,而後將轉換後的str值傳遞path函數的第一個參數,組合成真正的字符串URL,最後reverse函數再返回這個字符串URL。

以前已經學到過一些django內置的 url 轉換器,包括有 int 、 uuid 等。有時候這些內置的 url轉換器並不能知足咱們的需求,所以django給咱們提供了一個接口可讓咱們本身定義本身的url轉換器,即本身定義一個轉換器類來實現轉換。自定義 url 轉換器按照如下五個步驟來走就能夠了:

  1. 定義一個類。
  2. 在類中定義一個屬性 regex ,這個屬性是用來保存 url 轉換器規則的正則表達式。
  3. 實現 to_python(self,value) 方法,這個方法是將 url 中的值轉換一下,而後傳給視圖函數
    的。
  4. 實現 to_url(self,value) 方法,這個方法是在作 url 反轉的時候,將傳進來的參數轉換後拼
    接成一個正確的url。
  5. 將定義好的轉換器,註冊到django中。
    爲了更好的描述如何自定義URL轉換器,這裏咱們從新建立一個名爲my_converters的Django項目,而後建立一個名爲my_app的app。

(1)修改my_converters主包的urls.py文件代碼以下:

from django.urls import path
from my_app import views
from django.urls import register_converter

#1. 定義一個類
class FourDigitYearConverter:
    #2. 定義一個正則表達式
    regex = '[0-9]{4}'

    #3. 定義to_python方法
    def to_python(self, value):
        return int(value)

    #4. 定義to_url方法
    def to_url(self, value):
        return '%04d' % value

#5. 註冊到django中
register_converter(FourDigitYearConverter, 'four_year')

urlpatterns = [
    path('index/<four_year:year>/', views.index)
]

(2)修改my_app包的views.py文件代碼以下:

from django.http import HttpResponse

def index(request, year):
    text = "你輸入的年份是:%s" % year
    return HttpResponse(text)

而後按下Ctrl+S保存,輸入127.0.0.1:8000/index/220,由於不知足咱們本身定義的URL轉換器的正則表達式,因此頁面顯示不出來,報如下錯誤:
談談Python之Django搭建企業級官網(第三篇下)
再次輸入127.0.0.1:8000/index/2018,頁面訪問成功,說明咱們定義的four_year轉換器有效,以下圖:
談談Python之Django搭建企業級官網(第三篇下)
咱們其實除了在regex下修改,也能夠隨意編寫to_python函數和to_url函數的內部實現,徹底按照咱們的意願進行工做,很靈活呢。對於自定義URL轉換器有個更好的改進之處,就是咱們能夠新建一個converters.py文件專門保存咱們自定義的URL轉換器,但有個問題就是,這個文件是咱們本身新建的,Django怎麼會知道而且執行裏面的代碼呢?若是Django不執行的話,那咱們這個本身定義的URL轉換器不就失去做用了嗎?哈哈,有個方法能夠解決,有沒有發現就是每一個app包裏都會有個init.py文件,其實Django每次運行項目都會執行這個文件,那麼當咱們在init.py文件裏導入這個cnverters.py文件,那麼這個converters.py文件的代碼就會自動被執行(導入表明導入模塊的代碼,例如:from . import converters)

相關文章
相關標籤/搜索