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

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

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

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

Сайты 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 view для обоих сайтов. Код view, который отображает данную историю, просто проверяет, находится ли запрошенная история на текущем сайте. Он выглядит примерно так:

    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

Конечно, жёстко кодировать 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

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

Наконец, чтобы избежать повторяющегося кода fallback, фреймворк предоставляет функцию 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.0/ref/contrib/sites/

Spec-Zone.ru

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