Thursday, November 12, 2009

psycopg2 on Snow Leopard

I use PostgreSQL with Django, and recently upgraded my MacBook Pro to Snow Leopard (OS X 10.6). Everything went smoothly, except trying to actually run Django. As it turns out, the problem was with using psycopg2 with Python 2.6 on Snow Leopard, because Python is 64-bit by default, and is unable to load the 32-bit psycopg2 module. The error thrown is:

Symbol not found: _PQbackendPID

The solution I found was to re-install Python 2.6, after configuring it to compile in 32-bit.

While doing the standard steps (./configure, make, make install), replace ./configure with:

time ./configure --with-universal-archs=32-bit --enable-universalsdk=/Developer/SDKs/MacOSX10.6.sdk

Note: This put the newly-compiled Python in a location not on the PATH:
/usr/local/bin/python2.6

However, I was able to use that Python version to create my new virtualenv, and everything worked smoothly from there on. I hope this helps somebody, because in my Google searching I found a lot of people with the same problem, but none of the solutions given in any detail worked for me.

Monday, November 2, 2009

Enforcing Session Timeout in Django with JavaScript

This article could also be called "How mixing AJAX and Django sessions broke my app."

Here's the rundown: Sessions are set to expire for security reasons. Normally, clicking a link or submitting a form would cause the browser to be redirected to the login page. Obviously this is bad because sensitive information might be visible on the screen. Not quite so obvious is the fact that, when your form submission requires AJAX validation of an address, that validation breaks behind the scenes. The poor user just gets an error message about an invalid address, and can't attempt the form submission which would send them to the login page.

The solution we used relies on having the session expiration added to the context in a context processor, then using JavaScript in our base template to redirect to the logout page if the user is inactive for too long.

Here's the JavaScript, which should be placed in a template block. Inheriting templates should have {{ block.super }} inside the block tags.

            <script type="text/javascript">

//for the session timeout
session_timeout = {{session_timeout}};
page_load_time = new Date().getTime();


function check_session_timeout(){

// check timeout
active_seconds = (new Date().getTime() - page_load_time) / 1000;
if (active_seconds >= session_timeout){
window.location = '/logout/';
}else{
setTimeout('check_session_timeout()', 5000)
}

}

//Start checking for session timeout
check_session_timeout();

</script>

Sunday, November 1, 2009

Address Validation with Google Maps

We've recently begun validating user-submitted addresses via Google Maps. As it turns out, this is very easy to do. All that's necessary is an HTTP call to this URL:

http://maps.google.com/maps/geo

We are using Django and Python. Below is a simple function to handle the geocoding. We have additional code to call this via AJAX via jQuery, as well as internal code to enforce our validation requirements.

The code not seen in this function comes from the Django settings file, and it only contains the arguments required by Google. Documentation for that is on Google's page describing use.

Here are the arguments we send (aside from, obviously, the address itself, which is urlencoded and sent as argument 'q'):

key: our Google Maps key
sensor: false
output: json
oe: utf8

Output:

Google Geocoding responds with a lot of useful information. The address object is 'Placemark.' For ambiguous addresses, it may return multiple placemarks. For our purposes, this counts as an invalid address. It also returns an accuracy number, from zero through nine. This is helpful in determining whether your user entered an address specific enough for your purposes. In our case, we can accept four, a fairly low score, since we are just estimating driving distance. But if you're planning to send something by mail, a higher score (at least eight) is required.

If there's interest, I may provide some JavaScript code, or maybe even a fully-functional example page in a later post. Our primary application requires a login to use, and is not available to the public.

def get_address_info(**kwargs):

#expects to receive address fields, bumps
#them up against Google, and returns the
#validation status

geocode_options = settings.GEOCODE_OPTIONS

address_string = ' '.join([
kwargs['street_line1'],
kwargs['street_line2'],
kwargs['city'],
kwargs['state'],
kwargs['postal_code'],
kwargs['country']]
).encode('utf-8')

geocode_options['q'] = address_string

try:
output = urlopen("%s?%s" % (settings.GEOCODE_URL, urlencode(geocode_options)))
except Exception, ex:
return False

geocode = simplejson.loads(output.read())

result = {
'latitude': '',
'longitude': '',
'accuracy': 0,
'address': '',
'multiple': False,
}

#if Google returned something juicy
if 'Placemark' in geocode:
if len(geocode['Placemark']) > 1:
result['multiple'] = True

result['accuracy'] = geocode['Placemark'][0]['AddressDetails']['Accuracy']

try:
result['address'] = geocode['Placemark'][0]['address']
except Exception, ex:
log.debug("Exception adding address to result dict: %s" % (ex,))

if (len(geocode['Placemark']) == 1 and result['accuracy'] >= 8):
result['latitude'] = geocode['Placemark'][0]['Point']['coordinates'][0]
result['longitude'] = geocode['Placemark'][0]['Point']['coordinates'][1]

return result