﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
421	[patch] new events framework	maurycypw@…	Adrian Holovaty	"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.

Patch:

{{{
--- django_src/django/core/events.py    1970-01-01 01:00:00.000000000 +0100
+++ django_src.events/django/core/events.py     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 django_src.events/django/core/events.pyc differ
Files django_src/django/core/exceptions.pyc and django_src.events/django/core/exceptions.pyc differ
Files django_src/django/core/formfields.pyc and django_src.events/django/core/formfields.pyc differ
Files django_src/django/core/__init__.pyc and django_src.events/django/core/__init__.pyc differ
Files django_src/django/core/meta/fields.pyc and django_src.events/django/core/meta/fields.pyc differ
diff -urN django_src/django/core/meta/__init__.py django_src.events/django/core/meta/__init__.py
--- django_src/django/core/meta/__init__.py     2005-08-26 15:03:29.000000000 +0200
+++ django_src.events/django/core/meta/__init__.py      2005-08-26 16:01:42.000000000 +0200
@@ -766,9 +766,11 @@
     return isinstance(other, self.__class__) and getattr(self, opts.pk.column) == getattr(other, opts.pk.column)

 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, opts.pk.column, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column))
     db.db.commit()

+
     # 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, opts.pk.column) 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' % f.name: getattr(self, f.name)}):
                 os.remove(file_name)
+
     # 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'):
}}}"	enhancement	closed	Core (Other)		normal	fixed		wojtek@…	Design decision needed	1	0	0	0	0	0
