Reputation: 2094
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>
Upvotes: 5
Views: 981
Reputation: 599490
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.
Upvotes: 1
Reputation: 40052
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...
Upvotes: 2
Reputation: 15849
Can't you just get a count grouped by career? And do an outer join if you need the zero rows returned too.
Upvotes: 1