initial version of the sms written at DBA
authormako@atdot.cc <>
Tue, 2 Oct 2007 13:31:28 +0000 (09:31 -0400)
committermako@atdot.cc <>
Tue, 2 Oct 2007 13:31:28 +0000 (09:31 -0400)
create.sql [new file with mode: 0644]
db.py [new file with mode: 0644]
rapidsms.py [new file with mode: 0755]
templates/_error.tmpl [new file with mode: 0644]
templates/edit_questions.tmpl [new file with mode: 0644]
templates/index.tmpl [new file with mode: 0644]
templates/layout.tmpl [new file with mode: 0644]
templates/new_name.tmpl [new file with mode: 0644]
templates/questionaires.tmpl [new file with mode: 0644]
templates/show_names.tmpl [new file with mode: 0644]
templates/success.tmpl [new file with mode: 0644]

diff --git a/create.sql b/create.sql
new file mode 100644 (file)
index 0000000..49705a7
--- /dev/null
@@ -0,0 +1,38 @@
+DROP TABLE IF EXISTS people;
+CREATE TABLE people (
+    id INT NOT NULL auto_increment,
+    name VARCHAR(255) NULL DEFAULT NULL, 
+    phone_number VARCHAR(255) NOT NULL,
+    PRIMARY KEY (id)
+);
+
+DROP TABLE IF EXISTS questionaires;
+CREATE TABLE questionaires (
+    id INT NOT NULL auto_increment,
+    code VARCHAR(100) NOT NULL,  -- always a letter
+    description VARCHAR(255) NOT NULL,  
+    PRIMARY KEY (id)
+);
+
+DROP TABLE IF EXISTS questions;
+CREATE TABLE questions (
+    id INT NOT NULL auto_increment,
+    questionaire_id INT NOT NULL,
+    code VARCHAR(100) NOT NULL , -- always a number
+    longdesc VARCHAR(255) NULL DEFAULT NULL,
+    shortdesc VARCHAR(20) NULL DEFAULT NULL,
+    datatype VARCHAR(100) NOT NULL,  -- number, char, bool
+    PRIMARY KEY (id)
+);
+
+DROP TABLE IF EXISTS answers;
+CREATE TABLE answers (
+    id INT NOT NULL auto_increment,
+    question_id INT NOT NULL,
+    person_id INT NOT NULL,
+    answer VARCHAR(255) NOT NULL,
+    recieved TIMESTAMP NOT NULL DEFAULT NOW(),
+    PRIMARY KEY (id)
+);
+
+-- add group table in the future
diff --git a/db.py b/db.py
new file mode 100644 (file)
index 0000000..c0e0640
--- /dev/null
+++ b/db.py
@@ -0,0 +1,60 @@
+from storm.locals import *
+database = create_database("mysql:uniphone")
+store = Store(database)
+
+class Person(object):
+    __storm_table__ = "people"
+    id = Int(primary=True)
+    name = Unicode()
+    phone_number = Unicode()
+
+    def __init__(self, **kw):
+        self.name = unicode(kw['name'])
+        self.phone_number = unicode(kw['phone_number'])
+
+class Questionaire(object):
+    __storm_table__ = "questionaires"
+    id = Int(primary=True)
+    code = Unicode()
+    description = Unicode()
+
+    def __init__(self, **kw):
+        self.code = unicode(kw['code'])
+        self.description = unicode(kw['description'])
+
+class Question(object):
+    __storm_table__ = "questions"
+    id = Int(primary=True)
+    code = Unicode()
+    longdesc = Unicode()
+    shortdesc = Unicode()
+    datatype = Unicode()
+    questionaire_id = Int()
+    questionaire = Reference(questionaire_id, Questionaire.id)
+
+    def __init__(self, **kw):
+        self.code = unicode(kw['code'])
+        self.longdesc = unicode(kw['longdesc'])
+        self.shortdesc = unicode(kw['shortdesc'])
+        self.datatype = unicode(kw['datatype'])
+        self.questionaire_id = int(kw['questionaire_id'])
+
+class Answer(object):
+    __storm_table__ = "answers"
+    id = Int(primary=True)
+    answer = Unicode()
+    received = DateTime()
+    question_id = Int()
+    question = Reference(question_id, Question.id)
+    people_id = Int()
+    person = Reference(people_id, Person.id)
+
+    def __init__(self, **kw):
+        self.answer = unicode(kw['answer'])
+        self.received = unicode(kw['received']) # have this change to the right type
+
+# define the many to one relationships
+Person.answers = ReferenceSet(Person.id, Answer.id)
+Questionaire.questions = ReferenceSet(Questionaire.id, Question.questionaire_id)
+Question.answers = ReferenceSet(Question.id, Answer.id)
+
diff --git a/rapidsms.py b/rapidsms.py
new file mode 100755 (executable)
index 0000000..92a441b
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/env python
+
+# UNICEF SMS Phone Application
+#
+# Copyright (C) 2007 Benjamin Mako Hill <mako@atdot.cc>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the Affero General Public License as published
+# by the Free Software Foundation, either version 1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the Affero General Public License
+# along with this program.  If not, see
+# <http://http://www.affero.org/oagpl.html>.
+
+import web
+import sys, os, re
+
+from db import *
+
+# recalculate the path based on the location of the file
+from jinja import Environment, FileSystemLoader
+jinja_env = Environment(loader=FileSystemLoader(os.path.dirname(__file__) + "/templates/"))
+
+def render(filename, vars={}):
+    """Render Jinja Templates"""
+
+    web.header("Content-Type","text/html; charset=utf-8")
+
+    tmpl = jinja_env.get_template(filename)
+
+    vars['ctx'] = web.ctx
+    vars['home'] = web.ctx.home
+    vars['homepath'] = web.ctx.homepath
+    #vars['session'] = web.ctx.environ['com.saddi.service'].session
+
+    print tmpl.render(vars)
+
+# the url map for the application
+urls = ( '/?', 'index',
+         '/?new_name', 'new_name',
+         '/?show_names', 'show_names',
+         '/?questionaires', 'questionaires',
+         '/?edit_questions/(\d+)', 'edit_questions')
+
+class index:
+    def GET(self):
+        render('index.tmpl')
+
+class new_name:
+    def GET(self):
+        render('new_name.tmpl')
+
+    def POST(self):
+        input = web.input()
+
+        errors = []
+        if not input.name:
+            errors.append('You must provide a name.')
+        if not input.phone_number:
+            errors.append('You must provide a phone number.')
+        elif not re.match(r'[0-9\+\- ]', input.phone_number):
+            errors.append('Your phone number must be only numbers.')
+
+        if not errors:
+            for error in self.validate_phone(input.phone_number):
+                errors.append(error)
+        if errors:
+            render('new_name.tmpl', locals())
+        else:
+            # try to save it to the database
+            new_person = Person(name=input.name, phone_number=input.phone_number)
+            # TODO make this work
+            # errors.append('There was an error saving to the database')
+            store.add(new_person)
+            store.commit()
+            render('success.tmpl', locals())
+
+    def validate_phone(self, phone_number):
+        errors = []
+        if len(phone_number) < 7:
+            errors.append('Your phone number is too short')
+        else:
+            people = store.find(Person)
+            for person in people:
+                if phone_number[-7:] == person.phone_number[-7:]:
+                    errors.append('That phone number already exists in our datbase.')
+                    break
+        
+        return(errors)
+
+class show_names:
+    def GET(self):
+        people = store.find(Person)
+        render('show_names.tmpl', locals())
+
+
+class questionaires:
+    def GET(self):
+        questionaires = store.find(Questionaire)
+        render('questionaires.tmpl', locals())
+
+    def POST(self):
+        questionaires = store.find(Questionaire)
+        input = web.input()
+
+        errors = []
+        if not input.code:
+            errors.append('You must enter a code.')
+
+        if not errors:
+            new_questionaire = Questionaire(code=input.code,
+                description=input.description)
+            store.add(new_questionaire)
+            store.commit()
+            
+        render('questionaires.tmpl', locals())
+
+class edit_questions:
+    def GET(self, id):
+        input = web.input()
+        questionaire = store.get(Questionaire, int(id))
+        render('edit_questions.tmpl', locals())
+
+    def POST(self, id):
+        input = web.input()
+        questionaire = store.get(Questionaire, int(id))
+
+        errors = []
+        if not input.longdesc:
+            errors.append('You must have a long description.')
+
+        code = self.__get_code(questionaire)
+
+        if not errors:
+            new_question = Question(code=code,
+                                    shortdesc=input.shortdesc,
+                                    datatype=input.datatype,
+                                    longdesc=input.longdesc,
+                                    questionaire_id=questionaire.id)
+            store.add(new_question)
+            store.commit()
+            
+        render('edit_questions.tmpl', locals())
+
+    def __get_code(self, questionaire):
+        """Return a new code that is higher than the others."""
+
+        codes = list(int(q.code) for q in questionaire.questions)
+        return(len(codes) + 1)
+
+class test:
+    def GET(self):
+        render('test.tmpl')
+
+    def POST(self):
+        render('test_sent.tmpl')
+
+class raw_sms_in:
+    def POST(self):
+        #TODO magic to parse sms
+        pass
+
+
+web.webapi.internalerror = web.debugerror
+if __name__ == "__main__":
+    web.run(urls, globals(), web.reloader)
+
+application = web.wsgifunc(web.webpyfunc(urls, globals()))
+
diff --git a/templates/_error.tmpl b/templates/_error.tmpl
new file mode 100644 (file)
index 0000000..2c07afe
--- /dev/null
@@ -0,0 +1,9 @@
+<div id="errormsg">
+<h3>Error</h3>
+
+<ul>
+{% for error in errors %}
+<li>{{error}}</li>
+{% endfor %}
+</ul>
+</div>
diff --git a/templates/edit_questions.tmpl b/templates/edit_questions.tmpl
new file mode 100644 (file)
index 0000000..9f52033
--- /dev/null
@@ -0,0 +1,46 @@
+{% extends 'layout.tmpl' %}
+
+{% block content %}
+<h2>List of Questions for {{questionaire.description}}</h2>
+
+
+<table>
+<tr>
+<th>Code</th>
+<th>Long Description</th>
+<th>Short Description</th>
+<th>Data Type</th>
+</tr>
+
+{% for q in questionaire.questions %}
+<tr>
+<td>{{q.code}}</td>
+<td>{{q.longdesc}}</td>
+<td>{{q.shortdesc}}</td>
+<td>{{q.datatype}}</td>
+</tr>
+{% endfor %}
+</table>
+
+<h2>New Question</h2>
+
+<form method="POST"
+      action="{{homepath}}/edit_questions/{{questionaire.id}}">
+
+<p><label for="shortdesc">Short Description</label>
+<input name="shortdesc" type="text" size="15" value="{{shortdesc}}"  /></p>
+
+<p><label for="longdesc">Long Description</label>
+<input name="longdesc" type="text" size="50" value="{{longdesc}}"  /></p>
+
+<p><label for="datatype">Data Type</lable>
+<select name="datatype">
+<option value="number" />Number
+<option value="word" />Word
+<option value="bool" />Yes/No
+</select></p>
+
+<input type="submit" value="Save" />
+</form>
+
+{% endblock %}
diff --git a/templates/index.tmpl b/templates/index.tmpl
new file mode 100644 (file)
index 0000000..98a1e15
--- /dev/null
@@ -0,0 +1,10 @@
+{% extends 'layout.tmpl' %}
+
+{% block content %}
+
+<ul>
+<li><a href="{{homepath}}/new_name">Enter a new name/phone number</a></li>
+<li><a href="{{homepath}}/show_names">Display names and phone numbers</a></li>
+<li><a href="{{homepath}}/questionaires">Display questionaires</a></li>
+</ul>
+{% endblock %}
diff --git a/templates/layout.tmpl b/templates/layout.tmpl
new file mode 100644 (file)
index 0000000..cc48dbe
--- /dev/null
@@ -0,0 +1,12 @@
+<html>
+<head>
+<title>UNICEF Phone Application</title>
+</head>
+<body>
+<h1>UNICEF Phone Application</h1>
+<div id="content">
+{% block content %}
+{% endblock %}
+</div>
+</body>
+</html>
diff --git a/templates/new_name.tmpl b/templates/new_name.tmpl
new file mode 100644 (file)
index 0000000..51546d0
--- /dev/null
@@ -0,0 +1,21 @@
+{% extends 'layout.tmpl' %}
+
+{% block content %}
+<h2>Enter Name/Phone Number</h2>
+
+{% if errors %}
+{% include '_error.tmpl' %}
+{% endif %}
+
+<form method="POST" action="{{homepath}}/new_name">
+
+<p><label>Name</label>
+<input name="name" type="text" size="50" value="{{input.name}}" /></p>
+
+<p><label>Phone Number</label>
+<input name="phone_number" type="text" size="50"
+       value="{{input.phone_number}}" /></p>
+
+<input type="submit" value="Save" />
+</form>
+{% endblock %}
diff --git a/templates/questionaires.tmpl b/templates/questionaires.tmpl
new file mode 100644 (file)
index 0000000..ba8ae24
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends 'layout.tmpl' %}
+
+{% block content %}
+<h2>List of Questionaires</h2>
+
+<table>
+<tr>
+<th>Code</th>
+<th>Description</th>
+<th>Num. Questions</th>
+<th></th>
+</tr>
+
+{% for q in questionaires %}
+<tr>
+<td>{{q.code}}</td>
+<td>{{q.description}}</td>
+<td>{{len(q.questions) or 0}}</td>
+<td><a href="{{homepath}}/edit_questions/{{q.id}}">edit</a></td>
+</tr>
+{% endfor %}
+</table>
+
+<h2>New Questionaire</h2>
+
+<form method="POST" action="{{homepath}}/questionaires">
+<p><label for="code">Code</label>
+<input name="code" type="text" size="3" value="{{code}}"  /></p>
+
+<p><label for="description">Description</label>
+<input name="description" type="text" size="50" value="{{description}}"  /></p>
+
+<input type="submit" value="Save" />
+</form>
+
+{% endblock %}
diff --git a/templates/show_names.tmpl b/templates/show_names.tmpl
new file mode 100644 (file)
index 0000000..3308a52
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends 'layout.tmpl' %}
+
+{% block content %}
+<h2>List of Registered Phone Numbers</h2>
+
+<table>
+<tr>
+<th>Name</th>
+<th>Phone Number</th>
+</tr>
+
+{% for person in people %}
+<tr>
+<td>{{person.name}}</td>
+<td>{{person.phone_number}}</td>
+</tr>
+{% endfor %}
+</table>
+
+{% endblock %}
diff --git a/templates/success.tmpl b/templates/success.tmpl
new file mode 100644 (file)
index 0000000..46acb75
--- /dev/null
@@ -0,0 +1,9 @@
+{% extends 'layout.tmpl' %}
+
+{% block content %}
+<h2>Success!</h2>
+
+Successfully entered name <strong>{{input.name}}</strong> for phone
+number <strong>{{input.phone_number}}</strong>.
+
+{% endblock %}

Benjamin Mako Hill || Want to submit a patch?