Accessing dictionary keyed by object in django template

Question:

I am trying to access dict values in my django template. The dict keys are objects. Here is a snippet of my codebase:

models.py

class ProductCategory(models.Mode):
    name = models.CharField(max_length=64, blank=False, null=False, unique=True)
    slug = models.SlugField(blank=False, null=False, unique=True)

class Product(models.Model):
    category = models.ForeignKey(ProductCategory, on_delete = models.CASCADE)

    # other fields
    pass

    def __str__(self):
        return self.title

views.py

def index(request):
    products = Product.objects.all()
    categorized_products = {}

    for p in products:
        prod_category = p.category
        temp = categorized_products.get(prod_category,[])
        temp.append(p)
        categorized_products[prod_category] = temp

    context = {'products_dict': categorized_products, 'categories': categorized_products.keys() }

    return render(request,'product/index.html', context=context)

mytemplate.html (relevant snippet)

            <div class="tab-content">
                {% for category in categories %}
                {% if not forloop.counter0 %}
                <div class="tab-pane fade in active" id="{{ category.slug }}">  
                {% else %}                  
                <div class="tab-pane fade in" id="{{ category.slug }}">
                {% endif %}
                    <div >
                        <h4>{{ category.description }}</h4>
                        <div class="container-fluid"> 
                            <div class="row">                                                                  
                                <div class="col-md-3"  style="border:1px solid red;">
                                {% for product in products_dict.category %}
                                    {{ product }}
                                {% endfor %}

When I step through the code with a debugger, I can see that the variable products_dict is a dict and correctly populated by the view. However, when I run the code, the for loop code is not executed.

Similarly, when I run the same code I have in views.py in the shell, the dict is correctly populated, so I know that there is data in the database and that the retrieved data is being correctly passed to the django template.

So why am I unable to access the dict value in the template and why is the product not displaying in my template?

Answers:

The simple answer is that Django templates don’t do that. Dot reference in template variables does dictionary lookup (among other things) but not based on the value of another variable name:

Note that “bar” in a template expression like {{ foo.bar }} will be interpreted as a literal string and not using the value of the variable “bar”, if one exists in the template context.

https://docs.djangoproject.com/en/3.0/ref/templates/language/#variables

Generally I would solve this by arranging for something iterable to exist on the category values directly, rather than trying to use the category object as a dict key. That could be a reverse FK manager:

{% for product in category.products_set.all %}

Or something I set on the in-memory category objects in the view, if I need to transform or filter the products per-category:

categories = ProductCategory.objects.all()
for category in categories:
    category.products = [transform_product_somehow(product) for product in category.products_set.all()]

(Optionally with use of fetch_related to prefetch the category products rather than doing an additional query per category.)

Answered By: Peter DeGlopper

You can use a custom filter for that. In custom_tags.py:

from django import template

register = template.Library()

@register.filter
def get_obj_field(obj, key):
    return obj[key]

Then load the tags in django:

{% load custom_tags %}

And use it like this:

{% for product in products_dict|get_obj_field:category %}
   {{ product }}
{% endfor %}
Answered By: Robin de Rozario
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.