Фреймворк «sites»
Django поставляется с необязательным фреймворком «sites». Он служит для связывания объектов и функциональности с определёнными веб-сайтами, а также хранит доменные имена и «полные» имена ваших сайтов, работающих на Django.
Используйте его, если ваша единая установка Django обслуживает более одного сайта и вам необходимо как-то их различать.
Фреймворк «sites» в основном основан на простой модели:
-
class models.Site -
Модель для хранения
domainиnameатрибутов веб-сайта.-
domain -
Полное доменное имя, связанное с веб-сайтом. Например,
www.example.com.Поле
domainбыло установлено какunique.
-
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.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», выполните следующие шаги:
- Добавьте
'django.contrib.sites'в настройкуINSTALLED_APPS. -
Определите настройку
SITE_ID:SITE_ID = 1
- Запустите
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()
Менеджер текущего сайта
-
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='/home/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='/home/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.
Серверный middleware для сайта
Если вы часто используете этот шаблон:
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...
есть простой способ избежать повторений. Добавьте django.contrib.sites.middleware.CurrentSiteMiddleware в MIDDLEWARE_CLASSES. Middleware устанавливает атрибут site в каждом объекте запроса, так что вы можете использовать request.site для получения текущего сайта.
Как Django использует фреймворк сайтов
Хотя использование фреймворка сайтов не обязательно, это настоятельно рекомендуется, так как Django использует его в нескольких местах. Даже если ваша установка Django обслуживает только один сайт, вы должны потратить две секунды на создание объекта сайта с вашим domain и name, и указать его ID в вашей настройке SITE_ID.
Вот как Django использует фреймворк сайтов:
- В
redirects frameworkкаждый объект перенаправления связан с определенным сайтом. При поиске Django учитывает текущий сайт. - В
flatpages frameworkкаждая страница flatpage связана с определенным сайтом. При создании flatpage вы указываете егоSite, иFlatpageFallbackMiddlewareпроверяет текущий сайт при получении отображаемых flatpage. - В
syndication framework, шаблоны дляtitleиdescriptionавтоматически имеют доступ к переменной{{ site }}, которая является объектомSite, представляющим текущий сайт. Кроме того, обработчик для предоставления URL-адресов элементов будет использоватьdomainиз текущего объектаSite, если вы не укажете полный домен. - В
authentication framework, представлениеdjango.contrib.auth.views.login()передает имя текущего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), но получает свои данные из объекта DjangoHttpRequest, а не из базы данных.-
__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, который всегда будет использовать неизмененный хост.Добавлена возможность поиска текущего сайта на основе
request.get_host().Добавлена возможность повторного поиска с удалением порта.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/ref/contrib/sites/