Недавно уже было описано, как создать свой плагин для CMS. И упомянуто, что у плагина могут быть настройки. Поскольку эти настройки хранятся в самой обыкновенной джанговской модели, в них и может быть размещено всё, что может быть размещено в модели. Но если с полями вроде CharField
или TextField
работать очень просто, то бывают и менее очевидные ситуации.
Наример, плагин должен отображать контактную информацию компании, включая адрес, телефон, чего-там-ещё-придумают, и карту. Примерно так:
Точка на карте должна устанавливаться через настройки плагина:
А сами контактные данные должны быть набором пар ключ-значение, при этом их должно быть произвольное количество. Мало ли, завтра компания откроет аккаунт в фэйсбуке и сочтёт нужным указать его в этом списке. Нужно будет добавить новую пару, не меняя код. Самым удобным будет нечто вроде инлайнов в админке:
Интерфейс редактирования плагинов тесно связан с админкой, это как раз и поможет.
Карта
В данном случае модель настройки плагина будет содержать одно поле — выбор точки на карте. Несмотря на то, что задача кажется весьма тривиальной, частой, обыденной, нормального готового решения под эту задачу мне найти не удалось. Каждое из множества решений, представленных на djangopackages, обладает какими-либо недостатками. Но своё писать — как из пушки по воробьям. В моём случае применено это решение. После лёгкого допила его можно использовать с python3. Весь код настроек плагина уложился в пару строк:
from addresspicker.fields import AddressPickerField
class MapPluginSettings(CMSPlugin):
address = AddressPickerField("Расположение на карте")
Связанная модель
Здесь всё несколько мудрёнее.
Начало простое. Всё крайне очевидно:
class ContactPluginSettings(CMSPlugin):
title = models.CharField("заголовок", max_length=32)
class ContactPluginDataSettings(models.Model):
plugin = models.ForeignKey(ContactPluginSettings, related_name="contact_data")
key = models.CharField("название свойства", max_length=64)
value = models.TextField("значение свойства", max_length=512)
ordering = models.IntegerField("сортировка", default=100)
def __str__(self):
return "{}: {}".format(self.key, self.value)
class Meta:
ordering = ['ordering']
verbose_name = "клочок инфы"
verbose_name_plural = "контактная информация"
Но теперь нужно вынести связанную модель в интерфейс редактирования. Для этого воспользуемся классами из django.contrib.admin
:
from django.contrib import admin
class ItemInlineAdmin(admin.StackedInline):
model = ContactPluginDataSettings
class ContactPlugin(CMSPluginBase):
model = ContactPluginSettings
inlines = [ItemInlineAdmin]
После этого окно редактирования настроек будет выглядеть как на скриншоте выше. И работать будет. Данные будут сохраняться. А вот при попытке опубликовать изменения случится неудача.
Дело в том, что в Django-CMS каждая страница существует в двух экземплярах — опубликованная и черновик. Всё редактирование происходит на черновике. При публикации черновик копируется в чистовую версию. А вот как раз связанные свойства по умолчанию не копируются. И это нужно сделать вручную. В модели плагина нужно определить метод copy_relations()
. В нашем случае так:
class ContactPluginSettings(CMSPlugin):
title = models.CharField("заголовок", max_length=32)
def copy_relations(self, oldinstance):
for c in oldinstance.contact_data.all():
c.pk = None
c.plugin = self
c.save()
В шаблоне плагина связанные данные можно использовать так же, как и в любых других шаблонах:
<dl class="contact_data">
{% for item in instance.contact_data.all %}
<dt>{{ item.key }}</dt>
<dd>{{ item.value|safe }}</dd>
{% endfor %}
</dl>