「サイト」フレームワーク—Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.2.x/ref/contrib/sites
移動先:案内検索

「サイト」フレームワーク

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が変更された場合に備えて、そのようにサイト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.txtmessage.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/'

サイトフレームワークの有効化

サイトフレームワークを有効にするには、次の手順に従います。

  1. 'django.contrib.sites':setting: `INSTALLED_APPS` 設定に追加します。

  2. :setting: `SITE_ID` 設定を定義します:

    SITE_ID = 1
  3. :djadmin: `migrate` を実行します。

django.contrib.sitesは、 post_migrate シグナルハンドラーを登録し、ドメインexample.comexample.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 に関連付けられているオブジェクトのみを含めます。

必須:setting: `SITE_ID`

CurrentSiteManagerは、:setting: `SITE_ID` 設定が設定で定義されている場合にのみ使用できます。


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インストールが単一のサイトのみに電力を供給している場合でも、domainnameを使用してサイトオブジェクトを作成し、でその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 オブジェクトと同様のインターフェイスを備えています。 リクエストのドメインを調べることで、domainnameを推測することができます。 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 には適用されません。