Skip to content


Subversion checkout URL

You can clone with
Download ZIP
tree: fe406d8ab0
Fetching contributors…

Cannot retrieve contributors at this time

160 lines (133 sloc) 6.637 kB
#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4
import threading, time
from .models import *
from rapidsms.apps.base import AppBase
from rapidsms.messages import IncomingMessage, OutgoingMessage, ErrorMessage
from rapidsms.models import Connection
class App (AppBase):
'''The training app saves error messages in a queue before they go out
and waits for someone to either flag them as "ready to go out" or
override the default response by adding their own. This is meant
to be used in trainings, to provide live feedback via SMS'''
PRIORITY = "last"
def ajax_GET_pending_count(self, params):
# returns JUST the number of messages in waiting, which
# indicates whether anyone is waiting for attention
return MessageInWaiting.objects.filter(status="P").count()
def ajax_GET_pending(self, params):
# return all of the messages in waiting,
# each of which contain their responses
return MessageInWaiting.objects.filter(status="P")
def ajax_POST_accept(self, params, form):
msg = MessageInWaiting.objects.get(pk=int(form["msg_pk"]))
# there might be one response, or there
# might be many -- make it iterable
responses = form.get("responses", [])
if not type(responses) == list:
responses = [responses]
for resp in responses:
# if this response was present in the original
# set, and remains unchanged, just "confirm" it
originals = msg.responses.filter(text=resp, type="O")
if len(originals):
originals[0].type = "C"
# this response is new, or changed!
# create a new ResponseInWaiting object
# find any remaining original responses, which
# were removed in the webui, and delete them
# mark the incoming message as "handled", so
# it will be picked up by the responder_loop
msg.status = "H"
# TODO: send something more useful
# back to the browser to confirm
return True
def start (self):
"""Configure your app in the start phase."""
# Start the Responder Thread -----------------------------------------"[responder] Starting up...")
# interval to check for responding (in seconds)
response_interval = 10
# start a thread for responding
responder_thread = threading.Thread(
responder_thread.daemon = True
def parse (self, message):
"""Parse and annotate messages in the parse phase."""
def cleanup (self, message):
if (self._requires_action(message)):
# create the message in waiting object and response
# in waiting objects for this, and assume someone will
# deal with them.
# This app should never be running in a production environment
# unless someone is very carefully tracking the incoming messages"Queueing up %s for further handling" % message)
in_waiting = MessageInWaiting.from_message(message)
for response in message.responses:
resp_in_waiting = ResponseInWaiting.objects.create(type='O', originator=in_waiting,text=response.text)
# blank out the responses, they will be sent by the responder thread
# after moderation
message.responses = []
def outgoing (self, message):
"""Handle outgoing message notifications."""
def stop (self):
"""Perform global app cleanup when the application is stopped."""
def _requires_action(self, message):
# this message requires action if and only if
# 1. No response is indicated "ok"
# "ok" by any app should override any other errors
# 2. Some message is indicated "app error" or "generic errror"
# "none" is the default, and we haven't updated every app to correctly
# say "ok" on real responses, so only do this for known errors
to_return = False
for response in message.responses:
if isinstance(response, ErrorMessage):
to_return = True
elif isinstance(response, OutgoingMessage):
return False
return to_return
# Responder Thread --------------------
def responder_loop(self, seconds=10):"Starting responder...")
while True:
# look for any new handled messages
# in the database, and send the responses
for msg_in_waiting in MessageInWaiting.objects.filter(status="H"):"Responding to %s.", msg_in_waiting)
for response in msg_in_waiting.responses.all():"Response: %s.", response)
# only send confirmed or added responses
if response.type != "O":
db_connection = msg_in_waiting.get_connection()
if db_connection is not None:
db_backend = db_connection.backend
# we need to get the real backend from the router
# to properly send it
real_backend = self.router.get_backend(db_backend.slug)
if real_backend:
connection = Connection(real_backend, db_connection.identity)
response_msg = OutgoingMessage(connection, response.text)
# TODO: should we fail harder here? This will permanently
# disable responses to this message which is bad.
self.error("Can't find backend %s. Messages will not be sent")
# mark the original message as responded to
# wait until it's time to check again
Jump to Line
Something went wrong with that request. Please try again.