source: trunk/call/call.py@ 460

Last change on this file since 460 was 460, checked in by Nicholas Riley, 16 years ago

Simple Google App Engine project - client for Vonage Click2Call.

File size: 5.3 KB
Line 
1from google.appengine.api import urlfetch, users
2from google.appengine.ext import db
3from google.appengine.ext.webapp import RequestHandler, WSGIApplication, template
4from google.appengine.ext.db import djangoforms
5from urllib import urlencode
6from wsgiref.handlers import CGIHandler
7
8try:
9 from django import newforms as forms
10except ImportError:
11 from django import forms
12
13try:
14 from django.contrib.localflavor.us.forms import USPhoneNumberField
15except ImportError:
16 from django.contrib.localflavor.usa.forms import USPhoneNumberField
17
18import logging, os, re
19
20logging.getLogger().setLevel(logging.DEBUG)
21
22CLICK2CALL_URL = 'https://secure.click2callu.com/tpcc/makecall'
23
24def vonageize_number(number):
25 number = re.sub(r'\D', '', number)
26 if len(number) == 0:
27 raise forms.ValidationError('Please enter a telephone number.')
28 if number[0] == '1':
29 number = number[1:]
30 if len(number) != 10:
31 raise forms.ValidationError('Please enter a 10-digit US telephone number.')
32 return '1' + number
33
34class VonageUSNumberProperty(db.PhoneNumberProperty):
35 def validate(self, value):
36 return vonageize_number(value)
37
38class VonageNumber(db.Model):
39 label = db.StringProperty()
40 username = db.StringProperty(required=True)
41 password = db.StringProperty(required=True)
42 number = VonageUSNumberProperty(required=True)
43
44class AllowedUser(db.Model):
45 user = db.UserProperty(required=True)
46
47def login_required(f):
48 def handle(self):
49 user = users.get_current_user()
50 if user is None:
51 return self.redirect(users.create_login_url(self.request.uri))
52 if AllowedUser.gql('WHERE user = :1', user).get() is None:
53 if users.is_current_user_admin():
54 AllowedUser(user=user).put()
55 else:
56 email = user.email()
57 logging.error('Denied access to ' + email)
58 return self.unavailable(
59 'Your account <b>%s</b> does not have permission to use this service.' %
60 email)
61 return f(self)
62 return handle
63
64def admin_required(f):
65 def handle(self):
66 if not users.is_current_user_admin():
67 return self.unavailable('This function requires you to be an administrator.')
68 return f(self)
69 return handle
70
71class NumberForm(djangoforms.ModelForm):
72 password = forms.CharField(widget=forms.widgets.PasswordInput)
73 number = USPhoneNumberField(label='Vonage number')
74
75 class Meta:
76 model = VonageNumber
77 exclude = ['label']
78
79class BasePage(RequestHandler):
80 def render(self, **kw):
81 self.response.out.write(template.render(self.template_path, kw))
82
83 def unavailable(self, msg):
84 self.render(unavailable=msg,
85 logout_url=users.create_logout_url(self.request.uri))
86
87class NumbersPage(BasePage):
88 template_path = os.path.join(os.path.dirname(__file__), 'numbers.html')
89
90 @admin_required
91 def get(self):
92 self.render(form=NumberForm(instance=VonageNumber.all().get()))
93
94 @admin_required
95 def post(self):
96 form = NumberForm(data=self.request.POST,
97 instance=VonageNumber.all().get())
98 if not form.is_valid():
99 return self.render(form=form)
100 try:
101 form.save()
102 except Exception, e:
103 return self.render(form=form, error=e.message)
104 self.redirect('/')
105
106class MainPage(BasePage):
107 template_path = os.path.join(os.path.dirname(__file__), 'index.html')
108
109 def error(self, msg, **kw):
110 self.render(error=msg, tonumber=self.request.get('tonumber', ''), **kw)
111
112 def failure(self, msg):
113 logging.error('TPCC server error: ' + msg)
114 self.error('The Vonage Click2Call server could not complete your call.',
115 failure=msg)
116
117 def no_number(self):
118 if users.is_current_user_admin():
119 return self.redirect('/numbers')
120 self.unavailable('No phone numbers have been configured. Please ask an administrator to log in and add a phone number.')
121
122 @login_required
123 def get(self):
124 if VonageNumber.all().get() is None:
125 return self.no_number()
126 self.render()
127
128 @login_required
129 def post(self):
130 fromnumber = VonageNumber.all().get()
131 if fromnumber is None:
132 return self.no_number()
133
134 try:
135 tonumber = vonageize_number(self.request.get('tonumber', ''))
136 except Exception, e:
137 return self.error(e.message)
138
139 body = urlencode(
140 dict(username=fromnumber.username, password=fromnumber.password,
141 fromnumber=fromnumber.number, tonumber=tonumber))
142 try:
143 response = urlfetch.fetch(CLICK2CALL_URL + '?' + body)
144 # get random auth errors with body, urlfetch.POST)
145 except urlfetch.Error, e:
146 return self.failure(e.message)
147
148 if response.status_code != 200 or response.content[:3] != '000':
149 return self.failure(response.content)
150
151 logging.info('Dialing %s' % tonumber)
152 return self.render(
153 message='Dialing. Please answer your phone when it rings.')
154
155def main():
156 CGIHandler().run(WSGIApplication([('/', MainPage),
157 ('/numbers', NumbersPage)], debug=True))
158
159if __name__ == "__main__":
160 main()
Note: See TracBrowser for help on using the repository browser.