Yunus
Yunus

Reputation: 415

Looping over for loop inside another for loop in Django

First see my code please :

My model :

class review_product_individual(models.Model):

 product_id  = models.CharField(max_length=30)
 product_star = models.CharField(max_length=30)
 product_review_message = models.CharField(max_length=255,blank=True)
 product_reviewed_by = models.CharField(max_length=30)
 product_review_date = models.DateTimeField(auto_now_add=True, blank=True)

In views :

product_review = review_product_individual.objects.filter(product_id = productID).order_by('-product_review_date') 

Here product_star will contain 1 to 5 rating and stored as char. Now what I want to do is this I will access each product_review object in Django template in for loop and inside the for loop I will iterate product_review.product_star times.please see the template code here :`

                {% for product_review in product_review %}
            <li>
                <div class="review-heading">
                    <h5 class="name">{{product_review.product_reviewed_by}}</h5>
                    <p class="date">27 DEC 2018, 8:0 PM</p>
                    <div class="review-rating">
                    {% for i in product_review.product_star %}

                        <i class="fa fa-star"></i>

                    {% endfor %}


                        <i class="fa fa-star-o empty"></i>
                    </div>
                </div>
                <div class="review-body">
                    <p>{{product_review.product_review_message}}</p>
                </div>
            </li>

            {% endfor %}

But only one star is printing as the value of {{product_review.product_star}} =3 I want 3 stars. I could not do in views because product_review contains many objects. So how can I do it ?

Upvotes: 1

Views: 545

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476503

I find it weird that you store the rating as a CharField (so a string), instead of using an IntegerField, with an IntegerField, you can calculate sums, averages, order elements numerically (not lexicographically), etc.

So I would advice you to use an IntegerField instead:

class ReviewProductIndividual(models.Model):
    product_id  = models.CharField(max_length=30)
    product_star = models.IntegerField()
    product_review_message = models.CharField(max_length=255,blank=True)
    product_reviewed_by = models.CharField(max_length=30)
    product_review_date = models.DateTimeField(auto_now_add=True, blank=True)

So now we can store ratings with a numerical rating.

Next your problem with the template is that you iterate over a string. If you iterate over a string, you iterate over its characters. So that means that if you stored '5', it will iterate once: and the iterator will obtain the character 5. But you want to iterate five times.

What you can do is for example define a {% range ... %} tag, like this snippet does. We can for example create a package (with an __init__.py file) in appname/templatetags/range.py (and thus add an empty file appname/templatetags/__init__.py):

from django.template import Library, Node, TemplateSyntaxError

register = Library()

class RangeNode(Node):

    def __init__(self, num, context_name):
        self.num = Variable(num)
        self.context_name = context_name

    def render(self, context):
        context[self.context_name] = range(int(self.num.resolve(context)))
        return ""

@register.tag
def num_range(parser, token):
    """
    Takes a number and iterates and returns a range (list) that can be 
    iterated through in templates

    Syntax:
    {% num_range 5 as some_range %}

    {% for i in some_range %}
      {{ i }}: Something I want to repeat\n
    {% endfor %}

    Produces:
    0: Something I want to repeat 
    1: Something I want to repeat 
    2: Something I want to repeat 
    3: Something I want to repeat 
    4: Something I want to repeat
    """
    try:
        fnctn, num, trash, context_name = token.split_contents()
    except ValueError:
        raise TemplateSyntaxError, "%s takes the syntax %s number_to_iterate\
            as context_variable" % (fnctn, fnctn)
    if not trash == 'as':
        raise TemplateSyntaxError, "%s takes the syntax %s number_to_iterate\
            as context_variable" % (fnctn, fnctn)
    return RangeNode(num, context_name)

Note that this is a slighly modified version (updated with the comment in the snippet).

Now we can use this {% num_range ... %} tag:

{% for product_review in product_review %}
<li>
    <div class="review-heading">
        <h5 class="name">{{product_review.product_reviewed_by}}</h5>
        <p class="date">27 DEC 2018, 8:0 PM</p>
        <div class="review-rating">
        {% num_range product_review.product_star as star_range %}
        {% for _ in star_range %}

            <i class="fa fa-star"></i>

        {% endfor %}


            <i class="fa fa-star-o empty"></i>
        </div>
    </div>
    <div class="review-body">
        <p>{{product_review.product_review_message}}</p>
    </div>
</li>
{% endfor %}

Upvotes: 1

Related Questions