2

How can I make the for product in products loop break after the if condition is fulfilled 3 times. I have been trying to set up a counter but that isn't working... because set is not accepted inside of for loops. Though testing it out a bit more it isn't being accepted anywhere.

When I use set it throws this exception or a variation of it: Invalid block tag on line 11: 'set', expected 'endblock'. Did you forget to register or load this tag?

I am aware that I should put all the logic I'm using Templates for inside of the view and then pass it through the dictionary but it would take too much time to research everything about that and i just want to be done with this.

Honestly I am really tired it is 6am and idk what to do. Thank you for your help in advance. :)

Edit: I know I have to use namespace() for set to be able to propagate across scopes. But set itself is still raising the same exception as above.

Edit2: I thought that django uses jinja2 as it's templating language but it seems like that is not the case. I fixed the mentions of jinja2 as I haven't changed any of the defaults that come with a fresh install of django.

HTML

{% for category in categories %}
    <div>
        <h5 class="text-line"><a href="" class="store-category"><span>{{category.name}}</span></a></h5>
    </div>
        <!-- Cards Container -->
        <div class="shop-cards">
        {% set counter = 0 %}
        {% for product in products %}
            {% if product.category.categoryID == category.categoryID %}
                <!-- CARD 1 -->
                <div class="card">
                    <image class="product-image" src="{% static 'images/product-4.png' %}"></image>

                    <div class="card-category-price">
                        <h3 class="product-name">{{product.name}}</h3>
                        <h3 class="product-price">{{product.price}}</h3>
                    </div>
                </div>
            {% endif %}
        {% endfor %}  
        </div>
{% endfor %}
Cewu00
  • 138
  • 9
  • Are you sure that you are using Jinja2 and not Django Templates? – Dauros Jun 30 '22 at 04:25
  • @Dauros I might have a misconception but I thought that Django uses the language jinja2 for templates. I just started learning Django like 4 days ago so the answer is "whatever comes default on the Django install". I'm not sure. :/ Probably Django Templates then. – Cewu00 Jun 30 '22 at 04:31
  • By default Django uses Django Templates. However you can use Jinja2: https://docs.djangoproject.com/en/4.0/topics/templates/#django.template.backends.jinja2.Jinja2 or https://github.com/niwinz/django-jinja – Dauros Jun 30 '22 at 04:34
  • @Dauros Oh... I will edit the tag and title then. It is the default setting then since I haven't changed anything that major. – Cewu00 Jun 30 '22 at 04:36
  • To answer your question: Django does not support variable assignments in for loops, so you have to implement this functionality with a custom template tag, or just use Jinja2 that supports it with namespace objects. – Dauros Jun 30 '22 at 04:43
  • @Dauros Is creating a custom template tag hard for a beginner? Do you think I would be better of just installing jinja2? – Cewu00 Jun 30 '22 at 04:47
  • It's not that hard, see e.g this SO answer: https://stackoverflow.com/a/37755722/5451046 However if you have Jinja2 experience just use it with Django as well, most of the major Django libraries support it. – Dauros Jun 30 '22 at 04:54
  • @Dauros Ok, thank you. I will take a look at it when i wake up tomorrow. – Cewu00 Jun 30 '22 at 05:02

2 Answers2

2

You can't (by design).Django is opinionated by design, and the template language is intended for display and not for logic.

You can use Jinja instead.

Or, you can do the complicated stuff in Python and feed the results to the template language through the context. Bear in mind that appending arbitrary objects to a list in Python is a cheap operation. So (in a CBV) something like

context['first_three'] = self.get_first_three( whatever)
...

def get_first_three(self, whatever):
    first_three = []

    for obj in  ...:
        if complicated stuff ...
            first_three.append( obj)
            if len(first_three) == 3:
                break

     return first_three

BTW you can't (easily?) implement break in Django templating, but you can easily give it the ability to count:

class Counter( object):
    def __init__(self):
       self.n = 0
    def value(self):
       return self.n
    def incr(self):
       self.n += 1
       return ''

In the context pass context['counter'] = Counter()

In the template refer to {{counter.value}} as much as you want, and in a conditional where you want to count occurences, use {{counter.incr}}. But, it's hacky, and probably best avoided.

nigel222
  • 7,582
  • 1
  • 14
  • 22
  • Thanks for the answer. I will implement a break just by placing the html inside of an `if counter.value <= 2`. You declare the `class counter` inside of the view right? Tbh as soon as i started using django templates instead of jinja2 in my google searches some answers started popping up XD Thanks! The hacky way will have to do for now. – Cewu00 Jun 30 '22 at 12:36
  • @Cewu00 Put the class declaration anywhere that makes sense in your project. To keep it DRY, I'd put the declaration somewhere that it can be imported from. I have a `utils` not-app folder for stuff like this. – nigel222 Jun 30 '22 at 13:05
  • Okay! I have already made everything work. Thank you lots for your help. :D – Cewu00 Jun 30 '22 at 13:08
1

The problem seems to arise due to a misconception of the templating functionality. It is to be used mainly for display purposes and not filtering purposes, and you are facing a filtering problem:

I want to filter the 3 first elements of a list that fulfill a specific condition

is your main issue and templating frameworks will be poor at solving your issue, whereas this is exactly what python and Django's ORM are good at.

Why don't you first filter in Django, then display in template? For example as follows:

...
products = queryset.filter(abc=condition)[:3]
context = Context({ 'products': products }) 
return HttpResponse(template.render(context))
Damien Baldy
  • 456
  • 2
  • 7
  • Yeah, I understand this is not how it is supposed to work. But this seemed like the easiest solution. Tbh I don't really know much about how the views really interact with the templates... so that is why I have been avoiding it. Though... I would probably have to make a matrix instead of a list. Because the 3 products that have to be passed are category dependent for each loop of `for category in categories`. I don't think you are able to return data from the template to the view function? – Cewu00 Jun 30 '22 at 12:52