Фреймворк «сайты» ¶
Django поставляется с дополнительным фреймворком «сайты». Это крючок для связывания объектов и функций с определенными веб-сайтами, а также место хранения доменных имен и «подробных» имен ваших сайтов на Django.
Используйте его, если ваша единственная установка Django работает на нескольких сайтах, и вам нужно каким-то образом различать эти сайты.
Фреймворк сайтов в основном основан на этой модели:
-
класс
models.
Site
¶ Модель для хранения
domain
иname
атрибутов веб - сайта.-
domain
¶ Полное доменное имя, связанное с сайтом. Например,
www.example.com
.
-
name
¶ «Подробное» название веб-сайта, удобочитаемое для человека.
-
Параметр SITE_ID
указывает идентификатор базы данных
Site
объекта, связанного с этим конкретным файлом настроек. Если параметр не указан,
get_current_site()
функция попытается получить текущий сайт, сравнив его
domain
с именем хоста из request.get_host()
метода.
Как вы это используете, зависит от вас, но Django автоматически использует это несколькими способами с помощью нескольких соглашений.
Пример использования ¶
Зачем вам использовать сайты? Лучше всего это объяснить на примерах.
Связывание контента с несколькими сайтами ¶
В 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)
Это дает те же преимущества, что описаны в последнем разделе.
Подключение к текущему сайту из просмотров ¶
Вы можете использовать структуру сайтов в ваших представлениях 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
Это также имеет то преимущество, что проверяет, установлена ли структура сайтов, и возвращает 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,
),
'[email protected]%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, '[email protected]', [user.email])
# ...
В этом случае, вы должны создать subject.txt
и message.txt
файлы шаблонов для как LJWorld.com и Lawrence.com шаблон каталогов. Это дает вам больше гибкости, но также и сложнее.
Рекомендуется Site
максимально использовать объекты, чтобы удалить ненужную сложность и избыточность.
Получение текущего домена для полных URL ¶
get_absolute_url()
Соглашение Django удобно для получения URL-адреса ваших объектов без имени домена, но в некоторых случаях вы можете захотеть отобразить полный URL-адрес - с http://
доменом и всем остальным - для объекта. Для этого можно использовать фреймворк сайтов. Пример:
>>> 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/'
Включение фреймворка сайтов ¶
Чтобы включить структуру сайтов, выполните следующие действия:
Добавьте
'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
¶
-
класс
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 не создаст для вас автоматический
менеджер. Также обратите внимание, что определенные части Django, а именно сайт администратора Django и общие представления, используют тот менеджер, который определен первым в модели, поэтому, если вы хотите, чтобы ваш сайт администратора имел доступ ко всем объектам (а не только к специфическим для сайта), вставьте свою модель, прежде чем определять .objects = models.Manager()
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
, и указать его идентификатор в ваших SITE_ID
настройках.
Вот как Django использует фреймворк сайтов:
- В каждом объекте перенаправления связан с конкретным сайтом. Когда Django ищет перенаправление, он принимает во внимание текущий сайт.
redirects framework
- В приложении каждая плоская страница связана с определенным сайтом. Когда плоская страница создается, вы указываете ее , и программа
проверяет текущий сайт при получении плоских страниц для отображения.
flatpages framework
Site
FlatpageFallbackMiddleware
- В шаблонах для и автоматически предоставляется доступ к переменной , которая является
объектом, представляющим текущий сайт. Кроме того, ловушка для предоставления URL-адресов элементов будет использовать из текущего объекта, если вы не укажете полностью определенный домен.
syndication framework
title
description
{{ site }}
Site
domain
Site
- В ,
передает текущее
имя в шаблоне как
.
authentication framework
django.contrib.auth.views.LoginView
Site
{{ site_name }}
- Ярлык view (
django.contrib.contenttypes.views.shortcut
) использует домен текущегоSite
объекта при вычислении URL-адреса объекта. - В административной структуре ссылка «просмотр на сайте» использует текущий
Site
для определения домена сайта, на который будет выполняться перенаправление.
RequestSite
объекты ¶
Некоторые приложения django.contrib используют фреймворк сайтов, но спроектированы таким образом, что не требует установки фреймворка сайтов в вашу базу данных. (Некоторые люди не хотят или просто не могут установить дополнительную таблицу базы данных, которая требуется платформе сайтов.) Для этих случаев платформа предоставляет django.contrib.sites.requests.RequestSite
класс, который можно использовать в качестве запасного варианта,
когда база данных поддерживает фреймворк сайтов недоступен.
-
класс
requests.
RequestSite
¶ Класс , который разделяет первичный интерфейс
Site
(то есть, у него естьdomain
иname
атрибуты) , но получает данные от ДжангоHttpRequest
объекта , а не из базы данных.-
__init__
( запрос ) ¶ Устанавливает
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
( запрос ) ¶ Функция, которая проверяет,
django.contrib.sites
установлена ли она, и возвращает либо текущийSite
объект, либоRequestSite
объект на основе запроса. Он ищет текущий сайт в зависимости отrequest.get_host()
того,SITE_ID
не задан ли параметр.И домен, и порт могут быть возвращены,
request.get_host()
если в заголовке Host явно указан порт, напримерexample.com:80
. В таких случаях, если поиск не выполняется из-за того, что хост не соответствует записи в базе данных, порт удаляется, и поиск повторяется только с доменной частью. Это не относится к тому,RequestSite
который всегда будет использовать неизмененный хост.