Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Django – Display list of products with specifications from a foreign key

I have a Product model, and a ProductCustomField model which holds all of the specifications for each product. It has a foreign key back to Product.

I just want to be able to list each product along with the specifications for that product. As the code is right now, for each product, it displays the specifications for all products.

model

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

class Product(models.Model):
    name = models.CharField(max_length=255, unique=True)

    def __str__(self):
        return self.name[:50]


class ProductCustomField(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    key = models.CharField(max_length=255)
    value = models.CharField(max_length=255)

    def __str__(self):
        return self.key[:50]

view

def product_lines_view(request):
    context = {
        'product_list': Product.objects.all(),
        'spec_list': ProductCustomField.objects.all(),
    }
    return render(request, 'product_lines/product_list.html', context)

template

{% for product in product_list %}
    <h2>{{ product.name }}</h2>
    <ul>
        {% for spec in spec_list %}
            <li>{{ spec.key }}</li>
            <li>{{ spec.value }}</li>
    </ul>
        {% endfor %}
{% endfor %}

>Solution :

You don’t need to load every spec and pass it to spec_list, that is why you are seeing all specs multiple times.

Your view could look like:

def product_lines_view(request):
    context = {
        'product_list': Product.objects.all(),
    }
    return render(request, 'product_lines/product_list.html', context)

And your template could look like:

{% for product in product_list %}
    <h2>{{ product.name }}</h2>
    <ul>
        {% for spec in product.productcustomfield_set.all %}
            <li>{{ spec.key }}</li>
            <li>{{ spec.value }}</li>
        {% endfor %}
    </ul> 
{% endfor %}

The explanation is, Django automatically sets a reverse relationship attribute, that is composed by the name of the model that it has relation and the sufix _set. So by doing that you would get every ProductCustomField that has a relation with each specific product.

You can change this behavior by using the keyword related_name, by doing on the models.py:

class ProductCustomField(models.Model):
    product = models.ForeignKey(
        Product, 
        on_delete=models.CASCADE, 
        related_name="specs"
    )
    key = models.CharField(max_length=255)
    value = models.CharField(max_length=255)

    def __str__(self):
        return self.key[:50]

So you would be able to call on the template like this:

{% for product in product_list %}
    <h2>{{ product.name }}</h2>
    <ul>
        {% for spec in product.specs.all %}
            <li>{{ spec.key }}</li>
            <li>{{ spec.value }}</li>
        {% endfor %}
    </ul> 
{% endfor %}

You should be aware that this is a breaking change and all places that are currently using productcustomfield_set won’t work anymore and should be changed to specs.

Here is the docs for django related_name keyword: https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.ForeignKey.related_name

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading