Фреймворк «sites»
Django поставляется с необязательным фреймворком «sites». Он служит для связывания объектов и функциональности с конкретными веб-сайтами, а также хранит доменные имена и описательные названия ваших сайтов, работающих на Django.
Используйте его, если одно ваше приложение Django обслуживает более одного сайта и вам нужно каким-то образом их различать.
Фреймворк «sites» в основном основан на простой модели:
-
class models.Site -
Модель для хранения
domainиnameатрибутов веб-сайта.-
domain -
Полное доменное имя, связанное с веб-сайтом. Например,
www.example.com.Изменено в Django 1.9:Поле
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
Поскольку текущий сайт хранится в базе данных, каждый вызов 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.
Средствами для работы с сайтом
Если вы часто используете этот шаблон:
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...
существует простой способ избежать повторений. Добавьте django.contrib.sites.middleware.CurrentSiteMiddleware в MIDDLEWARE. Middleware устанавливает атрибут site на каждом объекте запроса, поэтому вы можете использовать request.site для получения текущего сайта.
Как Django использует фреймворк сайтов
Хотя использование фреймворка сайтов не является обязательным, его настоятельно рекомендуется, так как Django использует его в нескольких местах. Даже если ваша установка Django обслуживает только один сайт, вам следует потратить две секунды на создание объекта сайта с вашим domain и name, и указать его идентификатор в настройке 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, который всегда будет использовать неизменённый хост.Изменено в Django 1.9:Добавлен повторный поиск с удалением порта.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/ref/contrib/sites/