Geek Stuff

Happy Developers

Posted in Geek Stuff on February 22nd, 2013 by admin – Be the first to comment

I’ve always been all about removing barriers to help people get where they want to go.  It’s how I get gratification from my work, my play, my entire life.  This week at the amazing API Strategy and Practice Conference I’m surrounded by engaged, curious, challenging people who are committed to bringing the API world to the next level.  We’ve grown the API ecosystem organically up until now, all doing our best to meet our own goals, but being around people who have looked at this world from so many different directions makes it possible for us to see the patterns.

I spoke yesterday on a panel on API design – and I talked, as I frequently do, about happy developers. Jakub Nešetřil of Apiary.io made an insightful comment when he noted that we’re used to designing APIs for computers – they are, after all, programmatic interfaces. But in this world where developers are the interaction level, we have to make user interface choices. Developers are users of your API and if you don’t treat them as such they’ll be frustrated and less likely to be successful.

My talk, as always, was peppered with visual metaphors.  Developers are the wild animals out on the savannah, looking for a watering hole they like.  When they come to check out your API they’re giving you a valuable gift of something they’ll never get more of – their time.  Show them you value and respect that gift by communicating with them clearly about your product, your goals, and giving them ways to engage right away.  Play with them – provide tutorials and examples that give them a sense of progress (yes, level them up.  Game theory isn’t a bad thing.  Developers are, by nature, gamers.  Use that.)  Give them building blocks of working code, like legos so they can build new toys.  Play with your developers.  Seriously.

And support them.  Not just in the usual ways, but if you want to earn developer love, get them out there evangelizing your product, helping you support other users… teach them to be successful.  Give them tools, give them strategies, tell them how to solve the problems on their own so when they’re hacking at 3AM they can finish the toy they wanted to build.  Celebrate their successes with them because when you work with them, those successes are yours as well.

Your developers are users, yes, but they are also your peers.  They may have a different context or be coming to your system from a foreign programming world, but they want to interact with your product, and helping them to be successful is the best way to ensure the success of your own platform.

Handling SMS Responses from Twilio using Django on Heroku

Posted in API, Geek Stuff, LinkedIn on December 22nd, 2011 by admin – 1 Comment

The final piece in the application I’m working on requires that I process SMS replies from Twilio.  To do this, I need a public-facing server that can handle a POST request from another server so it can do the right thing on my end.  This tutorial will walk through how to do that with a Django server on Heroku.  You can grab the twilio_sms application from within my GitHub project for this application, or you can build it based on the walkthrough here.

URL Setup

Add a mapping for /reply/ to the urls.py in your top level Django project.

urls.py

url(r'^reply', 'twilio_sms.views.sms_reply'),

You’ll need to tell Twilio where to send SMS replies on the dashboard.  For instance, my Heroku instance runs at http://falling-summer-4605.herokuapp.com so my SMS Response URL is set to http://falling-summer-4605.herokuapp.com/reply.

Creating twilio_sms

In order for that URL mapping to do the right thing it needs to point to the right application.  If you aren’t using the code samples from GitHub, you can create a new application with:

django-admin.py startapp twilio_sms

Now we need to build the views.py file to handle the responses.

The setup is similar to the linkedin application we made previously.

# Python
import oauth2 as oauth
import simplejson as json
import re
from xml.dom.minidom import getDOMImplementation,parse,parseString

# Django
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.conf import settings
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.models import User

# Project
from linkedin.models import UserProfile,SentArticle

# from settings.py
consumer = oauth.Consumer(settings.LINKEDIN_TOKEN, settings.LINKEDIN_SECRET)
client = oauth.Client(consumer)

We’ll create a convenience method to build SMS responses in the format expected by the Twilio server (so that the user gets a reasonable SMS response)

def createSmsResponse(responsestring):
	impl = getDOMImplementation()
	responsedoc = impl.createDocument(None,"Response",None)
	top_element = responsedoc.documentElement
	sms_element = responsedoc.createElement("Sms")
	top_element.appendChild(sms_element)
	text_node = responsedoc.createTextNode(responsestring)
	sms_element.appendChild(text_node)
	html = responsedoc.toxml(encoding="utf-8")
	return html

Because the POST will be coming from an external server without a session cookie, we need to use the @csrf_exempt decorator to tell Django to allow these POSTs without authentication.  For security, you might check the incoming IP to make sure it’s coming from Twilio, or make sure that the other information matches what you expect.  For this demo, we’ll allow it to proceed assuming it’s the right thing.

Grab the parameters, get the user’s phone number, and determine which of our users matches that phone number, then grab their credentials and create the LinkedIn client to make requests.

@csrf_exempt
def sms_reply(request):
    if request.method == 'POST':
        params = request.POST
        phone = re.sub('\+1','',params['From'])
        smsuser = User.objects.get(userprofile__phone_number=phone)
        responsetext = "This is my reply text"
        token = oauth.Token(smsuser.get_profile().oauth_token, smsuser.get_profile().oauth_secret)
        client = oauth.Client(consumer,token)

Figure out what the user wants us to do (save, search, cancel, help, level)

commandmatch = re.compile(r'(\w+)\b',re.I)
        matches = commandmatch.match(params['Body'])
        command = matches.group(0).lower()

“Cancel” tells the system the user doesn’t want notifications anymore.  For now, we’re going to keep their user and profile around, so that we don’t send them all the same articles again in the future. But sendArticles.py won’t send them anything if the level is set to zero.

        # Cancel notifications by setting score to zero
        # Command is 'cancel'
        if command == 'cancel':
        	profile = smsuser.get_profile()
        	profile.min_score = 0
        	profile.save()
        	return HttpResponse(createSmsResponse("Today SMS Service Cancelled"))

“Level <number>” tells the system the user wants to change their notification level.  Higher means fewer messages, as this is used as the “score” check against articles based on relevance.  See the previous post on Twilio notifications to see how this is implemented.  Notice that in this and the following methods we’re doing generic error catching – there’s a few reasons why it might fail, but the important thing is to tell the user their action didn’t succeed and give them a hint as to why that might be.

        # Change level for notifications by setting score to requested level
        # Command is 'level \d'
        if command == 'level':
        	levelmatch = re.compile(r'level (\d)(.*)',re.I)
        	matches = levelmatch.search(params['Body'])

        	try:
	        	level = int(matches.group(1))
	        except:
	        	e = sys.exc_info()[1]
	        	print "ERROR: %s" % (str(e))
	        	return HttpResponse(createSmsResponse("Please use a valid level (1-9)."))

        	profile = smsuser.get_profile()
        	profile.min_score = level
        	profile.save()
        	return HttpResponse(createSmsResponse("Today SMS minimum score changed to %d" % int(level)))

“Save <article number>” saves an article to the user’s LinkedIn saved articles.  Remember that in the setup we grabbed the credentials for the user who sent the SMS based on their phone number, so this (and share) are done against the LinkedIn API on their behalf.  In this new (preview only) API JSON doesn’t seem to be working well, so I’m building and using XML.

        # Save an article
        # Command is 'save <articlenum>'
        if command == 'save':
        	savematch = re.compile(r'save (\d+)(.*)',re.I)
        	matches = savematch.search(params['Body'])
        	try:
	        	article = matches.group(1)
        		sentarticle = SentArticle.objects.get(user=smsuser, id=article)
	        except:
	        	e = sys.exc_info()[1]
	        	print "ERROR: %s" % (str(e))
	        	return HttpResponse(createSmsResponse("Please use a valid article number with save."))

        	responsetext = "Saved article: %s" % (sentarticle.article_title)
        	saveurl = "http://api.linkedin.com/v1/people/~/articles"

        	# Oddly JSON doesn't seem to work with the article save API
        	# Using XML instead
        	impl = getDOMImplementation()
        	xmlsavedoc = impl.createDocument(None,"article",None)
        	top_element = xmlsavedoc.documentElement
        	article_content_element = xmlsavedoc.createElement("article-content")
        	top_element.appendChild(article_content_element)
        	id_element = xmlsavedoc.createElement("id")
        	article_content_element.appendChild(id_element)
        	text_node = xmlsavedoc.createTextNode(sentarticle.article_number)
        	id_element.appendChild(text_node)
        	body = xmlsavedoc.toxml(encoding="utf-8")

        	resp, content = client.request(saveurl, "POST",body=body,headers={"Content-Type":"text/xml"})
        	if (resp.status == 200):
        		return HttpResponse(createSmsResponse(responsetext))
        	else:
        		return HttpResponse(createSmsResponse("Unable to save post: %s" % content))

“Share <article number> <comment>” shares an article to the user’s network with a comment.  The comment shouldn’t really be optional, but typing on T-9 keyboards is a pain, so I wanted to give a default share message.  I’m not sure I love it as an answer though…

        # Share an article
        # Command is 'share <articlenum> <comment>'
        # If no comment is included, a generic one is sent
        if command == 'share':
        	sharematch = re.compile(r'Share (\d+) (.*)')
        	matches = sharematch.search(params['Body'])
        	try:
        		article = matches.group(1)
	        	sentarticle = SentArticle.objects.get(user=smsuser, id=article)
        		comment = matches.group(2)
	        except:
	        	if sentarticle and not comment:
	        		comment = "Sharing an article from the LinkedIn SMS System"
	        	else:
	        		e = sys.exc_info()[1]
	        		print "ERROR: %s" % (str(e))
	        		return HttpResponse(createSmsResponse("Please use a valid article number with share and include a comment."))

        	responsetext = "Shared article: %s" % (sentarticle.article_title)
        	shareurl = "http://api.linkedin.com/v1/people/~/shares"
        	body = {"comment":comment,
        		"content":{
        			"article-id":sentarticle.article_number
       	 		},
        	"visibility":{"code":"anyone"}
        	}

        	resp, content = client.request(shareurl, "POST",body=json.dumps(body),headers={"Content-Type":"application/json"})
        	if (resp.status == 201):
        		return HttpResponse(createSmsResponse(responsetext))
        	else:
        		return HttpResponse(createSmsResponse("Unable to share post: %s" % content))

If we’ve fallen through to here, the user may have asked for ‘help’ – but whatever they did we didn’t understand it so we should give them the help text in any case.

       # If command is help, or anything we didn't recognize, send help back
        helpstring = "Commands: 'cancel' to cancel Today SMS; 'level #number#' to change minimum score;"
        helpstring += "'save #article#' to save; 'share #article# #comment#' to share"
        return HttpResponse(createSmsResponse(help string))

… and, if the request wasn’t a POST, send a 405 response back to the system (it won’t be Twilio, it might have been someone else).  This URL is only for processing these SMS messages.

    # If it's not a post, return an error
    return HttpResponseNotAllowed('POST')

Sending SMS Notifications using Twilio, Django and the Heroku Scheduler

Posted in API, Geek Stuff, Heroku, LinkedIn, Python on December 21st, 2011 by admin – 2 Comments

For my Twilio SMS Demo App this month, I wanted to send notifications to LinkedIn members based on hot items for them in LinkedIn Today.  This is a tutorial on creating a python script to access user information in the Django database, pull information from a Web Service (in this case the LinkedIn Today API) and send an SMS to the user.  A couple of caveats are in order here to make sure that people don’t accidentally run afoul of the LinkedIn API Terms of Use when implementing something of this sort.

  • The LinkedIn Today API is only available internally at this point, and should only be used as an example of what’s possible
  • I have stored the user’s phone number in the database – when storing LinkedIn information you always need to notify the user that you’re doing that, explicitly.
All that having been said, you need the following to get this working:
  • A working Django system on Heroku.  If you set up my example LinkedIn Django application from this post, you’ll be able to extend that here.
  • A Twilio account – ideally with a purchased number, but you could get this system to work with the sandbox system.  You get $30 in Twilio credit to start out with, which is plenty to set up a demo of this sort.
  • A Google API key (this is optional, but it’ll get you more shortened links)

Install the Twilio Library

Assuming that you’re using the LinkedIn Django application from my previous post and are in the root level of that structure, you need to add the Twilio library to your installation:
% pip install twilio
% pip freeze > requirements.txt

Adding the Phone Number

Remember, if you’re going to store any information at all about a LinkedIn member, you need to explicitly tell them so.  That having been said, you can add the following to your LinkedIn application to get the phone number and store it.  I’m not going to provide the completed code to do this since you need to implement some user notification if you’re using this in a production system.

linkedin/models.py

class UserProfile(models.Model):
    user = models.ForeignKey(User)
    oauth_token = models.CharField(max_length=200)
    oauth_secret = models.CharField(max_length=200)
    phone_number = models.CharField(max_length=10)

linkedin/views.py

Modify the LinkedIn profile URL:

url = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,industry,phone-numbers)"

Add this logic before saving the user profile:

        if "phoneNumbers" in profile:
        	for phone_number in profile['phoneNumbers']['values']:
        		userprofile.phone_number = re.sub(r"\D","",phone_number['phoneNumber'])
        		if phone_number['phoneType'] == 'mobile':
        			continue

If you’ve already created your DB you’ll need to wipe it out and recreate it with these new models in order for this to work.  You can most easily do this by just starting a new project for this version – while you can drop the database locally and recreate it with syncdb, for heroku you’ll need to create a new cedar project so a new database will be created.

Creating the Job

Now we’ll walk through the code.  This file wants to live at your LinkedIn project level (so in the example, in the hellodjango directory).  It’s also available in the GitHub repository for django-linkedin-simple.

First, do some basic setup:

from django.core.management import setup_environ
import settings
setup_environ(settings)
from twilio.rest import TwilioRestClient
import oauth2 as oauth
import time
import simplejson
import datetime
import httplib2
import psycopg2

account = "TWILIO_ACCT_NUMBER"
token = "TWILIO_TOKEN"
twilioclient = TwilioRestClient(account, token)

consumer = oauth.Consumer(
        key="LINKEDIN_CONSUMER_KEY",
        secret="LINKEDIN_CONSUMER_SECRET")
url = "http://api.linkedin.com/v1/people/~/topics:(description,id,topic-stories:(topic-articles:(relevance-data,article-content:(id,title,resolved-url))))"
Grab the user profiles (this includes the user tokens and secrets)
users = User.objects.all()

Iterate over the users, grabbing their token, secret, django userid and phone number, then grab the LinkedIn Today feed for that user.

for djangouser in users:
	if djangouser.username == "admin":
		continue
	profile = UserProfile.objects.get(user=djangouser)
	token = oauth.Token(
        	key=profile.oauth_token,
        	secret=profile.oauth_secret)
	phone = profile.phone_number
	userid = profile.user_id
	user_articles = SentArticle.objects.filter(user=djangouser)

	# Now make the LinkedIn today call and get the articles in question
	client = oauth.Client(consumer, token)

	resp, content = client.request(url, headers={"x-li-format":'json'})
	results = simplejson.loads(content)

We only want to send alerts for articles which have a high “score”, indicating that they are currently hot topics for this user.  Look through the articles to find those “hot topics”:

	for topic in results['values']:
           for story in topic['topicStories']['values']:
                for article in story['topicArticles']['values']:
                        score = article['relevanceData']['score']
                        if score > 6:

Now, SMS messages are expensive for us (the application owner) and potentially for the user, so we only want to tell the user if they haven’t already heard about this article.

checkarticle = user_articles.filter(article_number__exact=article['articleContent']['id'])

If there aren’t any matching articles for this user, go ahead and send them a message, then log it in the database so we don’t tell them again later:

if len(checkarticle) == 0:
    # This is where we get the shortened URL from google because LinkedIn doesn't provide one
    http = httplib2.Http()
    body = {"longUrl": article['articleContent']['resolvedUrl']}
    resp,content = http.request("https://www.googleapis.com/urlshortener/v1/url?key=YOUR_GOOGLE_API_KEY","POST",body=simplejson.dumps(body),headers={"Content-Type":"application/json"})
    googleresponse = simplejson.loads(content)
    sentarticle = SentArticle(article_number=article['articleContent']['id'],user=djangouser,timestamp=datetime.datetime.today())
    sentarticle.save()
    bodytext = article['articleContent']['title'] + " " + googleresponse['id']
    bodytext += " ('save %s')" % sentarticle.id
    message = twilioclient.sms.messages.create(to="+1" + phone, from_="+YOUR_TWILIO_NUMBER", body=bodytext)

Great, we’ve got an application that’ll send messages for the users in the Django system when something hot is there to talk about.

Pushing to Heroku

Now we need the job to work on Heroku, and get scheduled.  If you went through my previous post using the existing GitHub example, the file is already up at Heroku, otherwise you’ll need to push the file to your Heroku instance:

% git add hellodjango/sendArticles.py
% git push heroku master

Whether it was already there or you just put it there, you should check to make sure that it runs correctly.  Make sure you’ve logged into the system using your LinkedIn ID and stored your phone number there.

% heroku run python hellodjango/sendArticles.py

Did it work?  Great!

Schedule it in Heroku

The Heroku Scheduler is a free add-on, but remember that since it uses machine time it can start racking up the charges if you don’t keep an eye on it.  You can add it using the command line, or you can add it under “Resources” for your application on the Heroku site.  Once you’ve added it you can manage it using the scheduler dashboard.

Pick whatever frequency you like, and put “python hellodjango/sendArticles.py” in the field, and you’re done.

Don’t forget to stop the scheduler when you’re no longer testing so you don’t get a surprise bill from Heroku later.

The next post will cover handling POST requests from Twilio in response to SMS replies from your users.

Using LinkedIn Authentication with Django on Heroku

Posted in API, Geek Stuff, Heroku, LinkedIn, Python on December 20th, 2011 by admin – 2 Comments

To build the LinkedIn Today SMS Notification System, I needed to find a framework where I could do the following:

  • Integrate LinkedIn OAuth with a more general user management system
  • Post the system to a public site
  • Handle POST requests from external clients

For this set of criteria, I chose to use Django and publish it using Heroku.  This post is similar to the Django Getting Started Guide for Heroku, but it’s designed for this specific use case – Using LinkedIn OAuth as the login system and integrating it with the Django user management system.  The code for this example is available here.

Here are the prerequisites for this tutorial:

  • Python, virtualenv, pip, and the Heroku toolbelt as described in Getting Started with Python on Heroku/Cedar.
  • An installed version of Postgres to test locally
  • A Postgres user and a database to use for Django (in this case I’m using ‘linkedin’)
  • A LinkedIn API Key.  If you haven’t worked with the LinkedIn API before, you’re encouraged to go through the Quick Start Guide.

Create Your Django Environment

For the basic setup I’m following, very closely, the tutorial for Heroku, so I’m leaving out some of the explanation – please refer to the original if you have questions about how things work.  The following steps will create a virtual environment (development sandbox), install needed modules there, and get your Django project and applications created.
% mkdir hellodjango && cd hellodjango
% virtualenv --no-site-packages vent
% source venv/bin/activate
% pip install Django psycopg2 oauth2 simplejson
% django-admin.py startproject hellodjango
% cd hellodjango
% python manage.py startapp linkedin

Now that you’ve done all these pieces, you have a working django system.

File Setup

In order to add the LinkedIn integration, you’ll need to edit the settings and URL files.  I’ve created a GitHub repository with the code needed for this example, and you can use that as your model – but I’ll walk through the changes here so you understand how they work.

hellodjango/settings.py

Edit the DATABASES section to configure it to use postgres, and your database:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'linkedin',
        'USER': 'postgres',
        'PASSWORD': 'xxx',
        'HOST': '',
        'PORT': '5432',
    }
}
You also need to add ‘linkedin’ to your INSTALLED_APPS:
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'linkedin'
    # Uncomment the next line to enable the admin:
    # 'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

And finally, you need to tell the application you want to use LinkedIn for authentication and set up your credentials (this can go anywhere in the settings.py file)

AUTH_PROFILE_MODULE= 'linkedin.UserProfile'
LINKEDIN_TOKEN='xxx'
LINKEDIN_SECRET='xxx'
LOGIN_URL='/login/'

hellodjango/urls.py

You also need to update the urls.py file to add the LinkedIn authentication pages.

url(r'^login/?$', 'linkedin.views.oauth_login'),
url(r'^logout/?$', 'linkedin.views.oauth_logout'),
url(r'^login/authenticated/?$', 'linkedin.views.oauth_authenticated'),
url(r'^$','linkedin.views.home'),

hellodjango/linkedin/models.py

Using LinkedIn Auth means we’ll need to store the user’s token and secret for later requests:

from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
    user = models.ForeignKey(User)
    oauth_token = models.CharField(max_length=200)
    oauth_secret = models.CharField(max_length=200)

hellodjango/linkedin/views.py

I’m including the code here, although it’ll be easier to just get it from GitHub.  This code demonstrates the full OAuth login flow, creating a new Django user using the response, and subsequently making a request using the stored information (home).

# Python
import oauth2 as oauth
import cgi
import simplejson as json
import datetime
import re
# Django
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
# Project
from linkedin.models import UserProfile
# from settings.py
consumer = oauth.Consumer(settings.LINKEDIN_TOKEN, settings.LINKEDIN_SECRET)
client = oauth.Client(consumer)
request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken'
access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken'
authenticate_url = 'https://www.linkedin.com/uas/oauth/authenticate'

# /login
def oauth_login(request):
    # Step 0. Get the current hostname and port for the callback
    if request.META['SERVER_PORT'] == 443:
     current_server = "https://" + request.META['HTTP_HOST']
    else:
     current_server = "http://" + request.META['HTTP_HOST']
     oauth_callback = current_server + "/login/authenticated"
    # Step 1. Get a request token from Provider.
    resp, content = client.request("%s?oauth_callback=%s" % (request_token_url,oauth_callback), "GET")
    if resp['status'] != '200':
        raise Exception("Invalid response from Provider.")
    # Step 2. Store the request token in a session for later use.
    request.session['request_token'] = dict(cgi.parse_qsl(content))
    # Step 3. Redirect the user to the authentication URL.
    url = "%s?oauth_token=%s" % (authenticate_url,
        request.session['request_token']['oauth_token'])
    print url
    return HttpResponseRedirect(url)
# / (requires login)
@login_required
def home(request):
    html = "<html><body>"
    token = oauth.Token(request.user.get_profile().oauth_token,request.user.get_profile().oauth_secret)
    client = oauth.Client(consumer,token)
    headers = {'x-li-format':'json'}
    url = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,headline)"
    resp, content = client.request(url, "GET", headers=headers)
    profile = json.loads(content)
    html += profile['firstName'] + " " + profile['lastName'] + "<br/>" + profile['headline']
    return HttpResponse(html)
# /logout (requires login)
@login_required
def oauth_logout(request):
    # Log a user out using Django's logout function and redirect them
    # back to the homepage.
    logout(request)
    return HttpResponseRedirect('/')
#/login/authenticated/
def oauth_authenticated(request):
    # Step 1. Use the request token in the session to build a new client.
    token = oauth.Token(request.session['request_token']['oauth_token'],
        request.session['request_token']['oauth_token_secret'])
    if 'oauth_verifier' in request.GET:
        token.set_verifier(request.GET['oauth_verifier'])
    client = oauth.Client(consumer, token)
    # Step 2. Request the authorized access token from Provider.
    resp, content = client.request(access_token_url, "GET")
    if resp['status'] != '200':
        print content
        raise Exception("Invalid response from Provider.")
    access_token = dict(cgi.parse_qsl(content))
    headers = {'x-li-format':'json'}
    url = "http://api.linkedin.com/v1/people/~:(id,first-name,last-name,industry)"
    token = oauth.Token(access_token['oauth_token'],
        access_token['oauth_token_secret'])
    client = oauth.Client(consumer,token)
    resp, content = client.request(url, "GET", headers=headers)
    profile = json.loads(content)    
    # Step 3. Lookup the user or create them if they don't exist.
    firstname = profile['firstName']
    lastname = profile['lastName']
    identifier = profile['id']
    try:
        user = User.objects.get(username=identifier)
    except User.DoesNotExist:
        user = User.objects.create_user(identifier,
            '%s@linkedin.com' % identifier,
            access_token['oauth_token_secret'])                 user.first_name = firstname
        user.last_name = lastname
        user.save()
        # Save our permanent token and secret for later.
        userprofile = UserProfile()
        userprofile.user = user
        userprofile.oauth_token = access_token['oauth_token']
        userprofile.oauth_secret = access_token['oauth_token_secret']
        userprofile.save()
    # Authenticate the user and log them in using Django's pre-built
    # functions for these things.
    user = authenticate(username=identifier,
        password=access_token['oauth_token_secret'])
    login(request, user)
    return HttpResponseRedirect('/')

For any future requests, you’ll grab the user’s token and secret from the profile to make the request just like what’s shown in the “home” example above.  I’ll go into this in more detail in the following posts, on creating scheduled requests and handling POST requests from external systems.

Procfile

This is needed at the top level (same level as the hellodjango project directory) to tell Heroku how to run your application.

web: python hellodjango/manage.py runserver "0.0.0.0:$PORT"

.gitignore

Ignore the venv directory and compiled python files

venv
*.pyc

Test Django Server Locally

Ok, now that you’ve gotten everything all set up (or grabbed it from github), you’re ready to run the server and make sure it works correctly.

% python manage.py syncdb
% python manage.py runserver

This should bring up a server on localhost which does the basic authentication dance and presents the user’s name and headline (so exciting!)

Deploy to Heroku

Finally, we can get to the Heroku deployment.  Again, this is taken almost verbatim from the Getting Started With Django document on Heroku, so refer to that for more information on what’s happening.

% git init
% git add .
% git commit -m "My LinkedIn Django App"
% heroku create --stack cedar
% git push heroku master
% heroku run python hellodjango/manage.py syncdb 
% heroku open

This should give you the same experience on Heroku that you have on your local system – the user is sent to LinkedIn to log in, they’re added to the system and presented a fascinating page with their basic information.

I’ve made this example as simple as possible to make it easy to integrate into your Django Application without unnecessary overhead.  Comments, clarifications or other questions welcome.

Hiring Generalists

Posted in Geek Stuff, LinkedIn on July 19th, 2011 by admin – 2 Comments

One of the things that a lot of companies in the Silicon Valley (and, by extension, the rest of the country) struggle with is the hiring process.  I work at a company where we have a lot of phenomenally talented people working here, and an almost infinite supply of talented people who want to work here.  As with a lot of other companies, we’ve been streamlining our process somewhat and have created a process where people from different groups interview candidates for particular skills, with the theory that the person might fit well somewhere else in the company, and we don’t want to miss that chance. We’ve got various specific skills we’re supposed to interview on, and it’s kind of a cookie cutter experience.  You have these skills or you don’t, someone likes you or they don’t.

There’s a problem with this kind of hiring, though.  It’s a great idea if you’re trying to hire someone who can already do exactly the tasks that you’re hiring them for.  However, an interview process like that is likely to eliminate people who can do a phenomenal job at that task even though they’ve never done it before.  I frequently joke that “I don’t know anything, but I can *do* anything.”  Which isn’t really true.  I know a lot of things.  But they don’t extend to CS algorithm puzzles because I don’t have a CompSci degree.  I can, in fact, study up and pass the Google Interview Questions… but I won’t.  Because I am a kick ass engineer, my code is solid, my problem solving skills are phenomenal, and more importantly, if the company needs me to do some magical new thing I wasn’t hired for, there’s a good chance I’ll be happy to do it, and do it exceedingly well.  I don’t want someone to hire me because I’m willing to pretend to be something I’m not.  I want them to hire me for the spectacularly cool things I bring to the company.  If they don’t value those more than my ability to reverse a linked list, then they don’t really get why they should hire me.

Sure, specialists are great.  You know what you’re getting, you can put them in front of a defined, bounded task and they’ll do it in the expected way.  But in today’s environment where companies need to stretch, innovate and grow – at a company where the monthly hackday consistently puts out products the company can leverage – companies need to understand the value of someone who can figure out how to do new tasks, not just hire people who can do the tasks already identified. Every company has people who they hired to do particular things because they can do them.  The spectacular companies also have a large percentage of individuals who thrive on taking on new challenges and exploring new frontiers.

On the other hand, as a generalist, I frequently have helpful feedback in these interviews where we’re looking for skills I don’t currently possess.  But my feedback is usually about the candidate as a thinker, as a doer, as a leader, as a participant in the conversation.  And less about the SQL statements.

Posted in Geek Stuff, LinkedIn on the blog.  Friends and Open Source on G+.

iPhone Beta Software: A Cautionary Tale

Posted in Geek Stuff on July 14th, 2011 by admin – 1 Comment

I know I’ve been working on all these high minded posts, about big hand wavy topics, but sometimes I guess blog posts can just be about your day. Especially when your day has some helpful lessons that other people might learn from.

I’ve had every iPhone since they first came out. I am an unapologetic Apple Fan Girl. I do my work on MacBook Pros, have iPods aplenty, and just generally live in iOS or OSX all the time. I frequently run beta software, and the Universe has generally protected me from the pitfalls of such behavior, but apparently this is not a given.

I have a new iPhone, which I got so I could switch to Verizon. Not because I hate AT&T, although they certainly deserve any amount of hatred folks want to dish out. I’m just not really a hater. But I wanted tethering. And I like shiny new things. And I like unlimited data. So I got the new phone and I switched. And other than the weird no-data-while-voicing rule, the phone has been pretty well behaved. There are times when it gets confused, after I’ve finished talking to someone, about how to be a data providing mobile device, but it generally remembers how after a few minutes. I just figure it’s trying to teach me patience.

This phone, though, does have a problem. Sometimes, for no apparent reason, it just shuts down. It’s going along fine, I’ve just finished a conversation, my battery is over half full, I do a couple of app-y things and look away, and when I look back the screen is black. Poking at the button doesn’t help. Pushing the power button and the front button to reset it does nothing. It’s just… gone. It tends to do this at really inconvenient times – when I really *need* it to do some sort of task that’s time sensitive. Once about 15 minutes have passed, plugging it into a computer brings it back to life as if nothing had happened. This happens about once every couple of weeks, and it’s really kind of annoying.

The last few days at work have been really stressful, and as a result my critical mind seems to have gone on walkabout. So when it performed this awesome maneuver yesterday when I was trying to log my bike ride to work, I got annoyed and decided to… upgrade it to the iOS 5 beta. Sadly, nobody was around to tell me what a colossally stupid idea this was – my phone was doing something that looked a lot like a hardware problem, so I decided to put some unstable software on it in case it might help.

Shockingly, it did not help. It turned my phone into something resembling a brick. My coworker Jeremy finally did manage to get it to upgrade to iOS 5, and I was happy with my phone for several hours, until… I plugged it into my laptop. Which didn’t have the newest version of iTunes on it. And my phone turned into something resembling a brick that knew a single trick – if plugged into a computer, any computer, it would boot to the Apple logo over and over, but wouldn’t do anything else. I pouted and poked at it, but to no avail. My phone wouldn’t recover for me. So I made an appointment at the Genius bar in Los Gatos for the morning and went to sleep.

The Genius bar in Los Gatos was spectacularly unhelpful. Since I had upgraded to a beta OS, they claimed, they couldn’t possibly do anything with the phone. They suggested I try restoring it on my computer again, and sent me on my way. So I went off to work, hoping that Jeremy could fix it again. He tried valiantly, for much of the day, with only minor scolding for thinking that installing unstable software on broken hardware was anything resembling a good idea. To no avail.

The story does have a happy ending, though. I made *another* appointment, at the Valley Fair Apple store, and the genius there was supremely helpful. And somehow, he convinced my phone that it wanted to be back on 4.28. Which was fine with me. If it starts doing the weird crashing thing again I’ll take it back without attempting any bizarre maneuvers next time.

So the moral of the story here is… don’t put beta software on your broken hardware. You know what, just don’t put beta software on your primary mobile device. It sucks to have no phone. Especially when it’s your own fault. iOS 5 is really cool, but you can wait a couple more months until the kinks have been worked out… trust me.

Posted in “Don’t be an idiot…” – actually, in Geek Stuff. On G+ I’ll send it to my Friends circle.

Hackdays: Playgrounds for the Imagination

Posted in API, Geek Stuff, JSAPI, LinkedIn on July 13th, 2011 by admin – Be the first to comment

One of the best things about working as an engineer at LinkedIn is the monthly hackdays. This is an amazing opportunity that feels a lot, to me, like playing the lottery – it’s a time I can set aside to let myself imagine the possible, to think about what people might want to do, play with, or learn.  Think of better ways to accomplish the things that frustrate me, or to share things I love with other people.  Individuals and teams of LinkedIn employees take the day (and sometimes the weekend afterwards) to create mockups, prototypes, and sometimes complete applications to demonstrate the ideas bouncing around in their head.  Our company benefits by getting tons of new ideas, and our engineers benefit because… well, because play is fun, and this is play.  And we get to present our ideas to the executive team, and earn Apple gift cards for our efforts.

Not wanting to keep all this fun to ourselves, on July 29 LinkedIn will be hosting our first ever Intern Hackday – we’re offering cool prizes and free food, and the opportunity to collaborate with a bunch of other inspired and inspirational interns for 24 hours.

But that’s not all!  The open source developers among you know that OSCON is coming up at the end of the month, and Jeremy Johnstone and I will be there all week talking to developers and helping them started with the LinkedIn platform.  We’ve just teamed up with Mashery, Urban Airship, and a host of other fantastic companies to help put together the API Hackday PDX on Saturday, July 30.  I’ll be there to give a presentation on our APIs and help people with LinkedIn platform questions, and LinkedIn is going to give 3 $100 Apple Gift Cards to the best applications using our platform – whether it be brand new applications, multi-platform mashups, or integrations into existing systems. Come show us what you’ve got, get some LinkedIn shwag, learn about our platform and win some Apple dough!

If you want to get a jump on the competition, head over to our Developer Portal and start playing with our APIs. We’ve got a Javascript API and a REST API, along with some plugins you can use for quick integrations.  We’ve got cool tutorials, documentation, and a very active community in our forums.

Here are some links to code walkthroughs you might find helpful:

    Posted in API, Geek Stuff, JSAPI and LinkedIn here.  Posted in Friends and Open Source in G+.

    The Price of a Monopoly

    Posted in Geek Stuff on July 12th, 2011 by admin – Be the first to comment

    Before coming to LinkedIn, I worked at Netflix, which has been one of my favorite companies for a very long time.  I’ve had an account with them since the very beginning, and I for a long time I’ve loved their loyalty to their existing customer base.  Until just a few years ago, any increase in price to their new member subscription price wasn’t passed along to existing members, who were allowed to keep their existing subscription prices until they chose to change to another plan.  When they added streaming, they added it as a bonus to the DVD plan – I know the goal here was to increase streaming viewership, but it was still very nice.  Until today’s change, every change they’ve made has looked at least a little bit like a benefit for the subscribers.

    Today, they’re rolling out a new plan.  Fortunately for me, at my particular level (4 unlimited DVDs plus unlimited streaming) the price is just about the same.  But for those people who are on a tighter budget and happy with the $10 for 2 unlimited DVDs plus streaming, they now have to choose one or the other for $8, or move to $16 for a slightly worse plan (only 1 DVD at a time).  Wow.  I mean, I think it’s nice that you can choose to have one or the other, so those people with no internet can just watch DVDs and those people who hate the USPS can just watch streaming, but really?  You can have one or the other for $8, or both for $16.  No discount to have both?  Really, Netflix?  And precisely no benefit for having been a customer for years?  I’m sorry, but 2 months notice is really not a reasonable amount of consideration.

    I realize that Blockbuster is dead.  And Amazon hasn’t really mastered the art of streaming subscriptions.  And Apple is struggling with the same thing. So Netflix can do what they want and they’ll continue to print money like gangbusters.  But you know, I liked giving our subscription money to a company that I felt valued their subscribers.  Even though my cost hasn’t changed at all, I’m much more likely to look elsewhere for my entertainment choices.  I know that may seem a little hypocritical as an Apple fan-girl, but continuing to give money to a company who makes it clear they’ve suddenly decided not to value their existing user base anymore feels a lot worse than continuing to buy items from a company that’s never pretended otherwise.

    Oh jeez, I have no idea how to tag this.  Um, Geek stuff.  And, uh.  Friends in G+.

     

    Doing what you Love

    Posted in Geek Stuff on July 11th, 2011 by admin – 2 Comments

    A friend of mine created an amazing company called DailyEndeavor, where real people with real jobs explain what their job is… really.  Not a “job description” where there’s a high-level buzzwordy list of things that you do, but actually… do you spend 75% of your time pushing papers around?  Would a detail-oriented person love or hate this job?  Who would make a great candidate?  This site is fantastic for people who have just graduated from college and don’t know what actual job they want to pursue, or people who are looking to shift careers mid-stream, or… just anyone who wants to understand a particular job better.

    I mention this now because I’ve been having a lot of discussions with my daughter about what she wants to do for her “job” when she “grows up”.  She’s 14 now, at that age where she needs to start putting some focus and attention on how she’ll feed herself once she’s no longer a kid.  She’s an amazing person, who loves to do many things at a time (marching band, professional Shakespeare, venture scouts, role playing games, art…) and I know there are lots of jobs she’d just love – and many that she would really detest.  I, of course, think she would love my job because it is perfect for me in every way and she shares my genetic code, but really… no.

    She currently thinks she really wants to be an animator at Pixar.  The girl is an amazing artist, don’t get me wrong, but she gets frustrated by the indirectness of computer art – and I’m not sure she’d really enjoy the demanding precision of such an endeavor.  She just spent a week on a tall ship with her grandmother and had the best time ever, and now she wants to spend at least a few years doing that sort of thing – which, to me, matches more closely with her personality.  But really, I don’t care what she does.  I just don’t want her to do a job she hates.  I’ve done that, even things I was particular fantastic at (typing title policies at an insurance company) and the entirety of your life is really dragged down when you do a job you dislike.

    I’ve had trouble explaining to her exactly how I ended up with the job I have, and why it is so perfect for me.  I have a BA in Philosophy. I failed the only computer class I took at UCSC because the terminals gave me migraines.  I took a job as a sysadmin at SGI in 1993, which I got because the interviewer liked my attitude even though I had no experience.  While doing that job, I discovered that I hated seeing people do things that were unnecessarily hard, or failing to understand information because it was poorly presented.  I created a hyperlinked sales system to run on Mosaic in 1994 because I didn’t like the Wingz experience for the Sales Managers.  I created tools to do things for users or answer their questions because I didn’t want them to have to do it manually.  I have, since the beginning of this experience, loved enabling other people by making things easier or clearer.

    My current main loves are the semantic web and data visualization, but that’s just because I want a user to be able to fully understand a data space without expending any work to figure it out.  Sharing our platform with other developers, and making them successful in helping our members, is a natural extension of this desire.  So I have the perfect job for me.  I get to write and debug code, which is fun.  I get to help people past stumbling blocks.  I get to talk in front of developers and get them excited about our system, which feeds my need for periodic attention waves.

    So, how to help my daughter?  I’m glad she doesn’t have the mindset I had in high school where you were supposed to breathlessly rush through all 16 years of el-hi-university and then off you go to work without stopping to consider where you were going.  I’d love her to take a year or a few after high school to wander around and just be young.  She’s studying Japanese, and while I know that at just-about-6-feet she’ll stick out there, I would love her to spend a year in Japan learning about their culture.  I envy her this freedom, but can’t wait to see what she does with it.

    If you haven’t see DailyEndeavor yet, I suggest you check it out.  He could always use more descriptions, and you might be surprised to discover what other people do all day.

    Posted in Geek stuff.  Sent to my Friends circle.  Really, that’s kind of a copout, isn’t it?  What kind of circle would make sense for this, I wonder.

    Race Training

    Posted in Fitness, Geek Stuff on July 10th, 2011 by admin – 1 Comment

    Well, I’ve signed up for a wacky adventure race for September 24, and will be signing up for the Catalina Triathlon on November 5th.  Just like every year, this means I’m going to have to actually get off my lazy butt and start training.  But how does one train for an urban adventure race?  And how does one do it without getting bogged down in specifics?  I can ride on the bike with the best of them, running is ok, I enjoy swimming but it takes more planning.  But really, 20-30 miles in 4-6 hours, with some implied “rest time” (shopping? trivia? scootering?) – how does someone train for that? I need to make sure my seat doesn’t get sore, that my feet can run a decent amount, and that I can keep going for 4-6 hours with some breaks.  I know how to train for the Sprint Triathlon in Catalina, but 4-6 hours is longer than any sprint tri I’ve ever done, so I need to work on my endurance before the Oyster.  I do need to make sure I get in enough cycling that I can easily cycle 25 miles if it turns out the Oyster is mostly that – and for my own sanity, and that of my teammates, I’d probably better work on getting my average speed up above 10MPH.

    But, you know, I get bogged down a lot in specifics when I’m training, and in this case it’s really not necessary.  So, let’s go with a really hand wavy SWAG (silly wild ass guess) at what a training program would look like for the Oyster. This year I’m going to go with the aerobic points system.  where you get differing points for biking, swimming and running.  Let’s assume 20 miles biking and 10 miles running.  Let’s go with the least-aggressive 1.5x/week-at-the-end plan and work up to that (remember that the Oyster will have lots of rest time available)

    So, for the Oyster, 1x distance/week is:
    20 miles biking: 20 points
    10 miles running (or walking): 40 points

    And (duh) 1.5x distance a week is 1.5 times that (or 90 points/week)

    So for July, 40 points a week is my goal

    For August, 60 points a week.

    The first 2 weeks of September, I’ll aim for 90 points, then the 3rd week of September I’ll go back to 60 (tapering).  Note that 90 points could look like:

    3000 yards swimming (30 points)
    30 miles biking (30 points)
    7.5 miles running (30 points)

    The Catalina Triathlon is effectively 8 points of swimming plus 15 points of biking plus 12 points of running, or a total of 35 points, so I’ll train October at about 60 points/week to be ready for the triathlon.

    Now, the tricky part is, as always, to track this stuff.  I’d love there to be an aerobic points iphone app that integrates with RunKeeper, but alas, nobody has written one.  So I’ll track my exercise with RunKeeper, and when I have time to put an application together, I’ll create one to track this stuff for me.  For now I’ll report my numbers to my Fitness circle to have some accountability, and I’m sure my Oyster team members will kick my ass if I don’t do a good job.

    Tagged as Fitness, APIs and Geek Stuff here, posted to my Fitness and Friends circles in G+.