-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit afd7fce
Showing
47 changed files
with
1,334 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
include README.rst | ||
recursive-include malnutrition/ui/static * | ||
recursive-include malnutrition/ui/templates * |
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,73 @@ | ||
This is a series of re-usable modules for the Kenyan and Malawi Malnutrition projects. | ||
|
||
forms | ||
--------------------- | ||
|
||
Contains a simple SMS form handler. Based on Django's forms, but doesn't have the same API, it specifies how the form will | ||
look, then takes the text and validates the input. The format is similar to Django forms:: | ||
|
||
class ReportForm(Form): | ||
sick = BooleanField() | ||
id = StringField(required=True, valid="(\d+)") | ||
This means we expect a y or n followed by a number. | ||
|
||
To process it pass in data eg:: | ||
|
||
rf = ReportForm("y 19122008") | ||
|
||
Was it valid?:: | ||
|
||
rf.is_valid() | ||
If not, there's a list of errors in the .errors attribute:: | ||
|
||
rf.errors | ||
|
||
You can also find the error on the field:: | ||
|
||
rf.sick.error | ||
And finally the parsed (eg: dates into datetime fields) value is there in data:: | ||
|
||
rf.sick.data | ||
You can make fields required, by passing through required=True in the Field construction. | ||
|
||
models | ||
--------------------- | ||
|
||
Models are some standard models, we've been reusing. These aren't included in your project, you have | ||
to include them, however that is pretty easy since they are all abstract. For example:: | ||
|
||
from malnutrition.models import case | ||
class Case(case.Case): | ||
pass | ||
A few notes on this: | ||
|
||
- Since the models are related to each other, you will likely get errors unless you pull all of them in and name them: Case, Provider, Facility, Zone | ||
|
||
- Report requires patient and provider | ||
|
||
- Log is standalone and requires nothing | ||
|
||
ui | ||
------------------------ | ||
|
||
A base reusable user interface allows you to start with a base login, logout and search etc without having to specify this in the project. | ||
|
||
For example:: | ||
|
||
# add in rapidsms_baseui | ||
INSTALLED_APPS = list(INSTALLED_APPS) | ||
INSTALLED_APPS.append('malnutrition.ui') | ||
|
||
Add in the URLs to your urls.py:: | ||
|
||
urlpatterns = patterns('', | ||
(r'^', include('malnutrition.ui.urls')), | ||
) | ||
|
||
And that should be it. |
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,2 @@ | ||
VERSION = (0, 1, 0) | ||
__version__ = '.'.join(map(str, VERSION)) |
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,6 @@ | ||
# I found my self writing lots of forms for SMS and handling the validation | ||
# which was a bit annoying | ||
# # sort of based on django forms, this is a form library that takes the incoming text | ||
# in an sms, assumes that its a series of fields, then parses the text into those fields | ||
from forms import Form | ||
from fields import StringField, GenderField, BooleanField, DateField |
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,54 @@ | ||
from forms import Field, FieldError | ||
import re | ||
|
||
class StringField(Field): | ||
def __init__(self, valid=None, required=False): | ||
self.rx = None | ||
if valid: | ||
self.rx = re.compile("^%s$" % valid) | ||
Field.__init__(self, required=required) | ||
|
||
def validate(self, text): | ||
if not self.rx: | ||
self.valid = True | ||
else: | ||
if self.rx.match(text): | ||
self.valid = True | ||
self.data = text | ||
else: | ||
raise FieldError, "The field %s did not match the required format." % (self.name) | ||
|
||
class GenderField(Field): | ||
def __init__(self, required=False): | ||
Field.__init__(self, required=required) | ||
|
||
def validate(self, text): | ||
if text.lower() not in ["m", "f"]: | ||
raise FieldError, "The field %s was not formatted correctly, got %s" % (self.name, text) | ||
self.valid = True | ||
self.data = text.lower() | ||
|
||
class BooleanField(Field): | ||
def __init__(self, required=False): | ||
Field.__init__(self, required=required) | ||
|
||
def validate(self, text): | ||
if text.lower() not in ["y", "n"]: | ||
raise FieldError, "The field %s was not formatted correctly, got %s" % (self.name, text) | ||
self.valid = True | ||
self.data = text.lower() == "y" | ||
|
||
class DateField(Field): | ||
def __init__(self, format="%d/%m/%Y", required=False): | ||
self.format = format | ||
Field.__init__(self, required=required) | ||
|
||
def validate(self, text): | ||
try: | ||
if isinstance(text, str): | ||
self.data = datetime.strptime(text, self.format).date() | ||
else: | ||
self.data = text | ||
except ValueError, e: | ||
raise FieldError, "The field %s was not formatted correctly, got %s, expecting in format %s" % (self.name, text, self.format) | ||
self.valid = True |
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,66 @@ | ||
from datetime import datetime, date | ||
import re | ||
|
||
class FieldError(Exception): | ||
pass | ||
|
||
class Field: | ||
creation_counter = 0 | ||
|
||
def __init__(self, required=None): | ||
assert required is not None | ||
self.name = None | ||
self.required = required | ||
self.data = None | ||
self.valid = False | ||
self.error = None | ||
|
||
self.creation_counter = Field.creation_counter | ||
Field.creation_counter += 1 | ||
|
||
class Form(object): | ||
def __init__(self, text): | ||
self.fields = [] | ||
self.errors = [] | ||
self.sep = " " | ||
|
||
for item in dir(self): | ||
obj = getattr(self, item) | ||
if issubclass(obj.__class__, Field): | ||
obj.name = item | ||
self.fields.append([obj.creation_counter, obj]) | ||
|
||
self.fields.sort() | ||
self.fields = [ f[1] for f in self.fields ] | ||
|
||
self.__call__(text) | ||
|
||
def __call__(self, text): | ||
text = text.strip().split(self.sep) | ||
|
||
for bit, field in map(None, text, self.fields): | ||
if field is None: | ||
self.errors.append("The text was longer than the form.") | ||
continue | ||
if not bit: | ||
if field.required: | ||
msg = "The field %s is required" % field.name | ||
field.error = msg | ||
self.errors.append(msg) | ||
continue | ||
|
||
try: | ||
if hasattr(field, "parser"): | ||
bit = field.parser(bit) | ||
field.validate(bit) | ||
except FieldError, e: | ||
field.error = str(e) | ||
self.errors.append(str(e)) | ||
|
||
if not self.errors: | ||
self.valid = True | ||
|
||
def is_valid(self): | ||
return not bool(self.errors) | ||
|
||
|
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,29 @@ | ||
import unittest | ||
|
||
from datetime import datetime, date | ||
import re | ||
|
||
from forms import Form | ||
from fields import BooleanField, StringField, DateField | ||
|
||
def parser(text): | ||
return date(year=int(text[4:8]),day=int(text[0:2]),month=int(text[2:4])) | ||
|
||
class test(unittest.TestCase): | ||
# These need to be expanded | ||
|
||
def testSimple(self): | ||
class ReportForm(Form): | ||
male = BooleanField() | ||
date = DateField(format="%m%d%Y") | ||
date.parser = parser | ||
id = StringField(required=True, valid="(\d+)") | ||
|
||
rf = ReportForm("y 19122008 123") | ||
assert rf.is_valid() | ||
assert rf.male.data | ||
assert rf.date.data.month == 12 | ||
assert not rf.errors | ||
|
||
if __name__=="__main__": | ||
unittest.main() |
Empty file.
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,77 @@ | ||
from django.db import models | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from datetime import datetime | ||
|
||
class Case(models.Model): | ||
GENDER_CHOICES = ( | ||
('M', _('Male')), | ||
('F', _('Female')), | ||
) | ||
|
||
ref_id = models.IntegerField(_('Case ID #'), null=True, db_index=True) | ||
first_name = models.CharField(max_length=255, db_index=True) | ||
last_name = models.CharField(max_length=255, db_index=True) | ||
gender = models.CharField(max_length=1, choices=GENDER_CHOICES) | ||
dob = models.DateField(_('Date of Birth')) | ||
guardian = models.CharField(max_length=255, null=True, blank=True) | ||
mobile = models.CharField(max_length=16, null=True, blank=True) | ||
provider = models.ForeignKey("Provider", db_index=True) | ||
zone = models.ForeignKey("Zone", null=True, db_index=True) | ||
village = models.CharField(max_length=255, null=True, blank=True) | ||
district = models.CharField(max_length=255, null=True, blank=True) | ||
created_at = models.DateTimeField() | ||
updated_at = models.DateTimeField() | ||
|
||
def __unicode__ (self): | ||
return "#%d" % self.ref_id | ||
|
||
def _luhn(self, x): | ||
parity = True | ||
sum = 0 | ||
for c in reversed(str(x)): | ||
n = int(c) | ||
if parity: | ||
n *= 2 | ||
if n > 9: n -= 9 | ||
sum += n | ||
return x * 10 + 10 - sum % 10 | ||
|
||
def save(self, *args): | ||
if not self.id: | ||
self.created_at = self.updated_at = datetime.now() | ||
else: | ||
self.updated_at = datetime.now() | ||
super(Case, self).save(*args) | ||
if not self.ref_id: | ||
self.ref_id = self._luhn(self.id) | ||
super(Case, self).save(*args) | ||
|
||
def get_dictionary(self): | ||
""" Return the data as a generic dictionary with some useful convenience methods done """ | ||
return { | ||
'ref_id': self.ref_id, | ||
'last_name': self.last_name.upper(), | ||
'first_name': self.first_name, | ||
'first_name_short': self.first_name.upper()[0], | ||
'gender': self.gender.upper()[0], | ||
'months': self.age(), | ||
'guardian': self.guardian, | ||
'village': self.village, | ||
} | ||
|
||
def years_months(self): | ||
now = datetime.now().date() | ||
ymonths = (now.year - self.dob.year) * 12 | ||
months = ymonths + (now.month - self.dob.month) | ||
return (now.year - self.dob.year, months) | ||
|
||
def age(self): | ||
years, months = self.years_months() | ||
if years > 3: | ||
return str(years) | ||
else: | ||
return "%sm" % months | ||
|
||
class Meta: | ||
abstract = True |
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,31 @@ | ||
from django.db import models | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
class Facility(models.Model): | ||
""" A generic model for a facility """ | ||
class Meta: | ||
abstract = True | ||
verbose_name_plural = "Facilities" | ||
|
||
def __unicode__ (self): | ||
return self.name | ||
|
||
CLINIC_ROLE = 1 | ||
DISTRIB_ROLE = 2 | ||
ROLE_CHOICES = ( | ||
(CLINIC_ROLE, _('Clinic')), | ||
(DISTRIB_ROLE, _('Distribution Point')), | ||
) | ||
|
||
name = models.CharField(max_length=255) | ||
role = models.IntegerField(choices=ROLE_CHOICES, default=CLINIC_ROLE) | ||
zone = models.ForeignKey("Zone", db_index=True) | ||
codename = models.CharField(max_length=255,unique=True,db_index=True) | ||
lon = models.FloatField(null=True,blank=True) | ||
lat = models.FloatField(null=True,blank=True) | ||
|
||
def get_dictionary(self): | ||
return { | ||
"name": self.name, | ||
"codename": self.codename | ||
} |
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,22 @@ | ||
from django.db import models | ||
from django.contrib.auth.models import User | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from datetime import datetime | ||
|
||
class MessageLog(models.Model): | ||
""" This is the raw dirt message log, useful for some things """ | ||
mobile = models.CharField(max_length=255, db_index=True) | ||
sent_by = models.ForeignKey(User, null=True) | ||
text = models.TextField(max_length=255) | ||
was_handled = models.BooleanField(default=False, db_index=True) | ||
created_at = models.DateTimeField(db_index=True) | ||
|
||
class Meta: | ||
ordering = ("-created_at",) | ||
abstract = True | ||
|
||
def save(self, *args): | ||
if not self.id: | ||
self.created_at = datetime.now() | ||
super(MessageLog, self).save(*args) |
Oops, something went wrong.