Как расширить модели Page и Title¶
Вы можете расширить модели cms.models.Page
и cms.models.Title
своими собственными полями (например, добавить иконку для каждой страницы), используя модели расширения: cms.extensions.PageExtension
и cms.extensions.TitleExtension
, соответственно.
Заголовок в сравнении с расширением страницы¶
Разница между расширением страницы и расширением заголовка связана с разницей между моделями cms.models.Page
и cms.models.Title
.
PageExtension
: используется для добавления полей, которые должны иметь одинаковые значения для разных языковых версий страницы - например, иконка.TitleExtension
: используется для добавления полей, которые должны иметь языковые значения для различных языковых версий страницы - например, ключевые слова.
Реализовать базовое расширение¶
Необходимо выполнить три основных действия:
добавить расширение модель
добавить расширение admin
добавить пункт меню панели инструментов для расширения
Пример расширения модели страницы¶
Модель¶
Чтобы добавить поле в модель Page, создайте класс, который наследуется от cms.extensions.PageExtension
. Ваш класс должен находиться в одном из models.py
(или модуле) вашего приложения.
Примечание
Поскольку PageExtension
(и TitleExtension
) наследуются от django.db.models.Model
, вы можете добавлять любое поле, но убедитесь, что вы не используете уникальное ограничение для любого из ваших добавленных полей, поскольку уникальность препятствует корректной работе механизма копирования расширения. Это означает, что вы не можете использовать отношения один-к-одному в модели расширения.
Наконец, вам нужно зарегистрировать модель с помощью extension_pool
.
Вот простой пример, который добавляет поле icon
на страницу:
from django.db import models
from cms.extensions import PageExtension
from cms.extensions.extension_pool import extension_pool
class IconExtension(PageExtension):
image = models.ImageField(upload_to='icons')
extension_pool.register(IconExtension)
Конечно, вам нужно будет сделать и запустить миграцию для этой новой модели.
Администратор¶
Чтобы сделать ваше расширение редактируемым, вы должны сначала создать класс admin, который является подклассом cms.extensions.PageExtensionAdmin
. Этот администратор управляет правами доступа к страницам.
Примечание
Если вы хотите использовать собственный класс администратора, убедитесь, что вы исключили живые версии расширений, используя filter(extended_object__publisher_is_draft=True)
в наборе запросов.
Продолжая пример модели выше, вот простой соответствующий PageExtensionAdmin
класс:
from django.contrib import admin
from cms.extensions import PageExtensionAdmin
from .models import IconExtension
class IconExtensionAdmin(PageExtensionAdmin):
pass
admin.site.register(IconExtension, IconExtensionAdmin)
Поскольку PageExtensionAdmin наследуется от ModelAdmin
, вы сможете использовать обычный набор свойств Django ModelAdmin
, соответствующий вашим потребностям.
Примечание
Обратите внимание, что поле, в котором указывается связь между расширением и страницей CMS, является нередактируемым, поэтому оно не отображается непосредственно в представлениях администратора страницы. Это может быть решено в будущем обновлении, а пока панель инструментов обеспечивает доступ к нему.
Элемент панели инструментов¶
Вы также захотите сделать вашу модель редактируемой из панели инструментов cms, чтобы связать каждый экземпляр модели расширения со страницей.
Чтобы добавить элементы панели инструментов для вашего расширения, создайте файл с именем cms_toolbars.py
в одном из ваших приложений и добавьте соответствующие пункты меню для расширения на каждой странице.
Вот простая версия для нашего примера. Этот пример добавляет узел к существующему меню Страница, называемый Иконка страницы. При его выборе открывается модальный диалог, в котором можно отредактировать поле Иконка страницы.
from cms.toolbar_pool import toolbar_pool
from cms.extensions.toolbar import ExtensionToolbar
from django.utils.translation import gettext_lazy as _
from .models import IconExtension
@toolbar_pool.register
class IconExtensionToolbar(ExtensionToolbar):
# defines the model for the current toolbar
model = IconExtension
def populate(self):
# setup the extension toolbar with permissions and sanity checks
current_page_menu = self._setup_extension_toolbar()
# if it's all ok
if current_page_menu:
# retrieves the instance of the current extension (if any) and the toolbar item URL
page_extension, url = self.get_page_extension_admin()
if url:
# adds a toolbar item in position 0 (at the top of the menu)
current_page_menu.add_modal_item(_('Page Icon'), url=url,
disabled=not self.toolbar.edit_mode_active, position=0)
Пример расширения модели титула¶
В этом примере мы создадим поле расширения Rating
, которое может быть применено к каждому Title
, другими словами, к каждой языковой версии каждого Page
.
Примечание
Пожалуйста, обратитесь к более подробному обсуждению примера расширения модели Page выше и, в частности, к специальным примечаниям.
Модель¶
from django.db import models
from cms.extensions import TitleExtension
from cms.extensions.extension_pool import extension_pool
class RatingExtension(TitleExtension):
rating = models.IntegerField()
extension_pool.register(RatingExtension)
Администратор¶
from django.contrib import admin
from cms.extensions import TitleExtensionAdmin
from .models import RatingExtension
class RatingExtensionAdmin(TitleExtensionAdmin):
pass
admin.site.register(RatingExtension, RatingExtensionAdmin)
Элемент панели инструментов¶
В данном примере нам нужно перебрать заголовки для страницы и заполнить ими меню.
from cms.toolbar_pool import toolbar_pool
from cms.extensions.toolbar import ExtensionToolbar
from django.utils.translation import gettext_lazy as _
from .models import RatingExtension
from cms.utils import get_language_list # needed to get the page's languages
@toolbar_pool.register
class RatingExtensionToolbar(ExtensionToolbar):
# defines the model for the current toolbar
model = RatingExtension
def populate(self):
# setup the extension toolbar with permissions and sanity checks
current_page_menu = self._setup_extension_toolbar()
# if it's all ok
if current_page_menu and self.toolbar.edit_mode_active:
# create a sub menu labelled "Ratings" at position 1 in the menu
sub_menu = self._get_sub_menu(
current_page_menu, 'submenu_label', 'Ratings', position=1
)
# retrieves the instances of the current title extension (if any)
# and the toolbar item URL
urls = self.get_title_extension_admin()
# we now also need to get the titleset (i.e. different language titles)
# for this page
page = self._get_page()
titleset = page.title_set.filter(language__in=get_language_list(page.node.site_id))
# create a 3-tuple of (title_extension, url, title)
nodes = [(title_extension, url, title.title) for (
(title_extension, url), title) in zip(urls, titleset)
]
# cycle through the list of nodes
for title_extension, url, title in nodes:
# adds toolbar items
sub_menu.add_modal_item(
'Rate %s' % title, url=url, disabled=not self.toolbar.edit_mode_active
)
Использование расширений¶
В шаблонах¶
Для доступа к расширению страницы в шаблонах страниц вы можете просто обратиться к соответствующему полю related_name, которое теперь доступно на объекте Page.
Расширения страниц¶
В соответствии с обычным механизмом именования related_name, соответствующее поле для доступа будет таким же, как и имя вашей модели PageExtension
, но в нижнем регистре. Если предположить, что класс вашей модели Page Extension - IconExtension
, то связь с моделью расширения страницы будет доступна в page.iconextension
. Отсюда вы можете получить доступ к дополнительным полям, которые вы определили в своем расширении, поэтому вы можете использовать что-то вроде:
{% load static %}
{# rest of template omitted ... #}
{% if request.current_page.iconextension %}
<img src="{% static request.current_page.iconextension.image.url %}">
{% endif %}
где request.current_page
- это обычный способ доступа к текущей странице, которая рендерит шаблон.
Важно помнить, что если оператор не назначил расширение страницы для каждой страницы, страница может не иметь отношения iconextension
, поэтому выше используется {% if ... %}...{% endif %}
.
Расширения названия¶
Чтобы получить расширение заголовка в шаблоне, получите объект Title
с помощью request.current_page.get_title_obj
. Используя приведенный выше пример, мы могли бы использовать:
{{ request.current_page.get_title_obj.ratingextension.rating }}
Работа с отношениями¶
Если ваш PageExtension
или TitleExtension
включает ForeignKey из другой модели или включает ManyToManyField, вы также должны переопределить метод copy_relations(self, oldinstance, language)
, чтобы эти поля копировались соответствующим образом, когда CMS делает копию вашего расширения для поддержки версионности и т.д.
Вот пример, в котором используется ManyToManyField
:
from django.db import models
from cms.extensions import PageExtension
from cms.extensions.extension_pool import extension_pool
class MyPageExtension(PageExtension):
page_categories = models.ManyToManyField(Category, blank=True)
def copy_relations(self, oldinstance, language):
for page_category in oldinstance.page_categories.all():
page_category.pk = None
page_category.mypageextension = self
page_category.save()
extension_pool.register(MyPageExtension)
Полный API панели инструментов¶
В приведенном выше примере используется Упрощенный API панели инструментов.
Если вам необходим полный контроль над расположением элементов панели инструментов расширения, вы можете использовать низкоуровневый API для редактирования панели инструментов в соответствии с вашими потребностями:
from cms.api import get_page_draft
from cms.toolbar_pool import toolbar_pool
from cms.toolbar_base import CMSToolbar
from cms.utils import get_cms_setting
from cms.utils.page_permissions import user_can_change_page
from django.urls import reverse, NoReverseMatch
from django.utils.translation import gettext_lazy as _
from .models import IconExtension
@toolbar_pool.register
class IconExtensionToolbar(CMSToolbar):
def populate(self):
# always use draft if we have a page
self.page = get_page_draft(self.request.current_page)
if not self.page:
# Nothing to do
return
if user_can_change_page(user=self.request.user, page=self.page):
try:
icon_extension = IconExtension.objects.get(extended_object_id=self.page.id)
except IconExtension.DoesNotExist:
icon_extension = None
try:
if icon_extension:
url = reverse('admin:myapp_iconextension_change', args=(icon_extension.pk,))
else:
url = reverse('admin:myapp_iconextension_add') + '?extended_object=%s' % self.page.pk
except NoReverseMatch:
# not in urls
pass
else:
not_edit_mode = not self.toolbar.edit_mode_active
current_page_menu = self.toolbar.get_or_create_menu('page')
current_page_menu.add_modal_item(_('Page Icon'), url=url, disabled=not_edit_mode)
Теперь, когда оператор вызывает «Редактировать эту страницу…» из панели инструментов, появится дополнительный пункт меню Page Icon ...
(в данном случае), который можно использовать для открытия модального диалога, где оператор может воздействовать на новое поле icon
.
Обратите внимание, что при сохранении расширения соответствующая страница помечается как имеющая неопубликованные изменения. Чтобы увидеть новые значения расширения, опубликуйте страницу.
Упрощенный API панели инструментов¶
Упрощенный API панели инструментов работает путем создания класса панели инструментов на основе ExtensionToolbar
, который предоставляет следующий API:
ExtensionToolbar.get_page_extension_admin()
: для расширений страниц, извлекает правильный URL администратора для соответствующего элемента панели инструментов; возвращает экземпляр расширения (илиNone
, если его не существует) и URL администратора для элемента панели инструментовExtensionToolbar.get_title_extension_admin()
: для расширений заголовков, извлекает правильный URL администратора для соответствующего элемента панели инструментов; возвращает список экземпляров расширений (илиNone
, если таковых не существует) и URL администратора для каждого заголовка текущей страницы