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

轉載請註明來源地址和原作者(CFishHome)

前沿

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

預備

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

模擬前端和後端

我們想實現: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文件添加以下代碼:

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文件添加以下代碼:

from django.http import HttpResponse

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

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

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

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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章