revision-up-to: | 11321 (1.1) |
---|
Django にはオプションとして使える “sites” フレームワークが付属しています。 “sites” はオブジェクトや機能を特定の Web サイトに関連付けるためのフックであ ると同時に、 Django で作成したサイトのドメイン名と「分かりやすい名前 verbose name」を保存しています。
一つの Django で複数のサイトを管理していて、サイト間に違いを持たせたい場合 に使ってください。
sites フレームワークは、簡単なモデルだけでできています:
django.contrib.sites.models.
Site
¶モデルには、 domain
と
name
という二つのフィールドがあり
ます。あるサイトの設定ファイルの SITE_ID
設定は、そのサイトを表
す Site
オブジェクトのデータベース上
での ID を指定します。
site の使い道は自由ですが、 Django では簡単な呼び出し規約で自動的に sites を使える方法を 2 種類提供しています。
どういう状況で sites を使うのでしょうか?例を挙げて説明しましょう。
LJWorld.com と Lawrence.com は同じニュース組織、Kansaz 州 Lawrence にある Lawrence Journal-World newspaper が管理しています。 LJWorld.com はニュース に、 Lawrence.com は地域の娯楽情報にフォーカスしていますが、編集者は 両方の サイトで同じニュースを公開したいと考える場合もあります。
この問題を何も考えずに扱うなら、サイト構築担当者に対して、 LJWorld.com と Lawrence.com に同じ記事を出すよう 2 度依頼することになります。しかしこれは サイト構築担当にとって非効率的ですし、同じ記事のコピーが複数データベースに 入ることになってしまいます。
もっとましで簡単な方法ががあります: どちらのサイトも同じデータベースを使い、
1 つの記事を複数のサイトに関連づけるというものです。この関係は、 Django モ
デルの用語で言えば Article
モデルの
ManyToManyField
で表現されます:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
sites = models.ManyToManyField(Site)
このモデルは、以下のようにいくつかの点で優れています:
site 構築担当が 1 つのインタフェース (Django admin サイト) で両方のサ イトにある全てのコンテンツを編集できます。
同じ記事をデータベース上に何度も書き込まなくて済み、一つのレコードと して保存できます。
サイト開発者は同じビューコードを両方のサイトで使えます。ビューコード では、リクエストされている記事が現在のサイト用のものかチェックします。 例えば以下のようなコードになるでしょう:
from django.conf import settings def article_detail(request, article_id): try: a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID) except Article.DoesNotExist: raise Http404 # ...
あるモデルを ForeignKey
を使って
他対一の関係で Site
に関連づけても構
いません。
例えば、ある記事をあるサイトだけで表示できるようにしたければ、モデルは以下 のように書きます:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site)
この方法でも、前節で述べたのと同じような恩恵を得られます。
低水準では、 Django ビューの中で sites フレームワークを使って、ビューを呼び 出しているサイトごとに固有の処理を実行できます。
例えば:
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
else:
# Do something else.
もちろん、上のようにサイト ID をハードコードするのは見栄えよくありません。 この種のハードコード化は、すぐに実行しなければならないハックのときにはベス トでしょう。同じことをよりクリーンに実現するには、サイトのドメイン名をチェッ クします:
from django.conf import settings
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get(id=settings.SITE_ID)
if current_site.domain == 'foo.com':
# Do something
else:
# Do something else.
settings.SITE_ID
の値から
Site
オブジェクトを取得するというイディ
オムはよく使われるので、 Site
のモデ
ルマネジャには get_current()
メソッドがあります。以下の例は上の例と同じ
です:
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
# Do something
else:
# Do something else.
LJWorld.com と Lawrence.com には e-mail による通知機能があり、読者が登録し ておくとニュースが届いたときに通知メールを受け取れるようになっています。こ のからくりは簡単で、 Web フォームから登録すると、「ご登録ありがとうございま した」という内容のメールを受け取ります。
登録処理を行うコードを二度開発するのは非効率的で冗長なので、舞台裏では複数
サイトで同じコードを使うようにします。ただし、「ご登録ありがとうございます」
通知はサイトごとに変えなければなりません。
Site
オブジェクトを使えば、現在のサイ
トの name
と domain
の値を使って「ありがとうございます」メッセージ
を抽象化できます。
フォーム処理ビューの例は以下の通りです:
from django.contrib.sites.models import Site
from django.core.mail import send_mail
def register_for_newsletter(request):
# フォームの値チェックなどを行い、ユーザを登録する
# ...
current_site = Site.objects.get_current()
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 では、 e-mail の件名は “Thanks for subscribing to lawrence.com alerts.” です。 LJWorld.com では、件名は “Thanks for subscribing to LJWorld.com alerts.” で、メール本体も同様です。
より柔軟 (で、重たい) 方法として、 Django テンプレートシステムを使った方法
と比較してみましょう。 Lawrence.com と LJWorld.com は別々のテンプレートディ
レクトリ (TEMPLATE_DIRS
) を持っているので、以下のようにすればサ
イト間の違いをテンプレートシステムに追い出せます:
from django.core.mail import send_mail
from django.template import loader, Context
def register_for_newsletter(request):
# フォームの値チェックなどを行い、ユーザを登録する
# ...
subject = loader.get_template('alerts/subject.txt').render(Context({}))
message = loader.get_template('alerts/message.txt').render(Context({}))
send_mail(subject, message, 'editor@ljworld.com', [user.email])
# ...
この場合、 LJWorld.com と Lawrence.com のテンプレートディレクトリ下に
subject.txt
と message.txt
の二つのテンプレートを作成します。
ただし、こうすればより柔軟にはなりますが、複雑さは増します。
不要な複雑さと冗長性を排除するためには、
Site
オブジェクトを可能な限り使うとよ
いでしょう。
Django の get_absolute_url()
は、オブジェクトの URL をドメイン名なしで
取得する際には便利ですが、場合によっては、完全な URL、すなわち http://
とドメイン名、その他全てを表示したいこともあるでしょう。これには sites フレー
ムワークを使います。簡単な例を示します:
>>> 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'
>>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'http://example.com/mymodel/objects/3/'
Site
オブジェクトをキャッシュする¶現在のサイトを表す Site
オブジェクト
は、もともとデータベースに保存されているので、
get_current()
を呼ぶたび
にデータベース呼び出しが発生してしまいます。ただし、 Django はもう少し賢く
Site
を扱います。
すなわち、現在のサイトをキャッシュし、それ以降 get_current()
を呼び出し
ても、データベースに触らずキャッシュから
Site
オブジェクトを返します。
何らかの理由で、データベースクエリを強制したい場合には、
clear_cache()`()
を使って
キャッシュを消去させられます:
# 最初の呼び出し: データベースから Site オブジェクトを取り出す
current_site = Site.objects.get_current()
# ...
# 2 回目の呼び出し: キャッシュから取り出す
current_site = Site.objects.get_current()
# ...
# 3 回目の呼び出しで、データベースクエリを強制する
Site.objects.clear_cache()
current_site = Site.objects.get_current()
CurrentSiteManager
¶django.contrib.sites.managers.
CurrentSiteManager
¶アプリケーション内で Site
が重要な働
きを持っている場合、
CurrentSiteManager
という便利なクラ
スを検討してみてください。
CurrentSiteManager
はモデルの
マネジャ クラスで、クエリ結果を現在の
Site
に関連づけられたオブジェクトだけ
に自動的にフィルタします。
CurrentSiteManager
を使うには、モデ
ルの中に明示的に追加します。例を示します:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site)
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
という名前のフィールドを探します。モデル内で site
以外の
名前の ForeignKey
や
ManyToManyField`
を定義している場
合、 CurrentSiteManager
に明示的に
フィールド名を渡す必要があります。以下のモデルでは、 publish_on
という
名前のフィールドがその例です:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager('publish_on')
CurrentSiteManager
を使って、存在し
ないフィールド名を渡そうとすると、 Django は ValueError
を送出します。
CurrentSiteManager
を使っていたとし
ても、結局は (サイト固有でない) 通常の Manager
をモデル内に持っておく必
要に迫られるでしょう。 マネジャのドキュメント
でも説明しましたが、手動でマネジャを定義すると、Django は
objects = models.Manager()
を使った自動マネジャ生成を行わなくなるからで
す。また、 Django の一部 (例えば admin サイトや汎用ビュー) では、モデル内で
最初に定義されている マネジャを常に使うようになっています。そのため、
admin サイトから全てのオブジェクトにアクセスしたい (サイト固有のフィルタリ
ングを行わない) 場合には、モデル内で objects = models.Manager()
を CurrentSiteManager
の定義より前
に配置しておかねばなりません。
Django を使う際、必ずしも sites フレームワークを使わねばならないというわけ
ではありません。とはいえ、 Django はいくつかの場所で sites の恩恵を利用でき
るので、ぜひとも sites を活用するよう勧めます。 Django を単一のサイトを駆動
するためだけに使っているとしても、ひと手間かけてサイトオブジェクトを作成し、
domain
と name
を指定して、その ID を SITE_ID
設定に指定
してみてください。
Django における sites フレームワークの役割は以下の通りです:
リダイレクションフレームワーク
では、各
リダイレクトオブジェクトが特定のサイトに関連づけられています。 Django が
リダイレクト先を探すとき、フ
レームワークは現在の SITE_ID
を考慮します。site
は現在の SITE_ID
に
設定されます。コメントを何らかのテンプレートタグでリスト表示する場合、現
在のサイトに関するコメントだけが表示されます。フラットページフレームワーク
では、各フ
ラットページは特定のサイトに関連づけられています。フラットページを作成す
る際には、 site
を指定せねばなりません。
FlatpageFallbackMiddleware
は現在の SITE_ID
をチェックして、表示すべきフラットページを取
得します。配信フレームワーク
では、 title
および description
のテンプレートから自動的に {{ site }}
にアクセ
スできます。 {{ site }}
は Site
オブジェクトで、現在の site を表現します。また、フィード項目の URL を提供
するためのフックでは、完全指定 (full-qualified) のドメインを指定していな
い場合、現在の Site
オブジェクトの
domain
を使います。認証フレームワーク
では、
django.contrib.auth.views.login()
ビューが現在の
Site
名を {{ site_name }}
とい
う形でテンプレートに渡しています。django.views.defaults.shortcut()
) では、オブ
ジェクトの URL のドメイン部を計算する際に現在の
Site
オブジェクトを使います。Site
オブジェクトに完全指定のホスト
名を定義しておくと、 admin フレームワークで、現在の
サイト上で表示 (view on site)
のリンク URL の構築に使います。RequestSite
オブジェクト¶django.contrib 内のアプリケーションの中には、
sites フレームワークを利用はできるけれども、 必須にはしない ようなものが
あります (sites を使いたくない人や、 sites フレームワークが必要とするデータ
ベーステーブルの作成が 不可能な 人もいるためです)。こうした場合のために、
sites フレームワークでは RequestSite
クラスを提供しています。このクラスは、データベースバックエンド上に sites フ
レームワークがない場合のフォールバックとして使われます。
RequestSite
オブジェクトは、通常の
Site
オブジェクトと同様のインタフェー
スを備えていますが、
__init__()
が
HttpRequest
オブジェクトを取るところが違います。この
オブジェクトはリクエストのドメイン情報を見ることで domain
や name
を決定します。 Site
オブジェクトとイ
ンタフェースを合わせるため、
RequestSite
オブジェクトにも
save()
や
delete()
といったメソッドが
ありますが、これらのメソッドを呼び出すと NotImplementedError
を送出
します。
Oct 26, 2017