Adding copy_relations outside of plugin using signals in django cms
July 6, 2021, 8:59 a.m.
In django-cms, it is sometimes necessary to add or extend copy_relations()
in a third party plugin model to handle foreign keys: ForeignKey
, OneToOneField
или ManyToManyField
.
For example, there is a certain third-party application (cms plugin) text_block
with the TextBlock
model:
# models.py class TextBlock(CMSPlugin): title = models.CharField(_('Title'), max_length=255) content = models.TextField(_('Content')) # cms_plugins.py class TextBlockPlugin(CMSPluginBase): model = TextBlock name = 'TextBlock' render_template = 'text_block.html'
And we need to add some extra field, for example, description
.
To do this, add a new application to the project, for example, text_block_ext
with the TextBlockExt
model:
class TextBlockExt(models.Model): text_block = models.OneToOneField(TextBlock, on_delete=models.CASCADE) description = models.TextField(_('Description'))
Ext
(in TextBlockExt
) - is an abbreviation for Extension, which is what I usually call models that extend someone's functionality.
Now we need to add the description
field to the TextBlock
form. This can be done by adding an inline admin class object for the TextBlock
model and re-registering the admin class in the cms_plugins.py
:
from text_block.cms_plugins import TextBlockPlugin as BaseTextBlockPlugin from text_block_ext.models import TextBlockExt plugin_pool.unregister_plugin(BaseTextBlockPlugin) class TextBlockExtInline(admin.StackedInline): model = TextBlockExt extra = 1 can_delete = False verbose_name = _('Additional options') verbose_name_plural = _('Additional options') @plugin_pool.register_plugin class TextBlockPlugin(BaseTextBlockPlugin): render_template = 'text_block_ext.html' inlines = (TextBlockExtInline, )
When adding / editing the TextBlock
plugin, now we will see our inline object with the description
field. By entering some value and saving the object, we will see that our template produces the saved data, for example, in a template like this:
<div class="text_block"> <h2>{{ instance.title }}</h2> {% instance.textblockext.description %}<div>Description = {{ instance.textblockext.description }}</div>{% endif %} <div>{{ instance.content }}</div> </div>
But the problem is that when the page is published, we will not see the saved description
field, since django-cms does not process foreign keys when publishing. In the usual way, this is done by adding copy_relations()
to the plugin model, but since we have a third-party plugin, we need to somehow handle foreign keys.
Adding the post_publish signal to our application
Fortunately, django-cms provides a post_publish
signal where we can get to our published component and add the foreign key TextBlockExt
. Add a signal receiver to text_block_ext/signals.py
, where we will process foreign keys:
from django.dispatch import receiver from cms.signals import post_publish from text_block.models import TextBlock @receiver(post_publish) def copy_relations(sender, instance, language, **kwargs): published_text_blocks = TextBlock.objects.filter(placeholder__page=instance.publisher_public) for published_text_block in published_text_blocks: text_block_parent = TextBlock.objects.get(id=published_text_block.parent_instance_id) if hasattr(text_block_parent, 'textblockext'): text_block_ext = text_block_parent.textblockext text_block_ext.id = None published_text_block.textblockext = text_block_ext published_text_block.textblockext.save()
The published plugin has a parent_instance_id
property through which you can get the unpublished plugin text_block_parent
: TextBlock.objects.get(id=published_text_block.parent_instance_id)
. To copy text_block_ext
, you just need to assign id
to None
. And then we assign the copied text_block_ext
to published_text_block.textblockext
.
Now, after the page is published, the published plugins will receive a text_block_ext
.
The post_publish
signal is not limited to adding custom copy_relations()
for third-party models. In the signal, you can perform many different functions useful for the site, for example, generate a fresh version of sitemap.xml
, notify the chief editor of the site that the site has been published or update statistics on published pages, etc.
Comments: 0