views:

62

answers:

3

I am attempting to create a profile page that shows the amount of dwarves that are assigned to each corresponding career. I have 4 careers, 2 jobs within each of those careers and of course many dwarves that each have a single job. How can I get a count of the number of dwarves in each of those careers? My solution was to hardcore the career names in the HTML and to make a query for each career but that seems like an excessive amount of queries.

Here's what I "want" to see:

Unassigned: 3
Construction: 2
Farming: 0
Gathering: 1

Here's my models. I add some complexity by not connecting Careers directly to my Dwarves model (they have connected by their jobs).

from django.contrib.auth.models import User
from django.db import models

class Career(models.Model):
    name = models.CharField(max_length = 64)

    def __unicode__(self):
        return self.name

class Job(models.Model):
    career = models.ForeignKey(Career)
    name = models.CharField(max_length = 64)
    career_increment = models.DecimalField(max_digits = 4, decimal_places = 2)
    job_increment = models.DecimalField(max_digits = 4, decimal_places = 2)

    def __unicode__(self):
        return self.name

class Dwarf(models.Model):
    job = models.ForeignKey(Job)
    user = models.ForeignKey(User)
    created = models.DateTimeField(auto_now_add = True)
    modified = models.DateTimeField(auto_now = True)
    name = models.CharField(max_length = 64)

    class Meta:
        verbose_name_plural = 'dwarves'

    def __unicode__(self):
        return self.name

EDIT 1 my view looks something like:

def fortress(request):
    careers = Career.objects.annotate(Count('dwarf_set'))
    return render_to_response('ragna_base/fortress.html', {'careers': careers})

and template:

{% for career in careers %}
    <li>{{ career.dwarf_set__count }}</li>
{% endfor %}

The error is:

Cannot resolve keyword 'dwarf_set' into field. Choices are: id, job, name

SOLUTION

view:

def fortress(request):
    careers = Career.objects.all().annotate(dwarfs_in_career = Count('job__dwarf'))
    return render_to_response('ragna_base/fortress.html', {'careers': careers})

template:

{% for career in careers reversed %}
    <li>{{ career.name }}: {{ career.dwarves_in_career }}</li>
{% endfor %}

EVEN BETTER SOLUTION

careers = Career.objects.filter(Q(job__dwarf__user = 1) | Q(job__dwarf__user__isnull = True)) \
    .annotate(dwarves_in_career = Count('job__dwarf'))

Don't forget to from django.db.models import Count, Q

What I like about the above solution was it not only returns careers that have dwarves working but even the careers that have none which was the next problem I encountered. Here's my view for completeness:

<ul>
{% for career in careers %}
    <li>{{ career.name }}: {{ career.dwarves_in_career }}</li>
{% endfor %}
</ul>
+1  A: 

Can't you just get a count grouped by career? And do an outer join if you need the zero rows returned too.

Rob Farley
+2  A: 

Django's ORM isn't gonna make this uber-simple. The simple way is to do something like:

for career in Career.objects.all(): career.dwarf_set.all().count()

That will execute 1 query for each job (O(n) complexity).

You could try to speed that up by using Django's Aggregation feature, but I'm not entirely sure if it'll do what you need. You'd have to take a look.

The third option is to use custom SQL, which will absolutely get the job done. You just have to write it, and maintain it as your app grows and changes...

Gabriel Hurley
+1  A: 

Does this do what you want?

from django.db.models import Count
Career.objects.annotate(Count('dwarf'))

Now each career object should have a dwarf__count property.

Daniel Roseman
Not bad! I do continue to get the error: Cannot resolve keyword 'dwarf_set' into field. Choices are: id, job, nameBut I am tryin' different things.
TheLizardKing
I feel this would work if my Dwarf model was directly related to my Career model but I chose to connect the two via my Job model to allow altering of careers.
TheLizardKing