「網站」框架¶
Django 附帶一個可選的「網站」框架。它是一個用於將物件和功能關聯到特定網站的鉤子,並且是您以 Django 驅動的網站的網域名稱和「詳細」名稱的存放位置。
如果您的單一 Django 安裝驅動多個網站,並且您需要以某種方式區分這些網站,請使用它。
網站框架主要基於此模型
- class models.Site¶
用於儲存網站的
domain
和name
屬性的模型。- domain¶
與網站相關聯的完整網域名稱。例如,
www.example.com
。
- name¶
網站的易於閱讀的「詳細」名稱。
SITE_ID
設定指定與該特定設定檔案相關聯的 Site
物件的資料庫 ID。 如果省略該設定,get_current_site()
函數將嘗試通過比較 domain
與 request.get_host()
方法中的主機名稱來取得目前的網站。
如何使用它取決於您,但 Django 會透過一些慣例自動在幾個方面使用它。
使用範例¶
為什麼要使用網站?最好透過範例來解釋。
將內容與多個網站關聯¶
LJWorld.com 和 Lawrence.com 網站由同一新聞機構營運 – 位於堪薩斯州勞倫斯的 Lawrence Journal-World 報紙。LJWorld.com 專注於新聞,而 Lawrence.com 專注於當地娛樂。但有時編輯人員希望在兩個網站上發表文章。
解決此問題的簡單方法是要求網站製作人員將同一個故事發佈兩次:一次用於 LJWorld.com,另一次用於 Lawrence.com。但這對於網站製作人員來說效率低下,並且在資料庫中儲存相同故事的多個副本是多餘的。
更好的解決方案是刪除內容重複:兩個網站都使用相同的文章資料庫,並且文章與一個或多個網站相關聯。在 Django 模型術語中,這由 Article
模型中的 ManyToManyField
表示
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
sites = models.ManyToManyField(Site)
這可以很好地完成幾件事
它讓網站製作人員可以在單一介面(Django 管理介面)中編輯兩個網站上的所有內容。
這表示同一個故事不必在資料庫中發佈兩次;它在資料庫中只有一個記錄。
它讓網站開發人員可以使用相同的 Django 檢視程式碼用於兩個網站。顯示指定故事的檢視程式碼會檢查請求的故事是否在目前的網站上。它看起來像這樣
from django.contrib.sites.shortcuts import get_current_site def article_detail(request, article_id): try: a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id) except Article.DoesNotExist: raise Http404("Article does not exist on this site") # ...
將內容與單一網站關聯¶
同樣地,您可以使用 ForeignKey
以多對一的關係將模型與 Site
模型關聯。
例如,如果只允許在單一網站上發佈文章,您將使用類似這樣的模型
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site, on_delete=models.CASCADE)
這具有與上一節中描述的相同優點。
從檢視中掛鉤到目前的網站¶
您可以在您的 Django 檢視中使用網站框架,根據呼叫檢視的網站來執行特定的動作。例如
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
pass
else:
# Do something else.
pass
像這樣硬式編碼網站 ID 是不穩定的,以防它們發生變更。完成相同操作的更簡潔方法是檢查目前網站的網域
from django.contrib.sites.shortcuts import get_current_site
def my_view(request):
current_site = get_current_site(request)
if current_site.domain == "foo.com":
# Do something
pass
else:
# Do something else.
pass
這也具有檢查是否已安裝網站框架的優點,如果未安裝,則傳回 RequestSite
實例。
如果您無法存取請求物件,您可以使用 Site
模型的管理員的 get_current()
方法。然後,您應該確保您的設定檔案確實包含 SITE_ID
設定。此範例與前一個範例相同
from django.contrib.sites.models import Site
def my_function_without_request():
current_site = Site.objects.get_current()
if current_site.domain == "foo.com":
# Do something
pass
else:
# Do something else.
pass
取得目前網域以供顯示¶
LJWorld.com 和 Lawrence.com 都具有電子郵件提醒功能,讓讀者可以註冊在發生新聞時收到通知。它非常基本:讀者在網頁表單上註冊,並立即收到一封電子郵件,內容為「感謝您的訂閱」。
實作此註冊處理程式碼兩次是低效且多餘的,因此這些網站會在幕後使用相同的程式碼。但是,每個網站的「感謝您註冊」通知都需要不同。透過使用 Site
物件,我們可以將「感謝」通知抽象化為使用目前網站的 name
和 domain
的值。
以下是表單處理檢視的範例
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = get_current_site(request)
send_mail(
"Thanks for subscribing to %s alerts" % current_site.name,
"Thanks for your subscription. We appreciate it.\n\n-The %s team."
% (current_site.name,),
"editor@%s" % current_site.domain,
[user.email],
)
# ...
在 Lawrence.com 上,此電子郵件的主旨為「感謝您訂閱 lawrence.com 提醒」。在 LJWorld.com 上,此電子郵件的主旨為「感謝您訂閱 LJWorld.com 提醒」。電子郵件的訊息本文也是如此。
請注意,更靈活(但更繁重)的做法是使用 Django 的範本系統。假設 Lawrence.com 和 LJWorld.com 具有不同的範本目錄 (DIRS
),您可以像這樣將範本系統分流
from django.core.mail import send_mail
from django.template import loader
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template("alerts/subject.txt").render({})
message = loader.get_template("alerts/message.txt").render({})
send_mail(subject, message, "editor@ljworld.com", [user.email])
# ...
在這種情況下,您必須為 LJWorld.com 和 Lawrence.com 範本目錄建立 subject.txt
和 message.txt
範本檔案。這可讓您有更高的彈性,但也更複雜。
盡可能利用 Site
物件是一個好主意,以消除不必要的複雜性和多餘性。
取得完整 URL 的目前網域¶
Django 的 get_absolute_url()
慣例對於取得物件的 URL 而不包含網域名稱非常好用,但在某些情況下,您可能想要顯示物件的完整 URL – 包括 https://
和網域以及所有內容。若要執行此操作,您可以使用網站框架。一個範例
>>> from django.contrib.sites.models import Site
>>> obj = MyModel.objects.get(id=3)
>>> obj.get_absolute_url()
'/mymodel/objects/3/'
>>> Site.objects.get_current().domain
'example.com'
>>> "https://%s%s" % (Site.objects.get_current().domain, obj.get_absolute_url())
'https://example.com/mymodel/objects/3/'
啟用網站框架¶
若要啟用網站框架,請按照下列步驟操作
將
'django.contrib.sites'
新增至您的INSTALLED_APPS
設定。定義
SITE_ID
設定SITE_ID = 1
執行
migrate
。
django.contrib.sites
註冊了一個 post_migrate
信號處理器,它會建立一個名為 example.com
,網域為 example.com
的預設網站。這個網站也會在 Django 建立測試資料庫後建立。若要為您的專案設定正確的名稱和網域,您可以使用資料遷移。
為了在生產環境中提供不同的網站,您可以建立各自的設定檔,每個設定檔都有不同的 SITE_ID
(或許可以從一個共用的設定檔導入,以避免重複設定),然後為每個網站指定適當的 DJANGO_SETTINGS_MODULE
。
快取目前的 Site
物件¶
由於目前網站儲存在資料庫中,每次呼叫 Site.objects.get_current()
都可能導致資料庫查詢。但 Django 比這更聰明一點:在第一次請求時,目前的網站會被快取,後續的任何呼叫都會返回快取資料,而不是存取資料庫。
如果因任何原因您想強制執行資料庫查詢,可以使用 Site.objects.clear_cache()
告訴 Django 清除快取。
# First call; current site fetched from database.
current_site = Site.objects.get_current()
# ...
# Second call; current site fetched from cache.
current_site = Site.objects.get_current()
# ...
# Force a database query for the third call.
Site.objects.clear_cache()
current_site = Site.objects.get_current()
CurrentSiteManager
¶
- class managers.CurrentSiteManager¶
如果 Site
在您的應用程式中扮演關鍵角色,請考慮在您的模型中使用有用的 CurrentSiteManager
。這是一個模型管理器,會自動篩選其查詢,只包含與目前 Site
關聯的物件。
將 CurrentSiteManager
明確地加入您的模型來使用。例如
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager()
使用這個模型,Photo.objects.all()
將會返回資料庫中所有的 Photo
物件,但 Photo.on_site.all()
將只返回根據 SITE_ID
設定與目前網站關聯的 Photo
物件。
換句話說,這兩個語句是等效的
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
CurrentSiteManager
如何知道 Photo
的哪個欄位是 Site
呢?預設情況下,CurrentSiteManager
會尋找名為 site
的 ForeignKey
或名為 sites
的 ManyToManyField
來進行篩選。如果您使用名稱不是 site
或 sites
的欄位來識別您的物件與哪些 Site
物件相關,那麼您需要在您的模型上明確地將自訂欄位名稱作為參數傳遞給 CurrentSiteManager
。以下模型具有名為 publish_on
的欄位,示範了這一點
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager("publish_on")
如果您嘗試使用 CurrentSiteManager
並傳遞一個不存在的欄位名稱,Django 將會引發 ValueError
。
最後,請注意,您可能需要保留模型上的標準(非特定於網站的)Manager
,即使您使用了 CurrentSiteManager
。正如在管理器文件中所解釋的,如果您手動定義了一個管理器,那麼 Django 將不會為您建立自動的 objects = models.Manager()
管理器。還要請注意,Django 的某些部分,特別是 Django 管理網站和通用視圖,會使用模型中*首先*定義的管理器,因此如果您希望您的管理網站能夠存取所有物件(而不僅僅是特定於網站的物件),請在定義 CurrentSiteManager
之前,在您的模型中放入 objects = models.Manager()
。
網站中介軟體¶
如果您經常使用此模式
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...
為了避免重複,請將 django.contrib.sites.middleware.CurrentSiteMiddleware
加入 MIDDLEWARE
。該中介軟體會在每個請求物件上設定 site
屬性,因此您可以使用 request.site
來取得目前的網站。
Django 如何使用網站框架¶
雖然並非必須使用網站框架,但強烈建議您使用,因為 Django 在幾個地方都利用了它。即使您的 Django 安裝僅為單一網站提供服務,您也應該花兩秒鐘建立一個包含您的 domain
和 name
的網站物件,並在您的 SITE_ID
設定中指向其 ID。
以下是 Django 如何使用網站框架
在
重新導向 框架
中,每個重新導向物件都與特定的網站關聯。當 Django 搜尋重新導向時,會將目前的網站納入考量。在
靜態頁面 框架
中,每個靜態頁面都與特定的網站關聯。當建立靜態頁面時,您會指定其Site
,而FlatpageFallbackMiddleware
會檢查目前的網站,以檢索要顯示的靜態頁面。在
聯合內容 框架
中,title
和description
的範本會自動存取變數{{ site }}
,該變數是代表目前網站的Site
物件。此外,如果沒有指定完全限定的網域,則用於提供項目 URL 的鉤子將會使用目前Site
物件的domain
。在
驗證 框架
中,django.contrib.auth.views.LoginView
會將目前的Site
名稱以{{ site_name }}
傳遞給範本。捷徑視圖(
django.contrib.contenttypes.views.shortcut
)在計算物件的 URL 時,會使用目前Site
物件的網域。在管理框架中,「在網站上檢視」連結會使用目前的
Site
來計算它將重新導向的網站網域。
RequestSite
物件¶
某些 django.contrib 應用程式會利用網站框架,但其架構方式並不要求網站框架必須安裝在您的資料庫中。(有些人不想要,或只是無法安裝網站框架所需的額外資料庫表格。)對於這些情況,框架提供了一個 django.contrib.sites.requests.RequestSite
類別,當沒有以資料庫為基礎的網站框架時,可以將其作為回退機制。
- class requests.RequestSite¶
一個與
Site
共享主要介面的類別(即,它具有domain
和name
屬性),但其資料來自 DjangoHttpRequest
物件,而不是來自資料庫。- __init__(request)¶
將
name
和domain
屬性設定為get_host()
的值。
RequestSite
物件具有與一般 Site
物件相似的介面,但它的 __init__()
方法接受一個 HttpRequest
物件。它能夠透過查看請求的網域來推斷出 domain
和 name
。它具有 save()
和 delete()
方法來匹配 Site
的介面,但這些方法會引發 NotImplementedError
。
get_current_site
捷徑¶
最後,為了避免重複的回退程式碼,框架提供了一個 django.contrib.sites.shortcuts.get_current_site()
函數。
- shortcuts.get_current_site(request)¶
一個函數,用於檢查是否安裝了
django.contrib.sites
,並根據請求返回目前的Site
物件或RequestSite
物件。如果沒有定義SITE_ID
設定,它會根據request.get_host()
查詢目前網站。當 Host 標頭明確指定了一個端口時,例如
example.com:80
,request.get_host()
可能會返回網域和端口。在這種情況下,如果查詢失敗,因為主機與資料庫中的記錄不匹配,則會移除端口,並僅使用網域部分重試查詢。這不適用於RequestSite
,它將始終使用未修改的主機。