Django Class Based Views
August 19, 2011
I needed to receive paypal IPN notifications, and the sample code I found was using a Django class based view. It didn't quite work with Django 1.3 for various reasons, so I had to work out what was going on.
The View Class
I'm using two classes here. The first subclasses django.views.generic.base.View
, and provides generic functionality for processing a paypal IPN:
from django.conf import settings from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.generic.base import View from progperc.paypal.models import IpnLog import urllib class EndPoint(View): """" Provides generic functionality for receiving a PayPal IPN message. Delegates processing of message to a subclass. """ # this is the text returned by the view in the HTTP response default_response_text = 'Nothing to see here' def do_post_callback(self, url, args): """ POST to a given url with supplied parameters """ return urllib.urlopen(url, args).read() def verify(self, pRawPostData): """ This function posts the passed parameters back to paypal to ensure they are from paypal """ lPostData = "cmd=_notify-validate&%s" % pRawPostData lResponse = self.do_post_callback(settings.PAYPAL_IPN_VERIFY, lPostData) return lResponse == 'VERIFIED' def default_response(self): """ Return a response for the POST to ensure we return a HTTP 200 code """ return HttpResponse(self.default_response_text) def post(self, request, *args, **kwargs): """ Handle the POST from paypal """ data = dict(request.POST.items()) # We need to post that BACK to PayPal to confirm it if self.verify(request.raw_post_data): self.on_process(data) else: self.on_process_invalid(data) return self.default_response() def on_process(self, data): """ Overridden in subclass to process a valid IPN message """ pass def on_process_invalid(self, data): """ Overridden in subclass to process an invalid IPN message """ pass
Note that this class implements the post
method, which is passed the current request object. You can similarly implement get
if that's what you need:
def get(self, request, *args, **kwargs): pass def post(self, request, *args, **kwargs): pass
This call is done dynamically inside the framework, so it will support any HTTP method.
Subclass
We can then subclass this generic IPN functionality and apply specific code to run when a message is received. Here I'm doing the same thing in both cases, but you should get the idea:
class IpnEndPoint(EndPoint): """ Views for processing paypal IPN """ def on_process(self, data): # Do something with valid data from PayPal - e-mail it to yourself, # stick it in a database, generate a license key and e-mail it to the # user... whatever lLog = IpnLog() lLog.populate(data) lLog.type = 'valid' lLog.save() def on_process_invalid(self, data): # Do something with invalid data (could be from anywhere) - you # should probably log this somewhere lLog = IpnLog() lLog.populate(data) lLog.type = 'invalid' lLog.save()
URL
This class-based view needs to be added into urls.py
:
from django.conf.urls.defaults import * from progperc.paypal.views import IpnEndPoint urlpatterns = patterns('', (r'^ipn/$', IpnEndPoint.as_view()), )
Note that we're calling the as_view()
function on the view class (see Problems section below for what happens if you do this on an instance).
In our case, PayPal isn't posting back the CSRF token that Django expects, so we need to exempt this view from that processing. This can be done by using the decorator function directly in urls.py
:
from django.views.decorators.csrf import csrf_exempt urlpatterns = patterns('', (r'^ipn/$', csrf_exempt(IpnEndPoint.as_view())), )
Problems
I had a problem with the exception This method is available only on the view class.
. This was caused because I was using an instance in urls.py:
(r'^ipn/$', csrf_exempt(IpnEndPoint().as_view())), # this won't work!!
what I need to use was:
(r'^ipn/$', csrf_exempt(IpnEndPoint.as_view())),