Spec-Zone.ru › Django 5.2

Фреймворк «sites»

Django поставляется с необязательным фреймворком «sites». Он предназначен для ассоциации объектов и функциональности с конкретными веб-сайтами и хранит доменные имена и «полные» имена ваших сайтов, работающих на Django.

Используйте его, если одна установка Django обслуживает более одного сайта и вам нужно каким-то образом различать эти сайты.

Фреймворк «sites» в основном основан на этой модели:

class models.Site

Модель для хранения domain и name атрибутов веб-сайта.

domain

Полное доменное имя, связанное с веб-сайтом. Например, www.example.com.

name

Читаемое человеком «полное» имя веб-сайта.

Настройка SITE_ID указывает идентификатор базы данных объекта Site, связанного с этим файлом настроек. Если настройка опущена, функция get_current_site() попытается получить текущий сайт, сравнив domain с именем хоста из метода request.get_host().

Как использовать его, зависит от вас, но Django автоматически использует его несколькими способами, следуя нескольким соглашениям.

Примеры использования

Почему вы используете «sites»? Лучше всего объяснить на примерах.

Ассоциация контента с несколькими сайтами

Сайты LJWorld.com и Lawrence.com управлялись одной и той же новостной организацией — газетой Lawrence Journal-World в Лоуренсе, Канзас. LJWorld.com фокусировался на новостях, а Lawrence.com — на местных развлечениях. Но иногда редакторы хотели опубликовать статью на обоих сайтах.

Примитивный способ решения проблемы заключался в требовании от авторов публиковать одну и ту же историю дважды: один раз для LJWorld.com и снова для Lawrence.com. Но это неэффективно для авторов и избыточно хранить несколько копий одной и той же истории в базе данных.

Лучшее решение устраняет дублирование контента: оба сайта используют одну и ту же базу данных статей, и статья ассоциируется с одним или несколькими сайтами. В терминологии модели Django это представлено ManyToManyField в модели Article:

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")
        # ...
    

Ассоциация контента с одним сайтом

Аналогично, вы можете связать модель с моделью Site в отношении «многие ко одному», используя ForeignKey.

Например, если статья разрешена только на одном сайте, вы используете такую модель:

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)

Это имеет те же преимущества, что и в предыдущем разделе.

Включение текущего сайта в представления

Вы можете использовать фреймворк «sites» в ваших представлениях Django для выполнения определенных действий на основе сайта, на котором вызывается представление. Например:

from django.conf import settings


def my_view(request):
    if settings.SITE_ID == 3:
        # Do something.
        pass
    else:
        # Do something else.
        pass

Захардкодить идентификаторы сайта таким образом небезопасно в случае их изменения. Более чистым способом достижения той же цели является проверка домена текущего сайта:

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

Это также имеет преимущество проверки, установлен ли фреймворк «sites», и возвращает экземпляр RequestSite, если он не установлен.

Если у вас нет доступа к объекту запроса, вы можете использовать метод get_current() менеджера модели Site. Затем вы должны убедиться, что ваш файл настроек содержит настройку 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])

    # ...

В этом случае вам нужно будет создать subject.txt и message.txt файлы шаблонов для каталогов шаблонов LJWorld.com и Lawrence.com. Это дает вам больше гибкости, но также более сложно.

Рекомендуется максимально использовать объекты Site, чтобы устранить ненужную сложность и избыточность.

Получение текущего домена для полных URL-адресов

Конвенция Django get_absolute_url() удобна для получения URL-адреса ваших объектов без имени домена, но в некоторых случаях вам может потребоваться отобразить полный URL-адрес — с https://, именем домена и всем остальным — для объекта. Для этого можно использовать фреймворк «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'
>>> "https://%s%s" % (Site.objects.get_current().domain, obj.get_absolute_url())
'https://example.com/mymodel/objects/3/'

Включение фреймворка «sites»

Чтобы включить фреймворк «sites», выполните следующие действия:

  1. Добавьте 'django.contrib.sites' в настройку INSTALLED_APPS.
  2. Определите настройку SITE_ID:

    SITE_ID = 1
    
  3. Запустите migrate.

django.contrib.sites регистрирует обработчик сигнала post_migrate, который создает сайт по умолчанию с именем example.com и доменом example.com. Этот сайт также будет создан после того, как Django создаст тестовую базу данных. Чтобы установить правильное имя и домен для вашего проекта, вы можете использовать миграцию данных.

Для того чтобы обслуживать разные сайты в производстве, вы бы создали отдельный файл настроек для каждого SITE_ID (возможно, импортируя из общего файла настроек, чтобы избежать дублирования общих настроек), а затем указали соответствующий DJANGO_SETTINGS_MODULE для каждого сайта.

Кэширование объекта текущего Site

Так как текущий сайт хранится в базе данных, каждый вызов Site.objects.get_current() может привести к запросу к базе данных. Но Django немного умнее: при первом запросе текущий сайт кэшируется, и любые последующие вызовы возвращают кэшированные данные вместо обращения к базе данных.

Если по какой-либо причине вы хотите принудительно выполнить запрос к базе данных, вы можете сказать Django очистить кэш, используя Site.objects.clear_cache():

# 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()

Менеджер текущего сайта

class managers.CurrentSiteManager

Если объект сайта играет ключевую роль в вашем приложении, рассмотрите использование полезного менеджера менеджера текущего сайта в вашей модели(ях). Это менеджер модели, который автоматически фильтрует запросы, включающие только объекты, связанные с текущим сайтом.

Обязательная настройка SITE_ID

Менеджер текущего сайта можно использовать только тогда, когда настройка SITE_ID определена в ваших настройках.

Используйте менеджер текущего сайта, добавив его в вашу модель явно. Например:

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 объекты, связанные с текущим сайтом, в соответствии с настройкой SITE_ID.

Другими словами, эти два оператора эквивалентны:

Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()

Как менеджер текущего сайта узнал, какой поле Photo был полем сайта? По умолчанию менеджер текущего сайта ищет либо ForeignKey с именем site, либо ManyToManyField с именем sites для фильтрации. Если вы используете поле с именем, отличным от site или sites, чтобы идентифицировать, с какими объектами сайта связан ваш объект, тогда вам необходимо явно передать имя пользовательского поля в качестве параметра к менеджеру текущего сайта в вашей модели. Следующая модель, которая имеет поле, называемое 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")

Если вы попытаетесь использовать менеджер текущего сайта и передадите имя поля, которого не существует, Django выдаст ошибку ValueError.

Наконец, обратите внимание, что вам, вероятно, потребуется сохранить обычный (не зависящий от сайта) Manager в вашей модели, даже если вы используете менеджер текущего сайта. Как объяснено в документации по менеджерам, если вы вручную определяете менеджер, Django не создаст для вас автоматический менеджер objects = models.Manager(). Также обратите внимание, что некоторые части Django — в частности, сайт администрирования Django и обобщенные представления — используют менеджер, который определен первым в модели, поэтому, если вы хотите, чтобы ваш сайт администрирования имел доступ ко всем объектам (а не только к объектам, специфичным для сайта), поместите 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 и указать его ID в настройке SITE_ID.

Вот как Django использует фреймворк сайтов:

  • В redirects framework каждый объект перенаправления связан с определенным сайтом. Когда Django ищет перенаправление, он учитывает текущий сайт.
  • В flatpages framework каждая статическая страница связана с определенным сайтом. При создании статической страницы вы указываете её сайт, и FlatpageFallbackMiddleware проверяет текущий сайт при получении статических страниц для отображения.
  • В syndication framework, шаблоны для title и description автоматически имеют доступ к переменной {{ site }}, которая является объектом сайта, представляющим текущий сайт. Кроме того, обработчик для предоставления URL-адресов элементов будет использовать domain из объекта текущего сайта, если вы не укажете полный домен.
  • В authentication framework, django.contrib.auth.views.LoginView передает имя текущего сайта в шаблон в качестве {{ site_name }}.
  • Сокращенное представление (django.contrib.contenttypes.views.shortcut) использует домен текущего объекта сайта при расчете URL-адреса объекта.
  • В фреймворке администрирования ссылка «Просмотреть на сайте» использует текущий сайт, чтобы определить домен сайта, на который будет перенаправление.

RequestSite объекты

Некоторые приложения django.contrib используют фреймворк сайтов, но спроектированы так, что не требуют установки фреймворка сайтов в вашей базе данных. (Некоторые люди не хотят или просто не могут установить дополнительную таблицу базы данных, требуемую фреймворком сайтов.) В таких случаях фреймворк предоставляет класс django.contrib.sites.requests.RequestSite, который может использоваться в качестве резервного варианта, когда фреймворк сайтов с поддержкой базы данных недоступен.

class requests.RequestSite

Класс, который разделяет основной интерфейс Site (т.е., он имеет domain и name атрибуты), но получает свои данные из объекта Django HttpRequest, а не из базы данных.

__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 на основе запроса. Он ищет текущий сайт на основе request.get_host(), если значение настройки SITE_ID не определено.

И домен, и порт могут быть возвращены request.get_host(), когда заголовок Host имеет явно указанный порт, например, example.com:80. В таких случаях, если поиск не удается из-за того, что хост не соответствует записи в базе данных, порт удаляется, и поиск повторяется только с частью домена. Это не относится к RequestSite, который всегда будет использовать неизмененный хост.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/ref/contrib/sites/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API