Фреймворк «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? Лучше всего это объяснить на примерах.
Связывание контента с несколькими сайтами
Сайты 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 loader
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template("alerts/subject.txt").render({})
message = loader.get_template("alerts/message.txt").render({})
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.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.
Middleware для сайтов
Если вы часто используете этот шаблон:
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, и указать его 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.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) но получает данные из объекта 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 Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.2/ref/contrib/sites/