Implementing CNN style votes in Django, Episode I
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
- Voting may be implemented on any page.
- Acty of voting does not refresh the page.
- Developer / user may sort by votes.
- Accomodate concurrent votes from multiple users.
- Limit votes to 5 a day.
- Allow voting capability to be extensible.
Design
- Use generic AJAX-ish interface for processing vote.
- Use simple view for backend database work.
- 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.
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:
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.
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.
Model Implementation
The model implementation is pretty straight forward. Note that any query on this table is automatically sorted by descending votes. Also any table you wish to vote upon must have a ‘votes’ column.
class Gallery(models.Model):
title = models.CharField(maxlength=20)
image = models.URLField(verify_exists=True)
link = models.URLField(verify_exists=True,blank=True)
votes = models.PositiveIntegerField(default=0)
class Meta:
ordering = ['-votes']
class Admin:
pass
def __str__(self):
return self.title
Conclusion
That should be it. Once you get all this up and running you can expand the voting to include more tables in your models just by adding an import of that model to the back end handler and the same model to the template side ‘a href’ call.
Some of the things we may want to add include:
- Add login requirement prior to voting. This is achieved by modifying the session logic in vote.py
- Create ability to list voted material in terms of popularity.
- Change vote count to vote velocity like digg.com.
More from Aware Labs
- Implementing CNN Style Votes In Django, Episode II
- Goodbye WebFaction Django Hosting – A Reflection
- Everything A Django Developer Needs To Create Logins
- Custom Actions In Django Admin Object Editor
- Django Generic XSS Safe Tags
Aware Labs Recommends
- Popularizing Django — Or Reusable apps considered harmful. (USwaretech)
- Django’s tipping point (Antonio Cangiano)
- An Interview with Jacob Kaplan-Moss – Creator of Django (USwaretech)
-
Tom

![Recommend [AwareLabs]](http://s3.amazonaws.com/arkayne-media/img/badge/logo-recommend-badge-medium.png)