Инфраструктура "сайтов"

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 . Если вы используете поле с именем, отличным от `` сайта '', или 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, особенно сайт администратора и общие представления, используют первый обработчик, определенный для модели, поэтому, если вы хотите, чтобы сайт администратора имел доступ ко всем объектам (а не только к тем, что в 'конкретный сайт), добавьте в свои шаблоны перед определением .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 ищет перенаправление, он учитывает текущий сайт.application de redirection
  • В «» каждая статическая страница связана с определенным сайтом. Когда создается статическая страница, вы должны указать ее , и промежуточное ПО проверяет текущий сайт, когда находит статические страницы для отображения.application des pages statiques Site FlatpageFallbackMiddleware
  • В l » шаблоны для и автоматически получают доступ к переменной , которая является объектом, представляющим текущий сайт. Аналогично, точка входа для предоставления URL-адресов элементов использует атрибут текущего объекта, если вы не укажете полностью определенный домен.infrastructure de syndication title description {{ site }} Site domain Site
  • В » представление передает имя текущего объекта в шаблон как .infrastructure d'authentification 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 в Django, а не из базы данных.

__init__( запрос )

Устанавливает атрибуты name и в domain соответствии со значениями, полученными из get_host() .

Объект RequestSite имеет интерфейс, подобный Site обычному объекту , за исключением того, что его метод __init__() принимает объект HttpRequest . Он может определить домен и имя из него, исследуя домен запроса. У него есть методы 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 ©2020 All rights reserved