Call Tracking with Python and Django

This Django web application shows how you can use Twilio to track the effectiveness of different marketing channels. 

Call tracking home page

This application has three main features:

  • It purchases phone numbers from Twilio to use in different marketing campaigns (like a billboard or a bus advertisement)
  • It forwards incoming calls for those phone numbers to a salesperson
  • It displays charts showing data about the phone numbers and the calls they receive

Check out how Whatclinic.com used Twilio to build a call tracking platform for healthcare providers.

In this tutorial, we'll point out the key bits of code that make this application work. Check out the project README on GitHub to see how to run the code yourself.

Search for available phone numbers

Call tracking requires us to search for and buy phone numbers on demand, associating a specific phone number with a lead source. This utility module uses the Twilio Python helper library to search for phone numbers by area code and return a list of numbers that are available for purchase.

Loading Code Samples...
Language
from django.conf import settings
from twilio.rest import Client

import os

# Uses credentials defined in TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN
# environment variables
account_sid = settings.TWILIO_ACCOUNT_SID
auth_token = settings.TWILIO_AUTH_TOKEN

client = Client(account_sid, auth_token)


def search_phone_numbers(area_code=None):
    """Queries the Twilio REST API to get phone numbers available for puchase"""
    # You can change the country argument to search outside the US
    # area_code is an optional parameter
    numbers = client.available_phone_numbers("US") \
                    .local \
                    .list(area_code=area_code)

    # Returns 30 by default - let's trim the list for UX purposes
    return numbers[:10]


def purchase_phone_number(phone_number):
    """Purchases a new phone number from the Twilio API"""
    # Use a TwiML Application SID so all our numbers use the same voice URL
    number = client.incoming_phone_numbers.create(
        phone_number=phone_number,
        voice_application_sid=settings.TWIML_APPLICATION_SID)

    return number
call_tracking/utils.py
Search Phone Numbers

call_tracking/utils.py

Now let's see how we will display these numbers for the user to purchase them and enable their campaigns.

Display available phone numbers

We display a form to the user on the app's home page which allows them to search for a new phone number by area code. At the view level, we use the utility module we created earlier to actually search for numbers, and display an HTML page to the user with a list of numbers they can choose to buy.

Loading Code Samples...
Language
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
from twilio.twiml.voice_response import VoiceResponse

from .forms import AreaCodeForm, PurchaseNumberForm
from .models import LeadSource, Lead
from .utils import search_phone_numbers, purchase_phone_number


# Home page view and JSON views to power the charts
def home(request):
    """Renders the home page"""
    context = {}

    # Add the area code form - default to 415
    context['form'] = AreaCodeForm({'area_code': '415'})

    # Add the list of lead sources
    context['lead_sources'] = LeadSource.objects.all()

    return render(request, 'index.html', context)


def leads_by_source(request):
    """Returns JSON data about the lead sources and how many leads they have"""
    # Invoke a LeadSource classmethod to get the data
    data = LeadSource.objects.get_leads_per_source()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


def leads_by_city(request):
    """Returns JSON data about the different cities leads come from"""
    # Invoke a Lead classmethod to get the data
    data = Lead.objects.get_leads_per_city()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


# Views for purchase number workflow
def list_numbers(request):
    """Uses the Twilio API to generate a list of available phone numbers"""
    form = AreaCodeForm(request.POST)

    if form.is_valid():
        # We received a valid area code - query the Twilio API
        area_code = form.cleaned_data['area_code']

        available_numbers = search_phone_numbers(area_code=area_code)

        # Check if there are no numbers available in this area code
        if not available_numbers:
            messages.error(
                request,
                'There are no Twilio numbers available for area code {0}. Search for numbers in a different area code.'.format(area_code))
            return redirect('home')

        context = {}
        context['available_numbers'] = available_numbers

        return render(request, 'call_tracking/list_numbers.html', context)
    else:
        # Our area code was invalid - flash a message and redirect back home
        bad_area_code = form.data['area_code']
        messages.error(request, '{0} is not a valid area code. Please search again.'
                       .format(bad_area_code))

        return redirect('home')


def purchase_number(request):
    """Purchases a new phone number using the Twilio API"""
    form = PurchaseNumberForm(request.POST)

    if form.is_valid():
        # Purchase the phone number
        phone_number = form.cleaned_data['phone_number']
        twilio_number = purchase_phone_number(phone_number.as_e164)

        # Save it in a new LeadSource object
        lead_source = LeadSource(incoming_number=twilio_number.phone_number)
        lead_source.save()

        messages.success(
            request,
            'Phone number {0} has been purchased. Please add a name for this lead source.'.format(
                twilio_number.friendly_name))

        # Redirect to edit lead page
        return redirect('edit_lead_source', pk=lead_source.pk)
    else:
        # In the unlikely event of an error, redirect to the home page
        bad_phone_number = form.data['phone_number']
        messages.error(request, '{0} is not a valid phone number. Please search again.'
                       .format(bad_phone_number))

        return redirect('home')


class LeadSourceUpdateView(SuccessMessageMixin, UpdateView):
    """Powers a form to edit Lead Sources"""

    model = LeadSource
    fields = ['name', 'forwarding_number']
    success_url = reverse_lazy('home')
    success_message = 'Lead source successfully updated.'


# View used by Twilio API to connect callers to the right forwarding
# number for that lead source
@csrf_exempt
def forward_call(request):
    """Connects an incoming call to the correct forwarding number"""
    # First look up the lead source
    source = LeadSource.objects.get(incoming_number=request.POST['Called'])

    # Create a lead entry for this call
    lead = Lead(
        source=source,
        phone_number=request.POST['Caller'],
        city=request.POST['CallerCity'],
        state=request.POST['CallerState'])
    lead.save()

    # Respond with some TwiML that connects the caller to the forwarding_number
    r = VoiceResponse()
    r.dial(source.forwarding_number.as_e164)

    return HttpResponse(r)
call_tracking/views.py
Available Numbers View

call_tracking/views.py

We've seen how we can display available phone numbers for purchase with the help of the Twilio Python helper library. Now let's look at how we can buy an available phone number.

Buy a phone number

Our purchase_phone_number utility function takes a phone number as its sole parameter and uses our Twilio API client to actually purchase one of the available phone numbers we searched for earlier.

Loading Code Samples...
Language
from django.conf import settings
from twilio.rest import Client

import os

# Uses credentials defined in TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN
# environment variables
account_sid = settings.TWILIO_ACCOUNT_SID
auth_token = settings.TWILIO_AUTH_TOKEN

client = Client(account_sid, auth_token)


def search_phone_numbers(area_code=None):
    """Queries the Twilio REST API to get phone numbers available for puchase"""
    # You can change the country argument to search outside the US
    # area_code is an optional parameter
    numbers = client.available_phone_numbers("US") \
                    .local \
                    .list(area_code=area_code)

    # Returns 30 by default - let's trim the list for UX purposes
    return numbers[:10]


def purchase_phone_number(phone_number):
    """Purchases a new phone number from the Twilio API"""
    # Use a TwiML Application SID so all our numbers use the same voice URL
    number = client.incoming_phone_numbers.create(
        phone_number=phone_number,
        voice_application_sid=settings.TWIML_APPLICATION_SID)

    return number
call_tracking/utils.py
Purchase Phone Number

call_tracking/utils.py

If you don't know where you can get this application SID, don't panic, the next step will show you how.

Set webhook URLs in a TwiML Application

When we purchase a phone number, we specify a voice application SID. This is an identifier for a TwiML application, which you can create through the REST API or your Twilio Console.

Create TwiML App

Associate a phone number with a lead source

Once we search for and buy a Twilio number, we need to associate it with a lead source in our database. This is the core of a call tracking application. Any phone calls to our new Twilio number will be attributed to this source.

Loading Code Samples...
Language
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
from twilio.twiml.voice_response import VoiceResponse

from .forms import AreaCodeForm, PurchaseNumberForm
from .models import LeadSource, Lead
from .utils import search_phone_numbers, purchase_phone_number


# Home page view and JSON views to power the charts
def home(request):
    """Renders the home page"""
    context = {}

    # Add the area code form - default to 415
    context['form'] = AreaCodeForm({'area_code': '415'})

    # Add the list of lead sources
    context['lead_sources'] = LeadSource.objects.all()

    return render(request, 'index.html', context)


def leads_by_source(request):
    """Returns JSON data about the lead sources and how many leads they have"""
    # Invoke a LeadSource classmethod to get the data
    data = LeadSource.objects.get_leads_per_source()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


def leads_by_city(request):
    """Returns JSON data about the different cities leads come from"""
    # Invoke a Lead classmethod to get the data
    data = Lead.objects.get_leads_per_city()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


# Views for purchase number workflow
def list_numbers(request):
    """Uses the Twilio API to generate a list of available phone numbers"""
    form = AreaCodeForm(request.POST)

    if form.is_valid():
        # We received a valid area code - query the Twilio API
        area_code = form.cleaned_data['area_code']

        available_numbers = search_phone_numbers(area_code=area_code)

        # Check if there are no numbers available in this area code
        if not available_numbers:
            messages.error(
                request,
                'There are no Twilio numbers available for area code {0}. Search for numbers in a different area code.'.format(area_code))
            return redirect('home')

        context = {}
        context['available_numbers'] = available_numbers

        return render(request, 'call_tracking/list_numbers.html', context)
    else:
        # Our area code was invalid - flash a message and redirect back home
        bad_area_code = form.data['area_code']
        messages.error(request, '{0} is not a valid area code. Please search again.'
                       .format(bad_area_code))

        return redirect('home')


def purchase_number(request):
    """Purchases a new phone number using the Twilio API"""
    form = PurchaseNumberForm(request.POST)

    if form.is_valid():
        # Purchase the phone number
        phone_number = form.cleaned_data['phone_number']
        twilio_number = purchase_phone_number(phone_number.as_e164)

        # Save it in a new LeadSource object
        lead_source = LeadSource(incoming_number=twilio_number.phone_number)
        lead_source.save()

        messages.success(
            request,
            'Phone number {0} has been purchased. Please add a name for this lead source.'.format(
                twilio_number.friendly_name))

        # Redirect to edit lead page
        return redirect('edit_lead_source', pk=lead_source.pk)
    else:
        # In the unlikely event of an error, redirect to the home page
        bad_phone_number = form.data['phone_number']
        messages.error(request, '{0} is not a valid phone number. Please search again.'
                       .format(bad_phone_number))

        return redirect('home')


class LeadSourceUpdateView(SuccessMessageMixin, UpdateView):
    """Powers a form to edit Lead Sources"""

    model = LeadSource
    fields = ['name', 'forwarding_number']
    success_url = reverse_lazy('home')
    success_message = 'Lead source successfully updated.'


# View used by Twilio API to connect callers to the right forwarding
# number for that lead source
@csrf_exempt
def forward_call(request):
    """Connects an incoming call to the correct forwarding number"""
    # First look up the lead source
    source = LeadSource.objects.get(incoming_number=request.POST['Called'])

    # Create a lead entry for this call
    lead = Lead(
        source=source,
        phone_number=request.POST['Caller'],
        city=request.POST['CallerCity'],
        state=request.POST['CallerState'])
    lead.save()

    # Respond with some TwiML that connects the caller to the forwarding_number
    r = VoiceResponse()
    r.dial(source.forwarding_number.as_e164)

    return HttpResponse(r)
call_tracking/views.py
Create Lead Source View

call_tracking/views.py

So far our method for creating a Lead Source and associating a Twilio phone number with it is pretty straightforward. Now let's have a closer look at our Lead Source model which will store this information.

The LeadSource model

The LeadSource model associates a Twilio number to a named lead source (like "Wall Street Journal Ad" or "Dancing guy with sign"). It also tracks a phone number to which we'd like all the calls redirected, like your sales or support help line.

Loading Code Samples...
Language
from django.db import models
from django.db.models import Count
from django.utils.encoding import python_2_unicode_compatible
from phonenumber_field.modelfields import PhoneNumberField


class LeadSourceManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_source' method"""

    def get_leads_per_source(self):
        """Get the number of leads for each lead source"""
        # Use Django's annotate feature to include the number of leads
        # on each lead source
        queryset = self.all().annotate(Count('lead')).order_by('name')

        # Extract the source names and lead counts and make them a regular list
        data = list(queryset.values('name', 'lead__count'))

        return data


@python_2_unicode_compatible
class LeadSource(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text='E.g. "Downtown billboard"')
    incoming_number = PhoneNumberField(
        unique=True,
        help_text='A phone number purchased through Twilio')
    forwarding_number = PhoneNumberField(
        blank=True,
        help_text='People who call this lead source will be connected with this phone number. Must include international prefix - e.g. +1 555 555 55555')

    # Apply our custom manager
    objects = LeadSourceManager()

    def __str__(self):
        if self.name:
            return '{0} - {1}'.format(self.name, self.incoming_number)
        else:
            return '(not yet named) - {0}'.format(self.incoming_number)


class LeadManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_city' method"""

    def get_leads_per_city(self):
        """Get the number of leads for each city"""
        # Use Django's annotate feature to include the number of leads
        # from each distinct city
        queryset = self.all().values('city').annotate(
            Count('id')).order_by('city')

        # Extract the cities and lead counts and make them a regular list
        data = list(queryset.values('city', 'id__count'))

        return data


@python_2_unicode_compatible
class Lead(models.Model):
    source = models.ForeignKey(LeadSource)
    phone_number = PhoneNumberField()
    timestamp = models.DateTimeField(auto_now_add=True)

    # A couple examples of fields you could track for each incoming call
    # See https://www.twilio.com/docs/api/twiml/twilio_request for more
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=2)

    # Apply our custom manager
    objects = LeadManager()

    def __str__(self):
        return '{0}, {1} at {2}'.format(self.city, self.state, self.timestamp)
call_tracking/models.py
Lead Source Model

call_tracking/models.py

As the application will be collecting leads and associating them to each LeadSource or campaign, it is necessary to have a Lead model as well to keep track of each Lead as it comes in and associate it to the LeadSource.

Define the Lead model

A Lead represents a phone call generated by a LeadSource. Each time somebody calls a phone number associated with a LeadSource, we'll use the Lead model to record some of the data Twilio gives us about their call.

Loading Code Samples...
Language
from django.db import models
from django.db.models import Count
from django.utils.encoding import python_2_unicode_compatible
from phonenumber_field.modelfields import PhoneNumberField


class LeadSourceManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_source' method"""

    def get_leads_per_source(self):
        """Get the number of leads for each lead source"""
        # Use Django's annotate feature to include the number of leads
        # on each lead source
        queryset = self.all().annotate(Count('lead')).order_by('name')

        # Extract the source names and lead counts and make them a regular list
        data = list(queryset.values('name', 'lead__count'))

        return data


@python_2_unicode_compatible
class LeadSource(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text='E.g. "Downtown billboard"')
    incoming_number = PhoneNumberField(
        unique=True,
        help_text='A phone number purchased through Twilio')
    forwarding_number = PhoneNumberField(
        blank=True,
        help_text='People who call this lead source will be connected with this phone number. Must include international prefix - e.g. +1 555 555 55555')

    # Apply our custom manager
    objects = LeadSourceManager()

    def __str__(self):
        if self.name:
            return '{0} - {1}'.format(self.name, self.incoming_number)
        else:
            return '(not yet named) - {0}'.format(self.incoming_number)


class LeadManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_city' method"""

    def get_leads_per_city(self):
        """Get the number of leads for each city"""
        # Use Django's annotate feature to include the number of leads
        # from each distinct city
        queryset = self.all().values('city').annotate(
            Count('id')).order_by('city')

        # Extract the cities and lead counts and make them a regular list
        data = list(queryset.values('city', 'id__count'))

        return data


@python_2_unicode_compatible
class Lead(models.Model):
    source = models.ForeignKey(LeadSource)
    phone_number = PhoneNumberField()
    timestamp = models.DateTimeField(auto_now_add=True)

    # A couple examples of fields you could track for each incoming call
    # See https://www.twilio.com/docs/api/twiml/twilio_request for more
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=2)

    # Apply our custom manager
    objects = LeadManager()

    def __str__(self):
        return '{0}, {1} at {2}'.format(self.city, self.state, self.timestamp)
call_tracking/models.py
Lead Model

call_tracking/models.py

The backend part of the code which creates a LeadSource as well as a Twilio Number is complete. The next part of the application will be the webhooks that will handle incoming calls and forward them to the appropriate sales team member. Let's us see the way these webhooks are built.

Forward calls and create leads

Whenever a customer calls one of our Twilio numbers, Twilio will send a POST request to the URL associated with this view function (should be /call-tracking/forward-call).

We use the incoming call data to create a new Lead for a LeadSource, then return TwiML that connects our caller with the forwarding_number of our LeadSource.

Loading Code Samples...
Language
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
from twilio.twiml.voice_response import VoiceResponse

from .forms import AreaCodeForm, PurchaseNumberForm
from .models import LeadSource, Lead
from .utils import search_phone_numbers, purchase_phone_number


# Home page view and JSON views to power the charts
def home(request):
    """Renders the home page"""
    context = {}

    # Add the area code form - default to 415
    context['form'] = AreaCodeForm({'area_code': '415'})

    # Add the list of lead sources
    context['lead_sources'] = LeadSource.objects.all()

    return render(request, 'index.html', context)


def leads_by_source(request):
    """Returns JSON data about the lead sources and how many leads they have"""
    # Invoke a LeadSource classmethod to get the data
    data = LeadSource.objects.get_leads_per_source()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


def leads_by_city(request):
    """Returns JSON data about the different cities leads come from"""
    # Invoke a Lead classmethod to get the data
    data = Lead.objects.get_leads_per_city()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


# Views for purchase number workflow
def list_numbers(request):
    """Uses the Twilio API to generate a list of available phone numbers"""
    form = AreaCodeForm(request.POST)

    if form.is_valid():
        # We received a valid area code - query the Twilio API
        area_code = form.cleaned_data['area_code']

        available_numbers = search_phone_numbers(area_code=area_code)

        # Check if there are no numbers available in this area code
        if not available_numbers:
            messages.error(
                request,
                'There are no Twilio numbers available for area code {0}. Search for numbers in a different area code.'.format(area_code))
            return redirect('home')

        context = {}
        context['available_numbers'] = available_numbers

        return render(request, 'call_tracking/list_numbers.html', context)
    else:
        # Our area code was invalid - flash a message and redirect back home
        bad_area_code = form.data['area_code']
        messages.error(request, '{0} is not a valid area code. Please search again.'
                       .format(bad_area_code))

        return redirect('home')


def purchase_number(request):
    """Purchases a new phone number using the Twilio API"""
    form = PurchaseNumberForm(request.POST)

    if form.is_valid():
        # Purchase the phone number
        phone_number = form.cleaned_data['phone_number']
        twilio_number = purchase_phone_number(phone_number.as_e164)

        # Save it in a new LeadSource object
        lead_source = LeadSource(incoming_number=twilio_number.phone_number)
        lead_source.save()

        messages.success(
            request,
            'Phone number {0} has been purchased. Please add a name for this lead source.'.format(
                twilio_number.friendly_name))

        # Redirect to edit lead page
        return redirect('edit_lead_source', pk=lead_source.pk)
    else:
        # In the unlikely event of an error, redirect to the home page
        bad_phone_number = form.data['phone_number']
        messages.error(request, '{0} is not a valid phone number. Please search again.'
                       .format(bad_phone_number))

        return redirect('home')


class LeadSourceUpdateView(SuccessMessageMixin, UpdateView):
    """Powers a form to edit Lead Sources"""

    model = LeadSource
    fields = ['name', 'forwarding_number']
    success_url = reverse_lazy('home')
    success_message = 'Lead source successfully updated.'


# View used by Twilio API to connect callers to the right forwarding
# number for that lead source
@csrf_exempt
def forward_call(request):
    """Connects an incoming call to the correct forwarding number"""
    # First look up the lead source
    source = LeadSource.objects.get(incoming_number=request.POST['Called'])

    # Create a lead entry for this call
    lead = Lead(
        source=source,
        phone_number=request.POST['Caller'],
        city=request.POST['CallerCity'],
        state=request.POST['CallerState'])
    lead.save()

    # Respond with some TwiML that connects the caller to the forwarding_number
    r = VoiceResponse()
    r.dial(source.forwarding_number.as_e164)

    return HttpResponse(r)
call_tracking/views.py
Call Forwarding

call_tracking/views.py

Once we have forwarded calls and created leads, we will have a lot of incoming calls that will create leads, and that will be data for us but we need to transform that data into information in order to get benefits from it. So, let's see how we get statistics from these sources on the next step.

Get statistics about our lead sources

One useful statistic we can get from our data is how many calls each LeadSource has received. We use the Django ORM's annotate feature to make a list containing each LeadSource and a count of its Lead models. We create a custom model manager so that we can easily access this data in our views.

Loading Code Samples...
Language
from django.db import models
from django.db.models import Count
from django.utils.encoding import python_2_unicode_compatible
from phonenumber_field.modelfields import PhoneNumberField


class LeadSourceManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_source' method"""

    def get_leads_per_source(self):
        """Get the number of leads for each lead source"""
        # Use Django's annotate feature to include the number of leads
        # on each lead source
        queryset = self.all().annotate(Count('lead')).order_by('name')

        # Extract the source names and lead counts and make them a regular list
        data = list(queryset.values('name', 'lead__count'))

        return data


@python_2_unicode_compatible
class LeadSource(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text='E.g. "Downtown billboard"')
    incoming_number = PhoneNumberField(
        unique=True,
        help_text='A phone number purchased through Twilio')
    forwarding_number = PhoneNumberField(
        blank=True,
        help_text='People who call this lead source will be connected with this phone number. Must include international prefix - e.g. +1 555 555 55555')

    # Apply our custom manager
    objects = LeadSourceManager()

    def __str__(self):
        if self.name:
            return '{0} - {1}'.format(self.name, self.incoming_number)
        else:
            return '(not yet named) - {0}'.format(self.incoming_number)


class LeadManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_city' method"""

    def get_leads_per_city(self):
        """Get the number of leads for each city"""
        # Use Django's annotate feature to include the number of leads
        # from each distinct city
        queryset = self.all().values('city').annotate(
            Count('id')).order_by('city')

        # Extract the cities and lead counts and make them a regular list
        data = list(queryset.values('city', 'id__count'))

        return data


@python_2_unicode_compatible
class Lead(models.Model):
    source = models.ForeignKey(LeadSource)
    phone_number = PhoneNumberField()
    timestamp = models.DateTimeField(auto_now_add=True)

    # A couple examples of fields you could track for each incoming call
    # See https://www.twilio.com/docs/api/twiml/twilio_request for more
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=2)

    # Apply our custom manager
    objects = LeadManager()

    def __str__(self):
        return '{0}, {1} at {2}'.format(self.city, self.state, self.timestamp)
call_tracking/models.py
Leads By Source Stats

call_tracking/models.py

Up until this point, we have been focusing on the backend code to our application. Which is ready to start handling incoming calls or leads. Next, let's turn our attention to the client side. Which, in this case, is a simple Javascript application, along with Chart.js which will render these stats in an appropriate way.

Visualize our statistics with Chart.js

Back on the home page, we fetch call tracking statistics in JSON from the server using jQuery. We display the stats in colorful pie charts we create with Chart.js.

Call tracking charts

Loading Code Samples...
Language
// Renders the charts on the home page using Chart.js

// Make all charts responsive
Chart.defaults.global.responsive = true;

// Configure the leadsBySource bar chart
$.get("/call-tracking/leads-by-source", function(data) {
  var chartData = [];
  for (var i=0; i<data.length; i++) {
    chartData.push({
      value: data[i]['lead__count'],
      label: data[i]['name']
    });
  }

  var ctx = document.getElementById("leadsBySource").getContext("2d");
  var leadsBySource = new Chart(ctx).Pie(chartData);
});

// Configure the leadsByCity bar chart
$.get("/call-tracking/leads-by-city", function(data) {
  var chartData = [];
  for (var i=0; i<data.length; i++) {
    chartData.push({
      value: data[i]['id__count'],
      label: data[i]['city']
    });
  }

  var ctx = document.getElementById("leadsByCity").getContext("2d");
  var leadsByCity = new Chart(ctx).Pie(chartData);
});
call_tracking/static/js/callTracking.js
Pie Chart from Stats

call_tracking/static/js/callTracking.js

That's it! Our ASP.NET MVC application is now ready to purchase new phone numbers, forward incoming calls, and record some statistics for our business.

Where to next?

That's it! Our Django application is now ready to purchase new phone numbers, forward incoming calls, and record some statistics for our business.

If you're a Python developer working with Twilio, you might also enjoy these tutorials:

Click-To-Call (Flask)

Put a button on your web page that connects visitors to live support or sales people via telephone.

Appointment Reminders (Django)

Send your customers automatic reminders ahead of appoiments using Twilio SMS.

Did this help?

Thanks for checking this tutorial out! If you have any feedback to share with us please contact us on Twitter, we'd love to hear it.

Agustin Camino
Samuel Mendes
David Prothero
Andrew Baker
Jose Oliveros

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 django.conf import settings
from twilio.rest import Client

import os

# Uses credentials defined in TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN
# environment variables
account_sid = settings.TWILIO_ACCOUNT_SID
auth_token = settings.TWILIO_AUTH_TOKEN

client = Client(account_sid, auth_token)


def search_phone_numbers(area_code=None):
    """Queries the Twilio REST API to get phone numbers available for puchase"""
    # You can change the country argument to search outside the US
    # area_code is an optional parameter
    numbers = client.available_phone_numbers("US") \
                    .local \
                    .list(area_code=area_code)

    # Returns 30 by default - let's trim the list for UX purposes
    return numbers[:10]


def purchase_phone_number(phone_number):
    """Purchases a new phone number from the Twilio API"""
    # Use a TwiML Application SID so all our numbers use the same voice URL
    number = client.incoming_phone_numbers.create(
        phone_number=phone_number,
        voice_application_sid=settings.TWIML_APPLICATION_SID)

    return number
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
from twilio.twiml.voice_response import VoiceResponse

from .forms import AreaCodeForm, PurchaseNumberForm
from .models import LeadSource, Lead
from .utils import search_phone_numbers, purchase_phone_number


# Home page view and JSON views to power the charts
def home(request):
    """Renders the home page"""
    context = {}

    # Add the area code form - default to 415
    context['form'] = AreaCodeForm({'area_code': '415'})

    # Add the list of lead sources
    context['lead_sources'] = LeadSource.objects.all()

    return render(request, 'index.html', context)


def leads_by_source(request):
    """Returns JSON data about the lead sources and how many leads they have"""
    # Invoke a LeadSource classmethod to get the data
    data = LeadSource.objects.get_leads_per_source()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


def leads_by_city(request):
    """Returns JSON data about the different cities leads come from"""
    # Invoke a Lead classmethod to get the data
    data = Lead.objects.get_leads_per_city()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


# Views for purchase number workflow
def list_numbers(request):
    """Uses the Twilio API to generate a list of available phone numbers"""
    form = AreaCodeForm(request.POST)

    if form.is_valid():
        # We received a valid area code - query the Twilio API
        area_code = form.cleaned_data['area_code']

        available_numbers = search_phone_numbers(area_code=area_code)

        # Check if there are no numbers available in this area code
        if not available_numbers:
            messages.error(
                request,
                'There are no Twilio numbers available for area code {0}. Search for numbers in a different area code.'.format(area_code))
            return redirect('home')

        context = {}
        context['available_numbers'] = available_numbers

        return render(request, 'call_tracking/list_numbers.html', context)
    else:
        # Our area code was invalid - flash a message and redirect back home
        bad_area_code = form.data['area_code']
        messages.error(request, '{0} is not a valid area code. Please search again.'
                       .format(bad_area_code))

        return redirect('home')


def purchase_number(request):
    """Purchases a new phone number using the Twilio API"""
    form = PurchaseNumberForm(request.POST)

    if form.is_valid():
        # Purchase the phone number
        phone_number = form.cleaned_data['phone_number']
        twilio_number = purchase_phone_number(phone_number.as_e164)

        # Save it in a new LeadSource object
        lead_source = LeadSource(incoming_number=twilio_number.phone_number)
        lead_source.save()

        messages.success(
            request,
            'Phone number {0} has been purchased. Please add a name for this lead source.'.format(
                twilio_number.friendly_name))

        # Redirect to edit lead page
        return redirect('edit_lead_source', pk=lead_source.pk)
    else:
        # In the unlikely event of an error, redirect to the home page
        bad_phone_number = form.data['phone_number']
        messages.error(request, '{0} is not a valid phone number. Please search again.'
                       .format(bad_phone_number))

        return redirect('home')


class LeadSourceUpdateView(SuccessMessageMixin, UpdateView):
    """Powers a form to edit Lead Sources"""

    model = LeadSource
    fields = ['name', 'forwarding_number']
    success_url = reverse_lazy('home')
    success_message = 'Lead source successfully updated.'


# View used by Twilio API to connect callers to the right forwarding
# number for that lead source
@csrf_exempt
def forward_call(request):
    """Connects an incoming call to the correct forwarding number"""
    # First look up the lead source
    source = LeadSource.objects.get(incoming_number=request.POST['Called'])

    # Create a lead entry for this call
    lead = Lead(
        source=source,
        phone_number=request.POST['Caller'],
        city=request.POST['CallerCity'],
        state=request.POST['CallerState'])
    lead.save()

    # Respond with some TwiML that connects the caller to the forwarding_number
    r = VoiceResponse()
    r.dial(source.forwarding_number.as_e164)

    return HttpResponse(r)
from django.conf import settings
from twilio.rest import Client

import os

# Uses credentials defined in TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN
# environment variables
account_sid = settings.TWILIO_ACCOUNT_SID
auth_token = settings.TWILIO_AUTH_TOKEN

client = Client(account_sid, auth_token)


def search_phone_numbers(area_code=None):
    """Queries the Twilio REST API to get phone numbers available for puchase"""
    # You can change the country argument to search outside the US
    # area_code is an optional parameter
    numbers = client.available_phone_numbers("US") \
                    .local \
                    .list(area_code=area_code)

    # Returns 30 by default - let's trim the list for UX purposes
    return numbers[:10]


def purchase_phone_number(phone_number):
    """Purchases a new phone number from the Twilio API"""
    # Use a TwiML Application SID so all our numbers use the same voice URL
    number = client.incoming_phone_numbers.create(
        phone_number=phone_number,
        voice_application_sid=settings.TWIML_APPLICATION_SID)

    return number
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
from twilio.twiml.voice_response import VoiceResponse

from .forms import AreaCodeForm, PurchaseNumberForm
from .models import LeadSource, Lead
from .utils import search_phone_numbers, purchase_phone_number


# Home page view and JSON views to power the charts
def home(request):
    """Renders the home page"""
    context = {}

    # Add the area code form - default to 415
    context['form'] = AreaCodeForm({'area_code': '415'})

    # Add the list of lead sources
    context['lead_sources'] = LeadSource.objects.all()

    return render(request, 'index.html', context)


def leads_by_source(request):
    """Returns JSON data about the lead sources and how many leads they have"""
    # Invoke a LeadSource classmethod to get the data
    data = LeadSource.objects.get_leads_per_source()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


def leads_by_city(request):
    """Returns JSON data about the different cities leads come from"""
    # Invoke a Lead classmethod to get the data
    data = Lead.objects.get_leads_per_city()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


# Views for purchase number workflow
def list_numbers(request):
    """Uses the Twilio API to generate a list of available phone numbers"""
    form = AreaCodeForm(request.POST)

    if form.is_valid():
        # We received a valid area code - query the Twilio API
        area_code = form.cleaned_data['area_code']

        available_numbers = search_phone_numbers(area_code=area_code)

        # Check if there are no numbers available in this area code
        if not available_numbers:
            messages.error(
                request,
                'There are no Twilio numbers available for area code {0}. Search for numbers in a different area code.'.format(area_code))
            return redirect('home')

        context = {}
        context['available_numbers'] = available_numbers

        return render(request, 'call_tracking/list_numbers.html', context)
    else:
        # Our area code was invalid - flash a message and redirect back home
        bad_area_code = form.data['area_code']
        messages.error(request, '{0} is not a valid area code. Please search again.'
                       .format(bad_area_code))

        return redirect('home')


def purchase_number(request):
    """Purchases a new phone number using the Twilio API"""
    form = PurchaseNumberForm(request.POST)

    if form.is_valid():
        # Purchase the phone number
        phone_number = form.cleaned_data['phone_number']
        twilio_number = purchase_phone_number(phone_number.as_e164)

        # Save it in a new LeadSource object
        lead_source = LeadSource(incoming_number=twilio_number.phone_number)
        lead_source.save()

        messages.success(
            request,
            'Phone number {0} has been purchased. Please add a name for this lead source.'.format(
                twilio_number.friendly_name))

        # Redirect to edit lead page
        return redirect('edit_lead_source', pk=lead_source.pk)
    else:
        # In the unlikely event of an error, redirect to the home page
        bad_phone_number = form.data['phone_number']
        messages.error(request, '{0} is not a valid phone number. Please search again.'
                       .format(bad_phone_number))

        return redirect('home')


class LeadSourceUpdateView(SuccessMessageMixin, UpdateView):
    """Powers a form to edit Lead Sources"""

    model = LeadSource
    fields = ['name', 'forwarding_number']
    success_url = reverse_lazy('home')
    success_message = 'Lead source successfully updated.'


# View used by Twilio API to connect callers to the right forwarding
# number for that lead source
@csrf_exempt
def forward_call(request):
    """Connects an incoming call to the correct forwarding number"""
    # First look up the lead source
    source = LeadSource.objects.get(incoming_number=request.POST['Called'])

    # Create a lead entry for this call
    lead = Lead(
        source=source,
        phone_number=request.POST['Caller'],
        city=request.POST['CallerCity'],
        state=request.POST['CallerState'])
    lead.save()

    # Respond with some TwiML that connects the caller to the forwarding_number
    r = VoiceResponse()
    r.dial(source.forwarding_number.as_e164)

    return HttpResponse(r)
from django.db import models
from django.db.models import Count
from django.utils.encoding import python_2_unicode_compatible
from phonenumber_field.modelfields import PhoneNumberField


class LeadSourceManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_source' method"""

    def get_leads_per_source(self):
        """Get the number of leads for each lead source"""
        # Use Django's annotate feature to include the number of leads
        # on each lead source
        queryset = self.all().annotate(Count('lead')).order_by('name')

        # Extract the source names and lead counts and make them a regular list
        data = list(queryset.values('name', 'lead__count'))

        return data


@python_2_unicode_compatible
class LeadSource(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text='E.g. "Downtown billboard"')
    incoming_number = PhoneNumberField(
        unique=True,
        help_text='A phone number purchased through Twilio')
    forwarding_number = PhoneNumberField(
        blank=True,
        help_text='People who call this lead source will be connected with this phone number. Must include international prefix - e.g. +1 555 555 55555')

    # Apply our custom manager
    objects = LeadSourceManager()

    def __str__(self):
        if self.name:
            return '{0} - {1}'.format(self.name, self.incoming_number)
        else:
            return '(not yet named) - {0}'.format(self.incoming_number)


class LeadManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_city' method"""

    def get_leads_per_city(self):
        """Get the number of leads for each city"""
        # Use Django's annotate feature to include the number of leads
        # from each distinct city
        queryset = self.all().values('city').annotate(
            Count('id')).order_by('city')

        # Extract the cities and lead counts and make them a regular list
        data = list(queryset.values('city', 'id__count'))

        return data


@python_2_unicode_compatible
class Lead(models.Model):
    source = models.ForeignKey(LeadSource)
    phone_number = PhoneNumberField()
    timestamp = models.DateTimeField(auto_now_add=True)

    # A couple examples of fields you could track for each incoming call
    # See https://www.twilio.com/docs/api/twiml/twilio_request for more
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=2)

    # Apply our custom manager
    objects = LeadManager()

    def __str__(self):
        return '{0}, {1} at {2}'.format(self.city, self.state, self.timestamp)
from django.db import models
from django.db.models import Count
from django.utils.encoding import python_2_unicode_compatible
from phonenumber_field.modelfields import PhoneNumberField


class LeadSourceManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_source' method"""

    def get_leads_per_source(self):
        """Get the number of leads for each lead source"""
        # Use Django's annotate feature to include the number of leads
        # on each lead source
        queryset = self.all().annotate(Count('lead')).order_by('name')

        # Extract the source names and lead counts and make them a regular list
        data = list(queryset.values('name', 'lead__count'))

        return data


@python_2_unicode_compatible
class LeadSource(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text='E.g. "Downtown billboard"')
    incoming_number = PhoneNumberField(
        unique=True,
        help_text='A phone number purchased through Twilio')
    forwarding_number = PhoneNumberField(
        blank=True,
        help_text='People who call this lead source will be connected with this phone number. Must include international prefix - e.g. +1 555 555 55555')

    # Apply our custom manager
    objects = LeadSourceManager()

    def __str__(self):
        if self.name:
            return '{0} - {1}'.format(self.name, self.incoming_number)
        else:
            return '(not yet named) - {0}'.format(self.incoming_number)


class LeadManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_city' method"""

    def get_leads_per_city(self):
        """Get the number of leads for each city"""
        # Use Django's annotate feature to include the number of leads
        # from each distinct city
        queryset = self.all().values('city').annotate(
            Count('id')).order_by('city')

        # Extract the cities and lead counts and make them a regular list
        data = list(queryset.values('city', 'id__count'))

        return data


@python_2_unicode_compatible
class Lead(models.Model):
    source = models.ForeignKey(LeadSource)
    phone_number = PhoneNumberField()
    timestamp = models.DateTimeField(auto_now_add=True)

    # A couple examples of fields you could track for each incoming call
    # See https://www.twilio.com/docs/api/twiml/twilio_request for more
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=2)

    # Apply our custom manager
    objects = LeadManager()

    def __str__(self):
        return '{0}, {1} at {2}'.format(self.city, self.state, self.timestamp)
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.template.context_processors import csrf
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
from twilio.twiml.voice_response import VoiceResponse

from .forms import AreaCodeForm, PurchaseNumberForm
from .models import LeadSource, Lead
from .utils import search_phone_numbers, purchase_phone_number


# Home page view and JSON views to power the charts
def home(request):
    """Renders the home page"""
    context = {}

    # Add the area code form - default to 415
    context['form'] = AreaCodeForm({'area_code': '415'})

    # Add the list of lead sources
    context['lead_sources'] = LeadSource.objects.all()

    return render(request, 'index.html', context)


def leads_by_source(request):
    """Returns JSON data about the lead sources and how many leads they have"""
    # Invoke a LeadSource classmethod to get the data
    data = LeadSource.objects.get_leads_per_source()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


def leads_by_city(request):
    """Returns JSON data about the different cities leads come from"""
    # Invoke a Lead classmethod to get the data
    data = Lead.objects.get_leads_per_city()

    # Return it as JSON - use safe=False because we're sending a JSON array
    return JsonResponse(data, safe=False)


# Views for purchase number workflow
def list_numbers(request):
    """Uses the Twilio API to generate a list of available phone numbers"""
    form = AreaCodeForm(request.POST)

    if form.is_valid():
        # We received a valid area code - query the Twilio API
        area_code = form.cleaned_data['area_code']

        available_numbers = search_phone_numbers(area_code=area_code)

        # Check if there are no numbers available in this area code
        if not available_numbers:
            messages.error(
                request,
                'There are no Twilio numbers available for area code {0}. Search for numbers in a different area code.'.format(area_code))
            return redirect('home')

        context = {}
        context['available_numbers'] = available_numbers

        return render(request, 'call_tracking/list_numbers.html', context)
    else:
        # Our area code was invalid - flash a message and redirect back home
        bad_area_code = form.data['area_code']
        messages.error(request, '{0} is not a valid area code. Please search again.'
                       .format(bad_area_code))

        return redirect('home')


def purchase_number(request):
    """Purchases a new phone number using the Twilio API"""
    form = PurchaseNumberForm(request.POST)

    if form.is_valid():
        # Purchase the phone number
        phone_number = form.cleaned_data['phone_number']
        twilio_number = purchase_phone_number(phone_number.as_e164)

        # Save it in a new LeadSource object
        lead_source = LeadSource(incoming_number=twilio_number.phone_number)
        lead_source.save()

        messages.success(
            request,
            'Phone number {0} has been purchased. Please add a name for this lead source.'.format(
                twilio_number.friendly_name))

        # Redirect to edit lead page
        return redirect('edit_lead_source', pk=lead_source.pk)
    else:
        # In the unlikely event of an error, redirect to the home page
        bad_phone_number = form.data['phone_number']
        messages.error(request, '{0} is not a valid phone number. Please search again.'
                       .format(bad_phone_number))

        return redirect('home')


class LeadSourceUpdateView(SuccessMessageMixin, UpdateView):
    """Powers a form to edit Lead Sources"""

    model = LeadSource
    fields = ['name', 'forwarding_number']
    success_url = reverse_lazy('home')
    success_message = 'Lead source successfully updated.'


# View used by Twilio API to connect callers to the right forwarding
# number for that lead source
@csrf_exempt
def forward_call(request):
    """Connects an incoming call to the correct forwarding number"""
    # First look up the lead source
    source = LeadSource.objects.get(incoming_number=request.POST['Called'])

    # Create a lead entry for this call
    lead = Lead(
        source=source,
        phone_number=request.POST['Caller'],
        city=request.POST['CallerCity'],
        state=request.POST['CallerState'])
    lead.save()

    # Respond with some TwiML that connects the caller to the forwarding_number
    r = VoiceResponse()
    r.dial(source.forwarding_number.as_e164)

    return HttpResponse(r)
from django.db import models
from django.db.models import Count
from django.utils.encoding import python_2_unicode_compatible
from phonenumber_field.modelfields import PhoneNumberField


class LeadSourceManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_source' method"""

    def get_leads_per_source(self):
        """Get the number of leads for each lead source"""
        # Use Django's annotate feature to include the number of leads
        # on each lead source
        queryset = self.all().annotate(Count('lead')).order_by('name')

        # Extract the source names and lead counts and make them a regular list
        data = list(queryset.values('name', 'lead__count'))

        return data


@python_2_unicode_compatible
class LeadSource(models.Model):
    name = models.CharField(
        max_length=100,
        blank=True,
        help_text='E.g. "Downtown billboard"')
    incoming_number = PhoneNumberField(
        unique=True,
        help_text='A phone number purchased through Twilio')
    forwarding_number = PhoneNumberField(
        blank=True,
        help_text='People who call this lead source will be connected with this phone number. Must include international prefix - e.g. +1 555 555 55555')

    # Apply our custom manager
    objects = LeadSourceManager()

    def __str__(self):
        if self.name:
            return '{0} - {1}'.format(self.name, self.incoming_number)
        else:
            return '(not yet named) - {0}'.format(self.incoming_number)


class LeadManager(models.Manager):
    """A custom manager which adds a 'get_leads_per_city' method"""

    def get_leads_per_city(self):
        """Get the number of leads for each city"""
        # Use Django's annotate feature to include the number of leads
        # from each distinct city
        queryset = self.all().values('city').annotate(
            Count('id')).order_by('city')

        # Extract the cities and lead counts and make them a regular list
        data = list(queryset.values('city', 'id__count'))

        return data


@python_2_unicode_compatible
class Lead(models.Model):
    source = models.ForeignKey(LeadSource)
    phone_number = PhoneNumberField()
    timestamp = models.DateTimeField(auto_now_add=True)

    # A couple examples of fields you could track for each incoming call
    # See https://www.twilio.com/docs/api/twiml/twilio_request for more
    city = models.CharField(max_length=100)
    state = models.CharField(max_length=2)

    # Apply our custom manager
    objects = LeadManager()

    def __str__(self):
        return '{0}, {1} at {2}'.format(self.city, self.state, self.timestamp)
// Renders the charts on the home page using Chart.js

// Make all charts responsive
Chart.defaults.global.responsive = true;

// Configure the leadsBySource bar chart
$.get("/call-tracking/leads-by-source", function(data) {
  var chartData = [];
  for (var i=0; i<data.length; i++) {
    chartData.push({
      value: data[i]['lead__count'],
      label: data[i]['name']
    });
  }

  var ctx = document.getElementById("leadsBySource").getContext("2d");
  var leadsBySource = new Chart(ctx).Pie(chartData);
});

// Configure the leadsByCity bar chart
$.get("/call-tracking/leads-by-city", function(data) {
  var chartData = [];
  for (var i=0; i<data.length; i++) {
    chartData.push({
      value: data[i]['id__count'],
      label: data[i]['city']
    });
  }

  var ctx = document.getElementById("leadsByCity").getContext("2d");
  var leadsByCity = new Chart(ctx).Pie(chartData);
});