.. _ref-contrib-sites: ====================== "sites" フレームワーク ====================== :revision-up-to: 11321 (1.1) .. module:: django.contrib.sites :synopsis: 複数のウェブサイトを一つのデータベースと Django プロジェクト で操作できるようにします。 Django にはオプションとして使える "sites" フレームワークが付属しています。 "sites" はオブジェクトや機能を特定の Web サイトに関連付けるためのフックであ ると同時に、 Django で作成したサイトのドメイン名と「分かりやすい名前 verbose name」を保存しています。 一つの Django で複数のサイトを管理していて、サイト間に違いを持たせたい場合 に使ってください。 sites フレームワークは、簡単なモデルだけでできています: .. class:: django.contrib.sites.models.Site モデルには、 :attr:`~django.contrib.sites.models.Site.domain` と :attr:`~django.contrib.sites.models.Site.name` という二つのフィールドがあり ます。あるサイトの設定ファイルの :setting:`SITE_ID` 設定は、そのサイトを表 す :class:`~django.contrib.sites.models.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`` モデルの :class:`~django.db.models.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 # ... .. _ljworld.com: http://www.ljworld.com/ .. _lawrence.com: http://www.lawrence.com/ コンテンツを単一のサイトに関連づける ------------------------------------ あるモデルを :class:`~django.db.models.fields.related.ForeignKey` を使って 他対一の関係で :class:`~django.contrib.sites.models.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. :setting:`settings.SITE_ID ` の値から :class:`~django.contrib.sites.models.Site` オブジェクトを取得するというイディ オムはよく使われるので、 :class:`~django.contrib.sites.models.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 フォームから登録すると、「ご登録ありがとうございま した」という内容のメールを受け取ります。 登録処理を行うコードを二度開発するのは非効率的で冗長なので、舞台裏では複数 サイトで同じコードを使うようにします。ただし、「ご登録ありがとうございます」 通知はサイトごとに変えなければなりません。 :class:`~django.contrib.sites.models.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 は別々のテンプレートディ レクトリ (:setting:`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 のテンプレートディレクトリ下に :file:`subject.txt` と :file:`message.txt` の二つのテンプレートを作成します。 ただし、こうすればより柔軟にはなりますが、複雑さは増します。 不要な複雑さと冗長性を排除するためには、 :class:`~django.contrib.sites.models.Site` オブジェクトを可能な限り使うとよ いでしょう。 現在のドメインの完全な URL を取得する ------------------------------------- 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`` オブジェクトをキャッシュする ============================================== .. versionadded:: 1.0 現在のサイトを表す :class:`~django.contrib.sites.models.Site` オブジェクト は、もともとデータベースに保存されているので、 :meth:`~django.contrib.sites.models.Site.objects.get_current` を呼ぶたび にデータベース呼び出しが発生してしまいます。ただし、 Django はもう少し賢く :class:`~django.contrib.sites.models.Site` を扱います。 すなわち、現在のサイトをキャッシュし、それ以降 ``get_current()`` を呼び出し ても、データベースに触らずキャッシュから :class:`~django.contrib.sites.models.Site` オブジェクトを返します。 何らかの理由で、データベースクエリを強制したい場合には、 :meth:`~django.contrib.sites.models.Site.objects.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`` ====================== .. class:: django.contrib.sites.managers.CurrentSiteManager アプリケーション内で :class:`~django.contrib.sites.models.Site` が重要な働 きを持っている場合、 :class:`~django.contrib.sites.managers.CurrentSiteManager` という便利なクラ スを検討してみてください。 :class:`~django.contrib.sites.managers.CurrentSiteManager` はモデルの :ref:`マネジャ ` クラスで、クエリ結果を現在の :class:`~django.contrib.sites.models.Site` に関連づけられたオブジェクトだけ に自動的にフィルタします。 :class:`~django.contrib.sites.managers.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()`` を使うと、 :setting:`SITE_ID` 設定に従って、現在のサイトに関連づけられた ``Photo`` オ ブジェクトだけを返します。 つまり、以下の二つの文は等価になります:: Photo.objects.filter(site=settings.SITE_ID) Photo.on_site.all() :class:`~django.contrib.sites.managers.CurrentSiteManager` は ``Photo`` の どのフィールドが :class:`~django.contrib.sites.models.Site` なのかをどうやっ て見付けるのでしょう? :class:`~django.contrib.sites.managers.CurrentSiteManager` はデフォルトでは ``site`` という名前のフィールドを探します。モデル内で ``site`` *以外の* 名前の :class:`~django.db.models.fields.related.ForeignKey` や :class:`~django.db.models.fields.related.ManyToManyField`` を定義している場 合、 :class:`~django.contrib.sites.managers.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') :class:`~django.contrib.sites.managers.CurrentSiteManager` を使って、存在し ないフィールド名を渡そうとすると、 Django は :exc:`ValueError` を送出します。 :class:`~django.contrib.sites.managers.CurrentSiteManager` を使っていたとし ても、結局は (サイト固有でない) 通常の ``Manager`` をモデル内に持っておく必 要に迫られるでしょう。 :ref:`マネジャのドキュメント ` でも説明しましたが、手動でマネジャを定義すると、Django は ``objects = models.Manager()`` を使った自動マネジャ生成を行わなくなるからで す。また、 Django の一部 (例えば admin サイトや汎用ビュー) では、モデル内で *最初に定義されている* マネジャを常に使うようになっています。そのため、 admin サイトから全てのオブジェクトにアクセスしたい (サイト固有のフィルタリ ングを行わない) 場合には、モデル内で ``objects = models.Manager()`` を :class:`~django.contrib.sites.managers.CurrentSiteManager` の定義より前 に配置しておかねばなりません。 Django 内での site フレームワークの役割 ======================================= Django を使う際、必ずしも sites フレームワークを使わねばならないというわけ ではありません。とはいえ、 Django はいくつかの場所で sites の恩恵を利用でき るので、ぜひとも sites を活用するよう勧めます。 Django を単一のサイトを駆動 するためだけに使っているとしても、ひと手間かけてサイトオブジェクトを作成し、 ``domain`` と ``name`` を指定して、その ID を :setting:`SITE_ID` 設定に指定 してみてください。 Django における sites フレームワークの役割は以下の通りです: * :mod:`リダイレクションフレームワーク ` では、各 リダイレクトオブジェクトが特定のサイトに関連づけられています。 Django が リダイレクト先を探すとき、フ レームワークは現在の :setting:`SITE_ID` を考慮します。 * コメントフレームワークでは、各コメントが特定のサイトに関連づけられて います。コメントが投稿されると、 ``site`` は現在の :setting:`SITE_ID` に 設定されます。コメントを何らかのテンプレートタグでリスト表示する場合、現 在のサイトに関するコメントだけが表示されます。 * :mod:`フラットページフレームワーク ` では、各フ ラットページは特定のサイトに関連づけられています。フラットページを作成す る際には、 ``site`` を指定せねばなりません。 :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` は現在の :setting:`SITE_ID` をチェックして、表示すべきフラットページを取 得します。 * :mod:`配信フレームワーク ` では、 ``title`` および ``description`` のテンプレートから自動的に ``{{ site }}`` にアクセ スできます。 ``{{ site }}`` は :class:`~django.contrib.sites.models.Site` オブジェクトで、現在の site を表現します。また、フィード項目の URL を提供 するためのフックでは、完全指定 (full-qualified) のドメインを指定していな い場合、現在の :class:`~django.contrib.sites.models.Site` オブジェクトの ``domain`` を使います。 * :mod:`認証フレームワーク ` では、 :func:`django.contrib.auth.views.login` ビューが現在の :class:`~django.contrib.sites.models.Site` 名を ``{{ site_name }}`` とい う形でテンプレートに渡しています。 * ショートカットビュー (:func:`django.views.defaults.shortcut`) では、オブ ジェクトの URL のドメイン部を計算する際に現在の :class:`~django.contrib.sites.models.Site` オブジェクトを使います。 * :class:`~django.contrib.sites.models.Site` オブジェクトに完全指定のホスト 名を定義しておくと、 admin フレームワークで、現在の ``サイト上で表示 (view on site)`` のリンク URL の構築に使います。 ``RequestSite`` オブジェクト ============================ .. _requestsite-objects: .. versionadded:: 1.0 :ref:`django.contrib ` 内のアプリケーションの中には、 sites フレームワークを利用はできるけれども、 *必須にはしない* ようなものが あります (sites を使いたくない人や、 sites フレームワークが必要とするデータ ベーステーブルの作成が *不可能な* 人もいるためです)。こうした場合のために、 sites フレームワークでは :class:`~django.contrib.sites.models.RequestSite` クラスを提供しています。このクラスは、データベースバックエンド上に sites フ レームワークがない場合のフォールバックとして使われます。 :class:`~django.contrib.sites.models.RequestSite` オブジェクトは、通常の :class:`~django.contrib.sites.models.Site` オブジェクトと同様のインタフェー スを備えていますが、 :meth:`~django.contrib.sites.models.RequestSite.__init__` が :class:`~django.http.HttpRequest` オブジェクトを取るところが違います。この オブジェクトはリクエストのドメイン情報を見ることで ``domain`` や ``name`` を決定します。 :class:`~django.contrib.sites.models.Site` オブジェクトとイ ンタフェースを合わせるため、 :class:`~django.contrib.sites.models.RequestSite` オブジェクトにも :meth:`~django.contrib.sites.models.RequestSite.save` や :meth:`~django.contrib.sites.models.RequestSite.delete` といったメソッドが ありますが、これらのメソッドを呼び出すと :exc:`NotImplementedError` を送出 します。