source: trunk/call/call.py @ 461

Last change on this file since 461 was 461, checked in by Nicholas Riley, 12 years ago

Use string continuations to improve readability.

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