Django, PayPal and the NVP API

A couple of weeks ago, I had to implement Website Payments Standard on Green Man Gaming. Surprisingly, or maybe down to my lack of decent Google Skills, I was unable to find many decent implementations that did anything other than IPN or Website Payments Pro.

It may surprise people to know that there is another way other than IPN. That is, you can use what PayPal call the Name Value Pair API. In essence, a stack of POST calls. There is/was one implementation I found doing this with Django at Google Code – however, it wasn’t very pythonic and had a few bugs/bad things that don’t match the API. So… I forked it!

Naturally, I have left my fork open to for everyone else and it’s at github under the leepa/django-paypal-driver project. I hope it’s useful to others as well.

0 Comments

Push on the iPhone

I have recently started doing development on the iPhone. It’s great fun. I am particularly interested in the Push aspect of this. I’m also a big fan of Python.

There’s a number of RESTful things that can be done with Ruby on Rails. However, the whole ‘packaged application’ thing is quite new to that framework. To Django, it’s the staple diet of how to get things done. So I’m currently part way through a generic application for Django for sending APN (Apple Push Notification) requests as well as dealing with the feedback connection.

Currently I have the push side working quite well. Just a warning, this is my first stab at this and it requires Python 2.6, or the relevant backports for ssl and json installed on 2.5.

from django.db import models
from django.conf import settings

from socket import socket

import datetime
import struct
import ssl
import binascii
import json

class iPhone(models.Model):
    """
    Represents an iPhone used to push

    udid - the iPhone Unique Push Identifier (64 chars of hex)
    last_notified_at - when was a notification last sent to the phone
    test_phone - is this a phone that should be included in test runs
    notes - just a small notes field so that we can put in things like "Lee's iPhone"
    failed_phone - Have we had feedback about this phone? If so, flag it.
    """
    udid = models.CharField(blank=False, max_length=64)
    last_notified_at = models.DateTimeField(blank=True, default=datetime.datetime.now)
    test_phone = models.BooleanField(default=False)
    notes = models.CharField(blank=True, max_length=100)
    failed_phone = models.BooleanField(default=False)

    class Admin:
        list_display = ('',)
        search_fields = ('',)

    def send_message(self, alert, badge=0, sound="chime", sandbox=True,
                        custom_params={}, action_loc_key=None, loc_key=None,
                        loc_args=[], passed_socket=None):
        """
        Send a message to an iPhone using the APN server, returns whether
        it was successful or not.

        alert - The message you want to send
        badge - Numeric badge number you wish to show, 0 will clear it
        sound - chime is shorter than default! Replace with None/"" for no sound
        sandbox - Are you sending to the sandbox or the live server
        custom_params - A dict of custom params you want to send
        action_loc_key - As per APN docs
        loc_key - As per APN docs
        loc_args - As per APN docs, make sure you use a list
        passed_socket - Rather than open/close a socket, use an already open one

        This requires IPHONE_APN_PUSH_CERT in settings.py to be the full
        path to the cert/pk .pem file.
        """
        aps_payload = {}

        alert_payload = alert
        if action_loc_key or loc_key or loc_args:
            alert_payload = {'body' : alert}
            if action_loc_key:
                alert_payload['action-loc-key'] = action_loc_key
            if loc_key:
                alert_payload['loc-key'] = loc_key
            if loc_args:
                alert_payload['loc-args'] = loc_args

        aps_payload['alert'] = alert_payload

        if badge:
            aps_payload['badge'] = badge

        if sound:
            aps_payload['sound'] = sound        

        payload = custom_params
        payload['aps'] = aps_payload

        s_payload = json.dumps(payload, separators=(',',':'))

        fmt = "!cH32sH%ds" % len(s_payload)
        command = '\x00'
        msg = struct.pack(fmt, command, 32, binascii.unhexlify(self.udid), len(s_payload), s_payload)

        if passed_socket:
            passed_socket.write(msg)
        else:
            host_name = 'gateway.sandbox.push.apple.com' if sandbox else 'gateway.push.apple.com'
            s = socket()
            c = ssl.wrap_socket(s,
                                ssl_version=ssl.PROTOCOL_SSLv3,
                                certfile=settings.IPHONE_APN_PUSH_CERT)
            c.connect((host_name, 2195))
            c.write(msg)
            c.close()

        return True

    def __unicode__(self):
        return u"iPhone %s" % self.udid

So how do we use it? Well rather easy. First you need to set up your certificates with Apple. You’ll need to set up a specific AppID and provisioning profile for your application (i.e. you can’t use *).

The guide on Apple’s site covers how to do this. Python needs it in a combined PEM format to work. Other sites claim you have to export from Keychain Access and that you’ll need to convert both to .pem. I only had to convert the private key:

openssl pkcs12 -in pkey.p12 -out pkey.pem -nodes -clcerts
cat cert.pem pkey.pem > iphone_ck.pem

I then altered my settings.py to have the new entry I added:

IPHONE_APN_PUSH_CERT = os.path.join(PROJECT_ROOT, "iphone_ck.pem")

Note the full path. I always have this at the top of settings.py to make my like easier:

import os
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))

Now, it’s just case of making your iPhone app register with push and getting a unique ID. You have to use your real phone to do this. If you’re in a hurry and just want to test it out you can cheat and just pop this in applicationDidFinishLaunching delegate:

	// Register for push notifications
	[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

Then you probably want this to get the ID out into the console:

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
	// Registration was successful so we'll
	// set up our device token etc.
	deviceToken = devToken;
	NSLog(@"devToken=%@",deviceToken);
    self.registered = YES;
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
	// This is expected on the emulator so that's fine
    NSLog(@"Error in registration. Error: %@", err);
}

Then you’ll be good to go! Run the app on the phone and the console will output the ID. Created an iPhone object and send a message. Job done.

10 Comments

Debugging SMTP

I saw this today on djangosnippets and figured it’s useful enough for me to post! Sometimes when writing a webapp you need to debug the output of your e-mail sending. This can be quite hard as things have to, usually, go via an SMTP server. What you really want is a quick way to grab the output with all the headers that YOUR code is setting – you can, easily, with Python.

OSX/Unix/Linux

sudo /usr/lib/python2.5/smtpd.py \
    -n -c DebuggingServer localhost:25

(obviously on Solaris/OpenSolaris you want to use pfexec instead of sudo!)

Windows

c:\python25\python.exe c:\python25\lib\smtpd.py \
    -n -c DebuggingServer localhost:25

You then get an output in the terminal window you ran it in every time a message hits the server. Handy.

2 Comments

DTrace in Scripted Languages

Well it’s been a very busy couple of months since I posted on here. We’ll skip through why I haven’t posted in a blink of an eye though.

I got promoted (yay), Wedding prep is going well and is on track, Work is extremely busy – leaving not much time for other pursuits.

Anyway… I’ve been playing with DTrace recently. For those that don’t know what DTrace is I sincerely suggest you check out the video of the Google Tech Talk by Bryan Cantrill on Google Video.

My favourite bit of the video is where he recounts his early weeks at Sun and talking that software is different.

“Software is different, software is unique. And we try and draw analogies between software and other things we build; those analogies always come apart”

And at 4:26: “This isn’t a thing! This doesn’t exist! We’re not seeing a manifestation! We’re seeing a representation of an abstraction. This doesn’t exist anymore than your name exists. Your name doesn’t exist. We made it up! Doesn’t this bother you?”

Now although Bryan then uses this as a starting point of the whole DTrace debugging method – he has another distinct point. A point which has come true at nearly every point in my career. You can’t design the design before the design.

So, back to DTrace.

I love it. No really, I do. The real power of it for me comes from monitoring the three main scripted languages: Python, PHP and Ruby (on and off Rails).

On my Solaris 10 box (yes, no fancy Developer Express on this testing). I now have Ruby 1.8.6 (with p111), Python 2.5.1 and PHP 5.2.0 working 100% with DTrace. That is – I can actually trace through what a running PHP script in Apache is doing.

That is immensely powerful.

Here’s the thing though. The PHP addon for DTrace doesn’t work. I have fixed it and sent the information on how to fix it back to it’s maintainer (hopefully an official fix will come out in a few days from him). Ruby 1.8.6, although there is a patch, doesn’t have the latest security fixes. Not overly useful? I have patched that locally (if anyone reads this and wants the patch – let me know).

Python 2.5.1? That doesn’t appear to exist at Sun. Although someone made a rough patch for it. It doesn’t compile, work or anything. Why release it if you haven’t even compiled it…? No matter. It took a couple of hours – but I got that working too in the end.  That one however is a ‘new’ patch. Again – if anyone wants this patch, let me know.

1 Comment