經過前一天得學習(Django學習路線之從虛擬環境開始Django初體驗),相信你已經對Django已經有了基本的認識,今天來具體學習一下MVC思想的V(視圖)
Django視圖
新建一個名爲django_view的項目:
並新建一個名爲news的app:
在新建的app裏的 views.py文件裏補充如下代碼:
from django.http import HttpResponse
# Create your views here.
def news(request):
return HttpResponse("新聞首頁")
回到項目的urls.py文件做映射:
from news import views
剛導入的時候可能會有如下報錯:
這時候只需要對項目文件夾進行如下操作即可:
把映射寫完後:
from news import views
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news)
]
進入網站界面:
因爲沒有定義首頁,所以首頁會報錯,但是進入news的界面已經定義了,所以直接進入即可:
方法裏的request參數
接下來我們回到news的views.py文件,看一下request的類型:
def news(request):
print(type(request))
return HttpResponse("新聞首頁")
看一下request的類型:
<class ‘django.core.handlers.wsgi.WSGIRequest’>
可以看出這是一個類,下面看一下這個類的源碼:
from django.core.handlers.wsgi import WSGIRequest
點進去看一下:
看一下這個類裏的方法:
既然是類,我們就可以調用裏面的方法,下面來試一下調用method方法:
清空輸出再刷新一下頁面:
發現這是一個GET請求
注意事項
- request參數是必須的
- 返回的HttpResponse()也是必須的
如果直接返回字符串會報錯,如下圖所示:
‘str’ object has no attribute ‘get’
視圖的總結
視圖一般都寫在app的views.py中
並且視圖的第一個參數永遠都是request(一個HttpRequest)對象。這個對象存儲了請求過來的所有信息,包括攜帶的參數以及一些頭部信息等。在視圖中,一般是完成邏輯相關的操作。
比如這個請求是添加一篇博客,那麼可以通過request來接收到這些數據,然後存儲到數據庫中,最後再把執行的結果返回給瀏覽器。
視圖函數的返回結果必須是HttpResponseBase對象或者子類的對象。示例代碼如下:
news/views.py:
from django.http import HttpResponse
def news(request):
return HttpResponse("這是新聞首頁")
urls.py:
from news import views
urlpatterns = [
path("news",views.news)
]
URL映射
基本原理
視圖寫完後,要與URL進行映射,也即用戶在瀏覽器中輸入什麼url的時候可以請求到這個視圖函數
向瀏覽器輸入:http://127.0.0.1:8000/news/
且urls.py裏添加了映射:
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news)
]
因此才能訪問到如下界面:
換句話說,在用戶輸入了某個url,請求網站的時候,django會從項目的urls.py文件中尋找對應的視圖
另外,打開settings.py文件,找到ROOT_URLCONF:
如果把這一行註釋,那麼就無法找到對應的視圖了
問題的引入
假設我多了一個book的界面:
from django.contrib import admin
from django.urls import path
from news import views
from book import views
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news)
path('books/', views.news)
]
這時會出現兩個views,這時因爲不知道使用哪一個views,所以會報錯
要解決這一問題,請繼續往下看
URL中添加參數
有時候,url中包含了一些參數需要動態調整。
比如我的某篇CSDN文章的url:
https://blog.csdn.net/zbp_12138/article/details/105962054
後面的105962054就是我這篇文章的id
那麼主頁的url就可以寫成:
https://blog.csdn.net/zbp_12138/article/details/
那麼如何在django中實現這種需求呢。這時候我們可以在path函數中,使用尖括號的形式來定義一個參數
指定參數
urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news),
# news_id 必須和視圖函數中的參數保持一致
path('news/<news_id>', views.news_detail)
]
news_id 必須和視圖函數中的參數保持一致
在views.py裏補充對應的方法:
def news_detail(request,news_id):
return HttpResponse("新聞詳情頁-{}".format(news_id))
效果如下:
但是這樣的寫法不夠安全,我們隨意打開百度搜索一個頁面:
可以看到後面的id都是亂碼,那麼怎麼實現這一功能呢?
查詢字符串的方式傳遞參數
下面的部分把剛剛的代碼註釋,便於大家比對
urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('news/', views.news),
# news_id 必須和視圖函數中的參數保持一致
# path('news/<news_id>', views.news_detail)
# 查詢字符串的方式傳遞參數
path('news/news_detail/', views.news_detail)
]
裏面就不使用尖括號了,改動主要在views.py裏:
# def news_detail(request,news_id):
# return HttpResponse("新聞詳情頁-{}".format(news_id))
def news_detail(request):
news_id = request.GET.get("news_id")
return HttpResponse("新聞詳情頁-{}".format(news_id))
這次把參數放在方法裏
來看一下效果:http://127.0.0.1:8000/news/news_detail/?news_id=2
URL模塊化
回到URL映射裏提到的那個問題,這裏我們新建一個名爲book的app:
在urls.py裏添加如下代碼:
from news import views
from book import views
直接運行會發生報錯:
AttributeError: module ‘book.views’ has no attribute ‘news’
也就是說當前有兩個views,程序不知道使用哪個views,而且現在只有兩個app,到後期app更多時,這樣的寫法肯定會有問題
因此我們可以用as語句來優化一下:
from news import views
from book import views as bviews
不過這也有個問題,雖然不報錯了,但是如果把所有的app的views中的視圖都放在urls.py中進行映射,肯定會讓代碼顯得非常亂
因此django給我們提供了一個方法,可以在app內部包含自己的url匹配規則,而在項目的urls.py中再統一包含這個app的urls。使用這個技術需要藉助include函數。下面是示例代碼
django_views/urls.py文件:
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path("news/",include("news.urls"))
]
在news裏新建一個urls.py:
from django.urls import path
from . import views #從當前目錄導入views
urlpatterns = [
path("",views.news),
path('news_detail/', views.news_detail)
]
如此一來,在django_views/urls.py文件中把所有的和news這個app相關的url都移動到news/urls.py中了,django_first/urls.py中,通過include函數包含news.urls,以後在請求news相關的url的時候都需要加一個news的前綴
那麼在book這個app裏也是同樣的做法
在django_view\urls.py裏補充book的url映射:
urlpatterns = [
path("news/",include("news.urls")),
path("book/",include("book.urls"))
]
在book\views裏補充方法:
from django.http import HttpResponse
# Create your views here.
def book(request):
return HttpResponse("圖書首頁")
def book_detail(request, book_id):
return HttpResponse("圖書詳情頁-{}".format(book_id))
在book裏新建urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("",views.book),
path("book_detail/<book_id>",views.book_detail)
]
效果是一樣的:
這裏不知道大家有沒有注意到,book_id應該爲int類型:
def book_detail(request, book_id):
print(type(book_id))
return HttpResponse("圖書詳情頁-{}".format(book_id))
打印一下:
很明顯,並不是int類型,傳一個字符串也是可以的:
那麼這裏就出現一個問題,我無法用這裏的id做SQL查詢,或者是在列表里根據id取值
這時試一下強制類型轉換:
def book_detail(request, book_id):
print(type(book_id))
new_book_id = int(book_id)
print(type(new_book_id))
return HttpResponse("圖書詳情頁-{}".format(new_book_id))
保存並運行一下:
直接就報錯了: invalid literal for int() with base 10: ‘zbp’
Django內置轉換器
這裏有一個優化的方法,在book\urls.py裏:
path("book_detail/<int:book_id>",views.book_detail
尖括號裏由<book_id>改成 <int: book_id>
這時再試一下輸入字符串:
報錯信息變成了:“沒有找到頁面”,這樣的效果是比直接報錯要好的
另外,輸入一個數字,輸出一下id的類型:
可以看到已經變成了int類型
除了int和str,還有其他的類型可以輸出:
from django.urls import converters
來看一下Django內置的5個類型:
UUID:https://www.cnblogs.com/franknihao/p/7307224.html
URL命名與反轉
-
爲什麼需要URL命名
因爲在項目開發的過程中URL地址可能經常變動,如果寫死會經常去修改 -
如何給一個URL指定名稱
path("",views.index,name=“index”) -
應用命名空間
在多個app之間可能產生同名的URL,這時候爲了避免這種情況,可以使用命名空間來加以區分。在urls.py中添加app_name即可。
下面新建兩個app,分別是前臺和後臺:
接下來是代碼補充,跟前面講到的方法是一樣的,我們再一起來過一遍,加深印象:
來測試一下:
重定向
接下來要實現的功能是判斷用戶是否登陸,如果沒有登陸,就跳轉到登陸界面
拿知乎的官網舉例:https://www.zhihu.com/
直接訪問時,會直接跳轉到登陸界面:https://www.zhihu.com/signin?next=%2F
下面來到front\views.py實現這一功能,我們一步步來:
def index(request):
# url是否傳遞username
username = request.GET.get("username")
if username:
return HttpResponse("前臺首頁")
else:
# 跳轉到登陸界面
pass
下面我們需要redirect(),進行重定向,完整代碼如下:
from django.shortcuts import render,redirect
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否傳遞username
username = request.GET.get("username")
if username:
return HttpResponse("前臺首頁")
else:
# 跳轉到登陸界面
return redirect("login/")
def login(request):
return HttpResponse("前臺登錄界面")
下面來看一下效果: http://127.0.0.1:8000/front/
自動跳轉到了: http://127.0.0.1:8000/front/login/
但這時有個問題,這裏的映射都必須對應:
這樣一來,改起來就會很麻煩,app一多就更難改了,這時就要用到反轉
反轉
在front\urls.py裏增加一個參數name:
from django.urls import path
from . import views
urlpatterns = [
path("",views.index),
path("login/",views.login, name = "login")
]
這樣一來,前面的映射寫什麼地址都行,都不影響頁面的跳轉
在front\views.py裏多導入一個reverse庫:
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否傳遞username
username = request.GET.get("username")
if username:
return HttpResponse("前臺首頁")
else:
# 跳轉到登陸界面
return redirect(reverse("login"))
def login(request):
return HttpResponse("前臺登錄界面")
跳轉時使用反轉,參數對應於front\urls.py裏參數name的值
而這時也有一個問題,前臺和後臺的name一樣的話:
在多個app之間可能產生同名的URL,這時候爲了避免這種情況,可以使用命名空間來加以區分
在cms\urls.py里加上app_name:
app_name = "cms"
在cms\views.py裏:
這時來看一下效果,輸入http://127.0.0.1:8000/cms/:
得到的返回:http://127.0.0.1:8000/cms/login/
應用命名空間和實例命名空間
一個app,可以創建多個實例。可以使用多個URL映射同一個App。在做反轉的時候,如果使用應用命名空間,就會發生混淆,爲了避免這個問題,可以使用實例命名空間,實例命名空間使用,namespace=‘實例命名空間’
urls.py:
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('cms1/', include("cms.urls",namespace='cms1')),
path('cms2/', include("cms.urls",namespace='cms2')),
path('front/', include("front.urls")),
]
URL反轉傳遞參數
如果這個url中需要傳遞參數,那麼可以通過kwargs來傳遞參數
front\views.py裏的reverse加入kwargs:
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否傳遞username
username = request.GET.get("username")
if username:
return HttpResponse("前臺首頁")
else:
# 跳轉到登陸界面
return redirect(reverse("front:login", kwargs={"uid":1}))
def login(request, uid):
return HttpResponse("前臺登錄界面-接收到的uid爲{}".format(uid))
front\views.py:
from django.urls import path
from . import views
app_name = "front"
urlpatterns = [
path("",views.index),
path("login/<uid>",views.login, name = "login")
]
下面是效果:
訪問http://127.0.0.1:8000/front/
如果要傳入字符串:
把front\urls.py裏的path改成path(“login/”,views.login, name = “login”),即去掉< uid >
然後到front\views.py,把代碼改成:
from django.shortcuts import render,redirect,reverse
from django.http import HttpResponse
# Create your views here.
def index(request):
# url是否傳遞username
username = request.GET.get("username")
if username:
return HttpResponse("前臺首頁")
else:
# 跳轉到登陸界面
login_url = reverse("front:login") + "?name=zbp"
return redirect(login_url)
def login(request):
uid = request.GET.get("name")
return HttpResponse("前臺登錄界面-接收到的uid爲{}".format(uid))
因爲django中的reverse反轉url的時候不區分GET請求和POST請求,因此不能在反轉的時候添加查詢字符串的參數
效果如下:
指定默認的參數
在book\views.py裏添加圖書列表以及對應方法:
book_list = ["a", "b", "c", "d"]
def book_lists(request):
return HttpResponse(book_list[0])
在book\urls.py補充映射:
urlpatterns = [
path("",views.book),
path("book_detail/<int:book_id>",views.book_detail),
path("book_lists/",views.book_lists)
]
效果如下:
圖書詳情頁的功能也可以進行完善:
book\views.py:
def book_detail(request, book_id = 1):
print(type(book_id))
return HttpResponse("圖書詳情頁-{}".format(book_list[book_id]))
給book_id指定一個默認參數1
book\urls.py:
urlpatterns = [
path("",views.book),
path("book_detail/<int:book_id>",views.book_detail),
path("book_detail/",views.book_detail),
path("book_lists/",views.book_lists)
]
看一下效果:
默認的參數是1,傳一個其他的參數:
re_path函數
有時候在寫url匹配的時候,想要寫使用正則表達式來實現一些複雜的需求,那麼這時候我們可以使用re_path來實現
re_path的參數和path參數一模一樣,只不過第一個參數也就是route參數可以爲一個正則表達式
在news\urls.py裏添加re_path:
urlpatterns = [
path("",views.news),
path('news_detail/', views.news_detail),
# r是原生字符串
re_path(r'news_list/(?P<year>\d{4})', views.news_list)
]
年份是4位的,問號大P用於命名< year >, 匹配4個長度
在news\viewss.py裏補充news_list方法:
def news_list(request, year):
return HttpResponse("新聞列表頁面-{}年".format(year))
來看一下效果:
這裏需要注意的是,如果傳入5位的參數,也只會匹配前4位:
如果要匹配月份,寫法如下:
re_path(r"^article_list/(?P<mouth>\d{2})/",views.article_mouth)