Как управлять сложной конфигурацией apphook¶
В Как создать apphooks мы обсудили некоторые основные моменты использования apphooks. В этом документе мы рассмотрим некоторые более сложные возможности реализации.
Прикрепление приложения несколько раз¶
Определение пространства имен на уровне класса¶
Если вы хотите прикрепить приложение несколько раз к разным страницам, то класс, определяющий apphook, должен иметь атрибут app_name
:
class MyApphook(CMSApp):
name = _("My Apphook")
app_name = "myapp"
def get_urls(self, page=None, language=None, **kwargs):
return ["myapp.urls"]
app_name
делает три ключевые вещи:
Оно предоставляет пространство имен fallback для представлений и шаблонов, обращающих URL.
Он раскрывает поле Имя экземпляра приложения в админке страницы при применении apphook.
Он устанавливает имя экземпляра apphook по умолчанию (которое вы увидите в поле Имя экземпляра приложения).
Мы объясним их на примере. Предположим, что представления или шаблоны вашего приложения используют reverse('myapp:index')
или {% url 'myapp:index' %}
.
В этом случае пространство имен всех применяемых вами apphooks должно соответствовать myapp
. Если это не так, то использующие их страницы будут выдавать ошибку NoReverseMatch
.
Вы можете задать пространство имен для экземпляра apphook в поле Имя экземпляра приложения. Однако, если экземпляр с таким значением уже существует, вам нужно будет задать что-то другое. В данном случае, пока app_name = "myapp"
, это не имеет значения; даже если система не найдет совпадения с именем экземпляра, она вернется к тому, которое жестко заложено в классе.
Другими словами, установка app_name
правильно гарантирует, что URL-реверсирование будет работать, поскольку оно устанавливает пространство имен отката соответствующим образом.
Установите пространство имен на уровне экземпляра¶
С другой стороны, Имя экземпляра приложения будет переопределять app_name
если будет найдено совпадение.
Такая схема позволяет использовать несколько экземпляров приложений и пространств имен, если требуется такая гибкость, гарантируя при этом простой способ заставить их работать, когда это не так.
Документация Django Reversing namespaced URLs содержит больше информации о том, как это работает, но упрощенная версия такова:
Сначала он попытается найти соответствие для Имя экземпляра приложения.
Если это не удается, он попытается найти совпадение для
app_name
.
Конфигурации Apphook¶
Расстановка имен ваших apphooks также позволяет управлять дополнительной конфигурацией apphook, хранящейся в базе данных, на основе каждого экземпляра.
Основные понятия¶
Чтобы передать конфигурацию, которую могут принимать различные экземпляры apphook, необходимо создать модель Django - каждый экземпляр apphook будет экземпляром этой модели и будет управляться через администратора Django обычным способом.
После настройки конфигурация apphook может быть применена к экземпляру apphook в Дополнительных настройках страницы, к которой принадлежит экземпляр apphook:
Конфигурация затем загружается в представления приложения для этого пространства имен и будет использоваться для определения его поведения.
Создание конфигурации приложения фактически создает пространство имен экземпляра apphook. После создания пространство имен конфигурации не может быть изменено - если требуется другое пространство имен, необходимо создать новую конфигурацию.
Пример конфигурации apphook¶
Чтобы проиллюстрировать, как все это работает, мы создадим новое приложение FAQ, которое предоставляет простой список вопросов и ответов, вместе с классом apphook и моделью конфигурации apphook, которая позволяет ему существовать в нескольких местах на сайте в различных конфигурациях.
Мы будем считать, что у вас уже есть работающий проект django CMS.
Использование вспомогательных приложений¶
В этом примере мы будем использовать пару простых вспомогательных приложений, просто чтобы облегчить нашу работу.
Aldryn Apphooks Config¶
Aldryn Apphooks Config - это вспомогательное приложение, которое облегчает разработку конфигурируемых apphooks. Например, оно предоставляет AppHookConfig
для подкласса и другие полезные компоненты для экономии вашего времени.
В этом примере мы будем использовать Aldryn Apphooks Config, так как мы рекомендуем его. Однако вы не обязаны использовать его в своих проектах; при желании вы можете собрать необходимый вам код вручную.
Для его установки используйте pip install aldryn-apphooks-config
.
Aldryn Apphooks Config в свою очередь устанавливает Django AppData, который обеспечивает элегантный способ расширения одного приложения другим; мы воспользуемся и этим.
Создайте новое приложение FAQ¶
python manage.py startapp faq
Создайте модель FAQ Entry
¶
models.py
:
from aldryn_apphooks_config.fields import AppHookConfigField
from aldryn_apphooks_config.managers import AppHookConfigManager
from django.db import models
from faq.cms_appconfig import FaqConfig
class Entry(models.Model):
app_config = AppHookConfigField(FaqConfig)
question = models.TextField(blank=True, default='')
answer = models.TextField()
objects = AppHookConfigManager()
def __unicode__(self):
return self.question
class Meta:
verbose_name_plural = 'entries'
Поле app_config
является ForeignKey
для модели конфигурации apphook; мы создадим ее в ближайшее время. Эта модель будет хранить конкретную конфигурацию пространства имен и позволит назначить каждый FAQ Entry пространству имен.
Пользовательский AppHookConfigManager
существует для того, чтобы облегчить фильтрацию набора запросов Entries
с помощью удобного ярлыка: Entry.objects.namespace('foobar')
.
Определите подкласс AppHookConfig¶
В новом файле cms_appconfig.py
в приложении FAQ:
from aldryn_apphooks_config.models import AppHookConfig
from aldryn_apphooks_config.utils import setup_config
from app_data import AppDataForm
from django.db import models
from django import forms
from django.utils.translation import gettext_lazy as _
class FaqConfig(AppHookConfig):
paginate_by = models.PositiveIntegerField(
_('Paginate size'),
blank=False,
default=5,
)
class FaqConfigForm(AppDataForm):
title = forms.CharField()
setup_config(FaqConfigForm, FaqConfig)
Реализацию можно оставить совершенно пустой, поскольку минимальная схема уже определена в абстрактной родительской модели, предоставляемой Aldryn Apphooks Config.
Здесь мы определяем дополнительное поле в модели, paginate_by
. Мы будем использовать его позже, чтобы контролировать количество записей, отображаемых на странице.
Мы также установили FaqConfigForm
, который использует AppDataForm
для добавления поля в FaqConfig
, не касаясь его модели.
Поле title также может быть просто полем модели, например paginate_by
. Но мы используем AppDataForm для демонстрации этой возможности.
Определите его свойства администратора¶
В admin.py
нам нужно определить все поля, которые мы хотели бы отобразить:
from django.contrib import admin
from .cms_appconfig import FaqConfig
from .models import Entry
from aldryn_apphooks_config.admin import ModelAppHookConfig, BaseAppHookConfig
class EntryAdmin(ModelAppHookConfig, admin.ModelAdmin):
list_display = (
'question',
'answer',
'app_config',
)
list_filter = (
'app_config',
)
admin.site.register(Entry, EntryAdmin)
class FaqConfigAdmin(BaseAppHookConfig, admin.ModelAdmin):
def get_config_fields(self):
return (
'paginate_by',
'config.title',
)
admin.site.register(FaqConfig, FaqConfigAdmin)
get_config_fields
определяет поля, которые должны быть отображены. Любые поля, использующие формы AppData, должны иметь префикс config.
.
Определите сам apphook¶
Теперь давайте создадим apphook и настроим его на поддержку нескольких экземпляров. В cms_apps.py
:
from aldryn_apphooks_config.app_base import CMSConfigApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import gettext_lazy as _
from .cms_appconfig import FaqConfig
@apphook_pool.register
class FaqApp(CMSConfigApp):
name = _("Faq App")
app_name = "faq"
app_config = FaqConfig
def get_urls(self, page=None, language=None, **kwargs):
return ["faq.urls"]
Определите вид списка для записей FAQ¶
У нас есть все основы. Теперь мы добавим представление списка для записей FAQ, которое будет отображать записи только для текущего используемого пространства имен. В views.py
:
from aldryn_apphooks_config.mixins import AppConfigMixin
from django.views import generic
from .models import Entry
class IndexView(AppConfigMixin, generic.ListView):
model = Entry
template_name = 'faq/index.html'
def get_queryset(self):
qs = super().get_queryset()
return qs.namespace(self.namespace)
def get_paginate_by(self, queryset):
try:
return self.config.paginate_by
except AttributeError:
return 10
AppConfigMixin
избавляет вас от необходимости устанавливать какие-либо атрибуты в представлении - они автоматически устанавливаются для экземпляра класса представления:
текущее пространство имен в
self.namespace
конфигурация пространства имен (экземпляр FaqConfig) в
self.config
текущее приложение в
current_app parameter
, переданное классуResponse
В данном случае мы фильтруем, чтобы показать только записи, назначенные текущему пространству имен в get_queryset
. qs.namespace
, благодаря менеджеру моделей, который мы определили ранее, является эквивалентом qs.filter(app_config__namespace=self.namespace)
.
В get_paginate_by
мы используем значение из нашей модели appconfig.
Определите шаблон¶
В faq/templates/faq/index.html
:
{% extends 'base.html' %}
{% block content %}
<h1>{{ view.config.title }}</h1>
<p>Namespace: {{ view.namespace }}</p>
<dl>
{% for entry in object_list %}
<dt>{{ entry.question }}</dt>
<dd>{{ entry.answer }}</dd>
{% endfor %}
</dl>
{% if is_paginated %}
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% else %}
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% else %}
next
{% endif %}
</span>
</div>
{% endif %}
{% endblock %}
URLconf¶
urls.py
:
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r'^$', views.IndexView.as_view(), name='index'),
]
Соедините все вместе¶
Наконец, мы добавляем faq
к INSTALLED_APPS
, затем создаем и запускаем миграции:
python manage.py makemigrations faq
python manage.py migrate faq
Теперь все должно быть готово.
Создайте две страницы с помощью apphook faq
(не забудьте опубликовать их), с разными пространствами имен и разными конфигурациями. Также создайте несколько записей, назначенных двум пространствам имен.
Вы можете экспериментировать с различными конфигурациями поведения (в данном случае доступна только пагинация), а также с тем, как различные экземпляры Entry
могут быть связаны с определенным apphook.