views:

50

answers:

2

I'm trying to output the following data in my django templates.

Countries would be ordered descending by # of stories. Cities would be ordered descending by # of stories (under that country)

Country A(# of stories)
  City A (# of stories)
  City B (# of stories)

Country B(# of stories)
  City A (# of stories)
  City B (# of stories)

My models are the following:

# Create your models here.
class Country(models.Model):
    name = models.CharField(max_length=50)

class City(models.Model):
    name = models.CharField(max_length=50)
    country = models.ForeignKey(Country)

class Story(models.Model):
    city = models.ForeignKey(City)
    country = models.ForeignKey(Country)
    name = models.CharField(max_length=255)

What's the easiest way to do this?

A: 

http://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#regroup

leoluk
That's a cool function, wasn't aware of it. However my main issue is that I need to generate sums of stories per country and per city, and then sort by those sums. I want to know the best way to do this.
Maverick
A: 

This solution worked for me. You will need to tweak it to pass it to a template though.

from django.db.models import Count
all_countries = Country.objects.annotate(Count('story')).order_by('-story__count')

for country in all_countries:
    print "Country %s (%s)" % (country.name, country.story__count)
    all_cities = City.objects.filter(country = country).annotate(Count('story')).order_by('-story__count')
    for city in all_cities:
        print "\tCity %s (%s)" % (city.name, city.story__count)

Update

Here is one way of sending this information to the template. This one involves the use of a custom filter.

@register.filter
def get_cities_and_counts(country):
    all_cities = City.objects.filter(country = country).annotate(Count('story')).order_by('-story__count')
    return all_cities

View:

def story_counts(request, *args, **kwargs):
    all_countries = Country.objects.annotate(Count('story')).order_by('-story__count')
    context = dict(all_countries = all_countries)
    return render_to_response(..., context)

And in your template:

{% for country in all_countries %}
    <h3>{{ country.name }} ({{ country.story__count }})</h3>
    {% for city in country|get_cities_and_counts %}
        <p>{{ city.name }} ({{ city.story__count }})</p>
    {% endfor %}
{% endfor %}

Update 2

Variant with a custom method in model.

class Country(models.Model):
    name = models.CharField(max_length=50)

    def _get_cities_and_story_counts(self):
        retrun City.objects.filter(country = self).annotate(Count('story')).order_by('-story__count')
    city_story_counts = property(_get_cities_and_story_counts)

This lets you avoid defining a filter. The template code changes to:

{% for country in all_countries %}
    <h3>{{ country.name }} ({{ country.story__count }})</h3>
    {% for city in country.city_story_counts %}
        <p>{{ city.name }} ({{ city.story__count }})</p>
    {% endfor %}
{% endfor %}
Manoj Govindan
Thanks, but is there a natural way to pass this into a data structure that I can work in templates? Or do I need to build a custom dict at this point?
Maverick
Added one way of doing it. This involves a filter.
Manoj Govindan
I like this answer, but was wondering if there's a more "django" way of doing this. Something like this answer, http://stackoverflow.com/questions/1010848/traversing-multi-dimensional-dictionary-in-django/1011145#1011145
Maverick
@Maverick: I get your point. However (short of adding a method to the model) I could not find a way to directly get the counts per city in the template for each country :(
Manoj Govindan
@Maverick: Updated answer. See above.
Manoj Govindan
Well, it does what it needs to do. Thank you!
Maverick