Skip to content

Implementing CNN style votes in Django, Episode I

by Paul Kenjora on April 2nd, 2007

Lets say you want to allow people to vote on items within your Django project. These items could be anything, in any coimbination, etc… This is relatively simple once you have the right pieces in place. I implemented this functionality recently on my www.joosthot.com site.

Requirements

  1. Voting may be implemented on any page.
  2. Acty of voting does not refresh the page.
  3. Developer / user may sort by votes.
  4. Accomodate concurrent votes from multiple users.
  5. Limit votes to 5 a day.
  6. Allow voting capability to be extensible.

Design

  1. Use generic AJAX-ish interface for processing vote.
  2. Use simple view for backend database work.
  3. Embedd vote count in each database item being voted upon.

Front End Implementation

The front end consists of both Django template implementation and Javascript code to perform the call. The following javascript is generic and should need almost no modification. Adjust alert messages to suit your needs. This code implements the AJAXD-ish interface. I say ‘ish’ because its not XML. Note you may need to modify the ‘url’ parameter to suit your projects.

vote.js


var xmlHttp;
var m_id = "ERROR";
var m_status = "ERROR";
var m_value = "ERROR";

function voteCall(model, id)
{
var url = "/vote/";

xmlHttp=GetXmlHttpObject(stateChanged);

xmlHttp.open("POST", url , false);
xmlHttp.setRequestHeader('content-type', 'text/plain');
xmlHttp.send('model=' + model + '&id=' + id);
}

function stateChanged()
{
if (xmlHttp.readyState==4 xmlHttp.readyState=="complete")
{
var token = xmlHttp.responseText.split(':');
//alert(xmlHttp.responseText)
m_id = token[0];
m_status = token[1];
m_value = token[2];
}
}

function GetXmlHttpObject(handler)
{
var objXmlHttp=null;

if (navigator.userAgent.indexOf("Opera")>=0)
{
alert("This doesn't work in Opera");
return;
}
if (navigator.userAgent.indexOf("MSIE")>=0)
{
var strName="Msxml2.XMLHTTP";
if (navigator.appVersion.indexOf("MSIE 5.5")>=0)
{
strName="Microsoft.XMLHTTP";
}
try
{
objXmlHttp=new ActiveXObject(strName);
objXmlHttp.onreadystatechange=handler ;
return objXmlHttp;
}
catch(e)
{
alert("Error. Scripting for ActiveX might be disabled");
return;
}
}
if (navigator.userAgent.indexOf("Mozilla")>=0)
{
objXmlHttp=new XMLHttpRequest();
objXmlHttp.onload=handler;
objXmlHttp.onerror=handler;
return objXmlHttp;
}
}

function vote(model, id)
{
voteCall(model, id);

if (m_status == "OK")
{
if (document.getElementById)
{
var target = document.getElementById('vote-' + m_id);
target.innerHTML = m_value;
}
}
else
{
alert("You only get 5 votes a day!");
}
return;
}

To call the above javascript on an item include the following code in your template:

item.html

In the above code snippet, ‘joosthot.Gallery’ is the name of the table I’m voting on, ‘g.id’ is the item id, and ‘g.votes’ is the number of votes the item currently has. Note that g was pulled directly from the database.

Back End Implementation

The back end handler that modifies the database is a simple Django view. Again this code is fairly generic. The only pieces that need to be modified are the imports for each table you wish to vote upon and the number of votes you allow per day. Everything else will work as is. Make sure that the name of the model you pass encode in the template snippet above is imported below. This is from the ‘votes.py’ file.

vote.py


from django.http import HttpResponse
from django.core.exceptions import ObjectDoesNotExist

from django.db.models import get_model
from datetime import datetime

from grab.joosthot.models import Gallery
from grab.joosthot.models import YouTube
from grab.joosthot.models import MySpace
from grab.joosthot.models import Quote

def run(request):
model_name = ''
model_id = 0

try:
if request.session['votes_given'] > 5:
return HttpResponse("Error", mimetype="text/plain")
else:
request.session['votes_given'] += 1
except KeyError:
request.session['votes_given'] = 1

if request.POST:
model_name = request.POST['model']
model_id = request.POST['id']
else:
return HttpResponse("Error", mimetype="text/plain")

if len(model_name) == 0 or model_id == 0:
return HttpResponse("Error", mimetype="text/plain")
else:
model = get_model(*model_name.split('.'))
thing = model.objects.get(id=model_id)
thing.votes = thing.votes + 1
thing.save()

return HttpResponse(str(model_id) + ":OK:" + str(thing.votes), mimetype="text/plain")

You will also need to configure your 'urls.py' to tie the front end javascript with this back end handler.