Signals¶
djangocms-versioning sends two signals around every version state change, so you can hook in caching, search indexing, notifications, logging and similar side effects.
For worked examples, see React to version changes with signals.
Available Signals¶
- pre_version_operation¶
Sent before a version state change occurs.
Signal sender: The content model class (e.g.,
PostContent)
- post_version_operation¶
Sent after a version state change has been completed successfully.
Signal sender: The content model class (e.g.,
PostContent)
Signal Parameters¶
Both signals emit the following keyword arguments:
Parameter |
Description |
Type |
|---|---|---|
|
The content model class (e.g., PostContent, PageContent) |
Model class |
|
The Version instance being operated on |
Version |
|
The type of operation being performed |
str (see Operations below) |
|
A unique token to tie pre and post signals together |
str (UUID) |
|
(For publish operations) List of versions that will be unpublished |
list of Version objects |
|
(For unpublish operations) List of versions that will be published as replacements |
list of Version objects |
Version Operations¶
The operation parameter can have one of these values (from djangocms_versioning.constants):
Operation Constant |
Description |
|---|---|
|
A new draft version has been created (or version moved to draft state) |
|
A draft version has been published |
|
A published version has been unpublished |
|
A draft version has been archived |
Signal Token¶
Each emission includes a unique token that ties the matching pre and post
signals together. Use it to stash state in the pre handler and retrieve it in the
post handler — for example to correlate the two halves of an operation in logs, to
time it, or to carry forward identifiers without holding a reference to the Version:
signal_state = {}
@receiver(pre_version_operation)
def before_version_change(sender, obj, operation, token, **kwargs):
signal_state[token] = {
"start_time": timezone.now(),
"operation": operation,
"version_id": obj.pk,
}
@receiver(post_version_operation)
def after_version_change(sender, obj, operation, token, **kwargs):
state = signal_state.pop(token, {})
duration = timezone.now() - state.get("start_time", timezone.now())
print(f"Operation {operation} took {duration.total_seconds()}s")
Signal Execution Order¶
A first-time publish (nothing to replace) emits just two signals —
pre_version_operation then post_version_operation, both with
operation = OPERATION_PUBLISH.
When the publish replaces an already-published version, that version is
unpublished as part of the same operation, so four signals fire. The new
version is published first; the old one is then unpublished; the post publish
signal comes last:
pre_version_operation—OPERATION_PUBLISH,obj= the new versionThe new version transitions
draft→publishedpre_version_operation—OPERATION_UNPUBLISH,obj= the old version, withto_be_published= the new versionThe old version transitions
published→unpublishedpost_version_operation—OPERATION_UNPUBLISH,obj= the old version, withto_be_published= the new versionpost_version_operation—OPERATION_PUBLISH,obj= the new version, withunpublished=[old version]
(The pre/post distinction in step labels is the signal; steps 2 and 4
are state transitions that emit no signal of their own.)
Note
This exact sequence is pinned by the test
tests/test_signals.py::TestVersioningSignals.test_publish_signals_fired_with_to_be_published_and_unpublished,
so any change to the emission order will fail the test suite.