「サイト」フレームワーク
Djangoには、オプションの「サイト」フレームワークが付属しています。 これは、オブジェクトと機能を特定のWebサイトに関連付けるためのフックであり、Djangoを利用したサイトのドメイン名と「詳細な」名前の保持場所です。
単一のDjangoインストールが複数のサイトに電力を供給し、何らかの方法でそれらのサイトを区別する必要がある場合に使用します。
サイトフレームワークは主にこのモデルに基づいています。
- class models.Site
Webサイトの
domain
およびname
属性を格納するためのモデル。- domain
Webサイトに関連付けられている完全修飾ドメイン名。 たとえば、
www.example.com
です。
- name
ウェブサイトの人間が読める「逐語的な」名前。
:setting: `SITE_ID` 設定は、その特定の設定ファイルに関連付けられている Site オブジェクトのデータベースIDを指定します。 設定を省略した場合、 get_current_site()関数は、ドメインを request.get_host()のホスト名と比較して現在のサイトを取得しようとします。 ] 方法。
これをどのように使用するかはあなた次第ですが、Djangoはいくつかの規則を介して自動的にいくつかの方法でそれを使用します。
使用例
なぜサイトを使うのですか? それは例を通して最もよく説明されます。
コンテンツを複数のサイトに関連付ける
LJWorld.com サイトと Lawrence.com サイトは、同じ通信社であるカンザス州ローレンスのローレンスジャーナルワールド紙によって運営されています。 LJWorld.comはニュースに焦点を合わせ、Lawrence.comは地元の娯楽に焦点を合わせました。 しかし、編集者が両方のサイトに記事を公開したい場合がありました。
この問題を解決する素朴な方法は、サイトプロデューサーに同じストーリーを2回公開するように要求することです。1回はLJWorld.com用、もう1回はLawrence.com用です。 しかし、それはサイトプロデューサーにとって非効率的であり、同じストーリーの複数のコピーをデータベースに保存することは冗長です。
より良い解決策は、コンテンツの重複を取り除きます。両方のサイトが同じ記事データベースを使用し、記事が1つ以上のサイトに関連付けられています。 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管理者)で両方のサイトのすべてのコンテンツを編集できます。
つまり、同じストーリーをデータベースに2回公開する必要はありません。 データベースには1つのレコードしかありません。
これにより、サイト開発者は両方のサイトに同じ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 モデルに多対1の関係で関連付けることができます。
たとえば、記事が1つのサイトでのみ許可されている場合は、次のようなモデルを使用します。
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()
メソッドを使用できます。 次に、設定ファイルに:setting: `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の両方に電子メールアラート機能があり、ニュースが発生したときに読者がサインアップして通知を受け取ることができます。 これは非常に基本的なことです。読者がWebフォームにサインアップすると、すぐに「サブスクリプションをありがとう」という電子メールが届きます。
このサインアップ処理コードを2回実装するのは非効率的で冗長であるため、サイトはバックグラウンドで同じコードを使用します。 ただし、「サインアップしていただきありがとうございます」の通知は、サイトごとに異なる必要があります。 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のテンプレートディレクトリが異なると仮定します( :setting: `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, '[email protected]', [user.email])
# ...
この場合、LJWorld.comとLawrence.comの両方のテンプレートディレクトリ用にsubject.txt
とmessage.txt
のテンプレートファイルを作成する必要があります。 これにより柔軟性が向上しますが、複雑さも増します。
Site オブジェクトを可能な限り活用して、不要な複雑さと冗長性を排除することをお勧めします。
完全なURLの現在のドメインを取得する
Djangoのget_absolute_url()
規則は、ドメイン名なしでオブジェクトのURLを取得するのに適していますが、場合によっては、オブジェクトの完全なURL(http://
とドメインおよびすべて)を表示したい場合があります。 。 これを行うには、サイトフレームワークを使用できます。 例:
>>> 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'
を:setting: `INSTALLED_APPS` 設定に追加します。:setting: `SITE_ID` 設定を定義します:
SITE_ID = 1
:djadmin: `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 を使用することを検討してください。 これはモデル manager であり、クエリを自動的にフィルタリングして、現在の 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()
は、現在のサイトに関連付けられているPhoto
オブジェクトのみを返します。 :setting: `SITE_ID` 設定。
言い換えると、これら2つのステートメントは同等です。
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
CurrentSiteManager は、Photo
のどのフィールドがサイトであるかをどのようにして知りましたか? デフォルトでは、 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
を発生させます。
最後に、 CurrentSiteManager を使用している場合でも、モデルで通常の(サイト固有ではない)Manager
を維持することをお勧めします。 マネージャーのドキュメントで説明されているように、マネージャーを手動で定義すると、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 を:setting: `MIDDLEWARE` に追加してください。 ミドルウェアはすべてのリクエストオブジェクトにsite
属性を設定するため、request.site
を使用して現在のサイトを取得できます。
Djangoがサイトフレームワークを使用する方法
サイトフレームワークを使用する必要はありませんが、Djangoがいくつかの場所でそれを利用しているため、強くお勧めします。 Djangoインストールが単一のサイトのみに電力を供給している場合でも、domain
とname
を使用してサイトオブジェクトを作成し、でそのIDを指定するのに2秒かかる必要があります。設定: `SITE_ID` 設定。
Djangoがサイトフレームワークを使用する方法は次のとおりです。
- リダイレクトフレームワークでは、各リダイレクトオブジェクトは特定のサイトに関連付けられています。 Djangoがリダイレクトを検索するとき、現在のサイトが考慮されます。
- flatpagesフレームワークでは、各フラットページは特定のサイトに関連付けられています。 フラットページを作成するときに、その Site を指定すると、 FlatpageFallbackMiddleware は、表示するフラットページを取得する際に現在のサイトをチェックします。
- シンジケーションフレームワークでは、
title
およびdescription
のテンプレートは、サイトである変数テンプレート:Site
に自動的にアクセスできます。現在のサイトを表すオブジェクト。 また、完全修飾ドメインを指定しない場合、アイテムURLを提供するためのフックは、現在の Site オブジェクトのdomain
を使用します。 - 認証フレームワークでは、 django.contrib.auth.views.LoginView は現在のサイト名を
テンプレート:Site name
としてテンプレートに渡します。 - ショートカットビュー(
django.contrib.contenttypes.views.shortcut
)は、オブジェクトのURLを計算するときに、現在の Site オブジェクトのドメインを使用します。 - 管理フレームワークでは、「サイトで表示」リンクは現在のサイトを使用して、リダイレクト先のサイトのドメインを決定します。
RequestSiteオブジェクト
一部の django.contrib アプリケーションは、サイトフレームワークを利用しますが、データベースにインストールするサイトフレームワークを必要としない方法で設計されています。 (一部の人々は、サイトフレームワークが必要とする追加のデータベーステーブルをインストールしたくない、または単に可能ではありません。)そのような場合、フレームワークは django.contrib.sitesを提供します。 .requests.RequestSite クラス。これは、データベースにバックアップされたサイトフレームワークが利用できない場合のフォールバックとして使用できます。
- class requests.RequestSite
- Site のプライマリインターフェイスを共有する(つまり、
domain
属性とname
属性を持つ)クラスですが、Django HttpRequest オブジェクトからデータを取得しますデータベースからではなく。
- __init__(request)
name
およびdomain
属性を get_host()の値に設定します。
RequestSite オブジェクトは、 __ init __()メソッドが HttpRequest オブジェクトを受け取ることを除いて、通常の Site オブジェクトと同様のインターフェイスを備えています。 リクエストのドメインを調べることで、domain
とname
を推測することができます。 Site のインターフェースに一致するsave()
およびdelete()
メソッドがありますが、これらのメソッドはNotImplementedError
を発生させます。
get_current_siteショートカット
最後に、繰り返しのフォールバックコードを回避するために、フレームワークは django.contrib.sites.shortcuts.get_current_site()関数を提供します。
- shortcuts.get_current_site(request)
django.contrib.sites
がインストールされているかどうかを確認し、リクエストに基づいて現在の Site オブジェクトまたは RequestSite オブジェクトを返す関数。 :setting: `SITE_ID` 設定が定義されていない場合は、 request.get_host()に基づいて現在のサイトを検索します。ホストヘッダーに明示的に指定されたポートがある場合、ドメインとポートの両方が request.get_host()によって返される場合があります。
example.com:80
。 このような場合、ホストがデータベース内のレコードと一致しないためにルックアップが失敗すると、ポートが削除され、ドメイン部分のみでルックアップが再試行されます。 これは、常に変更されていないホストを使用する RequestSite には適用されません。