[Answered ]-Is there a easier way to display my objects in categories?

1👍

def show_class_a_objects(request):
    class_b_objects = ClassB.objects.all().order_by('name')
    class_a_objects = ClassA.objects.all().order_by('name')

You don’t need to query the ClassA objects like this to achieve the grouping you want. When you create a ForeignKey on your ClassA -> ClassB, your ClassB will contain a relation that will give you back all ClassA objects which have ClassB as a ForeignKey:

view

def show_class_a_objects(request):
    class_b_objects = ClassB.objects.all().order_by('name')
    return render_to_response('show_objects.html', {
        'class_b_objects': class_b_objects, 
    })

template

{% for b in class_b_objects %}
    <tr>
    <td>{{b.name}}</td>
    </tr>
    {% for a in b.classa_set.all %}
        <tr>
        <td><a href="{{ a.get_absolute_url }}">{{a.name}}</a></td>
        </tr>
    {% endfor %}
{% endfor %}

You still need a nested for loop, but you no longer have to loop over the entire range of every single other class and do comparisons to find the right ones.

If you want a bit more control, you should move this into your view again and prebuild the dict:

view

def show_class_a_objects(request):
    class_b = ClassB.objects.all().order_by('name')
    class_b_objects = [(b, b.classa_set.all().order_by('name')) for b in class_b]
    return render_to_response('show_objects.html', {
        'class_b_objects': class_b_objects
    })

template

{% for b, a_list in class_b_objects %}
    <tr>
    <td>{{b.name}}</td>
    </tr>
    {% for a in a_list %}
        <tr>
        <td><a href="{{ a.get_absolute_url }}">{{a.name}}</a></td>
        </tr>
    {% endfor %}
{% endfor %}

Update: Reducing number of queries

As per the comments, I wanted to address the issue of increasing the amount of database queries from your original 2, to the amount of 1 + num_of_classb_objects. This is because while the original ClassB query is 1, each of the b.classa_set.all() relation calls is another query to fetch the related ClassA instances.

If you want to try and test an alternative, you can check out this blog post on efficient reverse lookups

Example (adapted from link to your model names):

qs = ClassB.objects.all()
obj_dict = dict([(obj.id, obj) for obj in qs])
objects = ClassA.objects.filter(classb__in=qs)
relation_dict = {}
for obj in objects:
    relation_dict.setdefault(obj.classb_id, []).append(obj)
for id, related_items in relation_dict.items():
    obj_dict[id]._related_items = related_items

Also, if you are running django >= 1.4, this functionality is supposedly official in the form of prefetch_related.

I have not tested this, but I wanted to present it as something for you to look into if n+1 queries are not acceptable to you.

👤jdi

1👍

To avoid the large number of queries generated by accessing each set of A via the reverse relation, you could do something like this:

>>> from itertools import groupby
>>> alla = ClassA.objects.filter().order_by('classb').select_related('classb')
>>> groups = []
>>> for key,group in groupby(alla, lambda a: a.classb_id):
...     groups.append(list(group))

Now you have an iterable of iterables! We are focusing on the class that has the foreign key simply because you only need to execute one query (that is more complicated, but could save you in the long run). What’s happening above, is that we are sorting the list of A by B, and then saving each list of A into it’s own list/iterable. Since A is capable of accessing B, you don’t need to worry about iterating through B.

>>> for g in groups:
...     print g[0].classb # this is your grouping/header
...     print g           # this is a list of your classa instances

You can get a little bit fancier if you like to reduce the iterations through the generator, but I find that by focusing on the Model with the ForeignKey, you get much better SQL performance at the cost of more complicated code in your view. The complication is (IMO) negligible, since a large number of SQL queries can be the death of your application.

0👍

Maybe you could a ManyToManyField() in your class B model to include multiple class A objects in every object B. Such that you only have to call that field to get all class A objects that has the same value of class B.

👤Yulong

Leave a comment