How to Repeat Parts of Jinja2 Template - templates

I realize a couple of similar questions on this have been asked, but they don't relate specifically to what I'm trying to do and I'm pretty much a total beginner, so the answers seem more advanced than what I think I'm expected to do.
I'm working on implementing a Jinja2 template with Python code into an html page of notes I've been keeping while taking this course.
So...
The html block I'm trying to template and repeat has the following structure:
<h1>Stage Number</h1>
<div class = "lesson">
<h2>Lesson Number</h2>
<div class="concept">
<div class="concept-title"> Title </div>
<div class="concept-description">
<p>Description paragraph</p>
<p>Description paragraph</p>
</div>
<div class="concept-title"> Title</div>
<div class="concept-description">
<p>Description paragraph</p>
</div>
</div>
</div>
For each stage, lesson numbers vary and each title has a varying number of description paragraphs.
My code is on Github (this is an edited version): https://github.com/graceehayden/Stage4Udacity-Session-2/blob/master/templates/index.html
My main.py file has the template code, which is supposed to be implemented in the index.html file.
The course just sort of went over the edge on me and re-watching videos or finding other YouTube tutorials on templating isn't helping because a lot of it is more advanced than I seem to be at this point.
Any help or pointers on how to straighten the variables out so they correlate properly with the index.html file would be so appreciated. When I pretend I don't have so many inputs, I am able to make a simple single variable work and show up when I run the app, but with the complexity I have now and need, it isn't functioning.

It looks like you mostly have the right idea with the code so far.
The big thing that is missing is that you need data types that are sequences to extend this to do what you want here.
Each lesson has a list of concepts with an associated description.
concept1 = {
'name' : 'concept1',
'description1' : 'This is the description for concept1!',
}
lesson1 = {
'name' : 'lesson1',
'concepts': [concept1, concept2] #the list here lets us have more than one value
}
Then each Stage is a data structure that contains a list of different lessons.
stage1 = {
'name' : 'stage1',
'lessons': [lesson1, lesson2],
}
Finally you stuff all these different stages into the template_values
template_values = {
'stages' : [stage1, stage2],
}
Then you need the templating to access the nested data:
{% for stage in stages %}
//do things with this particular stage
{% for lesson in stage['lessons'] %}
//do things with this lesson
{% for concept in lesson['concepts'] %}
//do things with individual concepts
{% endfor %}
{% endfor %}
{% endfor %}
Note: haven't tested the template yet, so might have made some mistakes accessing the dictionary.

Related

How to make a for loop break on counter in Django Templates?

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"><span>{{category.name}}</span></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 %}
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.
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))

Reference Wagtail root page in templates

Currently I display a link to my sites homepage in the base template using this code :
{% if page.slug != 'homepage' %}
<!-- insert link code here -->
{% endif %}
However I like to reuse my code and sometimes the page is called something else. What I would like to write is something like :
{% if page != request.site.root_page %}
However this does not work, although if I display the values of these fields in the template they are both equal or not as expected ...
<p>{{ request.site.root_page }} and {{ page }}<p>
Results in :
<p>Contact and Contact</p> # On the root page
or
<p>Contact and Test</p> # On a different page
The opposite problem to this as the data is not specific to one page, but to every page except one.
What am I missing?
Okay in the process of explaining the question, I also found the answer. It doesn't work (I believe) because although these values look the same, they are actually objects with different properties, therefore the comparison fails.
In order for the comparison to succeed you need to extract values from each object and compare them. So this will not work ...
{% if page != request.site.root_page %}
However both of these do work ...
{% if page.title != request.site.root_page.title %}
... or ...
{% if page.url != request.site.root_page.url %}
Kicking myself now, but hopefully my public humiliation will help someone in the future.

Django - the effect on page load speed of using include tag inside for loop

The odds are that this question will be banned, because this forum seems to me a site for "why it doesn't work"-type of questions, rather than "is it a good idea to do what I do" ones. And yet, I am very much concerned about preserving DRY-ness in my code.
I have a Django template which looks like this:
<ol id = 'task_list'>
{% for item in qs %}
{% include 'list_item.html' with item=item %}
{% endfor %}
</ol>
list_item.html:
<li>
{{item}}
</li>
The advantage (at least, for me) of this code: it positively affects DRYness when I have a ajax code which posts a request for creating new items of the list and renders them on the client side subsequently:
JS:
$.post('my_view_url', function(response)
$('#container').append(response);
Django view:
def my_view(response)
#...
return render_to_response('list_item.html',....)
This way, list_item.html helps me use the same HTML code for both initial rendering of existing elements and client-side rendering of newly created items.
The disadvantage is that {% include %} is known to be rather slow.
The question: Is this code pattern not a performance killer in case of paginated rendering of large arrays of data ?
Additional note:
AFAIK, {% block %} is faster than {% include %}. But I've got no idea how to rewrite the code pattern using block.

Django: How do I get the number of elements returned in a database call?

This seems to me like a very simple question, but I can't seem to find the answer.
All I need to do is determine the number of objects returned by a database query.
The specific circumstance is this: I have a model named Student. This model has a ManyToManyField member named courses_current, which relates to a table of Course models. When I pass my Student instance to a template, I want to be able to do something like the following (the syntax may not be exact, but you'll get the basic idea):
<div id="classes">
{% if student.classes_current.all.size == 0 %}
<h1> HEY! YOU AREN'T TAKING ANY CLASSES! REGISTER NOW!
{% else %}
Here are your courses:
<!-- ... -->
{% endif %}
</div>
Now, I'm fairly certain that X_set.all.size is not a real thing. In the manage.py shell I can just use len(student.classes_current.all()), but I don't know of any way to use built-in functions, and "dictionary-like objects" don't have .size() functions, so I'm at a loss. I'm sure there's a very simple solution (or at least I hope there is), but I can't seem to find it.
{{ student.classes_current.all.count }} but be warned that it doesn't fetch the objects so you will need to do a separate query if you want to loop over them.
If you need loop over the classes for tag has way to get what you need.
{% for cl in student.current_classes.all %}
{{ cl }}
{% empty %}
<h1>Hey! ...</h1>
{% endfor %}
Documentation https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#for-empty

What's the best way to allow an infinite amount of foreign keys to the same model?

I'm making a forum with django right now, and I want it so that anyone can comment on anyone else's comment. Right now I have a foreign key in my 'Comment' model that points back to itself so I can get the parent comment easily from any comment.
In my theory this worked great, because from any comment I could get all of its child comments, and then just keep branching down from there to get every single child comment. However I'm having trouble actually implementing this when it comes to getting the data from the view to the template.
I want it to be possible to have an infinite number of child comments, because who knows how long a discussion will last and I don't want to arbitrarily limit it. The problem I'm having is would you get all of those comments from the view to the template without losing their relationship to their parent comment?
Currently this is what the psuedocode for my code looks like:
#the view
def comment_page(request, forum, comment_id):
#this is the main comment that all others will stem from
main_comment = Comment.objects.get(id=comment_id)
children_comments = main_comment.comment_set.all()
#the template
{% for comment in children_comments %}
<p class='comment'>{{comment}}</p>
{% endfor %}
Obviously I'm not even trying to get all the child comments here, it just gets child comments of the very first post. What I don't understand is how can I then go through each of these child comments and then get all of theirs, and keep doing that for each new comment?
It makes the most sense to do it in the view since I am able to use Django's QuerySet API in there, but I don't see how I would be able to pass all of the comments to the template without losing their relationship to their parent. The only idea I can think of is to go through all of the comments in the view and build up a string of html that I just pass and simply display in the template, but that seems like a horrible idea because it'd be dealing with template related stuff in the view.
You might want to look into using a MPTT such as django-mptt
this can be implemented by a custom filter with an inclusion_tag that includes itself but causes a lots of queries to your db:
#register.inclusion_tag('children.html')
def comments_tree(comment):
children = comment.comment_set.all()
return {'children': children}
# children.html
<ul>
{% for child in children %}
<li> {{ child }}</li>
{% if child.comment_set.count > 0 %}
{% comments_tree child %}
{% endif %}
{% endfor %}
</ul>
# your template
{% comments_tree comment %}
This older question is probably of interest:
How can I render a tree structure (recursive) using a django template?
Edit: to future readers, don't do this as the inner for loop's comment variable does not substitute the outer comment variable during the loop execution, leading to infinite recursion. /Edit
If you need a recursive tree structure in your HTML page (i.e. a bunch of nested <div> tags), you can write a recursive "comment" template.
Sample: (untested)
{# comment.html #}
<p class='comment'>{{ comment.text }}</p>
{% if comment.children %}
{% for comment in comment.children %}
{% include "comment.html" %}
{% endfor %}
{% endfor %}
The for loop binds the comment template variable to each child before including itself.
Performance note: Unless your comment sets are often short, this will probably be very slow. I recommend that you make your comments non-editable and cache the result!
Alternate solution: If you don't need the recursive HTML <div> tags, you can write a generator that performs a pre-order traversal of the structure and yields (depth, comment) pairs. This would likely be far more efficient in rendering speed.