Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Masked Phone Numbers with Python and Flask


This Flask(link takes you to an external page) sample application is modeled after the rental experience created by AirBnB(link takes you to an external page) but with more Klingons(link takes you to an external page).

Host users can offer rental properties which other guest users can reserve. The guest and the host can then anonymously communicate via a disposable Twilio phone number created just for a reservation. In this tutorial, we'll show you the key bits of code to make this work.

To run this sample app yourself, download the code and follow the instructions on GitHub(link takes you to an external page).

(warning)

Warning

Read how Lyft uses masked phone numbers to let customers securely contact drivers.(link takes you to an external page)


Create a Reservation

create-a-reservation page anchor

The first step in connecting a guest and a host is creating a reservation.

We handle here a submission form for a new reservation. After we save the reservation to the database, we send the host an SMS message asking them to accept or reject the reservation.

Create A New Reservation

create-a-new-reservation page anchor

_232
from twilio.twiml.messaging_response import MessagingResponse
_232
from twilio.twiml.voice_response import VoiceResponse
_232
_232
from airtng_flask import db, bcrypt, app, login_manager
_232
from flask import g, request
_232
from flask.ext.login import login_user, logout_user, current_user, login_required
_232
_232
from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
_232
ReservationConfirmationForm, ExchangeForm
_232
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
_232
from airtng_flask.models import init_models_module
_232
_232
init_models_module(db, bcrypt, app)
_232
_232
from airtng_flask.models.user import User
_232
from airtng_flask.models.vacation_property import VacationProperty
_232
from airtng_flask.models.reservation import Reservation
_232
_232
_232
@app.route('/', methods=["GET", "POST"])
_232
@app.route('/register', methods=["GET", "POST"])
_232
def register():
_232
form = RegisterForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
_232
if User.query.filter(User.email == form.email.data).count() > 0:
_232
form.email.errors.append("Email address already in use.")
_232
return view('register', form)
_232
_232
user = User(
_232
name=form.name.data,
_232
email=form.email.data,
_232
password=form.password.data,
_232
phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
_232
area_code=str(form.phone_number.data)[0:3])
_232
_232
db.session.add(user)
_232
db.session.commit()
_232
login_user(user, remember=True)
_232
_232
return redirect_to('home')
_232
else:
_232
return view('register', form)
_232
_232
return view('register', form)
_232
_232
_232
@app.route('/login', methods=["GET", "POST"])
_232
def login():
_232
form = LoginForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
candidate_user = User.query.filter(User.email == form.email.data).first()
_232
_232
if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
_232
form.password.data):
_232
form.password.errors.append("Invalid credentials.")
_232
return view('login', form)
_232
_232
login_user(candidate_user, remember=True)
_232
return redirect_to('home')
_232
return view('login', form)
_232
_232
_232
@app.route('/logout', methods=["POST"])
_232
@login_required
_232
def logout():
_232
logout_user()
_232
return redirect_to('home')
_232
_232
_232
@app.route('/home', methods=["GET"])
_232
@login_required
_232
def home():
_232
return view('home')
_232
_232
_232
@app.route('/properties', methods=["GET"])
_232
@login_required
_232
def properties():
_232
vacation_properties = VacationProperty.query.all()
_232
return view_with_params('properties', vacation_properties=vacation_properties)
_232
_232
_232
@app.route('/properties/new', methods=["GET", "POST"])
_232
@login_required
_232
def new_property():
_232
form = VacationPropertyForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
host = User.query.get(current_user.get_id())
_232
_232
property = VacationProperty(form.description.data, form.image_url.data, host)
_232
db.session.add(property)
_232
db.session.commit()
_232
return redirect_to('properties')
_232
_232
return view('property_new', form)
_232
_232
_232
@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
_232
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
_232
@login_required
_232
def new_reservation(property_id):
_232
vacation_property = None
_232
form = ReservationForm()
_232
form.property_id.data = property_id
_232
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
guest = User.query.get(current_user.get_id())
_232
_232
vacation_property = VacationProperty.query.get(form.property_id.data)
_232
reservation = Reservation(form.message.data, vacation_property, guest)
_232
db.session.add(reservation)
_232
db.session.commit()
_232
_232
reservation.notify_host()
_232
_232
return redirect_to('properties')
_232
_232
if property_id is not None:
_232
vacation_property = VacationProperty.query.get(property_id)
_232
_232
return view_with_params('reservation', vacation_property=vacation_property, form=form)
_232
_232
_232
@app.route('/reservations', methods=["GET"])
_232
@login_required
_232
def reservations():
_232
user = User.query.get(current_user.get_id())
_232
_232
reservations_as_host = Reservation.query \
_232
.filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
_232
.join(VacationProperty) \
_232
.filter(Reservation.vacation_property_id == VacationProperty.id) \
_232
.all()
_232
_232
reservations_as_guest = user.reservations
_232
_232
return view_with_params('reservations',
_232
reservations_as_guest=reservations_as_guest,
_232
reservations_as_host=reservations_as_host)
_232
_232
_232
@app.route('/reservations/confirm', methods=["POST"])
_232
def confirm_reservation():
_232
form = ReservationConfirmationForm()
_232
sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."
_232
_232
user = User.query.filter(User.phone_number == form.From.data).first()
_232
reservation = Reservation \
_232
.query \
_232
.filter(Reservation.status == 'pending'
_232
and Reservation.vacation_property.host.id == user.id) \
_232
.first()
_232
_232
if reservation is not None:
_232
_232
if 'yes' in form.Body.data or 'accept' in form.Body.data:
_232
reservation.confirm()
_232
reservation.buy_number(user.area_code)
_232
else:
_232
reservation.reject()
_232
_232
db.session.commit()
_232
_232
sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
_232
reservation.notify_guest()
_232
_232
return twiml(_respond_message(sms_response_text))
_232
_232
_232
@app.route('/exchange/sms', methods=["POST"])
_232
def exchange_sms():
_232
form = ExchangeForm()
_232
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = MessagingResponse()
_232
response.message(form.Body.data, to=outgoing_number)
_232
return twiml(response)
_232
_232
_232
@app.route('/exchange/voice', methods=["POST"])
_232
def exchange_voice():
_232
form = ExchangeForm()
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = VoiceResponse()
_232
response.play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_232
response.dial(outgoing_number)
_232
return twiml(response)
_232
_232
_232
# controller utils
_232
@app.before_request
_232
def before_request():
_232
g.user = current_user
_232
uri_pattern = request.url_rule
_232
if current_user.is_authenticated and (
_232
uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
_232
redirect_to('home')
_232
_232
_232
@login_manager.user_loader
_232
def load_user(id):
_232
try:
_232
return User.query.get(id)
_232
except:
_232
return None
_232
_232
_232
def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
_232
reservation = Reservation.query \
_232
.filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
_232
.first()
_232
_232
if reservation is None:
_232
raise Exception('Reservation not found for {0}'.format(incoming_phone_number))
_232
_232
if reservation.guest.phone_number == incoming_phone_number:
_232
return reservation.vacation_property.host.phone_number
_232
_232
return reservation.guest.phone_number
_232
_232
_232
def _respond_message(message):
_232
response = MessagingResponse()
_232
response.message(message)
_232
return response

Part of our reservation system is receiving reservation requests from potential renters. However, these reservations need to be confirmed. Let's see how we would handle this step.


Before the reservation is finalized, the host needs to confirm that the property was reserved. Learn how to automate this process on our first AirTNG tutorial Workflow Automation.


_232
from twilio.twiml.messaging_response import MessagingResponse
_232
from twilio.twiml.voice_response import VoiceResponse
_232
_232
from airtng_flask import db, bcrypt, app, login_manager
_232
from flask import g, request
_232
from flask.ext.login import login_user, logout_user, current_user, login_required
_232
_232
from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
_232
ReservationConfirmationForm, ExchangeForm
_232
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
_232
from airtng_flask.models import init_models_module
_232
_232
init_models_module(db, bcrypt, app)
_232
_232
from airtng_flask.models.user import User
_232
from airtng_flask.models.vacation_property import VacationProperty
_232
from airtng_flask.models.reservation import Reservation
_232
_232
_232
@app.route('/', methods=["GET", "POST"])
_232
@app.route('/register', methods=["GET", "POST"])
_232
def register():
_232
form = RegisterForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
_232
if User.query.filter(User.email == form.email.data).count() > 0:
_232
form.email.errors.append("Email address already in use.")
_232
return view('register', form)
_232
_232
user = User(
_232
name=form.name.data,
_232
email=form.email.data,
_232
password=form.password.data,
_232
phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
_232
area_code=str(form.phone_number.data)[0:3])
_232
_232
db.session.add(user)
_232
db.session.commit()
_232
login_user(user, remember=True)
_232
_232
return redirect_to('home')
_232
else:
_232
return view('register', form)
_232
_232
return view('register', form)
_232
_232
_232
@app.route('/login', methods=["GET", "POST"])
_232
def login():
_232
form = LoginForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
candidate_user = User.query.filter(User.email == form.email.data).first()
_232
_232
if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
_232
form.password.data):
_232
form.password.errors.append("Invalid credentials.")
_232
return view('login', form)
_232
_232
login_user(candidate_user, remember=True)
_232
return redirect_to('home')
_232
return view('login', form)
_232
_232
_232
@app.route('/logout', methods=["POST"])
_232
@login_required
_232
def logout():
_232
logout_user()
_232
return redirect_to('home')
_232
_232
_232
@app.route('/home', methods=["GET"])
_232
@login_required
_232
def home():
_232
return view('home')
_232
_232
_232
@app.route('/properties', methods=["GET"])
_232
@login_required
_232
def properties():
_232
vacation_properties = VacationProperty.query.all()
_232
return view_with_params('properties', vacation_properties=vacation_properties)
_232
_232
_232
@app.route('/properties/new', methods=["GET", "POST"])
_232
@login_required
_232
def new_property():
_232
form = VacationPropertyForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
host = User.query.get(current_user.get_id())
_232
_232
property = VacationProperty(form.description.data, form.image_url.data, host)
_232
db.session.add(property)
_232
db.session.commit()
_232
return redirect_to('properties')
_232
_232
return view('property_new', form)
_232
_232
_232
@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
_232
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
_232
@login_required
_232
def new_reservation(property_id):
_232
vacation_property = None
_232
form = ReservationForm()
_232
form.property_id.data = property_id
_232
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
guest = User.query.get(current_user.get_id())
_232
_232
vacation_property = VacationProperty.query.get(form.property_id.data)
_232
reservation = Reservation(form.message.data, vacation_property, guest)
_232
db.session.add(reservation)
_232
db.session.commit()
_232
_232
reservation.notify_host()
_232
_232
return redirect_to('properties')
_232
_232
if property_id is not None:
_232
vacation_property = VacationProperty.query.get(property_id)
_232
_232
return view_with_params('reservation', vacation_property=vacation_property, form=form)
_232
_232
_232
@app.route('/reservations', methods=["GET"])
_232
@login_required
_232
def reservations():
_232
user = User.query.get(current_user.get_id())
_232
_232
reservations_as_host = Reservation.query \
_232
.filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
_232
.join(VacationProperty) \
_232
.filter(Reservation.vacation_property_id == VacationProperty.id) \
_232
.all()
_232
_232
reservations_as_guest = user.reservations
_232
_232
return view_with_params('reservations',
_232
reservations_as_guest=reservations_as_guest,
_232
reservations_as_host=reservations_as_host)
_232
_232
_232
@app.route('/reservations/confirm', methods=["POST"])
_232
def confirm_reservation():
_232
form = ReservationConfirmationForm()
_232
sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."
_232
_232
user = User.query.filter(User.phone_number == form.From.data).first()
_232
reservation = Reservation \
_232
.query \
_232
.filter(Reservation.status == 'pending'
_232
and Reservation.vacation_property.host.id == user.id) \
_232
.first()
_232
_232
if reservation is not None:
_232
_232
if 'yes' in form.Body.data or 'accept' in form.Body.data:
_232
reservation.confirm()
_232
reservation.buy_number(user.area_code)
_232
else:
_232
reservation.reject()
_232
_232
db.session.commit()
_232
_232
sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
_232
reservation.notify_guest()
_232
_232
return twiml(_respond_message(sms_response_text))
_232
_232
_232
@app.route('/exchange/sms', methods=["POST"])
_232
def exchange_sms():
_232
form = ExchangeForm()
_232
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = MessagingResponse()
_232
response.message(form.Body.data, to=outgoing_number)
_232
return twiml(response)
_232
_232
_232
@app.route('/exchange/voice', methods=["POST"])
_232
def exchange_voice():
_232
form = ExchangeForm()
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = VoiceResponse()
_232
response.play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_232
response.dial(outgoing_number)
_232
return twiml(response)
_232
_232
_232
# controller utils
_232
@app.before_request
_232
def before_request():
_232
g.user = current_user
_232
uri_pattern = request.url_rule
_232
if current_user.is_authenticated and (
_232
uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
_232
redirect_to('home')
_232
_232
_232
@login_manager.user_loader
_232
def load_user(id):
_232
try:
_232
return User.query.get(id)
_232
except:
_232
return None
_232
_232
_232
def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
_232
reservation = Reservation.query \
_232
.filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
_232
.first()
_232
_232
if reservation is None:
_232
raise Exception('Reservation not found for {0}'.format(incoming_phone_number))
_232
_232
if reservation.guest.phone_number == incoming_phone_number:
_232
return reservation.vacation_property.host.phone_number
_232
_232
return reservation.guest.phone_number
_232
_232
_232
def _respond_message(message):
_232
response = MessagingResponse()
_232
response.message(message)
_232
return response

Once the reservation is confirmed, we need to purchase a Twilio number that the guest and host can use to communicate.


Purchase a Twilio Number

purchase-a-twilio-number page anchor

Here we use the Twilio Python helper library to search for and buy a new phone number to associate with the reservation. We start by searching for a number with a local area code - if we can't find one, we take any available phone number in that country.

When we buy the number, we designate a TwiML Application that will handle webhook(link takes you to an external page) requests when the new number receives an incoming call or text.

We then save the new phone number on our Reservation model. Therefore when our app receives calls or messages to this number we know which reservation the call or text belongs to.

airtng_flask/models/reservation.py


_86
from airtng_flask.models import app_db, auth_token, account_sid, phone_number, application_sid
_86
from flask import render_template
_86
from twilio.rest import Client
_86
_86
DB = app_db()
_86
_86
_86
class Reservation(DB.Model):
_86
__tablename__ = "reservations"
_86
_86
id = DB.Column(DB.Integer, primary_key=True)
_86
message = DB.Column(DB.String, nullable=False)
_86
status = DB.Column(DB.Enum('pending', 'confirmed', 'rejected', name='reservation_status_enum'),
_86
default='pending')
_86
anonymous_phone_number = DB.Column(DB.String, nullable=True)
_86
guest_id = DB.Column(DB.Integer, DB.ForeignKey('users.id'))
_86
vacation_property_id = DB.Column(DB.Integer, DB.ForeignKey('vacation_properties.id'))
_86
guest = DB.relationship("User", back_populates="reservations")
_86
vacation_property = DB.relationship("VacationProperty", back_populates="reservations")
_86
_86
def __init__(self, message, vacation_property, guest):
_86
self.message = message
_86
self.guest = guest
_86
self.vacation_property = vacation_property
_86
self.status = 'pending'
_86
_86
def confirm(self):
_86
self.status = 'confirmed'
_86
_86
def reject(self):
_86
self.status = 'rejected'
_86
_86
def __repr__(self):
_86
return '<Reservation {0}>'.format(self.id)
_86
_86
def notify_host(self):
_86
self._send_message(self.vacation_property.host.phone_number,
_86
render_template('messages/sms_host.txt',
_86
name=self.guest.name,
_86
description=self.vacation_property.description,
_86
message=self.message))
_86
_86
def notify_guest(self):
_86
self._send_message(self.guest.phone_number,
_86
render_template('messages/sms_guest.txt',
_86
description=self.vacation_property.description,
_86
status=self.status))
_86
_86
def buy_number(self, area_code):
_86
numbers = self._get_twilio_client().available_phone_numbers("US") \
_86
.local \
_86
.list(area_code=area_code,
_86
sms_enabled=True,
_86
voice_enabled=True)
_86
_86
if numbers:
_86
number = self._purchase_number(numbers[0])
_86
self.anonymous_phone_number = number
_86
return number
_86
else:
_86
numbers = self._get_twilio_client().available_phone_numbers("US") \
_86
.local \
_86
.list(sms_enabled=True, voice_enabled=True)
_86
_86
if numbers:
_86
number = self._purchase_number(numbers[0])
_86
self.anonymous_phone_number = number
_86
return number
_86
_86
return None
_86
_86
def _purchase_number(self, number):
_86
return self._get_twilio_client().incoming_phone_numbers \
_86
.create(sms_application_sid=application_sid(),
_86
voice_application_sid=application_sid(),
_86
phone_number=number) \
_86
.phone_number
_86
_86
def _get_twilio_client(self):
_86
return Client(account_sid(), auth_token())
_86
_86
def _send_message(self, to, message):
_86
self._get_twilio_client().messages \
_86
.create(to,
_86
from_=phone_number(),
_86
body=message)

Now that each reservation has a Twilio Phone Number, we can see how the application will look up reservations as guest or host calls come in.


When someone messages or calls one of the Twilio numbers (that we purchased for a reservation) Twilio makes a request to the URL you set in the TwiML app. That request will contain some helpful metadata:

  • The incoming_phone_number number that originally called or sent an SMS.
  • The anonymous_phone_number Twilio number that triggered this request.

Take a look at Twilio's SMS Documentation and Twilio's Voice Documentation for a full list of the parameters you can use.

In our code we use the To parameter sent by Twilio to find a reservation that has the number we bought stored in it, as this is the number both hosts and guests will call and send SMSs to.


_232
from twilio.twiml.messaging_response import MessagingResponse
_232
from twilio.twiml.voice_response import VoiceResponse
_232
_232
from airtng_flask import db, bcrypt, app, login_manager
_232
from flask import g, request
_232
from flask.ext.login import login_user, logout_user, current_user, login_required
_232
_232
from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
_232
ReservationConfirmationForm, ExchangeForm
_232
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
_232
from airtng_flask.models import init_models_module
_232
_232
init_models_module(db, bcrypt, app)
_232
_232
from airtng_flask.models.user import User
_232
from airtng_flask.models.vacation_property import VacationProperty
_232
from airtng_flask.models.reservation import Reservation
_232
_232
_232
@app.route('/', methods=["GET", "POST"])
_232
@app.route('/register', methods=["GET", "POST"])
_232
def register():
_232
form = RegisterForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
_232
if User.query.filter(User.email == form.email.data).count() > 0:
_232
form.email.errors.append("Email address already in use.")
_232
return view('register', form)
_232
_232
user = User(
_232
name=form.name.data,
_232
email=form.email.data,
_232
password=form.password.data,
_232
phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
_232
area_code=str(form.phone_number.data)[0:3])
_232
_232
db.session.add(user)
_232
db.session.commit()
_232
login_user(user, remember=True)
_232
_232
return redirect_to('home')
_232
else:
_232
return view('register', form)
_232
_232
return view('register', form)
_232
_232
_232
@app.route('/login', methods=["GET", "POST"])
_232
def login():
_232
form = LoginForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
candidate_user = User.query.filter(User.email == form.email.data).first()
_232
_232
if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
_232
form.password.data):
_232
form.password.errors.append("Invalid credentials.")
_232
return view('login', form)
_232
_232
login_user(candidate_user, remember=True)
_232
return redirect_to('home')
_232
return view('login', form)
_232
_232
_232
@app.route('/logout', methods=["POST"])
_232
@login_required
_232
def logout():
_232
logout_user()
_232
return redirect_to('home')
_232
_232
_232
@app.route('/home', methods=["GET"])
_232
@login_required
_232
def home():
_232
return view('home')
_232
_232
_232
@app.route('/properties', methods=["GET"])
_232
@login_required
_232
def properties():
_232
vacation_properties = VacationProperty.query.all()
_232
return view_with_params('properties', vacation_properties=vacation_properties)
_232
_232
_232
@app.route('/properties/new', methods=["GET", "POST"])
_232
@login_required
_232
def new_property():
_232
form = VacationPropertyForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
host = User.query.get(current_user.get_id())
_232
_232
property = VacationProperty(form.description.data, form.image_url.data, host)
_232
db.session.add(property)
_232
db.session.commit()
_232
return redirect_to('properties')
_232
_232
return view('property_new', form)
_232
_232
_232
@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
_232
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
_232
@login_required
_232
def new_reservation(property_id):
_232
vacation_property = None
_232
form = ReservationForm()
_232
form.property_id.data = property_id
_232
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
guest = User.query.get(current_user.get_id())
_232
_232
vacation_property = VacationProperty.query.get(form.property_id.data)
_232
reservation = Reservation(form.message.data, vacation_property, guest)
_232
db.session.add(reservation)
_232
db.session.commit()
_232
_232
reservation.notify_host()
_232
_232
return redirect_to('properties')
_232
_232
if property_id is not None:
_232
vacation_property = VacationProperty.query.get(property_id)
_232
_232
return view_with_params('reservation', vacation_property=vacation_property, form=form)
_232
_232
_232
@app.route('/reservations', methods=["GET"])
_232
@login_required
_232
def reservations():
_232
user = User.query.get(current_user.get_id())
_232
_232
reservations_as_host = Reservation.query \
_232
.filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
_232
.join(VacationProperty) \
_232
.filter(Reservation.vacation_property_id == VacationProperty.id) \
_232
.all()
_232
_232
reservations_as_guest = user.reservations
_232
_232
return view_with_params('reservations',
_232
reservations_as_guest=reservations_as_guest,
_232
reservations_as_host=reservations_as_host)
_232
_232
_232
@app.route('/reservations/confirm', methods=["POST"])
_232
def confirm_reservation():
_232
form = ReservationConfirmationForm()
_232
sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."
_232
_232
user = User.query.filter(User.phone_number == form.From.data).first()
_232
reservation = Reservation \
_232
.query \
_232
.filter(Reservation.status == 'pending'
_232
and Reservation.vacation_property.host.id == user.id) \
_232
.first()
_232
_232
if reservation is not None:
_232
_232
if 'yes' in form.Body.data or 'accept' in form.Body.data:
_232
reservation.confirm()
_232
reservation.buy_number(user.area_code)
_232
else:
_232
reservation.reject()
_232
_232
db.session.commit()
_232
_232
sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
_232
reservation.notify_guest()
_232
_232
return twiml(_respond_message(sms_response_text))
_232
_232
_232
@app.route('/exchange/sms', methods=["POST"])
_232
def exchange_sms():
_232
form = ExchangeForm()
_232
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = MessagingResponse()
_232
response.message(form.Body.data, to=outgoing_number)
_232
return twiml(response)
_232
_232
_232
@app.route('/exchange/voice', methods=["POST"])
_232
def exchange_voice():
_232
form = ExchangeForm()
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = VoiceResponse()
_232
response.play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_232
response.dial(outgoing_number)
_232
return twiml(response)
_232
_232
_232
# controller utils
_232
@app.before_request
_232
def before_request():
_232
g.user = current_user
_232
uri_pattern = request.url_rule
_232
if current_user.is_authenticated and (
_232
uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
_232
redirect_to('home')
_232
_232
_232
@login_manager.user_loader
_232
def load_user(id):
_232
try:
_232
return User.query.get(id)
_232
except:
_232
return None
_232
_232
_232
def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
_232
reservation = Reservation.query \
_232
.filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
_232
.first()
_232
_232
if reservation is None:
_232
raise Exception('Reservation not found for {0}'.format(incoming_phone_number))
_232
_232
if reservation.guest.phone_number == incoming_phone_number:
_232
return reservation.vacation_property.host.phone_number
_232
_232
return reservation.guest.phone_number
_232
_232
_232
def _respond_message(message):
_232
response = MessagingResponse()
_232
response.message(message)
_232
return response

Next, let's see how to connect the guest and the host via SMS.


Our TwiML application should be configured to send HTTP requests to this controller method on any incoming text message. Our app responds with TwiML to tell Twilio what to do in response to the message.

If the initial message sent to the anonymous number was sent by the host, we forward it on to the guest. Likewise, if the original message was sent by the guest, we forward it to the host.

We wrote a helper function called gather_outgoing_phone_number to help us determine which party to forward the message to.


_232
from twilio.twiml.messaging_response import MessagingResponse
_232
from twilio.twiml.voice_response import VoiceResponse
_232
_232
from airtng_flask import db, bcrypt, app, login_manager
_232
from flask import g, request
_232
from flask.ext.login import login_user, logout_user, current_user, login_required
_232
_232
from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
_232
ReservationConfirmationForm, ExchangeForm
_232
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
_232
from airtng_flask.models import init_models_module
_232
_232
init_models_module(db, bcrypt, app)
_232
_232
from airtng_flask.models.user import User
_232
from airtng_flask.models.vacation_property import VacationProperty
_232
from airtng_flask.models.reservation import Reservation
_232
_232
_232
@app.route('/', methods=["GET", "POST"])
_232
@app.route('/register', methods=["GET", "POST"])
_232
def register():
_232
form = RegisterForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
_232
if User.query.filter(User.email == form.email.data).count() > 0:
_232
form.email.errors.append("Email address already in use.")
_232
return view('register', form)
_232
_232
user = User(
_232
name=form.name.data,
_232
email=form.email.data,
_232
password=form.password.data,
_232
phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
_232
area_code=str(form.phone_number.data)[0:3])
_232
_232
db.session.add(user)
_232
db.session.commit()
_232
login_user(user, remember=True)
_232
_232
return redirect_to('home')
_232
else:
_232
return view('register', form)
_232
_232
return view('register', form)
_232
_232
_232
@app.route('/login', methods=["GET", "POST"])
_232
def login():
_232
form = LoginForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
candidate_user = User.query.filter(User.email == form.email.data).first()
_232
_232
if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
_232
form.password.data):
_232
form.password.errors.append("Invalid credentials.")
_232
return view('login', form)
_232
_232
login_user(candidate_user, remember=True)
_232
return redirect_to('home')
_232
return view('login', form)
_232
_232
_232
@app.route('/logout', methods=["POST"])
_232
@login_required
_232
def logout():
_232
logout_user()
_232
return redirect_to('home')
_232
_232
_232
@app.route('/home', methods=["GET"])
_232
@login_required
_232
def home():
_232
return view('home')
_232
_232
_232
@app.route('/properties', methods=["GET"])
_232
@login_required
_232
def properties():
_232
vacation_properties = VacationProperty.query.all()
_232
return view_with_params('properties', vacation_properties=vacation_properties)
_232
_232
_232
@app.route('/properties/new', methods=["GET", "POST"])
_232
@login_required
_232
def new_property():
_232
form = VacationPropertyForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
host = User.query.get(current_user.get_id())
_232
_232
property = VacationProperty(form.description.data, form.image_url.data, host)
_232
db.session.add(property)
_232
db.session.commit()
_232
return redirect_to('properties')
_232
_232
return view('property_new', form)
_232
_232
_232
@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
_232
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
_232
@login_required
_232
def new_reservation(property_id):
_232
vacation_property = None
_232
form = ReservationForm()
_232
form.property_id.data = property_id
_232
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
guest = User.query.get(current_user.get_id())
_232
_232
vacation_property = VacationProperty.query.get(form.property_id.data)
_232
reservation = Reservation(form.message.data, vacation_property, guest)
_232
db.session.add(reservation)
_232
db.session.commit()
_232
_232
reservation.notify_host()
_232
_232
return redirect_to('properties')
_232
_232
if property_id is not None:
_232
vacation_property = VacationProperty.query.get(property_id)
_232
_232
return view_with_params('reservation', vacation_property=vacation_property, form=form)
_232
_232
_232
@app.route('/reservations', methods=["GET"])
_232
@login_required
_232
def reservations():
_232
user = User.query.get(current_user.get_id())
_232
_232
reservations_as_host = Reservation.query \
_232
.filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
_232
.join(VacationProperty) \
_232
.filter(Reservation.vacation_property_id == VacationProperty.id) \
_232
.all()
_232
_232
reservations_as_guest = user.reservations
_232
_232
return view_with_params('reservations',
_232
reservations_as_guest=reservations_as_guest,
_232
reservations_as_host=reservations_as_host)
_232
_232
_232
@app.route('/reservations/confirm', methods=["POST"])
_232
def confirm_reservation():
_232
form = ReservationConfirmationForm()
_232
sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."
_232
_232
user = User.query.filter(User.phone_number == form.From.data).first()
_232
reservation = Reservation \
_232
.query \
_232
.filter(Reservation.status == 'pending'
_232
and Reservation.vacation_property.host.id == user.id) \
_232
.first()
_232
_232
if reservation is not None:
_232
_232
if 'yes' in form.Body.data or 'accept' in form.Body.data:
_232
reservation.confirm()
_232
reservation.buy_number(user.area_code)
_232
else:
_232
reservation.reject()
_232
_232
db.session.commit()
_232
_232
sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
_232
reservation.notify_guest()
_232
_232
return twiml(_respond_message(sms_response_text))
_232
_232
_232
@app.route('/exchange/sms', methods=["POST"])
_232
def exchange_sms():
_232
form = ExchangeForm()
_232
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = MessagingResponse()
_232
response.message(form.Body.data, to=outgoing_number)
_232
return twiml(response)
_232
_232
_232
@app.route('/exchange/voice', methods=["POST"])
_232
def exchange_voice():
_232
form = ExchangeForm()
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = VoiceResponse()
_232
response.play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_232
response.dial(outgoing_number)
_232
return twiml(response)
_232
_232
_232
# controller utils
_232
@app.before_request
_232
def before_request():
_232
g.user = current_user
_232
uri_pattern = request.url_rule
_232
if current_user.is_authenticated and (
_232
uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
_232
redirect_to('home')
_232
_232
_232
@login_manager.user_loader
_232
def load_user(id):
_232
try:
_232
return User.query.get(id)
_232
except:
_232
return None
_232
_232
_232
def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
_232
reservation = Reservation.query \
_232
.filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
_232
.first()
_232
_232
if reservation is None:
_232
raise Exception('Reservation not found for {0}'.format(incoming_phone_number))
_232
_232
if reservation.guest.phone_number == incoming_phone_number:
_232
return reservation.vacation_property.host.phone_number
_232
_232
return reservation.guest.phone_number
_232
_232
_232
def _respond_message(message):
_232
response = MessagingResponse()
_232
response.message(message)
_232
return response

Let's see how to connect the guest and the host via phone call next.


Our Twilio application will send HTTP requests to this method on any incoming voice call. Our app responds with TwiML instructions that tell Twilio to Play an introductory MP3 audio file and then Dial either the guest or host, depending on who initiated the call.


_232
from twilio.twiml.messaging_response import MessagingResponse
_232
from twilio.twiml.voice_response import VoiceResponse
_232
_232
from airtng_flask import db, bcrypt, app, login_manager
_232
from flask import g, request
_232
from flask.ext.login import login_user, logout_user, current_user, login_required
_232
_232
from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
_232
ReservationConfirmationForm, ExchangeForm
_232
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
_232
from airtng_flask.models import init_models_module
_232
_232
init_models_module(db, bcrypt, app)
_232
_232
from airtng_flask.models.user import User
_232
from airtng_flask.models.vacation_property import VacationProperty
_232
from airtng_flask.models.reservation import Reservation
_232
_232
_232
@app.route('/', methods=["GET", "POST"])
_232
@app.route('/register', methods=["GET", "POST"])
_232
def register():
_232
form = RegisterForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
_232
if User.query.filter(User.email == form.email.data).count() > 0:
_232
form.email.errors.append("Email address already in use.")
_232
return view('register', form)
_232
_232
user = User(
_232
name=form.name.data,
_232
email=form.email.data,
_232
password=form.password.data,
_232
phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
_232
area_code=str(form.phone_number.data)[0:3])
_232
_232
db.session.add(user)
_232
db.session.commit()
_232
login_user(user, remember=True)
_232
_232
return redirect_to('home')
_232
else:
_232
return view('register', form)
_232
_232
return view('register', form)
_232
_232
_232
@app.route('/login', methods=["GET", "POST"])
_232
def login():
_232
form = LoginForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
candidate_user = User.query.filter(User.email == form.email.data).first()
_232
_232
if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
_232
form.password.data):
_232
form.password.errors.append("Invalid credentials.")
_232
return view('login', form)
_232
_232
login_user(candidate_user, remember=True)
_232
return redirect_to('home')
_232
return view('login', form)
_232
_232
_232
@app.route('/logout', methods=["POST"])
_232
@login_required
_232
def logout():
_232
logout_user()
_232
return redirect_to('home')
_232
_232
_232
@app.route('/home', methods=["GET"])
_232
@login_required
_232
def home():
_232
return view('home')
_232
_232
_232
@app.route('/properties', methods=["GET"])
_232
@login_required
_232
def properties():
_232
vacation_properties = VacationProperty.query.all()
_232
return view_with_params('properties', vacation_properties=vacation_properties)
_232
_232
_232
@app.route('/properties/new', methods=["GET", "POST"])
_232
@login_required
_232
def new_property():
_232
form = VacationPropertyForm()
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
host = User.query.get(current_user.get_id())
_232
_232
property = VacationProperty(form.description.data, form.image_url.data, host)
_232
db.session.add(property)
_232
db.session.commit()
_232
return redirect_to('properties')
_232
_232
return view('property_new', form)
_232
_232
_232
@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
_232
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
_232
@login_required
_232
def new_reservation(property_id):
_232
vacation_property = None
_232
form = ReservationForm()
_232
form.property_id.data = property_id
_232
_232
if request.method == 'POST':
_232
if form.validate_on_submit():
_232
guest = User.query.get(current_user.get_id())
_232
_232
vacation_property = VacationProperty.query.get(form.property_id.data)
_232
reservation = Reservation(form.message.data, vacation_property, guest)
_232
db.session.add(reservation)
_232
db.session.commit()
_232
_232
reservation.notify_host()
_232
_232
return redirect_to('properties')
_232
_232
if property_id is not None:
_232
vacation_property = VacationProperty.query.get(property_id)
_232
_232
return view_with_params('reservation', vacation_property=vacation_property, form=form)
_232
_232
_232
@app.route('/reservations', methods=["GET"])
_232
@login_required
_232
def reservations():
_232
user = User.query.get(current_user.get_id())
_232
_232
reservations_as_host = Reservation.query \
_232
.filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
_232
.join(VacationProperty) \
_232
.filter(Reservation.vacation_property_id == VacationProperty.id) \
_232
.all()
_232
_232
reservations_as_guest = user.reservations
_232
_232
return view_with_params('reservations',
_232
reservations_as_guest=reservations_as_guest,
_232
reservations_as_host=reservations_as_host)
_232
_232
_232
@app.route('/reservations/confirm', methods=["POST"])
_232
def confirm_reservation():
_232
form = ReservationConfirmationForm()
_232
sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."
_232
_232
user = User.query.filter(User.phone_number == form.From.data).first()
_232
reservation = Reservation \
_232
.query \
_232
.filter(Reservation.status == 'pending'
_232
and Reservation.vacation_property.host.id == user.id) \
_232
.first()
_232
_232
if reservation is not None:
_232
_232
if 'yes' in form.Body.data or 'accept' in form.Body.data:
_232
reservation.confirm()
_232
reservation.buy_number(user.area_code)
_232
else:
_232
reservation.reject()
_232
_232
db.session.commit()
_232
_232
sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
_232
reservation.notify_guest()
_232
_232
return twiml(_respond_message(sms_response_text))
_232
_232
_232
@app.route('/exchange/sms', methods=["POST"])
_232
def exchange_sms():
_232
form = ExchangeForm()
_232
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = MessagingResponse()
_232
response.message(form.Body.data, to=outgoing_number)
_232
return twiml(response)
_232
_232
_232
@app.route('/exchange/voice', methods=["POST"])
_232
def exchange_voice():
_232
form = ExchangeForm()
_232
outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)
_232
_232
response = VoiceResponse()
_232
response.play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_232
response.dial(outgoing_number)
_232
return twiml(response)
_232
_232
_232
# controller utils
_232
@app.before_request
_232
def before_request():
_232
g.user = current_user
_232
uri_pattern = request.url_rule
_232
if current_user.is_authenticated and (
_232
uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
_232
redirect_to('home')
_232
_232
_232
@login_manager.user_loader
_232
def load_user(id):
_232
try:
_232
return User.query.get(id)
_232
except:
_232
return None
_232
_232
_232
def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
_232
reservation = Reservation.query \
_232
.filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
_232
.first()
_232
_232
if reservation is None:
_232
raise Exception('Reservation not found for {0}'.format(incoming_phone_number))
_232
_232
if reservation.guest.phone_number == incoming_phone_number:
_232
return reservation.vacation_property.host.phone_number
_232
_232
return reservation.guest.phone_number
_232
_232
_232
def _respond_message(message):
_232
response = MessagingResponse()
_232
response.message(message)
_232
return response

That's it! We've just implemented anonymous communications that allow your customers to connect while protecting their privacy.


If you're a Python developer working with Twilio you might want to check out these other tutorials:

IVR: Phone Tree

Create a seamless customer service experience by building an IVR Phone Tree for your company.

Call Tracking

Measure the effectiveness of different marketing campaigns by assigning a unique phone number to different advertisements and track which ones have the best call rates while getting some data about the callers themselves.

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio(link takes you to an external page) to let us know what you think.


Rate this page: