Spec-Zone.ru › Django 5.1

Фреймворк «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.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()

Менеджер CurrentSiteManager

class managers.CurrentSiteManager

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

Обязательная SITE_ID

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

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

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

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

Наконец, обратите внимание, что вам, вероятно, захочется сохранить обычный (не специфичный для сайта) Manager в вашей модели, даже если вы используете CurrentSiteManager. Как объясняется в документации по менеджерам, если вы вручную определили менеджер, Django не создаст автоматического objects = models.Manager() менеджера за вас. Также обратите внимание, что некоторые части Django, в частности, сайт администрирования Django и общие представления, используют менеджер, определённый первым в модели. Поэтому, если вы хотите, чтобы ваш сайт администрирования имел доступ ко всем объектам (а не только к объектам, специфичным для сайта), поместите objects = models.Manager() в вашу модель перед определением CurrentSiteManager.

Средства обработки сайтов

Если вы часто используете этот шаблон:

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 каждая страница связана с определённым сайтом. При создании страницы вы указываете её Site, и FlatpageFallbackMiddleware проверяет текущий сайт при получении страниц для отображения.
  • В syndication framework шаблоны для title и description автоматически имеют доступ к переменной {{ site }}, которая является объектом Site, представляющим текущий сайт. Также обработчик для предоставления URL-адресов элементов будет использовать domain из объекта текущего Site, если вы не указали полностью квалифицированный домен.
  • В authentication framework django.contrib.auth.views.LoginView передаёт имя текущего Site в шаблон как {{ site_name }}.
  • Сокращённое представление (django.contrib.contenttypes.views.shortcut) использует домен текущего объекта Site при вычислении 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 имеет интерфейс, похожий на обычный объект 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.1/ref/contrib/sites/

Spec-Zone.ru

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