Spec-Zone.ru › Django 5.0

Фреймворк «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 автоматически использует его несколькими способами с помощью нескольких соглашений.

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

Зачем использовать сайты? Лучше всего это объяснить на примерах.

Связывание контента с несколькими сайтами

Сайты 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)

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

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

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

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-адрес — с 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'
>>> "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()

Менеджер 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, и укажите его идентификатор в настройке 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.0/ref/contrib/sites/

Spec-Zone.ru

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