Code

Opened 9 years ago

Closed 8 years ago

Last modified 7 years ago

#421 closed enhancement (fixed)

[patch] new events framework

Reported by: maurycypw@… Owned by: adrian
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:

Description

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'):

Attachments (0)

Change History (4)

comment:1 Changed 9 years ago by maurycypw@…

  • Cc wojtek@… added

comment:2 Changed 8 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 8 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 8 years ago by adrian

  • Resolution set to fixed
  • Status changed from new to closed

This is fixed in magic-removal.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.