My favorites | Sign in
Project Logo
                
Feeds:
People details
Project owners:
  floguy

Often it is necessary in Django to denormalize aggregation metadata about models. Whether they be votes, polls, or something else, it can often be helpful to have precomputed information about that data. This project provides an abstracted means for dealing with (simple) aggregation of models.

Update: I am looking for someone to take over maintenance of this project.

News

Download

The recommended download is always the "Featured" download listed to the right. To track the newest changes, follow subversion.

Note: Do not follow the instructions on the svn page. Instead, type this command:

svn checkout http://django-simpleaggregation.googlecode.com/svn/trunk/ django_simpleaggregation

Installation

Thank you for installing django-simpleaggregation.

To install, simply place this directory somewhere on your Python
path, or symlink to it from somewhere on your Python path.

Note that this application requires Python 2.3 or later, and a recent
Subversion checkout of Django. You can obtain Python from
http://www.python.org/ and Django from http://www.djangoproject.com/.

Tutorial

First, it must be added to INSTALLED_APPS:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django_simpleaggregation',
)

Now, let's set up some basic models:

from django.db import models

class Opinion(models.Model):
    title = models.CharField(maxlength=100)
    body = models.TextField()
    
    def __unicode__(self):
        return self.title

class Vote(models.Model):
    voter_name = models.CharField(maxlength=100)
    vote = models.IntegerField(choices=((-1, '-1'),(1, '+1')))
    opinion = models.ForeignKey(Opinion)
    
    def __unicode__(self):
        return u"%s voted %d for %s" % (self.voter_name, self.vote, self.opinion)

Then, run python manage.py syncdb

Let's set up an aggregate which counts how many votes a specific user casts (place this anywhere, probably right under the model):

from django_simpleaggregation import aggregates
aggregates.register(Vote, 'voter_name')

...and you're done! Wow, that was hard, wasn't it? (Rhetorical question...it was easy). This step should be familiar for anyone who is using the newforms_admin branch. In the background, a new Aggregate object is being created for each unique Vote.voter_name that is saved.

So that was easy, but not very interesting. After all, you could just do a .count() on a queryset to get the same results. So, now let's keep track of the sum of all votes (the score), but still count how many votes are cast on each object:

def vote_callback(obj, current_user_defined, increment_or_decrement):
    return current_user_defined + obj.vote

aggregates.register(Vote, 'opinion.id', vote_callback)

There are several things to note here. Firstly, we've passed in a dot-separated string representing the relationship path to take. So for every Vote.opinion.id which is unique, django_simpleaggregation will create a new Aggregate object.

Secondly, we've registered a callback function to compute some user_defined data. That callback always takes the same form of callback(obj, current_user_defined, increment_or_decrement) where obj is an instance of the registered object (Vote, in this case), current_user_defined is the value of the current user_defined data for the aggregate, and increment_or_decrement is either the string 'increment' or the string 'decrement'. This allows for different behavior depending on whether something is being incremented or decremented.

The user_defined aggregate gets set to whatever value is returned from the callback. In this case, we're simply setting it to the new score! This is a little harder, but I think we're doing pretty well.

Now lets create some views:

from mysite.example.models import Vote, Opinion
from django_simpleaggregation.helpers import AggregateAccessor
from django.shortcuts import render_to_response

def opinions(request):
    agg_access = AggregateAccessor(Vote, 'opinion.id', direction = 'descending', stop_before = 1)
    context = { 'opinions' : agg_access.get_list_by_user_defined() }
    return render_to_response('votes.html', context)

The AggregateAccessor the most complicated function in this project, so if you can get past this, you're home free. Anyways, AggregateAccessor takes these arguments:

Once you have that AggregateAccessor instance, you can get the objects by using either get_list_by_count() or get_list_by_user_defined().

Our result is a list of opinion objects. But that's not much good unless we display it. Let's do that now:

{% load simpleaggregation %}

{% for opinion in opinions %}
    {% get_aggregate opinion.vote_set.all opinion.id as aggregate %}
    <h1>{{ opinion.title }}</h1>
    <p>{{ opinion.body }}</p>
    <p>Number of Votes: {{ aggregate.count }}</p>
    <p>Score: {{ aggregate.user_defined }}</p>
{% endfor %}

Now, first things first, yes I know that this is totally incorrect HTML, but since it's a tutorial, let's forget about that. get_aggregate takes two possibilities as the its first argument:

  1. An object instance whose class has been registered for aggregation.
  2. An iterable of those object instances (the first value will be taken) for convenience, as you can see in our working example.

Well, that's all there is for now. Please post tickets, email me, add wiki pages, etc with comments, suggestions, or just to chat!

More

More is coming, but for now this is all. Oh, and I assume no responsibility if this breaks your code, your database, or your life in some way.









Hosted by Google Code