Spec-Zone.ru › Django 1.11

Фреймворк «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.db import models
from django.contrib.sites.models import Site

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.db import models
from django.contrib.sites.models import Site

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, Context

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.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

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.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager

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

Spec-Zone.ru

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