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.

Rate this article

0 from 5 (total 0 ratings)

You can send feedback, suggestions or comments on this article using this form:

Fields marked by star ( * ) is required.

Thank you for yor feedback!

After clicking the "Send" button, your message will be delivered to me on the mail.

Author of Article

Artem Maltsev

Web-developer, having the knowlenge of programming language - Python, framework - Django, content management system - Django CMS, platform of e-commerce site - Django Shop and many other applications, using this technologies.

The right to use content on this page https://vivazzi.pro/it/copy-relations-signals/:

Permission is granted to copy an content with its author and reference to the original without using the parameter rel="nofollow" in tag <a>. Usage:

Author of Article: Artem Maltsev
Link to article: <a href="https://vivazzi.pro/it/copy-relations-signals/">https://vivazzi.pro/it/copy-relations-signals/</a>

More: Terms of site usage

Comments: 0

You can leave a comment as an unregistered user.

But if you sing up, you can:

  • receive notifications
  • view your comments
  • be able to use all the functions of the developed services

To comment in one's own name you should log in or sign up on Vuspace website

Send

There is no search on this site, so I offer to use usual search engine, for example, Google, adding "vivazzi" after your request.

Try it

Select currency for displaying monetary values