Skip to content
This repository has been archived by the owner on Jun 15, 2021. It is now read-only.

Commit

Permalink
Merge ed6a3d3 into f3942ed
Browse files Browse the repository at this point in the history
  • Loading branch information
miltontony committed Jul 18, 2014
2 parents f3942ed + ed6a3d3 commit 990e118
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -2,7 +2,7 @@ language: python
python:
- "2.7"
install:
- "pip install coveralls coverage --use-wheel"
- "pip install coveralls coverage mock --use-wheel"
- "pip install -r requirements.pip --use-wheel"
before_script:
- psql -c 'create database mamasbm_test;' -U postgres
Expand Down
1 change: 1 addition & 0 deletions mamasbm/mamasbm/__init__.py
Expand Up @@ -22,6 +22,7 @@ def main(global_config, **settings):
config.add_route('admin', '/admin/')
config.add_route('admin_profiles', '/admin/profiles/')
config.add_notfound_view(views.not_found, append_slash=True)
config.scan("mamasbm.service.api")
config.scan("mamasbm.web.api")
config.scan("mamasbm.web.views")
return config.make_wsgi_app()
12 changes: 4 additions & 8 deletions mamasbm/mamasbm/models.py
@@ -1,8 +1,4 @@
import uuid
import calendar
import transaction

from mamasbm.web.csv_handler import CsvImporter

from sqlalchemy import Column, Index, Integer, Text, types, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
Expand Down Expand Up @@ -47,15 +43,14 @@ class MyModel(Base):
name = Column(Text)
value = Column(Integer)

Index('my_index', MyModel.name, unique=True, mysql_length=255)


class Profile(Base):
__tablename__ = 'profiles'
uuid = Column('uuid', UUID(), primary_key=True, default=uuid.uuid4)
title = Column(Text)
send_days = Column(Text)
message_profiles = relationship("MessageProfile", backref="profile")
message_profiles = relationship(
"MessageProfile", lazy="dynamic", backref="profile")

def get_send_days(self):
return [int(d) for d in self.send_days.split(',') if d]
Expand All @@ -74,7 +69,7 @@ class MessageProfile(Base):
uuid = Column('uuid', UUID(), primary_key=True, default=uuid.uuid4)
name = Column(Text)
profile_id = Column(UUID, ForeignKey('profiles.uuid'))
messages = relationship('Message', backref='message_profile')
messages = relationship('Message', lazy="dynamic", backref='message_profile')
send_day = Column(Integer)

def to_dict(self):
Expand All @@ -85,6 +80,7 @@ def to_dict(self):
'send_day': self.send_day,
'messages': [m.to_dict() for m in self.messages]
}
Index('message_profile_send_day_index', MessageProfile.send_day)


class Message(Base):
Expand Down
74 changes: 74 additions & 0 deletions mamasbm/mamasbm/service/api.py
@@ -0,0 +1,74 @@
from datetime import date

from cornice import Service
from sqlalchemy.exc import DBAPIError, StatementError

from mamasbm.models import DBSession, Profile
from mamasbm.service import validators


message = Service(
name='message',
path='/api/message.json',
description="Public facing messages api"
)


#helper method to allow testing (mockable)
def get_ref_date():
return date.today()


def get_message_counter(index, date, max_weeks):
if index:
return int(index)

today = get_ref_date()

prenatal = True if today < date else False
print today, date, prenatal, max_weeks, (date - today).days / 7, (max_weeks - 1) - ((today - date).days / 7)
if prenatal:
return (date - today).days / 7
return (max_weeks - 1) - ((today - date).days / 7)


@message.get(validators=validators.validate_get_message)
def get_message(request):
uuid = request.validated['uuid']
day = int(request.validated['day'])

index = request.validated.get('index')
date = request.validated.get('date')

try:
profile = DBSession.query(Profile).get(uuid)
if not profile:
request.errors.add('request', 'uuid', 'Profile not found.')
return

msg_profile = profile.message_profiles.filter_by(send_day=day).first()
if not msg_profile:
request.errors.add(
'request', 'day',
'This profile doesn\'t have messages for day %s.' % day)
return

messages = msg_profile.messages.order_by('week')
num_messages = messages.count()
if not num_messages:
request.errors.add(
'request', 'profile',
'No messages available for this profile')
return

msg_counter = get_message_counter(index, date, num_messages)
if msg_counter >= num_messages or msg_counter < 0:
request.errors.add('request', 'index', 'Index out of bounds')
return

return messages[msg_counter].to_dict()
except DBAPIError:
request.errors.add(
'db', 'DBAPIError', 'Could not connect to the database.')
except StatementError:
request.errors.add('db', 'ValueError', 'uuid is not valid.')
151 changes: 151 additions & 0 deletions mamasbm/mamasbm/service/tests.py
@@ -0,0 +1,151 @@
import os
import transaction
import uuid

from datetime import date
from mock import patch
from pyramid import testing
from unittest import TestCase
from webtest import TestApp

from mamasbm import main
from mamasbm.models import DBSession, Base, Profile, MessageProfile
from mamasbm.web import factory
from mamasbm.service import api


class TestApi(TestCase):
def setUp(self):
self.config = testing.setUp()
connection_string = os.environ.get(
"MAMASBM_TEST_CONNECTION_STRING", "sqlite://")
self.app = TestApp(main({}, **{'sqlalchemy.url': connection_string}))
Base.metadata.create_all()

def tearDown(self):
DBSession.remove()
testing.tearDown()
Base.metadata.drop_all()

def test_get_profiles_db_error(self):
# drop all the tables
Base.metadata.drop_all()

data = {'uuid': uuid.uuid4(), 'day': 1, 'index': 0}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEquals(resp.json['status'], 'error')
self.assertEquals(
resp.json['errors'][0]['description'],
'Could not connect to the database.'
)

def import_pregnancy_messages(self):
data = {'title': 'Mama basic', 'send_days': [1, 4]}
self.app.put_json('/web/api/profiles.json', data, status=200)
sample_file = os.path.join(
os.path.dirname(__file__), "../web/sample/english_pregnant.csv")

resp = self.app.get('/web/api/profiles.json', status=200)
profile_uuid = resp.json[0]['uuid'],
factory.build_message_profiles(
'English', sample_file, profile_uuid)
return profile_uuid

def test_get_message(self):
profile_uuid = self.import_pregnancy_messages()

data = {'uuid': profile_uuid, 'day': 1, 'index': 0}
resp = self.app.get('/api/message.json', data)
self.assertTrue(
resp.json['text'].startswith('Congrats on your pregnancy'))

data = {'uuid': profile_uuid, 'day': 1, 'index': 5}
resp = self.app.get('/api/message.json', data)
self.assertTrue(
resp.json['text'].startswith('Hello from MAMA. Most women start'))

data = {'uuid': profile_uuid, 'day': 1, 'index': 35}
resp = self.app.get('/api/message.json', data)
self.assertTrue(
resp.json['text'].startswith('Keep this SMS as your labour guide'))

data = {'uuid': profile_uuid, 'day': 4, 'index': 35}
resp = self.app.get('/api/message.json', data)
self.assertTrue(resp.json['text'].startswith('A tip from MAMA'))

def test_error_messages(self):
profile_uuid = self.import_pregnancy_messages()

data = {'uuid': str(uuid.uuid4()), 'day': 1, 'index': 0}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEqual(
resp.json['errors'][0]['description'], 'Profile not found.')

data = {'uuid': profile_uuid, 'day': 5, 'index': 0}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEqual(
resp.json['errors'][0]['description'],
'This profile doesn\'t have messages for day 5.')

data = {'uuid': profile_uuid, 'day': 1, 'index': 77}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEqual(
resp.json['errors'][0]['description'],
'Index out of bounds')

data = {'uuid': 'xxx', 'day': 1, 'index': 77}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEqual(
resp.json['errors'][0]['description'], 'uuid is not valid.')

# create a blank profile
data = {'title': 'Mama blank profile', 'send_days': [1, 2]}
self.app.put_json('/web/api/profiles.json', data, status=200)
resp = self.app.get('/web/api/profiles.json', status=200)
profile_uuid = resp.json[1]['uuid']
with transaction.manager:
profile = DBSession.query(Profile).get(profile_uuid)
msg_profile = MessageProfile(name='Blank - Tuesday')
msg_profile.send_day = 1
profile.message_profiles.append(msg_profile)

data = {'uuid': profile_uuid, 'day': 1, 'index': 0}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEqual(
resp.json['errors'][0]['description'],
'No messages available for this profile')

def test_required_fields(self):
resp = self.app.get('/api/message.json', status=400)
self.assertEqual(
resp.json['errors'][0]['description'],
'uuid is a required field.')
self.assertEqual(
resp.json['errors'][1]['description'],
'day is a required field.')
self.assertEqual(
resp.json['errors'][2]['description'],
'Either `index` or `date` is required.')

@patch('mamasbm.service.api.get_ref_date')
def test_get_message_by_date(self, mock_get_ref_date):
mock_get_ref_date.return_value = date(2014, 1, 1)
profile_uuid = self.import_pregnancy_messages()

data = {'uuid': profile_uuid, 'day': 1, 'date': '20140907'}
resp = self.app.get('/api/message.json', data)
self.assertEqual(resp.json['week'], 40)

data = {'uuid': profile_uuid, 'day': 1, 'date': '20141201'}
resp = self.app.get('/api/message.json', data, status=400)
self.assertEqual(
resp.json['errors'][0]['description'],
'Index out of bounds')

data = {'uuid': profile_uuid, 'day': 1, 'date': '20140301'}
resp = self.app.get('/api/message.json', data)
self.assertEqual(resp.json['week'], 13)

data = {'uuid': profile_uuid, 'day': 1, 'date': '20130601'}
resp = self.app.get('/api/message.json', data)
self.assertEqual(resp.json['week'], 10)
44 changes: 44 additions & 0 deletions mamasbm/mamasbm/service/validators.py
@@ -0,0 +1,44 @@
from datetime import datetime


def update_validated_field(request, data, key):
if key in data and data[key] is not None:
request.validated[key] = data[key]


def validate_required_field(request, data, key):
if key in data and data[key] is not None:
update_validated_field(request, data, key)
else:
request.errors.add(
'body', 'RequiredFieldError', '%s is a required field.' % key)


def validate_index_or_date_field(request, data):
has_index = has_date = False

if 'index' in data and data['index'] is not None:
update_validated_field(request, data, 'index')
has_index = True

if 'date' in data and data['date'] is not None:
try:
date = datetime.strptime(data['date'], "%Y%m%d").date()
request.validated['date'] = date
has_date = True
except ValueError:
request.errors.add(
'body', 'InvalidDateError',
'`date` must be in the format `yyyymmdd`')
return

if not (has_index or has_date):
request.errors.add(
'body', 'RequiredFieldError',
'Either `index` or `date` is required.')


def validate_get_message(request):
validate_required_field(request, request.GET, 'uuid')
validate_required_field(request, request.GET, 'day')
validate_index_or_date_field(request, request.GET)
15 changes: 1 addition & 14 deletions mamasbm/mamasbm/web/views.py
@@ -1,23 +1,10 @@
from pyramid.httpexceptions import HTTPNotFound
from pyramid.response import Response
from pyramid.view import view_config
from sqlalchemy.exc import DBAPIError

from mamasbm.models import (
DBSession,
MyModel,
)


@view_config(route_name='home', renderer='templates/home.pt')
def my_view(request):
try:
one = DBSession.query(MyModel).filter(MyModel.name == 'one').first()
except DBAPIError:
return Response(
'Pyramid is having a problem using your SQL database.',
content_type='text/plain', status_int=500)
return {'one': one, 'project': 'mamasbm'}
return {}


@view_config(route_name='admin', renderer='templates/admin.pt')
Expand Down
26 changes: 26 additions & 0 deletions mamasbm/migrations/versions/209040d06873_add_send_day_index.py
@@ -0,0 +1,26 @@
"""add send_day index
Revision ID: 209040d06873
Revises: 1c9e829abd8e
Create Date: 2014-07-16 16:13:09.723638
"""

# revision identifiers, used by Alembic.
revision = '209040d06873'
down_revision = '1c9e829abd8e'

from alembic import op
import sqlalchemy as sa


def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_index('message_profile_send_day_index', 'message_profiles', ['send_day'], unique=False)
### end Alembic commands ###


def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_index('message_profile_send_day_index', table_name='message_profiles')
### end Alembic commands ###
1 change: 1 addition & 0 deletions mamasbm/setup.py
Expand Up @@ -39,6 +39,7 @@
zip_safe=False,
test_suite='mamasbm',
install_requires=requires,
test_requires=['mock', ],
entry_points="""\
[paste.app_factory]
main = mamasbm:main
Expand Down

0 comments on commit 990e118

Please sign in to comment.