Skip to content

Commit

Permalink
Merge pull request #126 from praekelt/EPIC-OPENHELPD-23
Browse files Browse the repository at this point in the history
Add actions to pods
  • Loading branch information
rowanseymour authored Aug 15, 2016
2 parents ec15ae3 + 8990707 commit bcd5241
Show file tree
Hide file tree
Showing 26 changed files with 1,503 additions and 51 deletions.
55 changes: 51 additions & 4 deletions casepro/pods/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,59 @@ def config_json(self):
return json.dumps(self.config._config_data)

def read_data(self, params):
"""Should return the data that should be used to create the display for the pod."""
"""
Should return the data that should be used to create the display for the pod.
For the base implementation, the data should be an object with 'items' and 'actions' keys.
The items key should be a list of objects, that have 'name' and 'value' keys, with the value of the keys being
what will be displayed.
The 'actions' key should be a list of objects, that have 'type', 'name' and 'payload' keys, where type and
payload is what is sent to the 'perform_action' function to determine which button has been pressed, and 'name'
is the text that is displayed on the button.
Each action may include the following optional fields:
- ``busy_text``: used as the action's corresponding
button's text while waiting on a response from the pod's api side
when the action is triggered. Defaults to the value of the ``name``
field.
- ``confirm``: whether a confirmation modal should be shown to
confirm whether the user would like to perform the action. Defaults
to ``false``.
Example:
{
'items': [
{
'name': 'EDD',
'value': '2015-07-18',
},
],
'actions': [
{
'type': 'remove_edd',
'name': 'Remove EDD',
'payload': {},
'busy_text': 'Removing EDD',
'confirm': True
},
],
}
"""
return {}

def perform_action(self, params):
"""Should perform the action specified by params."""
return {}
def perform_action(self, type_, params):
"""
Should perform the action specified by the type and params (which are specified in the read function).
Returns a tuple (success, payload), where 'success' is a boolean value indicating whether the action was
successful or not. If true, a case action note will be created.
For the base implementation, payload is an object with a 'message' key, which is the error message if success
is false, or the message to place in the case action note if success is true.
"""
return (False, {'message': ''})


class PodPlugin(AppConfig):
Expand Down
178 changes: 163 additions & 15 deletions casepro/pods/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.core.urlresolvers import reverse
from django.test import modify_settings

from casepro.cases.models import CaseAction
from casepro.test import BaseCasesTest


Expand All @@ -13,6 +14,17 @@ class ViewPodDataView(BaseCasesTest):
"""
Tests relating to the view_pod_data view.
"""
def setUp(self):
super(ViewPodDataView, self).setUp()
contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name')
msg = self.create_message(self.unicef, 0, contact, 'Test message')
self.case = self.create_case(self.unicef, contact, self.moh, msg)
self.login(self.admin)

with self.settings(PODS=[{'label': 'base_pod'}]):
from casepro.pods import registry
reload(registry)

def test_invalid_method(self):
"""
If the request method is not GET, an appropriate error should be
Expand All @@ -30,11 +42,9 @@ def test_pod_doesnt_exist(self):
If the requested pod id is invalid, an appropriate 404 error should be
returned.
"""
with self.settings(PODS=[]):
from casepro.pods import registry
reload(registry)
response = self.url_get(
'unicef', reverse('read_pod_data', args=('0',)))
'unicef', reverse('read_pod_data', args=('1',)), params={'case_id': self.case.id})

self.assertEqual(response.status_code, 404)
self.assertEqual(response.json, {
'reason': 'Pod does not exist'}
Expand All @@ -44,14 +54,53 @@ def test_pod_valid_request(self):
"""
If it is a valid get request, the data from the pod should be returned.
"""
with self.settings(PODS=[{'label': 'base_pod'}]):
from casepro.pods import registry
reload(registry)
response = self.url_get(
'unicef', reverse('read_pod_data', args=('0',)))
'unicef', reverse('read_pod_data', args=('0',)), params={'case_id': self.case.id})

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json, {})

def test_case_id_required(self):
'''
If the case id is not present in the request, an error response should be returned.
'''
response = self.url_get(
'unicef', reverse('read_pod_data', args=('0',)))
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json, {
'reason': 'Request needs "case_id" query parameter'
})

def test_case_not_found(self):
'''
If the case is not found, an error response should be returned.
'''
response = self.url_get(
'unicef', reverse('read_pod_data', args=('0',)),
params={'case_id': 23})

self.assertEqual(response.status_code, 404)
self.assertEqual(response.json, {
'reason': 'Case with id 23 not found'
})

def test_unauthorized(self):
'''
If the user does not have read permission, the request should be denied.
'''
self.login(self.user4)

response = self.url_get(
'unicef', reverse('read_pod_data', args=('0',)),
params={'case_id': self.case.id})

self.assertEqual(response.status_code, 403)
self.assertEqual(response.json, {
'reason': (
"The request's authentication details do not corresond "
"to the required access level for accessing this resource")
})


@modify_settings(INSTALLED_APPS={
'append': 'casepro.pods.PodPlugin',
Expand All @@ -60,6 +109,19 @@ class PerformPodActionView(BaseCasesTest):
"""
Tests relating to the perform_pod_action view.
"""
def setUp(self):
super(PerformPodActionView, self).setUp()
contact = self.create_contact(self.unicef, 'contact-uuid', 'contact_name')
msg = self.create_message(self.unicef, 0, contact, 'Test message')
self.case = self.create_case(self.unicef, contact, self.moh, msg)
self.login(self.admin)

CaseAction.objects.all().delete()

with self.settings(PODS=[{'label': 'base_pod'}]):
from casepro.pods import registry
reload(registry)

def test_invalid_method(self):
"""
If the request method is not POST, an appropriate error should be
Expand All @@ -77,11 +139,8 @@ def test_pod_doesnt_exist(self):
If the requested pod id is invalid, an appropriate 404 error should be
returned.
"""
with self.settings(PODS=[]):
from casepro.pods import registry
reload(registry)
response = self.url_post_json(
'unicef', reverse('perform_pod_action', args=('0',)), {})
'unicef', reverse('perform_pod_action', args=('23',)), {'case_id': self.case.id})
self.assertEqual(response.status_code, 404)
self.assertEqual(response.json, {
'reason': 'Pod does not exist'}
Expand All @@ -100,14 +159,103 @@ def test_invalid_json(self):
'details': 'No JSON object could be decoded'
})

def test_case_id_required(self):
'''
If the case id is not present in the request, an error response should be returned.
'''
response = self.url_post_json(
'unicef', reverse('perform_pod_action', args=('0',)), {})
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json, {
'reason': 'Request object needs to have a "case_id" field'
})

def test_pod_valid_request(self):
"""
If it is a valid post request, the action should be performed.
"""
with self.settings(PODS=[{'label': 'base_pod'}]):
response = self.url_post_json(
'unicef', reverse('perform_pod_action', args=('0',)), {'case_id': self.case.id})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json, {
'success': False,
'payload': {
'message': '',
}
})

@modify_settings(INSTALLED_APPS={
'append': 'casepro.pods.tests.utils.SuccessActionPlugin',
})
def test_case_action_note_created_on_successful_action(self):
'''
If the action is successful, a case action note should be created.
'''
with self.settings(PODS=[{'label': 'success_pod'}]):
from casepro.pods import registry
reload(registry)

response = self.url_post_json(
'unicef', reverse('perform_pod_action', args=('0',)), {})
'unicef', reverse('perform_pod_action', args=('0',)), {
'case_id': self.case.id,
'action': {
'type': 'foo',
'payload': {'foo': 'bar'},
},
})

self.assertEqual(response.status_code, 200)
self.assertEqual(response.json, {})

message = "Type foo Params {u'foo': u'bar'}"
self.assertEqual(response.json, {
'success': True,
'payload': {
'message': message
}
})

[caseaction] = CaseAction.objects.all()
self.assertEqual(
caseaction.note,
"%s %s" % (self.admin.username, message))

def test_case_not_found(self):
'''
If the case is not found, an error response should be returned.
'''

response = self.url_post_json(
'unicef', reverse('perform_pod_action', args=('0',)), {
'case_id': 23,
'action': {
'type': 'foo',
'payload': {'foo': 'bar'},
},
})

self.assertEqual(response.status_code, 404)
self.assertEqual(response.json, {
'reason': 'Case with id 23 not found'
})

def test_unauthorized(self):
'''
If the user does not have update permission, the request should be denied.
'''
self.login(self.user4)

response = self.url_post_json(
'unicef', reverse('perform_pod_action', args=('0',)), {
'case_id': self.case.id,
'action': {
'type': 'foo',
'payload': {'foo': 'bar'},
},
})

self.assertEqual(response.status_code, 403)
self.assertEqual(response.json, {
'reason': (
"The request's authentication details do not corresond "
"to the required access level for accessing this resource")
})
13 changes: 13 additions & 0 deletions casepro/pods/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,16 @@ class DummyPodPlugin(PodPlugin):
directive = 'dummy-pod'
scripts = ('dummy-script.js',)
styles = ('dummy-style.css',)


class SuccessActionPod(Pod):
def perform_action(self, type_, params):
'''
Returns a successful action result with a message containing the type and params.
'''
return (True, {'message': 'Type %s Params %r' % (type_, params)})


class SuccessActionPlugin(DummyPodPlugin):
pod_class = SuccessActionPod
label = 'success_pod'
Loading

0 comments on commit bcd5241

Please sign in to comment.