Skip to content

Commit

Permalink
Merge pull request #40 from druids/CreatedDispatcher
Browse files Browse the repository at this point in the history
Added CreatedDispatcher, refactoring of dispatchers
  • Loading branch information
asgeirrr authored Feb 6, 2017
2 parents beb7e43 + f09e1c5 commit bda756a
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 17 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def send_email(user):
```

#### 4.1 Property Dispatcher
`chamber.models.dispatchers.StateDispatcher` is a versatile dispatcher that fires the given handler when a specified property of the model evaluates to `True`.
`chamber.models.dispatchers.PropertyDispatcher` is a versatile dispatcher that fires the given handler when a specified property of the model evaluates to `True`.

The example shows how to to register the aforementioned `send_email` handler to be dispatched after saving the object if the property `should_send_email` returns `True`.
```python
Expand Down Expand Up @@ -129,6 +129,22 @@ class MySmartModel(chamber_models.SmartModel):
)
```

#### 4.2 State Dispatcher
A common use-case is to perform an action whenever an instance of a particular model is created. `chamber.models.dispatchers.CreatedDispatcher` is provided to accommodate this need. An update of the instance will not trigger the handler.

```python
def my_handler(my_smart_model):
# Do that useful stuff when a new instance of MySmartModel is created
pass


class MySmartModel(chamber_models.SmartModel):

post_save_dispatchers = (
CreatedDispatcher(my_handler),
)
```

### Utils

#### `chamber.utils.remove_accent`
Expand Down
8 changes: 4 additions & 4 deletions chamber/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,10 @@ def _get_save_extra_kwargs(self):
def _pre_save(self, *args, **kwargs):
pass

def _call_dispatcher_group(self, group_name, changed_fields, *args, **kwargs):
def _call_dispatcher_group(self, group_name, change, changed_fields, *args, **kwargs):
if hasattr(self, group_name):
for dispatcher in getattr(self, group_name):
dispatcher(self, changed_fields, *args, **kwargs)
dispatcher(self, change, changed_fields, *args, **kwargs)

def _save(self, is_cleaned_pre_save=None, is_cleaned_post_save=None, force_insert=False, force_update=False,
using=None, update_fields=None, *args, **kwargs):
Expand All @@ -256,15 +256,15 @@ def _save(self, is_cleaned_pre_save=None, is_cleaned_post_save=None, force_inser
kwargs.update(self._get_save_extra_kwargs())

self._pre_save(change, self.changed_fields, *args, **kwargs)
self._call_dispatcher_group('pre_save_dispatchers', self.changed_fields, *args, **kwargs)
self._call_dispatcher_group('pre_save_dispatchers', change, self.changed_fields, *args, **kwargs)

if is_cleaned_pre_save:
self._clean_pre_save(*args, **kwargs)

super(SmartModel, self).save(force_insert=force_insert, force_update=force_update, using=using,
update_fields=update_fields)
self._post_save(change, self.changed_fields, *args, **kwargs)
self._call_dispatcher_group('post_save_dispatchers', self.changed_fields, *args, **kwargs)
self._call_dispatcher_group('post_save_dispatchers', change, self.changed_fields, *args, **kwargs)

if is_cleaned_post_save:
self._clean_post_save(*args, **kwargs)
Expand Down
22 changes: 13 additions & 9 deletions chamber/models/dispatchers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import unicode_literals

import inspect

from django.core.exceptions import ImproperlyConfigured


Expand All @@ -11,7 +9,8 @@ class BaseDispatcher(object):
If you subclass, be sure the __call__ method does not change signature.
"""
def _validate_init_params(self):
raise NotImplementedError
if not callable(self.handler):
raise ImproperlyConfigured('Registered handler must be a callable.')

def __init__(self, handler, *args, **kwargs):
self.handler = handler
Expand Down Expand Up @@ -50,19 +49,24 @@ def _can_dispatch(self, obj, *args, **kwargs):
return getattr(obj, self.property_name)


class CreatedDispatcher(BaseDispatcher):
"""
Calls registered handler if and only if an instance of the model is being created.
"""

def _can_dispatch(self, obj, change, *args, **kwargs):
return not change


class StateDispatcher(BaseDispatcher):

"""
Use this class to register a handler for transition of a model to a certain state.
"""
def _validate_init_params(self):
super(StateDispatcher, self)._validate_init_params()
if self.field_value not in {value for value, _ in self.enum.choices}:
raise ImproperlyConfigured('Enum of FieldDispatcher does not contain {}.'.format(self.field_value))
if not hasattr(self.handler, '__call__'):
raise ImproperlyConfigured('Handler of FieldDispatcher must be callable.')
if (len(inspect.getargspec(self.handler).args) != 1 or # pylint: disable=W1505
inspect.getargspec(self.handler).keywords): # pylint: disable=W1505
raise ImproperlyConfigured('Handler of FieldDispatcher must be a function of one parameter.')

def __init__(self, handler, enum, field, field_value):
self.enum = enum
Expand All @@ -71,5 +75,5 @@ def __init__(self, handler, enum, field, field_value):

super(StateDispatcher, self).__init__(handler, enum, field, field_value)

def _can_dispatch(self, obj, changed_fields, *args, **kwargs):
def _can_dispatch(self, obj, change, changed_fields, *args, **kwargs):
return self.field.get_attname() in changed_fields and getattr(obj, self.field.get_attname()) == self.field_value
6 changes: 6 additions & 0 deletions example/dj/apps/test_chamber/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ def create_test_dispatchers_model_handler(obj):
from .models import TestDispatchersModel # NOQA

TestDispatchersModel.objects.create()


def create_csv_record_handler(obj):
from .models import CSVRecord # NOQA

CSVRecord.objects.create()
5 changes: 3 additions & 2 deletions example/dj/apps/test_chamber/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

from chamber import models as chamber_models
from chamber.models import fields as chamber_fields
from chamber.models.dispatchers import PropertyDispatcher, StateDispatcher
from chamber.models.dispatchers import CreatedDispatcher, PropertyDispatcher, StateDispatcher
from chamber.utils.datastructures import ChoicesNumEnum, SequenceChoicesNumEnum, SubstatesChoicesNumEnum

from .handlers import (create_test_dispatchers_model_handler, create_test_fields_model_handler,
create_test_smart_model_handler)
create_test_smart_model_handler, create_csv_record_handler)


class ShortcutsModel(models.Model):
Expand Down Expand Up @@ -95,6 +95,7 @@ class TestDispatchersModel(chamber_models.SmartModel):
state = models.IntegerField(null=True, blank=False, choices=STATE.choices, default=STATE.FIRST)

pre_save_dispatchers = (
CreatedDispatcher(create_csv_record_handler, STATE, state, STATE.SECOND),
StateDispatcher(create_test_smart_model_handler, STATE, state, STATE.SECOND),
)

Expand Down
10 changes: 9 additions & 1 deletion example/dj/apps/test_chamber/tests/models/dispatchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

from germanium.tools import assert_equal # pylint: disable=E0401

from test_chamber.models import TestDispatchersModel, TestFieldsModel, TestSmartModel # pylint: disable=E0401
from test_chamber.models import (CSVRecord, TestDispatchersModel, TestFieldsModel, # pylint: disable=E0401
TestSmartModel) # pylint: disable=E0401


class DispatchersTestCase(TransactionTestCase):
Expand Down Expand Up @@ -34,3 +35,10 @@ def test_property_dispatcher(self):
TestDispatchersModel.objects.create()
assert_equal(TestFieldsModel.objects.count(), 1)
assert_equal(TestDispatchersModel.objects.count(), 1)

def test_created_dispatcher(self):
assert_equal(CSVRecord.objects.count(), 0)
m = TestDispatchersModel.objects.create()
assert_equal(CSVRecord.objects.count(), 1)
change_and_save(m, state=TestDispatchersModel.STATE.SECOND)
assert_equal(CSVRecord.objects.count(), 1)

0 comments on commit bda756a

Please sign in to comment.