Masked Phone Numbers with Python and Flask

Download the Code

Airtng

This Flask sample application is modeled after the rental experience created by AirBnB but with more Klingons.

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. On 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.

Read how Lyft uses masked phone numbers to let customers securely contact drivers.

Create a Reservation

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.

Loading Code Samples...
Language
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
Create a Reservation

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.

Confirm the Reservation

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.

Loading Code Samples...
Language
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
Confirm the Reservation

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

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 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.

Loading Code Samples...
Language
from airtng_flask.models import app_db, auth_token, account_sid, phone_number, application_sid
from flask import render_template
from twilio.rest import Client

DB = app_db()


class Reservation(DB.Model):
    __tablename__ = "reservations"

    id = DB.Column(DB.Integer, primary_key=True)
    message = DB.Column(DB.String, nullable=False)
    status = DB.Column(DB.Enum('pending', 'confirmed', 'rejected', name='reservation_status_enum'),
                       default='pending')
    anonymous_phone_number = DB.Column(DB.String, nullable=True)
    guest_id = DB.Column(DB.Integer, DB.ForeignKey('users.id'))
    vacation_property_id = DB.Column(DB.Integer, DB.ForeignKey('vacation_properties.id'))
    guest = DB.relationship("User", back_populates="reservations")
    vacation_property = DB.relationship("VacationProperty", back_populates="reservations")

    def __init__(self, message, vacation_property, guest):
        self.message = message
        self.guest = guest
        self.vacation_property = vacation_property
        self.status = 'pending'

    def confirm(self):
        self.status = 'confirmed'

    def reject(self):
        self.status = 'rejected'

    def __repr__(self):
        return '<Reservation {0}>'.format(self.id)

    def notify_host(self):
        self._send_message(self.vacation_property.host.phone_number,
                           render_template('messages/sms_host.txt',
                                           name=self.guest.name,
                                           description=self.vacation_property.description,
                                           message=self.message))

    def notify_guest(self):
        self._send_message(self.guest.phone_number,
                           render_template('messages/sms_guest.txt',
                                           description=self.vacation_property.description,
                                           status=self.status))

    def buy_number(self, area_code):
        numbers = self._get_twilio_client().available_phone_numbers("US") \
                                           .local \
                                           .list(area_code=area_code,
                                                 sms_enabled=True,
                                                 voice_enabled=True)

        if numbers:
            number = self._purchase_number(numbers[0])
            self.anonymous_phone_number = number
            return number
        else:
            numbers = self._get_twilio_client().available_phone_numbers("US") \
                                               .local \
                                               .list(sms_enabled=True, voice_enabled=True)

            if numbers:
                number = self._purchase_number(numbers[0])
                self.anonymous_phone_number = number
                return number

        return None

    def _purchase_number(self, number):
        return self._get_twilio_client().incoming_phone_numbers \
                                        .create(sms_application_sid=application_sid(),
                                                voice_application_sid=application_sid(),
                                                phone_number=number) \
                                        .phone_number

    def _get_twilio_client(self):
        return Client(account_sid(), auth_token())

    def _send_message(self, to, message):
        self._get_twilio_client().messages \
                                 .create(to,
                                         from_=phone_number(),
                                         body=message)
airtng_flask/models/reservation.py
Purchase a Twilio Number

airtng_flask/models/reservation.py

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.

Find a Reservation

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.

Loading Code Samples...
Language
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
Find a Reservation

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

Connect 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.

Loading Code Samples...
Language
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
Connect Via SMS

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

Connect Via Phone Call

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.

Loading Code Samples...
Language
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
Connect Via Phone Call

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

Where to Next?

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?

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

Airtng

Agustin Camino
David Prothero
Jose Oliveros
Andrew Baker

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
from airtng_flask.models import app_db, auth_token, account_sid, phone_number, application_sid
from flask import render_template
from twilio.rest import Client

DB = app_db()


class Reservation(DB.Model):
    __tablename__ = "reservations"

    id = DB.Column(DB.Integer, primary_key=True)
    message = DB.Column(DB.String, nullable=False)
    status = DB.Column(DB.Enum('pending', 'confirmed', 'rejected', name='reservation_status_enum'),
                       default='pending')
    anonymous_phone_number = DB.Column(DB.String, nullable=True)
    guest_id = DB.Column(DB.Integer, DB.ForeignKey('users.id'))
    vacation_property_id = DB.Column(DB.Integer, DB.ForeignKey('vacation_properties.id'))
    guest = DB.relationship("User", back_populates="reservations")
    vacation_property = DB.relationship("VacationProperty", back_populates="reservations")

    def __init__(self, message, vacation_property, guest):
        self.message = message
        self.guest = guest
        self.vacation_property = vacation_property
        self.status = 'pending'

    def confirm(self):
        self.status = 'confirmed'

    def reject(self):
        self.status = 'rejected'

    def __repr__(self):
        return '<Reservation {0}>'.format(self.id)

    def notify_host(self):
        self._send_message(self.vacation_property.host.phone_number,
                           render_template('messages/sms_host.txt',
                                           name=self.guest.name,
                                           description=self.vacation_property.description,
                                           message=self.message))

    def notify_guest(self):
        self._send_message(self.guest.phone_number,
                           render_template('messages/sms_guest.txt',
                                           description=self.vacation_property.description,
                                           status=self.status))

    def buy_number(self, area_code):
        numbers = self._get_twilio_client().available_phone_numbers("US") \
                                           .local \
                                           .list(area_code=area_code,
                                                 sms_enabled=True,
                                                 voice_enabled=True)

        if numbers:
            number = self._purchase_number(numbers[0])
            self.anonymous_phone_number = number
            return number
        else:
            numbers = self._get_twilio_client().available_phone_numbers("US") \
                                               .local \
                                               .list(sms_enabled=True, voice_enabled=True)

            if numbers:
                number = self._purchase_number(numbers[0])
                self.anonymous_phone_number = number
                return number

        return None

    def _purchase_number(self, number):
        return self._get_twilio_client().incoming_phone_numbers \
                                        .create(sms_application_sid=application_sid(),
                                                voice_application_sid=application_sid(),
                                                phone_number=number) \
                                        .phone_number

    def _get_twilio_client(self):
        return Client(account_sid(), auth_token())

    def _send_message(self, to, message):
        self._get_twilio_client().messages \
                                 .create(to,
                                         from_=phone_number(),
                                         body=message)
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response
from airtng_flask import db, bcrypt, app, login_manager
from flask import session, g, request, flash, Blueprint
from flask.ext.login import login_user, logout_user, current_user, login_required
import twilio.twiml

from airtng_flask.forms import RegisterForm, LoginForm, VacationPropertyForm, ReservationForm, \
    ReservationConfirmationForm, ExchangeForm
from airtng_flask.view_helpers import twiml, view, redirect_to, view_with_params
from airtng_flask.models import init_models_module

init_models_module(db, bcrypt, app)

from airtng_flask.models.user import User
from airtng_flask.models.vacation_property import VacationProperty
from airtng_flask.models.reservation import Reservation


@app.route('/', methods=["GET", "POST"])
@app.route('/register', methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if request.method == 'POST':
        if form.validate_on_submit():

            if User.query.filter(User.email == form.email.data).count() > 0:
                form.email.errors.append("Email address already in use.")
                return view('register', form)

            user = User(
                    name=form.name.data,
                    email=form.email.data,
                    password=form.password.data,
                    phone_number="+{0}{1}".format(form.country_code.data, form.phone_number.data),
                    area_code=str(form.phone_number.data)[0:3])

            db.session.add(user)
            db.session.commit()
            login_user(user, remember=True)

            return redirect_to('home')
        else:
            return view('register', form)

    return view('register', form)


@app.route('/login', methods=["GET", "POST"])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            candidate_user = User.query.filter(User.email == form.email.data).first()

            if candidate_user is None or not bcrypt.check_password_hash(candidate_user.password,
                                                                        form.password.data):
                form.password.errors.append("Invalid credentials.")
                return view('login', form)

            login_user(candidate_user, remember=True)
            return redirect_to('home')
    return view('login', form)


@app.route('/logout', methods=["POST"])
@login_required
def logout():
    logout_user()
    return redirect_to('home')


@app.route('/home', methods=["GET"])
@login_required
def home():
    return view('home')


@app.route('/properties', methods=["GET"])
@login_required
def properties():
    vacation_properties = VacationProperty.query.all()
    return view_with_params('properties', vacation_properties=vacation_properties)


@app.route('/properties/new', methods=["GET", "POST"])
@login_required
def new_property():
    form = VacationPropertyForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            host = User.query.get(current_user.get_id())

            property = VacationProperty(form.description.data, form.image_url.data, host)
            db.session.add(property)
            db.session.commit()
            return redirect_to('properties')

    return view('property_new', form)


@app.route('/reservations/', methods=["POST"], defaults={'property_id': None})
@app.route('/reservations/<property_id>', methods=["GET", "POST"])
@login_required
def new_reservation(property_id):
    vacation_property = None
    form = ReservationForm()
    form.property_id.data = property_id

    if request.method == 'POST':
        if form.validate_on_submit():
            guest = User.query.get(current_user.get_id())

            vacation_property = VacationProperty.query.get(form.property_id.data)
            reservation = Reservation(form.message.data, vacation_property, guest)
            db.session.add(reservation)
            db.session.commit()

            reservation.notify_host()

            return redirect_to('properties')

    if property_id is not None:
        vacation_property = VacationProperty.query.get(property_id)

    return view_with_params('reservation', vacation_property=vacation_property, form=form)


@app.route('/reservations', methods=["GET"])
@login_required
def reservations():
    user = User.query.get(current_user.get_id())

    reservations_as_host = Reservation.query \
        .filter(VacationProperty.host_id == current_user.get_id() and len(VacationProperty.reservations) > 0) \
        .join(VacationProperty) \
        .filter(Reservation.vacation_property_id == VacationProperty.id) \
        .all()

    reservations_as_guest = user.reservations

    return view_with_params('reservations',
                            reservations_as_guest=reservations_as_guest,
                            reservations_as_host=reservations_as_host)


@app.route('/reservations/confirm', methods=["POST"])
def confirm_reservation():
    form = ReservationConfirmationForm()
    sms_response_text = "Sorry, it looks like you don't have any reservations to respond to."

    user = User.query.filter(User.phone_number == form.From.data).first()
    reservation = Reservation \
        .query \
        .filter(Reservation.status == 'pending'
                and Reservation.vacation_property.host.id == user.id) \
        .first()

    if reservation is not None:

        if 'yes' in form.Body.data or 'accept' in form.Body.data:
            reservation.confirm()
            reservation.buy_number(user.area_code)
        else:
            reservation.reject()

        db.session.commit()

        sms_response_text = "You have successfully {0} the reservation".format(reservation.status)
        reservation.notify_guest()

    return twiml(_respond_message(sms_response_text))


@app.route('/exchange/sms', methods=["POST"])
def exchange_sms():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addSms(form.Body.data, to=outgoing_number)
    return twiml(response)


@app.route('/exchange/voice', methods=["POST"])
def exchange_voice():
    form = ExchangeForm()

    outgoing_number = _gather_outgoing_phone_number(form.From.data, form.To.data)

    response = twilio.twiml.Response()
    response.addPlay("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
    response.addDial(outgoing_number)
    return twiml(response)


# controller utils
@app.before_request
def before_request():
    g.user = current_user
    uri_pattern = request.url_rule
    if current_user.is_authenticated and (
                        uri_pattern == '/' or uri_pattern == '/login' or uri_pattern == '/register'):
        redirect_to('home')


@login_manager.user_loader
def load_user(id):
    try:
        return User.query.get(id)
    except:
        return None


def _gather_outgoing_phone_number(incoming_phone_number, anonymous_phone_number):
    reservation = Reservation.query \
        .filter(Reservation.anonymous_phone_number == anonymous_phone_number) \
        .first()

    if reservation.guest.phone_number == incoming_phone_number:
        return reservation.vacation_property.host.phone_number

    return reservation.guest.phone_number


def _respond_message(message):
    response = twilio.twiml.Response()
    response.message(message)
    return response