-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added model dispatchers and SmartQuerySet
- Loading branch information
Showing
7 changed files
with
277 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
from __future__ import unicode_literals | ||
|
||
import inspect | ||
|
||
from django.core.exceptions import ImproperlyConfigured | ||
|
||
|
||
class BaseDispatcher(object): | ||
""" | ||
Base dispatcher class that can be subclassed to call a handler based on a change in some a SmartModel. | ||
If you subclass, be sure the __call__ method does not change signature. | ||
""" | ||
def _validate_init_params(self): | ||
raise NotImplementedError | ||
|
||
def __init__(self, handler, *args, **kwargs): | ||
self.handler = handler | ||
self._validate_init_params() | ||
|
||
def __call__(self, obj, *args, **kwargs): | ||
""" | ||
`obj` ... instance of the SmartModel where the handler is being called | ||
Some dispatchers require additional params to evaluate the handler can be dispatched, | ||
these are hidden in args and kwargs. | ||
""" | ||
if self._can_dispatch(obj, *args, **kwargs): | ||
self.handler(obj) | ||
|
||
def _can_dispatch(self, obj, *args, **kwargs): | ||
raise NotImplementedError | ||
|
||
|
||
class PropertyDispatcher(BaseDispatcher): | ||
""" | ||
Use this class to register a handler to dispatch during save if the given property evaluates to True. | ||
""" | ||
|
||
def _validate_init_params(self): | ||
""" | ||
No validation is done as it would require to pass the whole model to the dispatcher. | ||
If the property is not defined, a clear error is shown at runtime. | ||
""" | ||
pass | ||
|
||
def __init__(self, handler, property_name): | ||
self.property_name = property_name | ||
super(PropertyDispatcher, self).__init__(handler, property_name) | ||
|
||
def _can_dispatch(self, obj, *args, **kwargs): | ||
return getattr(obj, self.property_name) | ||
|
||
|
||
class StateDispatcher(BaseDispatcher): | ||
|
||
""" | ||
Use this class to register a handler for transition of a model to a certain state. | ||
""" | ||
def _validate_init_params(self): | ||
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 | ||
self.field = field | ||
self.field_value = field_value | ||
|
||
super(StateDispatcher, self).__init__(handler, enum, field, field_value) | ||
|
||
def _can_dispatch(self, obj, changed_fields, *args, **kwargs): | ||
return self.field.get_attname() in changed_fields and getattr(obj, self.field.get_attname()) == self.field_value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from __future__ import unicode_literals | ||
|
||
|
||
def create_test_smart_model_handler(obj): | ||
from .models import TestSmartModel | ||
|
||
TestSmartModel.objects.create(name='name') | ||
|
||
|
||
def create_test_fields_model_handler(obj): | ||
from .models import TestFieldsModel | ||
|
||
TestFieldsModel.objects.create() | ||
|
||
|
||
def create_test_dispatchers_model_handler(obj): | ||
from .models import TestDispatchersModel | ||
|
||
TestDispatchersModel.objects.create() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from __future__ import unicode_literals | ||
|
||
from django.test import TransactionTestCase | ||
|
||
from chamber.shortcuts import change_and_save | ||
|
||
from germanium.tools import assert_equal # pylint: disable=E0401 | ||
|
||
from test_chamber.models import TestDispatchersModel, TestFieldsModel, TestSmartModel # pylint: disable=E0401 | ||
|
||
|
||
class DispatchersTestCase(TransactionTestCase): | ||
|
||
def test_state_dispatcher(self): | ||
m = TestDispatchersModel.objects.create() | ||
|
||
# Moving TestDispatcher model to SECOND state should create new TestSmartModel instance | ||
assert_equal(TestSmartModel.objects.count(), 0) | ||
change_and_save(m, state=TestDispatchersModel.STATE.SECOND) | ||
assert_equal(TestSmartModel.objects.count(), 1) | ||
|
||
# But subsequent saves should not create more instances | ||
change_and_save(m, state=TestDispatchersModel.STATE.SECOND) | ||
assert_equal(TestSmartModel.objects.count(), 1) | ||
|
||
# Moving back and forth between the states creates another instance | ||
change_and_save(m, state=TestDispatchersModel.STATE.FIRST) | ||
change_and_save(m, state=TestDispatchersModel.STATE.SECOND) | ||
assert_equal(TestSmartModel.objects.count(), 2) | ||
|
||
def test_property_dispatcher(self): | ||
# Saving the model should always fire up the one property handler, not the second | ||
assert_equal(TestFieldsModel.objects.count(), 0) | ||
TestDispatchersModel.objects.create() | ||
assert_equal(TestFieldsModel.objects.count(), 1) | ||
assert_equal(TestDispatchersModel.objects.count(), 1) |