4

In Django 1.9, I have a database table that contains car brands. I am trying to build an index (like one found at the back of a textbook) of car brands. For example:

A
Aston Martin
Audi
...
B
Bentley
BMW
...

Here is code from my view.py:

def home(request):

    car_index = {}
    alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N',
        'O','P','Q','R','S','T','U','V','W','X','Y','Z']

    for letter in alphabet:
        car_index[letter] = {}
        car_index[letter]['title'] = letter
        car_index[letter]['brands'] = Cars.objects.filter(brand__startswith = letter)

    return render(request, 'cars/home.html', {'car_index':car_index})

Here is code from my home.html template:

{% for each in car_index %}

    {{ each.title }}

    {% for brand in each.brands %}
        <a href="{{ brand.link }}">{{ brand.name }}</a><br>
    {% endfor %}

{% endfor %}

In view.py, I have tried .values() in the queryset, .items() in the template context. In the template, I have tried car_index.items, each.brands.items, each.brands[0]. Nothing works. With the code above as is, I get the titles: E D X I A U H B T S N K Q Z J Y W V O L R F G P C M, but no links. (I know how to sort, but working on links first)

I have read:

https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#for

how to iterate through dictionary in a dictionary in django template?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
FeFiFoFu
  • 1,069
  • 1
  • 11
  • 15
  • 1
    looks fine, what do you see if you print `each.brands` in template? – doniyor Jul 10 '16 at 03:30
  • 1
    Can you show the result of `{% for each in car_index %} {{each}} {% endfor %}`. Also have you tried `{% for key, value in each.items %}` – kapilsdv Jul 10 '16 at 03:34
  • @doniyor `each.brands` was blank. @KapilSachdev, your comment helped me figure it out. Amended code to: `{% for k,v in car_index.items %}` and `{{ v.title }}` and `{% for brand in v.brands %}`. Argh, after going back to other SO answers, I can see the answer now in plain sight. But with their data structured a bit different, my brain didn't make the leap. Thanks guys. I don't know the SO etiquette to award the accepted answer to a comment that answers. – FeFiFoFu Jul 10 '16 at 04:35

3 Answers3

4

better approach - cleaner code, apart from db effeciency:

alphabet = ['A','B','C','D','E','F','G', ..]

brands_list = []
for letter in alphabet:
    letter_ = {'cars': Cars.objects.filter(brand__startswith=letter), 'letter': letter}
    brands_list.append(letter_)

return render(request, 'cars/home.html', {'brands': brands_list})

template

{% for brand in brands %}    
    {{ brand.letter }}    
    {% for car in brand.cars %}
        <a href="{{ car.link }}">{{ car.name }}</a><br>
    {% endfor %}
{% endfor %}
doniyor
  • 36,596
  • 57
  • 175
  • 260
  • 1
    Wow, looks similar to my code a first glance, but actually is much different. I learnt a lot, thanks! I have marked this as accepted answer for a couple reasons: First, the structure of `brands_list` is better than my `car_index` because it eliminates the key I added (`car_index[letter]`) which in turn eliminates the need for `{% for k,v in car_index.items %}`. Better looking template and is how I thought the template should have worked originally. Second, it uses an array instead of the object that I used, which means I no longer need to sort the object - the alphabet comes out in order. – FeFiFoFu Jul 10 '16 at 06:02
  • @FeFiFoFu glad I could help – doniyor Jul 10 '16 at 06:31
2

Can I dissuade you from your current approach?

You are doing a full table scan in 26 steps. If the brand column is not unique, you will find that the names get repeated. If you have millions of records you will exhaust memory. If the brand column does not have an index, you will find that the following query is very very slow:

Cars.objects.filter(brand__startswith = letter)

There is a really simple solution. That too might involve a full table scan, but at least you are executing one slow query instead of 26.

Cars.objects.raw('SELECT max(id), SubStr(brand,1,1) AS letter, brand
    FROM myapp_cars GROUP BY substr(brand,1,1)')

This is with the use of raw queries. If you don't like them and happen to be fortunate enough to be on Postgresql you can use distinct to achieve the same objectively more elegantly.

e4c5
  • 52,766
  • 11
  • 101
  • 134
  • I see what you're saying. I'm not proficient with SQL so I'd be just copying your code without understanding, which is bad. But thanks for pointing out the inefficiency. Once finished version 1, I will learn SQL &/or change python code to hit DB once. – FeFiFoFu Jul 10 '16 at 17:26
  • Hi there, was going through some of my old answers when I came across this. How did this go? – e4c5 Nov 07 '16 at 15:10
  • I am delighted to hear :) – e4c5 Jan 07 '17 at 23:14
1

I'm glad my comment helped you somehow to solve your problem. I'm just posting it as an answer so that it may help others .

{% for each in car_index %}, just iterates out the current dict and doesn't unpack the dict of dicts which is the brands dict

As the car_index context contained a dictionary, the following would display the keys and values of the car_index dictionary and unpacks the brands dict.

{% for key,value in car_index.items %}

    {{ value.title }}

    {% for brand in value.brands %}
        <a href="{{ brand.link }}">{{ brand.name }}</a><br>
    {% endfor %}

{% endfor %}
kapilsdv
  • 719
  • 4
  • 10