|
Project Information
Featured
Downloads
|
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
DownloadThe 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 InstallationThank 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/. TutorialFirst, 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:
Well, that's all there is for now. Please post tickets, email me, add wiki pages, etc with comments, suggestions, or just to chat! PaginationPagination is now possible using django_simpleaggregation, as you may have noticed in the argument list to AggregateAccessor. Passing in keywords 'paginate_by' and 'page' will result in a single page to return upon the next get_list_by_count() or get_list_by_user_defined(). After one of those two methods is called, another method on the AggregateAccessor is useful: get_pagination_dict(). get_pagination_dict() returns a dictionary of some common pagination properties:
Also, each of those are accessible as methods on the AggregateAccessor instance. For example, if you have an AggregateAccessor instance called agg_access, calling agg_access.has_next() will be the exact same thing as agg_access.get_pagination_dict()['has_next']. MoreMore 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. |