Spec-Zone.ru › Django 2.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 автоматически использует его несколькими способами, следуя простым соглашениям.

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

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

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

Веб-сайты 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)

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

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

Вы можете использовать фреймворк «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 Context, loader

def register_for_newsletter(request):
    # Check form values, etc., and subscribe the user.
    # ...

    subject = loader.get_template('alerts/subject.txt').render(Context({}))
    message = loader.get_template('alerts/message.txt').render(Context({}))
    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()

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

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 admin и общие представления – используют тот менеджер, который определен первым в модели, поэтому, если вы хотите, чтобы ваш сайт admin имел доступ ко всем объектам (а не только к сайту-специфичным), поместите 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().

END_OF_DOCUMENT_MARKER
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/2.1/ref/contrib/sites/

Spec-Zone.ru

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