Jamming with Django (and Facebook too)

 reinhardt_nuages_8120726.jpg

A few days ago I posted about the experience Cameron and I had with Django in creating the Campus Church website. I only covered the model side of the model-view split that Django strongly encourages, since the sermon database I was using as an example didn’t have any data yet to create a view from. Well that’s changed now.

Refresher: We had decided the sermon database was  based on two objects, Sermon and Passage. Sermon described things like a sermon’s title, date, mp3 file, speaker name, and outline. The Passage object described the bible passage[s] associated with the sermon. We split that into its own object since there’s a one-to-many relationship between Sermon and Passage, i.e. we wanted to be able to associate multiple passages with a single sermon.

A fresh view on things

So now we have a look at the source of views.py in the Sermondb app directory (again, Django encourages a heavily modular design in your web applications. We subdivided the management of the site into several such applications, and the sermon database made its own).

import urllib
from django.template import Context, loader
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from campuschurch.sermondb.models import Sermon, Passage

def get_passages(sermon):
    return sermon.passage_set.all()

def index(request):
    latest_sermon_list = Sermon.objects.all().order_by('-date','-id')[:10]
    passages = map(get_passages, latest_sermon_list)
    t = loader.get_template('sermondb/index.html')
    c = Context({'latest_sermon_list': zip(latest_sermon_list,passages)})
    return HttpResponse(t.render(c))

Before I go any further, I should mention that this isn’t finished yet. We haven’t tackled pagination of results, but it’s not going to be difficult. Django makes that rather easy. So here’s what’s going on in this snippet. We import a bunch of modules. One comes from the default Python installation, some come from Django, some come from elsewhere in the Campus Church application.We define a function get_passages that takes in a Sermon and returns all the passages associated with it. As we’ll see in a moment, this is just a convenience function that really could’ve been replaced by a lambda expression. Take note of the way it actually gets the associated passages. Although we didn’t define anything about passages in the Sermon class (the relationship was only described in the Passage class), Django’s added some very convenient accessor methods to the sermon object.Then we come to the first real view. A view is a function that takes in a client request (in this case and usually, an HTTP request) and returns an HTML string. This first view, index, is describing the sermon database’s index page. It starts by getting the list of the latest sermons, using Django’s neat database API. Then using the convenience function get_passages and map(), we create a list of all the passages that apply to that page. Then we load in the relevant template and then render it. To render the template we pass it a context, created with a dictionary of variable names and values. In this case there’s only one value: the list of sermons to appear on the page and their corresponding lists of passages.Here’s the relevant snippets of the index page template:

<div>
<h1>sermons</h1>
{% if latest_sermon_list %}
{% for s in latest_sermon_list %}

<b><a href="https://paragraft.wordpress.com/wp-admin/%7B%7B%20s.0.id%20%7D%7D/">{{ s.0.title }} ({{ s.0.speaker }})</a></b>
{% if s.1 %}

Passage{% if not s.1|length_is:"1" %}s{% endif %}:
{% for p in s.1 %}
{{ p }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endif %}
{{ s.0.date|date:"F j, Y" }}

{% endfor %}

{% else %}

 No sermons are available (yet!).

{% endif %}</div>

It looks a bit messy, but primarily it’s just two nested for-loops. The outer one is running over the supplied list of sermon objects. The inner one runs over the passages associated with each sermon. I’ve excluded the Javascript included in this template that adds the drop-down play and download links, but that doesn’t really matter, because now we’re going to look at the other view in this app. It’s also pretty easy to do with jQuery.

def detail(request, id):
    t = loader.get_template('sermondb/detail.html')
    s = get_object_or_404(Sermon, pk=id)
    passages = get_passages(s)
    for p in passages:
        esvLookupURL = "http://www.esvapi.org/v2/rest/passageQuery?key=SNIPPED&passage=" + str(p.esvstr()) + "&include-headings=false&include-footnotes=false&include-audio-link=false&include-passage-references=false&include-short-copyright=false"
        p.esv = urllib.urlopen(esvLookupURL).read()
    c = Context({
        'sermon':s,
        'passages':passages,
         })
    return HttpResponse(t.render(c))

This is the view that gets rendered on sermon detail pages like this one. This looks much the same as the previous view in nature, with some slight differences. The function takes another parameter id that corresponds to the id of the sermon that is to be shown. This is passed in via urls.py, which lives in the root of the Django project directory and is basically a list of regular expressions matching different URL-types. The relevant sermon is found with the shortcut function get_object_or_404 and the database API, this time by specifying that we want the sermon object with a primary key equal to id. The other thing worthy of mention here is the usage of the ESV Online API. This is a seriously neat little web service, provided for free. We’ve written a method on our Passage class called esvstr() that renders the passage’s location into a format consistent with what the web service expects, and just use that to construct a URL. We use Python’s urllib module to make a request to that page, and include the resulting text. This isn’t a scaling solution, and really we should be caching it, but the terms of service limit us to not storing more of half of any book of the Bible at any one time. Given that we expect consecutive series of sermons to cover entire books at times (the first one will be covering all of Ephesians, for example), we decided it would be rather complicated to work out a caching solution that took that into account. Given our current traffic levels, we’re satisfied we’re not going to be at risk of breaking the 500 lookups/day limit on the service any time soon.

Sermonbook

As I mentioned last time, the promotion campaign for Campus Church is incorporating a lot of Facebook usage, since the target demograph (university students) is rather well represented on that site. There was a Facebook group from quite early on, but Cameron and I had the thought that it might be a good idea to reuse what we had already done on the site and create a Facebook application that would just embed a flash player for the latest sermon and a link or two back to the site. Our motivation was two-fold:

  • We had created an RSS feed for the sermon database (again, Django makes this pretty simple). But although I can’t really back it up with numbers, I don’t think it would be too risky an assertion to claim that the set of Facebook users is in any way a subset of the set of regular RSS reader users. A Facebook application would be a way to provide the same sort of functionality to users who might not otherwise use it.
  • The “Share application” functionality Facebook provides is an interesting promotion tool that we were interested in taking advantage of.

You can see the resulting application. Sermonbook is another application in the Django project, but it reuses the Sermondb models to query the same database. With the addition of some boilerplate Facebook code (which you can find on their developers’ wiki), even the views look remarkably similar. The basic process is that Facebook refers to a supplied URL on our site to get the required FBML (Facebook’s XML variation on HTML) to render the application, with a few complications I won’t go into. My aim here is to show what the modularity of Django gives you in terms of flexibility.

def canvas(request):
    latest_sermon_list = Sermon.objects.all().order_by('-date','-id')[:1]
    s = get_object_or_404(Sermon, pk=latest_sermon_list[0].id)
    passages = get_passages(s)
    for p in passages:
        esvLookupURL = "http://www.esvapi.org/v2/rest/passageQuery?key=SNIPPED&passage=" + str(p.esvstr()) + "&include-headings=false&include-footnotes=false&include-audio-link=false&include-passage-references=false&include-short-copyright=false"
        p.esv = urllib.urlopen(esvLookupURL).read()

    return render_to_response('facebook/canvas_details.fbml', {'name': 'stranger','key':'','sermon':s,'passages':passages})

...

def post_add(request): #The function that gets called when the user first adds the application
    b = FacebookUser(uid=request.facebook.uid)
    b.save()
    latest_sermon_list = Sermon.objects.all().order_by('-date','-id')[:10]
    fbml = '<br><a href="http://apps.facebook.com/campuschurch/">More about this sermon</a> | \
<br><a href="http://www.campuschurch.org.nz/sermons/">Browse other sermons</a>'
    request.facebook.profile.setFBML(fbml,request.facebook.uid)
    return request.facebook.redirect(request.facebook.get_url('profile', id=request.facebook.uid))
post_add = facebook.require_login()(post_add)

Really that last part should have been done with a template instead of just hardcoding the FBML, and I expect we’ll tweak that as soon as we can be bothered. Looking at it now, we could have saved on code duplication if the ESV lookup was done on the model, rather than in the view. We should probably tweak that too. Overall the process was pretty straight forward, apart from some trickiness with updates. Because of the way the Facebook application model works, we have to keep a list of all the UIDs of Facebook users who add our app. We’d be happier to not to have do that and would be quite content with some sort of iframe arrangement where it just blindly loads in some page. But iframe apps can’t be displayed inline on the user’s profile page for security reasons. So every time there’s an update, we now have the sermondb application go out and ping Facebook with a list of all our registered users and the updated FBML to display. It’s an inelegant solution, but these are the early days still of web service to web service integration I guess.

The finished app, inline on my profile page:

sermonbook.png

Advertisements

One response to “Jamming with Django (and Facebook too)

  1. Pingback: Jamming with Django « Paragraft

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s