Description
I frequently come across the case where it would be beneficial to be able to pass extra context through a model's .save()
method to my lifecycle hooks, i.e.,
# It would be helpful if the following call to `.save()` could (optionally)
# pass `a` and `b` through to the lifecycle hooks if they accept arguments
# in their function signature (in some way).
my_model.save(a=123, b=456)
To give a more concrete example, if we take the code snippet in django-lifecycle
's README.md
, and say that we now have the new requirement of not sending the email if the editor themself publishes the article.
from django_lifecycle import LifecycleModel, hook, BEFORE_UPDATE, AFTER_UPDATE
class Article(LifecycleModel):
contents = models.TextField()
updated_at = models.DateTimeField(null=True)
status = models.ChoiceField(choices=['draft', 'published'])
editor = models.ForeignKey(AuthUser)
@hook(BEFORE_UPDATE, WhenFieldHasChanged("contents", has_changed=True))
def on_content_change(self):
self.updated_at = timezone.now()
@hook(
AFTER_UPDATE,
condition=(
WhenFieldValueWas("status", value="draft")
& WhenFieldValueIs("status", value="published")
)
)
def on_publish(self, **context):
# Contrived example!
# Only send an email if the actioning user is not the editor
if context.get("actioning_user") != self.editor:
send_email(self.editor.email, "An article has published!")
Publishing an article may then look something like:
me = User.objects.get(pk=...)
article = Article.objects.get(pk=...)
article.contents = "..."
article.status = "published"
article.save(actioning_user=me)
Obviously this is a bit of a contrived example, as we would probably just have an Article.publish()
method that requires an actioning_user
, but I do think in general there are cases where allowing extra context makes sense - e.g., when arbitrary updates are made to a record in a given context (such as the request / response cycle), and we want lifecycle hooks to behave differently depending on said context.
I imagine this functionality could be implemented in various (possibly even backwards-compatible) ways, such as:
- Inspecting lifecycle hooks' function signatures to see if they accept
**kwargs
? - Inspecting lifecycle hooks' function signatures to see specifically what they expect?
- Require expected context to be specified in
@hook()
in some way?- e.g.,
@hook(AFTER_UPDATE, condition=..., required_context=["a", "b"])
- e.g.,
If a feature like this were to go any further, then there are probably also edge-cases to consider, such as:
- What if the required context is not passed in?
- Raise an exception?
- Skip the hook?
- Configurable behaviour?
The implementation details above haven't really been fleshed out, but in general I wanted to suggest and gauge the interest for allowing extra context in lifecycle hooks.