Skip to content
This repository has been archived by the owner on Sep 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #118 from postatum/104979708_last_login
Browse files Browse the repository at this point in the history
Allow triggering different events. Add auth events
  • Loading branch information
jstoiko committed Oct 7, 2015
2 parents bd29aa0 + e47eb1c commit 8cb7a5a
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 28 deletions.
15 changes: 15 additions & 0 deletions docs/source/event_handlers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ Utilities
def show(self):
...
**nefertari.events.trigger_instead**
Decorator which allows view method to trigged another event instead of default one. In the example above collection GET requests (``UsersView.index``) will trigger event which corresponds to item PATCH (``update``).

.. code-block:: python
from nefertari import view, events
class UsersView(view.BaseView):
@events.trigger_instead('update')
def index(self):
...
Examples
--------
Expand Down Expand Up @@ -146,3 +159,5 @@ API
.. autofunction:: nefertari.events.subscribe_to_events

.. autofunction:: nefertari.events.silent

.. autofunction:: nefertari.events.trigger_instead
9 changes: 4 additions & 5 deletions nefertari/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class TicketAuthRegisterView(TicketAuthViewMixin, BaseView):
root.add('account', view='path.to.TicketAuthRegisterView',
factory='nefertari.acl.AuthenticationACL')
"""
@events.trigger_instead('register')
def create(self, *args, **kwargs):
return self.register(*args, **kwargs)

Expand All @@ -77,12 +78,11 @@ class TicketAuthLoginView(TicketAuthViewMixin, BaseView):
root.add('login', view='path.to.TicketAuthLoginView',
factory='nefertari.acl.AuthenticationACL')
"""
@events.silent
@events.trigger_instead('login')
def create(self, *args, **kwargs):
return self.login(*args, **kwargs)


@events.silent
class TicketAuthLogoutView(TicketAuthViewMixin, BaseView):
""" Ticket auth logout view. Allows logout on GET and POST.
Expand All @@ -91,12 +91,10 @@ class TicketAuthLogoutView(TicketAuthViewMixin, BaseView):
root.add('logout', view='path.to.TicketAuthLogoutView',
factory='nefertari.acl.AuthenticationACL')
"""
@events.trigger_instead('logout')
def create(self, *args, **kwargs):
return self.logout(*args, **kwargs)

def show(self, *args, **kwargs):
return self.logout(*args, **kwargs)


class TokenAuthViewMixin(object):
""" View for auth operations to use with
Expand Down Expand Up @@ -160,6 +158,7 @@ class TokenAuthRegisterView(TokenAuthViewMixin, BaseView):
root.add('register', view='path.to.TokenAuthRegisterView',
factory='nefertari.acl.AuthenticationACL')
"""
@events.trigger_instead('register')
def create(self, *args, **kwargs):
return self.register(*args, **kwargs)

Expand Down
103 changes: 81 additions & 22 deletions nefertari/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ class BeforeCollectionOptions(RequestEvent):
pass


class BeforeLogin(RequestEvent):
pass


class BeforeLogout(RequestEvent):
pass


class BeforeRegister(RequestEvent):
pass


# 'After' events

class AfterIndex(RequestEvent):
Expand Down Expand Up @@ -141,6 +153,18 @@ class AfterCollectionOptions(RequestEvent):
pass


class AfterLogin(RequestEvent):
pass


class AfterLogout(RequestEvent):
pass


class AfterRegister(RequestEvent):
pass


""" Events run before a particular event action happened.
It's recommended to use these events to:
* Transform input
Expand All @@ -149,16 +173,20 @@ class AfterCollectionOptions(RequestEvent):
`event.set_field_value`.
"""
BEFORE_EVENTS = {
'index': BeforeIndex,
'show': BeforeShow,
'create': BeforeCreate,
'update': BeforeUpdate,
'replace': BeforeReplace,
'delete': BeforeDelete,
'update_many': BeforeUpdateMany,
'delete_many': BeforeDeleteMany,
'item_options': BeforeItemOptions,
'collection_options': BeforeCollectionOptions,
'index': BeforeIndex,
'show': BeforeShow,
'create': BeforeCreate,
'update': BeforeUpdate,
'replace': BeforeReplace,
'delete': BeforeDelete,
'update_many': BeforeUpdateMany,
'delete_many': BeforeDeleteMany,
'item_options': BeforeItemOptions,
'collection_options': BeforeCollectionOptions,

'login': BeforeLogin,
'logout': BeforeLogout,
'register': BeforeRegister,
}

""" Events run after a particular event action happened.
Expand All @@ -167,16 +195,20 @@ class AfterCollectionOptions(RequestEvent):
* Perform notifications/logging.
"""
AFTER_EVENTS = {
'index': AfterIndex,
'show': AfterShow,
'create': AfterCreate,
'update': AfterUpdate,
'replace': AfterReplace,
'delete': AfterDelete,
'update_many': AfterUpdateMany,
'delete_many': AfterDeleteMany,
'item_options': AfterItemOptions,
'collection_options': AfterCollectionOptions,
'index': AfterIndex,
'show': AfterShow,
'create': AfterCreate,
'update': AfterUpdate,
'replace': AfterReplace,
'delete': AfterDelete,
'update_many': AfterUpdateMany,
'delete_many': AfterDeleteMany,
'item_options': AfterItemOptions,
'collection_options': AfterCollectionOptions,

'login': AfterLogin,
'logout': AfterLogout,
'register': AfterRegister,
}


Expand Down Expand Up @@ -239,6 +271,10 @@ def trigger_events(view_obj):
request = view_obj.request

view_method = getattr(view_obj, request.action)
event_action = (
getattr(view_method, '_event_action', None) or
request.action)

do_trigger = not (
getattr(view_method, '_silent', False) or
getattr(view_obj, '_silent', False))
Expand All @@ -254,13 +290,13 @@ def trigger_events(view_obj):
if hasattr(view_obj.context, 'pk_field'):
event_kwargs['instance'] = view_obj.context

before_event = BEFORE_EVENTS[request.action]
before_event = BEFORE_EVENTS[event_action]
request.registry.notify(before_event(**event_kwargs))

yield

if do_trigger:
after_event = AFTER_EVENTS[request.action]
after_event = AFTER_EVENTS[event_action]
request.registry.notify(after_event(**event_kwargs))


Expand Down Expand Up @@ -311,6 +347,7 @@ def add_field_processors(config, processors, model, field):
BeforeUpdate,
BeforeReplace,
BeforeUpdateMany,
BeforeRegister,
)

def wrapper(event, _processors=processors, _field=field):
Expand Down Expand Up @@ -342,3 +379,25 @@ def silent(obj):
"""
obj._silent = True
return obj


def trigger_instead(event_action):
""" Specify action name to change event triggered by view method.
In the example above ``MyView.index`` method will trigger before/after
``update`` events.
.. code-block:: json
class MyView(BaseView):
@events.trigger_instead('update')
def index(self):
(...)
:param event_action: Event action name which should be triggered
instead of default one.
"""
def wrapper(func):
func._event_action = event_action
return func
return wrapper
45 changes: 44 additions & 1 deletion tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def test_trigger_events(self, mock_from):
context=ctx,
_silent=False)
view.index._silent = False
view.index._event_action = None

with patch.dict(events.BEFORE_EVENTS, {'index': mock_before}):
with patch.dict(events.AFTER_EVENTS, {'index': mock_after}):
Expand Down Expand Up @@ -82,6 +83,7 @@ class A(object):
context=ctx,
_silent=True)
view.index._silent = False
view.index._event_action = None

with patch.dict(events.BEFORE_EVENTS, {'index': mock_before}):
with patch.dict(events.AFTER_EVENTS, {'index': mock_after}):
Expand All @@ -108,6 +110,7 @@ class A(object):
context=ctx,
_silent=False)
view.index._silent = True
view.index._event_action = None

with patch.dict(events.BEFORE_EVENTS, {'index': mock_before}):
with patch.dict(events.AFTER_EVENTS, {'index': mock_after}):
Expand All @@ -119,6 +122,39 @@ class A(object):
assert not view.request.registry.notify.called
assert not mock_from.called

@patch('nefertari.utils.FieldData.from_dict')
def test_trigger_events_different_action(self, mock_from):
class A(object):
pass

mock_from.return_value = {'foo': 1}
mock_after = Mock()
mock_before = Mock()
ctx = A()
view = Mock(
Model=A,
request=Mock(action='index'),
_json_params={'bar': 1},
context=ctx,
_silent=False)
view.index._silent = None
view.index._event_action = 'delete'

with patch.dict(events.BEFORE_EVENTS, {'delete': mock_before}):
with patch.dict(events.AFTER_EVENTS, {'delete': mock_after}):
with events.trigger_events(view):
pass

mock_after.assert_called_once_with(
fields={'foo': 1}, model=view.Model, view=view)
mock_before.assert_called_once_with(
fields={'foo': 1}, model=view.Model, view=view)
view.request.registry.notify.assert_has_calls([
call(mock_before()),
call(mock_after()),
])
mock_from.assert_called_once_with({'bar': 1}, view.Model)


class TestHelperFunctions(object):
def test_subscribe_to_events(self):
Expand All @@ -143,6 +179,13 @@ class Foo(object):

assert Foo._silent

def test_trigger_instead_decorator(self):
@events.trigger_instead('foobar')
def foo():
pass

assert foo._event_action == 'foobar'

def test_add_field_processors(self):
event = Mock()
event.field.new_value = 'admin'
Expand All @@ -152,7 +195,7 @@ def test_add_field_processors(self):
events.add_field_processors(
config, [processor, processor],
model='User', field='username')
assert config.add_subscriber.call_count == 4
assert config.add_subscriber.call_count == 5
assert not event.set_field_value.called
assert not processor.called

Expand Down

0 comments on commit 8cb7a5a

Please sign in to comment.