Django templating: extend a block in same template? - django

So here is the set up (simplified for here):
In my template file I have two sections I would like to add content too by looping of a list of dictionaries, e.g.:
<div class="stuff-a"></div>
<div class="stuff-b"></div>
{% for thing in list %}
<!-- goes into div a -->
<!-- goes into div b -->
{% endfor %}
I thought this would work:
<div class="stuff-a">
{% block diva %}{% endblock %}
</div>
<div class="stuff-b">
{% block divb %}{% endblock %}
</div>
{% for thing in list %}
{% block diva %} thing.stuff_for_div_a {% endblock %}
{% block divb %} thing.stuff_for_div_b {% endblock %}
{% endfor %}
and then django complains about me using each block more than once - shame on me.
Now the solution to this (keeping the same schema) is to make an intermediate template file and then extend it.
My question is, is there a way to append to different specific areas in a django template without having to loop through a list twice or an intermediate template file?
This matters if the loop is somewhat complicated and you dont want to have to update two loops whenever you make a change.
When might this occur in practice (outside this example)? If you have a list where each element in a list references another list (e.g. the way bootstrap handles tabnation)

Now the solution to this (keeping the same schema) is to make an intermediate template file and then extend it.
You can't do this either because you can't use {% block %} in a loop for the same reason as you can't have a block more than once in the same template.
My question is, is there a way to append to different specific areas in a django template without having to loop through a list twice or an intermediate template file?
No you can't. And if ever you could do what you are trying to achieve, it would necessarily involve looping twice over the list behind the scenes.
This matters if the loop is somewhat complicated and you dont want to have to update two loops whenever you make a change.
It matters indeed if the loop is a generator where the next element is calculated at each iteration. But in this case you wouldn't be able to loop twice over the iterable anyway, which is necessary.
If it is a list already calculated and stored in memory, it doesn't matter.
When might this occur in practice (outside this example)? If you have a list where each element in a list references another list (e.g. the way bootstrap handles tabnation)
I don't see how this would be complicated.

Related

Django Templates - Best Practice in Avoiding Repeating Code that Require Different For Loops?

I'm creating a blog and have numerous pages that will display a list of articles. So, to avoid repeating that code, I'm trying to place it within a parent template that I can extend where needed.
The problem is I'll need a different for loop to display the article lists on each page/view. I figure the easiest approach would be to simply create blocks where I want the loop to start and close within the parent, then alter accordingly within each child.
However, Django doesn't allow you to close blocks that have an open for loop, despite closing the loop later in a different block.
My initial approach, in the parent, article_list.html:
<div class="row">
{% block loop_start %}
{% endblock loop_start %}
<div class="col-xs-12 col-sm-4">
<div class="card">
<a class="img-card">
<img class="img-fluid"src="../../static/{{ post.featured_image }}" />
</a>.... etc
I know I have to fix my src code.
Extends to child as:
{% block loop_start %}
{% for post in recent_articles %}
{% endblock loop_start %}
However, that doesn't work as noted above.
I've also tried wrapping the entire code for the article list in a block, extending it and performing the following within the child:
{% for post in recent_articles %}
{% block.super article_list %}
{% endblock article_list %}
{% endfor %}
That doesn't work either. Again, producing the same error as a block is closing before the loop ends. I've also tried closing the loop in the parent which doesn't work either.
Is there an easier way of going about this that I'm missing? I could pass the same variable to each view then implement the loop in the parent, but that seems janky and limiting.
What's best practice here?
You should take a look at a 'base.html' file. Take a look at this web-page: https://ultimatedjango.com/learn-django/lessons/create-the-project-base-template/
This will allow you to do {% extends 'base.html' %} all of which Django will handle.

Django template lists not iterable?

Running into a strange problem with Django's template, and being n00b I don't even know how to debug a template...
PROBLEM: variables of a type list somehow stopped being a list when passed into the template.
In my view, I have a bunch of variables passed to the template that are a dictionary of lists. Here's the code,
VIEW
project_image_design = {}
for p in projects:
project_image_design[p.id] = []
images = UploadedImage.objects.filter(project=p, image_type=UploadedImage.DESIGN)
for i in images:
project_image_design[p.id].append(i)
Here's the context. I have projects, each contains images. I created a dictionary, where the keys are the project id, and the value is a list of images associated with that project.
However, when I use this in the template, things go wrong,
TEMPLATE
{% for p in projects %}
<div class="row">
{% for list in project_image_design|get_item:p.id %}
{% for i in list %}
<div class="col-md-2"><img src = "{% get_static_prefix %}media/{{ i.filename }}"></div>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
So in the template, I'm iterating through projects, then using the project's id (p.id) to get the dictionary value, which is a list of images, and then iterating through that. The fancy get_item tag is just a way to access dictionary values through keys that not straight-forward variables (see: Django template how to look up a dictionary value with a variable).
Anyway, I get this error:
TypeError at /designer/my_projects/
'UploadedImage' object is not iterable
The error occurs on this line: {% for i in list %}, which is the line where I'm iterating through the list of images I retrieved using my project's id.
What's going on here????
I double-checked via pdb in the view, it all checks out. The variable being passed is indeed a dictionary of lists, I put a type on all the individual dictionary elements and they're all lists (like: type(project_image_design[1]) would return <class 'list'>).
Also, at the moment, all the lists are of length 1. I'm wondering maybe the template sort of deflates lists that are size 1? That'd seem like a pretty weird thing to do, probably not the reason.
Any help would be appreciated.
Also, how do I debug templates the way I can debug Python code? Like stepping through and stuff? Is that even possible?
It hasn't stopped being a list. But you have two nested for loops: you iterate through the items you get from the dictionary - confusingly calling each item list - and then attempt to iterate again through items in that "list". But the inner loop makes no sense: you should be doing simply:
{% for i in project_image_design|get_item:p.id %}
<div class="col-md-2"><img src = "{% get_static_prefix %}media/{{ i.filename }}"></div>
{% endfor %}
I'd also point out that your view logic is over-complicated. It could be reduced to simply this:
for p in projects:
project_image_design[p.id] = UploadedImage.objects.filter(project=p, image_type=UploadedImage.DESIGN)
And in fact it could be simplified even further: you don't need the dictionary, or the get_item tag, at all. Instead, provide a method on Project called something like design_images which just returns the images of that type:
def design_images(self):
return self.uploadedimage_set.filter(image_type=UploadedImage.DESIGN)
and removing the dictionary logic from the view altogether, and now your template can just be:
{% for p in projects %}
<div class="row">
{% for i in p.design_images %}
<div class="col-md-2"><img src = "{% get_static_prefix %}media/{{ i.filename }}"></div>
{% endfor %}
</div>
{% endfor %}

Django custom template tags imitating {% block %} behavior

I was studying Django custom template tags and got a question about the possibilities of custom tags.
Assume I have a construction like
{% extends "base.html" %}
{% block leftmenu %}
{% spaceless %}
<div id="#leftmenu">
...
</div>
{% endspaceless %}
{% endblock %}
and I want to shorten it like
{% extends "base.html" %}
{% load myawesometags %}
{% myblock leftmenu %}
...
{% endmyblock %}
I can't just create custom tag adding <div> and removing spaces cause without {% block ... %} the content won't take it's place in the base template. So, the question is: is it possible to emulate standard Django blocks in custom template tags?
It is possible to write a block tag, however you should know that block tag and extends tag work together. If you look at the code for BlockNode and ExtendsNode you will see how they render the template contents together.
ExtendNode captures all the block nodes from current template and the parent template. These blocks are stored in render_context with key BLOCK_CONTEXT_KEY and with value as instance of BlockContext. All the blocks are added to BlockContext using method add_blocks which uses FIFO (first-in-first-out) queue. Once all the block nodes are stored ExtendNode then renders the parent template. This causes the BlockNode instances to be rendered in the parent template. BlockNode then picks the BlockContext from render_context to get the block data. And because of FIFO, when BlockNode pops the block object from BlockContext it picks the last block, i.e. the one it encounters last in the inheritance. That's what I understood when reading the code. If I missed something please correct me.
You may be able to inherit BlockNode to customize it to a certain degree.
It is possible, though I think this kind of template tag is not expected to be customized.
The easiest way is probably to inherit the BlockNode class to do what you want and (somewhat) copy the do_block() function https://github.com/django/django/blob/master/django/template/loader_tags.py#L172 to use your BlockNode.

Django: Extends or Include?

My friend and I are having a small argument. In my current Django Project, I have created a file called menu.html which will contain a bunch of links configured and formatted into a list. Instead of manually hard-coding the menu into each page, I am currently including the menu using the following Django/Python code:
{% include 'menu.html' %}
However, my friend is suggesting that this is the incorrect way to do it. He said I need to use extends instead of include and then define content, something like this:
{% extend 'menu.html' %}
{% block content %}
The rest of my content here.
{% endblock %}
That's a bit of extra code. Does it really matter which I use? I would prefer to use the former.
Yes, it matters. First of all extends can only occur as the very first line of the file. Secondly, include pushes and pops a context object on the resolve stack, which means that value created in the context while in the include will go out of scope when it returns.
My rule is: create base.html template files that define the overall structure of your site and use liberal amounts of {% block foo %} around critical areas. Then all of your other templates extends the base (or something that itself extends the base) and you replace those blocks as needed.
include, on the other hand, is good for encapsulating things you may need to use in more than one place, maybe even on the same page.
Update:
I have been using my own library of template_tags for so long that I forget that Django's template language still has major gaps in functionality. The tag in question here is from an early django snippet called expr which I have heavily edited and extended. You can say, for example, {% expr 'Fred' as name %} (or any valid Python expression), and it will store the result in the 'name' slot in the current Context. If this occurs in an included template, name's value will be popped on exit from the template file.
You can sort of achieve this with the {% with %} tag, but expr gives me much greater flexibility, including doing arbitrarily complex calls. This originally came up when having to create complex cached objects that required expensive DBMS interactions that couldn't be done up in the view, they had to be invoked in the template itself.
Email me (in my profile) if you need to get deeper into this.
( his friend )
What I actually meant was defining a base.html so you can inherit a base template consistent of several generic sections, this one includes the doctype, html element defines 3 blocks for content and nav and optional area to override/insert script/link elements in the head.
<!doctype>
<html>
<head>
{%block extrahead %} {%endblock %}
</head>
{%block nav %}
<nav>
<ul>
<li>home</li>
</ul>
</nav>
<div id="content">
{% endblock %}
{%block content %}
{% endblock %}
</div>
</html>
Then you can define homepage.html:
{% extends "base.html" %}
{% block content %}
homepage content
{% endblock %}
homepage.html would then have the navigation because it extends base.html.
In this case placing the menu in base.html and extending from this seems to be more senseful.
including is great for splitting complex template and reusing those chunks.
let's say, you use the same list-style in different places of the site, but you send other queryset to it. as long, as you call the querysets the same, you only need to write the template code once.
Here I use differnt templates for normal- and ajax-requests. But using include let me re-use most parts in both templates

Indeterminite number of apps/widgets in Django template

I'm working on a site that will have a bunch of pages with an indeterminate amount of "apps" on each page. Something like a calendar app, and a random picture app, or whatever, each in a neat little box. While it's possible to write a template with a bunch of if tags that include other templates, this is a bit of a hassle. I'd like to pass in some variables and have forms on some of these apps, so it would get out of hand quickly. Writing custom inclusion tags will be better than {% include x %}, but it would still be a lot of if statements and writing out every possible app for each page.
Is there any way to loop over something like inclusion tags and include only those that are relevant? Any other completely different solution that I'm missing?
What I'm trying to avoid, whether I use {% include %} or inclusion tags, is this:
{% if apps.calendar %}
{% include "calendar.html" %}
{% endif %}
{% if apps.pictures %}
{% include "pictures.html" %}
{% endif %}
This would mean we'd have to update templates any time a new app was added. What would be nice is something like:
{% for app in apps %}
{% call appropriate include or inclusion tag %}
{% endfor %}
With very few exceptions we use our custom tags all over the place, so we deal with this by simply placing the following in the app/__init__.py file.
from django import template
template.add_to_builtins('content.templatetags.local_tags')
template.add_to_builtins('utils.cachetemplate')
So all of the pages have them available by default. Doesn't seem to impact performance and we use tag names that are unlikely to interfere with other stuff we might include. It's lazy but it works.
Update: OK, I think I better understand what you want. One way of doing this (although I don't really recommend it) is to put the check for the variables inside the included templates. This means you will always have the overhead of including the template, but it will make your other pages marginally less cluttered.
So instead of this in your main file:
{% if apps.calendar %}
{% include "calendar.html" %}
{% endif %}
you simply have:
{% include "calendar.html" %}
and in "calendar.html" you have:
{% if apps.calendar %}
whatever you do for your calendar app
{% endif %}
Update 2:
The last bit of code you show could be done as follows. This takes advantage of the the fact that {% include arg %} "resolves" its argument. This means it can be a variable or a method reference that returns a usable string value that is a template name.
{% for app in apps %}
{% include app %} <!-- or perhaps {% include app.template %} -->
{% endfor %}
Note: Django's template code does not handle top level callables correctly. This means your context cannot pass a reference to a function and expect it to be called and it's output inserted when referenced in a template. Ie. in the example above your list of apps may not be simple functions which you wish to be called. To work as expected a function reference must be either a member of a list or dict, or it must be a method of an object that is passed. We MonkeyPatched this problem long ago because we use curried functions in cached template fragments to defer some heavy DB work. This ticket has been open for over 2 years.