Setting up iOS Push Notifications (APNS) with Python/Django through pyapns


Working with iOS for the first time can be a bit frustrating. For me, the most frustrating part was working with developer.apple.com.
Here I hope to help make push notifications with Python as painless as possible.

Step 1: Enable for Apple Push Notification service and generate a certificate

  1. To get started, log in to you developer account.
  2. Click on iOS Provisioning Portal
  3. Click on App IDs
  4. If you already have an app id, click on “configure” otherwise create a new one, and then, click “configure”.
  5. Check “Enable for Apple Push Notification service”, then click on the “Configure button”, under “Development Push SSL” Certificate.
  6. Launch Keychain Access  (Command + Space + “Keychain Access”)
  7. Within the Keychain Access drop down menu, select Certificate Assistant, then select Request a Certificate from a Certificate Authority
  8. In the Certificate Information window, enter the following information:
    1. In the User Email Address field, enter your email address
    2. In the Common Name field, create a name for your private key
    3. In the Request is group, select the “Saved to disk” option
    4. Click Continue within Keychain Access to complete the CSR generating process
    5. Upload the CSR file you just created in the new developer.apple.com dialog window.
  9. Apple will now generate an SSL Certificate, for you to download. Download and open the newly generated certifcate.
  10. This should open up Keychain Assistant, in which you will see “Apple Development Push Services …”; right click on this item and select to “Export”. Save as “apns-dev-cert.p12”.
  11. Now, expand “Apple Development Push Services …”. Right click on the private key. Export this one as well, as “apns-dev-key.p12”.

Step 2: Convert the certificate to PEM format

  1. Load up Terminal (Command + Space + “Terminal”), and cd into the directory where you saved your *.p12 files.
  2. Convert the p12 fiels to PEM format with:
    openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
    openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12
    
  3. Remove the password with:
    openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem
    
  4. Combine the key and cert files into apns-dev.pem, which we will use when connecting to APNS with Python:
    cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem
    
  5. Upload apns-dev.pem to your server where you will be sending out push notifications from.

Step 3: Set up your iOS app to receive notifications

  1. In your AppDelegate.m file, override the following methods:
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        UIRemoteNotificationType allowedNotifications = UIRemoteNotificationTypeAlert
        | UIRemoteNotificationTypeSound
        | UIRemoteNotificationTypeBadge;
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:allowedNotifications];
        return YES;
    }
    - (void)application:(UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        NSString * tokenAsString = [[[deviceToken description]
                                     stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]
                                     stringByReplacingOccurrencesOfString:@" " withString:@""];
        NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://your_server.com/add_device_token/%@/", tokenAsString]];
        NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
        [NSURLConnection connectionWithRequest:request delegate: self];
    }
    

    The first method enables push notifications; the second one sends each device’s unique device token to your django app. Make sure to update the url to your django server.

Step 4: Add the necessary code to your Django app to store new iOS device tokens

  1. Add the following model to your models.py file:
    class DeviceToken(models.Model):
        token = models.CharField(max_length=255, unique=True)
        def __unicode__(self):
            return self.token
    
  2. Add the following url route to your Django urls file:
    url(r'^add_device_token/(?P[0-9a-z]+)/$', 'add_device_token'),
    
  3. Add the following to your views.py file:
    def add_device_token(request, token):
        try:
            device_token = models.DeviceToken(token=token)
            device_token.save()
        except IntegrityError:
            return HttpResponse('Token already added!')
        return HttpResponse('success')
    

Step 5: Set up pyapns

  1. Install pyapns with:
    sudo pip install pyapns
    
  2. Ensure that pyapns is properly installed by loading up the python interpreter and running:
    $ python
    Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53)
    [GCC 4.5.2] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import pyapns
    >>>
    
  3. Start the pyapns server (you should also add this to your server startup script):
    twistd -r epoll web --class=pyapns.server.APNSServer --port=7077
    
  4. Create a util.py file in your project, if it doesn’t already exist, and add the following class into it:
    import pyapns
    import xmlrpclib
    class PyapnsWrapper(object):
        def __init__(self, host, app_id, apns_certificate_file, mode='sandbox'):
            self.app_id = app_id
            pyapns.configure({'HOST': host})
            pyapns.provision(app_id,
                             open(apns_certificate_file).read(),
                             mode)
        def notify(self, token, message):
            try:
                pyapns.notify(self.app_id,
                              token,
                              {'aps':{'alert': message}})
            except xmlrpclib.Fault, e:
                print e
    
  5. Add the correct APNS variables to your settings.py file:
    APP_ID = 'your_app' # MAKE SURE THIS DOESN'T CONTAIN ANY PERIODS!
    APNS_HOST = 'http://localhost:7077/'
    APNS_CERTIFICATE_LOCATION = /path/to/apns-dev.pem # Created in step 2
    
  6. Next in the file where you would like to fire push notifications add the following:
    from django.conf import settings
    import util
    from models import DeviceToken
    pyapns_wrapper = util.PyapnsWrapper(settings.APNS_HOST,
                                        settings.APP_ID,
                                        settings.APNS_CERTIFICATE_LOCATION)
    def send_notifications(message):
        for token in DeviceToken.objects.all():
            pyapns_wrapper.notify(token.token, message)
    
  7. Now just call send_notifications(“your lovely message”). This should make all your iOS devices magically receive the message! Note that only those devices whose serial numbers have been added to the development provisioning profile will receive messages.

Step 6: Enjoy!

2 replies on “Setting up iOS Push Notifications (APNS) with Python/Django through pyapns”

  1. thanks for a really nice and to the point tutorial, hopefully it will work by the end 🙂
    when trying to install pypans:
    sudo pip install pypans
    i get the error
    Downloading/unpacking pypans
    Could not find any downloads that satisfy the requirement pypans
    Cleaning up…
    No distributions at all found for pypans
    googling pypans there are two libraries in Github one of djacobs / PyAPNs and another by samuraisam / pyapns sinse the later install is
    sudo easy_install pyapns
    i’ve choose it but again error occurs:
    Searching for pyapns
    Reading http://pypi.python.org/simple/pyapns/
    Best match: pyapns 0.4.0
    Downloading https://pypi.python.org/packages/source/p/pyapns/pyapns-0.4.0.tar.gz#md5=7cf327e794ed875103ac7cae4a26d41e
    Processing pyapns-0.4.0.tar.gz
    Running pyapns-0.4.0/setup.py -q bdist_egg –dist-dir /tmp/easy_install-mrRYFC/pyapns-0.4.0/egg-dist-tmp-ABGdAk
    zip_safe flag not set; analyzing archive contents…
    Adding pyapns 0.4.0 to easy-install.pth file
    Installed /usr/local/lib/python2.7/dist-packages/pyapns-0.4.0-py2.7.egg
    Processing dependencies for pyapns
    Searching for pyOpenSSL>=0.10
    Reading http://pypi.python.org/simple/pyOpenSSL/
    Best match: pyOpenSSL 0.13.1
    Downloading https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-0.13.1.tar.gz#md5=e27a3b76734c39ea03952ca94cc56715
    Processing pyOpenSSL-0.13.1.tar.gz
    Running pyOpenSSL-0.13.1/setup.py -q bdist_egg –dist-dir /tmp/easy_install-FWlyHp/pyOpenSSL-0.13.1/egg-dist-tmp-6OVz0I
    warning: no previously-included files matching ‘*.pyc’ found anywhere in distribution
    OpenSSL/crypto/crypto.c:14:20: fatal error: Python.h: No such file or directory
    compilation terminated.
    error: Setup script exited with error: command ‘gcc’ failed with exit status 1
    please advise…

Comments are closed.