Фреймворк «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)
Это имеет те же преимущества, что и описано в предыдущем разделе.
Включение текущего сайта из представлений
Вы можете использовать фреймворк «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' >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url()) 'http://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()
Менеджер CurrentSiteManager
-
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)
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)
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_CLASSES. Средство обработки устанавливает атрибут 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.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().
Устарело начиная с версии 1.7: Этот класс раньше был определён в
django.contrib.sites.models. Старое место импорта будет работать до Django 1.9. -
Объект 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, в зависимости от запроса.Устарело начиная с версии 1.7: Эта функция раньше была определена в
django.contrib.sites.models. Старое место импорта будет работать до Django 1.9.Теперь эта функция будет искать текущий сайт на основе
request.get_host(), если настройкаSITE_IDне определена.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/ref/contrib/sites/