Фреймворк «сайты»

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/'

Включение фреймворка сайтов

Чтобы включить структуру сайтов, выполните следующие действия:

  1. Добавьте 'django.contrib.sites'к своей INSTALLED_APPSнастройке.

  2. Определите SITE_IDнастройку:

    SITE_ID = 1
    
  3. Беги 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 frameworkSiteFlatpageFallbackMiddleware
  • В шаблонах для и автоматически предоставляется доступ к переменной , которая является объектом, представляющим текущий сайт. Кроме того, ловушка для предоставления URL-адресов элементов будет использовать из текущего объекта, если вы не укажете полностью определенный домен.syndication frameworktitledescription{{ site }}SitedomainSite
  • В , передает текущее имя в шаблоне как .authentication frameworkdjango.contrib.auth.views.LoginViewSite{{ 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который всегда будет использовать неизмененный хост.

Copyright ©2021 All rights reserved