Opened 11 years ago

Closed 11 years ago

Last modified 9 years ago

#421 closed enhancement (fixed)

[patch] new events framework

Reported by: maurycypw@… Owned by: Adrian Holovaty
Component: Core (Other) Version:
Severity: normal Keywords:
Cc: wojtek@… Triage Stage: Design decision needed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:


This patch rewrites events framework to the implementation. Instead of old-fashioned hooks, it introduces new term listeners watching for events.

API is quite simple:

>>> from django.core import events
>>> def example_listener(event, param=None):
...     print '%s -- %s' % (event, param)
>>> events.add_listener('example_event', example_listener)
>>> events.propagate_event('example_event', param='Greets to Brandlay Group')
example_event -- Greets to Brandlay Group
>>> events.delete_listener('example_event', example_listener)
>>> events.propagate_event('example_event', param='Greets to Brandlay Group')

Actual hooks _pre_save, _post_save, _pre_delete and _post_delete are rewritten to be 'pre_save_%s_%s' % (app_label, module_name), 'post_save_%s_%s' % (app_label, module_name) etc.

There are few advantages from new events framework over the current:

  • Hooking all objects, not only models. Currently implementation rely only on model events, like _pre_save, _post_save, _post_delete and other. As I've pointed in #364, there's a lot of scenarios when you want to listen on events not related to models.
  • Queueing listeners. Now you can add only one listener on event. With new events framework it's possible to call add_listener many times adding as many listeners, as you need.
  • Adding and deleting listeners at run-time explicitly. You don't have to add new methods to the models. You can call add_listener at the every line of your code, without need to move objects between layers.


--- django_src/django/core/    1970-01-01 01:00:00.000000000 +0100
+++     2005-08-26 17:40:20.000000000 +0200
@@ -0,0 +1,19 @@
+"Events framework."
+events = {}
+def add_listener(event, listener):
+    if not events.has_key(event):
+        events[event] = []
+    events[event].append(listener)
+def delete_listener(event, listener=None):
+    if not listener:
+        events[event] = []
+    else:
+        events[event].remove(listener)
+def propagate_event(event, **kwargs):
+    for listener in events[event]:
+        listener(event, **kwargs)
Files django_src/django/core/events.pyc and differ
Files django_src/django/core/exceptions.pyc and differ
Files django_src/django/core/formfields.pyc and differ
Files django_src/django/core/__init__.pyc and differ
Files django_src/django/core/meta/fields.pyc and differ
diff -urN django_src/django/core/meta/
--- django_src/django/core/meta/     2005-08-26 15:03:29.000000000 +0200
+++      2005-08-26 16:01:42.000000000 +0200
@@ -766,9 +766,11 @@
     return isinstance(other, self.__class__) and getattr(self, == getattr(other,

 def method_save(opts, self):
+    from django.core import events
     # Run any pre-save hooks.
-    if hasattr(self, '_pre_save'):
-        self._pre_save()
+    events.propagate_event('pre_save_%s_%s' % (opts.app_label, opts.module_name), model=opts)
     non_pks = [f for f in opts.fields if not f.primary_key]
     cursor = db.db.cursor()

@@ -802,15 +804,18 @@
         if opts.has_auto_field:
             setattr(self,, db.get_last_insert_id(cursor, opts.db_table,

     # Run any post-save hooks.
-    if hasattr(self, '_post_save'):
-        self._post_save()
+    events.propagate_event('post_save_%s_%s' % (opts.app_label, opts.module_name), model=opts)

 def method_delete(opts, self):
+    from django.core import events
     assert getattr(self, is not None, "%r can't be deleted because it doesn't have an ID."
     # Run any pre-delete hooks.
-    if hasattr(self, '_pre_delete'):
-        self._pre_delete()
+    events.propagate_event('pre_delete_%s_%s' % (opts.app_label, opts.module_name), model=opts)
     cursor = db.db.cursor()
     for rel_opts, rel_field in opts.get_all_related_objects():
         rel_opts_name = opts.get_rel_object_method_name(rel_opts, rel_field)
@@ -840,9 +845,9 @@
             # delete it from the filesystem.
             if os.path.exists(file_name) and not opts.get_model_module().get_list(**{'%s__exact' % getattr(self,}):
     # Run any post-delete hooks.
-    if hasattr(self, '_post_delete'):
-        self._post_delete()
+    events.propagate_event('post_save_%s_%s' % (opts.app_label, opts.module_name), model=opts)

 def method_get_next_in_order(opts, order_field, self):
     if not hasattr(self, '_next_in_order_cache'):

Change History (4)

comment:1 Changed 11 years ago by maurycypw@…

Cc: wojtek@… added

comment:2 Changed 11 years ago by hugo

This could (should) be integrated with the magic removal branch - at least that introduces an event framework and if we want to change from using hooks to events, it should use that framework.

comment:3 Changed 11 years ago by jim-django@…

Using classes or class instances rather than strings automatically gives you the advantage of python namespacing, much like exceptions are generally classes rather than strings now.

Also, most event frameworks that I'm used to use "fire_event" rather than "propagate". I don't know if that's a good thing or not.

comment:4 Changed 11 years ago by Adrian Holovaty

Resolution: fixed
Status: newclosed

This is fixed in magic-removal.

Note: See TracTickets for help on using tickets.
Back to Top