-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathapp.py
159 lines (133 loc) · 6.48 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/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"
originals[0].save()
# this response is new, or changed!
# create a new ResponseInWaiting object
else:
ResponseInWaiting(
originator=msg,
text=resp,
type="A").save()
# find any remaining original responses, which
# were removed in the webui, and delete them
msg.responses.filter(type="O").delete()
# mark the incoming message as "handled", so
# it will be picked up by the responder_loop
msg.status = "H"
msg.save()
# 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 -----------------------------------------
self.info("[responder] Starting up...")
# interval to check for responding (in seconds)
response_interval = 10
# start a thread for responding
responder_thread = threading.Thread(
target=self.responder_loop,
args=(response_interval,))
responder_thread.daemon = True
responder_thread.start()
def parse (self, message):
"""Parse and annotate messages in the parse phase."""
pass
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
self.info("Queueing up %s for further handling" % message)
in_waiting = MessageInWaiting.from_message(message)
in_waiting.save()
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."""
pass
def stop (self):
"""Perform global app cleanup when the application is stopped."""
pass
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):
self.info("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"):
self.info("Responding to %s.", msg_in_waiting)
for response in msg_in_waiting.responses.all():
self.info("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)
self.router.outgoing(response_msg)
else:
# 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
msg_in_waiting.status="R"
msg_in_waiting.save()
# wait until it's time to check again
time.sleep(seconds)