Tag Archives: widgets

jQuery Star Rating plugin as a frontend for Django-ratings

My current Django project, OpenDataPhilly, needed a ratings system and, guess what? Django has a ratings module! All done, right? Weeelllll.. the thing is, jQuery also has a beautiful and well-behaved ratings widget that is very easy to re-skin. How do you choose between two stable plugins? In this case there was no need. I decided to use the Django-ratings module for everything but the visual element and the jQuery Star Rating Plugin for some flashy effect.

So why blog about it then? The implementation was not quite as easy as it sounds.

First thing’s first: download and install the Django-ratings module the same way you would any other module. Follow their instructions to add a ratings field to your model and fix any import or database issues. You should wind up with something like this in your models file:

from djangoratings.fields import RatingField

class MyClass(models.Model):
    ....
    rating = RatingField(range=5)
    ...

Since we’re using jQuery for the front end, that’s all we need to do in the models file.

Next, download the jQuery plugin and put the files somewhere accessible by your app. I put them in my project’s static folder, but I think anywhere is fine. The plugin worked right out of the box for me, no tweaking necessary.

On to the slightly hard part! If you look in the jQuery plugin’s documentation, it expects a pile of radio buttons to skin. No divs or lists or other tricks of nice formatting, because the plugin does all that. Unfortunately, Django’s radio button widgets think they need to do all that, too. Someone has to be told ‘No‘ very firmly. Since the whole point of using the jQuery plugin is to have a pretty and easily skinable front end, we’re going to tell Django to stop rendering radio buttons quite as nicely as it does. And that’s going to take some doing.

Somewhere in your project, create a widgets.py file and hunt down the django.forms.widget.py file so you can copy a few things. The two classes we’re interested in are RadioInput and RadioFieldRenderer. Copy those two classes from Django’s file into your new one and close the Django file. We don’t want to accidentally mess something up in there.  First, we’ll need a few imports at the top of our file:

from django.forms.util import flatatt

from django.utils.encoding import StrAndUnicode, force_unicode
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

Next, rename the classes to something not in Django. I chose StarsRadioInput and StarsRadioFieldRenderer. Also the StarsRadioFieldRenderer makes a few references to the RadioInput class. Change these to StarsRadioInput, too. Now that the classes are safely renamed, there are two methods in charge of the html coming out of our custom widget: StarsRadioInput.tag and StarsRadioFieldRenderer.render. We basically need to strip out all of the labels and list tags so that the jQuery plugin can do its css magic.

Change the StarsRadioInput.tag method to:

def tag(self):
  if 'id' in self.attrs:
    self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
    final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
  if self.is_checked():
    final_attrs['checked'] = 'checked'
  return mark_safe(u'<input%s />' % flatatt(final_attrs))

And change the StarsRadioFieldRenderer.render method to

def render(self):
  return mark_safe(u'\n%s\n' % u'\n'.join([u'%s'
                   % force_unicode(w) for w in self]))

Now, all you have to do is make your form add our special renderer to the field and push the rating value into the right data model when the form is submitted.

In the forms.py file, add a field to your form. Don’t forget to create the rating choices tuple and add the ‘star’ class to the field.

RATING_CHOICES = ((1,1), (2,2), (3,3), (4,4), (5,5),)
forms.CharField(widget=forms.RadioSelect(renderer=StarsRadioFieldRenderer, attrs={'class':'star'}, choices=RATING_CHOICES))

In your form’s model, add this to the save method to connect the form data to the actual model’s data (if you need to):

def save(self, *args, **kwargs):
  myobject.rating.add(score=self.rating, user=self.user, ip_address=self.ip_address)
  super(CommentWithRating, self).save(*args, **kwargs)

Here’s what it looks like attached to a comments form:

django-rating and jquery star rating

django-rating and jquery star rating together

 

Fun With Simile

I want to send a shout out to anyone working on the Simile Timeline project.  Born out of the Semantic Interoperability of Metadata and Information in unLike Environments (SIMILE) project conducted by the MIT Libraries and MIT CSAIL, they are part of a suite of tools that enable visualization of seemingly massive datasets. We are implementing an in-house solution for planning, and the timeline is fast, slick, and solid.

I was rockin’ the maps with a version of timeline integrated with OpenLayers, appropriately named maptimeline. Abandoning all grasp of reality, I then added a Simile Timeplot to the interface, too.   I would have liked to be able to integrate the time plot and the timeline tighter, but at this point, that integration is still a bit clunky.

Coming back down to earth, I dropped the map and the timeplot, as they didn’t add value to an already cluttered interface. Nonetheless,  I am really impressed with how well all of these tools perform.