Jul

7

Like QuerySets, but crazier

I have a crazy QuerySet idea.

Imagine we have this:

class Task(models.Model):
foo = models.CharField()
 
class TaskStatus(models.Model):
task = models.ForeignKey(Task)
user = models.ForeignKey('auth.User')
status = models.CharField(default='not-done')
 
class Meta:
unique_together = ('task', 'user')

Now imagine we're listing Task.objects.all() on a page. We need to show the user's status for each task, so we do something like this:

b_list = []
for a in A.objects.all():
b_list.append(a.b_set.get_or_create(user=request.user))

That sucks because it's doing an extra db query for each object. We can work around that problem, but this also sucks because we're sort of needlessly creating new records when we don't need them. If the TaskStatus is being created with a default status, and we know that's what it will be because we're creating it, couldn't we just assume that status if there's no TaskStatus object and skip the creation? Yes, we could probably do that too. But now our code's going to look a little messier. I won't both writing it out, but you can imagine, right?

Here's my crazy idea:

Write a manager method that returns a custom QuerySet subclass that will do something like this internally:

    def get_for_user(self, user);
tasks = Task.objects.all()
task_status = dict([(ts.pk, ts) for ts in self.get_query_set().filter(user=user)])
for task in tasks:
if task.pk in task_status:
task = task_status[task.pk]
else:
task = TaskStatus(task=task, user=user))
yield task

But, you know, done up so it behaved like a queryset instead of a plain iterator, where you could filter, exclude, get, etc.. So what you get is a queryset that includes all the objects you would normally find from the db, plus some freshly created (and un-saved) objects.

It seems like something like this could let you avoid a lot of db hits and extra db entries and would be transparent to the code that was using it.

Implementing this for general use would require some deep Query fiddling, but I think it could be manageable if I wrote it for this one application instead.

So crazy it might work, or is it the wrong amount of craziness?