Skip to content
This repository has been archived by the owner on Mar 11, 2019. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
mwaaas committed Jan 21, 2017
2 parents 6080da4 + 61c902e commit 40d1ec1
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ WORKDIR /usr/src/app

COPY . /usr/src/app

RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir --exists-action w -r requirements.txt

EXPOSE 80

Expand Down
24 changes: 23 additions & 1 deletion ussd/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,17 @@ def get_text(self, text_context=None):
text_context
)

def evaluate_jija_expression(self, expression):
def evaluate_jija_expression(self, expression, extra_context=None):
if not expression.endswith("}}") and \
not expression.startswith("{{"):
expression = "{{ " + expression + " }}"

template = Template(expression)

context = self._get_context()
if extra_context is not None:
context.update(extra_context)

results = template.render(
ussd_request=self.ussd_request,
**context
Expand All @@ -276,6 +279,25 @@ def evaluate_jija_expression(self, expression):
return False
return True

def get_value_from_variables(self, variable, variables=None):
if self._contains_vars(variable):
# Check to see if the string we are trying to
# render is just referencing a single
# var. In this case we don't want to accidentally
# change the type of the variable
# to a string by using the jinja template renderer.
# We just want to pass it.
only_one = self.SINGLE_VAR.match(variable)
if only_one:
if variables is None:
variables = self._get_context()
return variables.get(
only_one.group(1)
)
# we don't support other jinja syntax a the moment
return []
return variable

@classmethod
def validate(cls, screen_name: str, ussd_content: dict) -> (bool, dict):
screen_content = ussd_content[screen_name]
Expand Down
20 changes: 1 addition & 19 deletions ussd/screens/menu_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,23 +219,6 @@ def evaluate_input(self):
return option.next_screen
return False

def get_value_from_variables(self, variable, all_variables: dict):
if self._contains_vars(variable):
# Check to see if the string we are trying to
# render is just referencing a single
# var. In this case we don't want to accidentally
# change the type of the variable
# to a string by using the jinja template renderer.
# We just want to pass it.
only_one = self.SINGLE_VAR.match(variable)
if only_one:
return all_variables.get(
only_one.group(1)
)
# we don't support other jinja syntax a the moment
return []
return variable

def get_items(self) -> list:
"""
This gets ListItems
Expand All @@ -253,8 +236,7 @@ def get_items(self) -> list:
loop_method = "_" + key
loop_value = value_

items = self.get_value_from_variables(loop_value,
self._get_session_items())
items = self.get_value_from_variables(loop_value)

return getattr(self, loop_method)(
text, value, items
Expand Down
35 changes: 32 additions & 3 deletions ussd/screens/router_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class RouterScreen(UssdHandlerAbstract):
This is the screen to direct to if all expression in router_options
failed.
3. with_items (optional)
Sometimes you want to loop over something until an item
passes the expression. In this case use with_items.
When using with_items you can use variable item in the
expression.
see in the example below for more explanation
Examples of router screens
.. literalinclude:: .././ussd/tests/sample_screen_definition/valid_router_screen_conf.yml
Expand All @@ -48,10 +56,31 @@ class RouterScreen(UssdHandlerAbstract):

def handle(self):
route_options = self.screen_content.get("router_options")
loop_items = [0]
if self.screen_content.get("with_items"):
loop_items = self.get_value_from_variables(
self.screen_content["with_items"]
) or loop_items

for item in loop_items:
extra_context = {
"item": item
}
if isinstance(loop_items, dict):
extra_context.update(
dict(
key=item,
value=loop_items[item],
item={item: loop_items[item]}
)
)

for option in route_options:
if self.evaluate_jija_expression(option['expression']):
return self.ussd_request.forward(option['next_screen'])
for option in route_options:
if self.evaluate_jija_expression(
option['expression'],
extra_context=extra_context
):
return self.ussd_request.forward(option['next_screen'])
return self.ussd_request.forward(
self.screen_content['default_next_screen']
)
40 changes: 39 additions & 1 deletion ussd/tests/sample_screen_definition/valid_router_screen_conf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ router_exa_1:
next_screen: 200_phone_number
- expression: "{{ phone_number == 202| string }}"
next_screen: 202_phone_number
- expression: "{{ phone_number in [203|string, 204|string, 205|string] }}"
next_screen: sample_router_screen_with_loop
- expression: "{{ phone_number in [ 206|string, 207|string] }}"
next_screen: sample_router_screen_with_dict_loop


200_phone_number:
type: quit_screen
Expand All @@ -19,4 +24,37 @@ router_exa_1:

default_screen:
type: quit_screen
text: This is the default screen
text: This is the default screen

sample_router_screen_with_loop:
type: router_screen
default_next_screen: default_screen
with_items: "{{ phone_numbers}}"
router_options:
- expression: "{{ item == 'registered' }}"
next_screen: registred_screen
- expression: "{{ item == 'not_registered'}}"
next_screen: not_registered

registred_screen:
type: quit_screen
text: You are registered user

not_registered:
type: quit_screen
text: You are not registered user

sample_router_screen_with_dict_loop:
type: router_screen
default_next_screen: default_screen
with_items:
phone_number: '207'
router_options:
- expression: '{{ key == "phone_number" and value == phone_number}}'
next_screen: 207_screen

207_screen:
type: quit_screen
text: >
This screen has been routed here because the
phone number is {{phone_number}}
57 changes: 57 additions & 0 deletions ussd/tests/test_router_screen.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ussd.tests import UssdTestCase
from ussd.core import ussd_session


class TestRouterHandler(UssdTestCase.BaseUssdTestCase):
Expand All @@ -13,6 +14,19 @@ class TestRouterHandler(UssdTestCase.BaseUssdTestCase):
)
)

@staticmethod
def add_phone_number_status_in_session(ussd_client):
session = ussd_session(ussd_client.session_id)

if ussd_client.phone_number == 205:
session['phone_numbers'] = ["no_status"]
else:
session["phone_numbers"] = ["registered",
"not_registered",
"not_there"]

session.save()

def test(self):
ussd_client = self.ussd_client(phone_number=200)

Expand All @@ -26,3 +40,46 @@ def test(self):

self.assertEqual("This is the default screen",
ussd_client.send(''))

def test_route_options_with_list_loop(self):

ussd_client = self.ussd_client(phone_number=203)
# add phone_number in session
self.add_phone_number_status_in_session(ussd_client)

# dial in
response = ussd_client.send('')

self.assertEqual(
"You are registered user",
response
)

ussd_client = self.ussd_client(phone_number=205)
# add phone_number in session
self.add_phone_number_status_in_session(ussd_client)

# dial in
response = ussd_client.send('')

self.assertEqual(
"This is the default screen",
response
)

def test_router_option_with_dict_loop(self):

ussd_client = self.ussd_client(phone_number=206)

self.assertEqual(
"This is the default screen",
ussd_client.send('')
)

ussd_client = self.ussd_client(phone_number=207)

self.assertEqual(
"This screen has been routed here because the "
"phone number is 207",
ussd_client.send('')
)

0 comments on commit 40d1ec1

Please sign in to comment.