Home > Back-end >  Django - Display list of products with specifications from a foreign key
Django - Display list of products with specifications from a foreign key

Time:03-30

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

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 %}

CodePudding user response:

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

CodePudding user response:

First, you don't need "spec_list" in your context:

context = {
    'product_list': Product.objects.all(),
}

Just, change the template:

{% 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>
</ul>
    {% endfor %}
{% endfor %}
  • Related