django template tags, work with an array output - arrays

I created a tag which output is an array:
from django import template
register = template.Library()
def create_array(context):
output=[1,2,3,4]
return output
register.simple_tag(takes_context=True)(create_array)
When I call this tag in the template: {% load create_array %} {% create_array %}, the array is printed, but I can't access to each element in this way:
{% load create_array %}
{% for i in create_array %} {{i}} {% forend %}
Any idea how can I access to each element?
The error is during template rendering: Exception Value: 'create_array' object is not iterable.
PS: I need "context" in the tag, so I just wrote a short example. I'm using Django version 1.6.2

#laffuste has a good point that you need to use the as command to store the array into a template variable before you iterate through it. However, there's a slightly neater way to write the tag -- this is, in fact, precisely what Assignment Tags were made for:
from django import template
register = template.Library()
#register.assignment_tag(takes_context=True)
def create_array(context):
return [1,2,3,4]
That's it -- the assignment tag does the nasty work of parsing and return a template-variable-friendly output. Everything in laffuste's answer for the template is exactly what you'd need, but just so you can have it all in one place, here it is reproduced again so it's all in one place:
{{ load my_custom_tags }}
{% create_array as my_array %}
{% for item in my_array %}
{{ item }}
{% endfor %}

To get an index of each object in the forloop, use a forloop.counter like so:
{% load create_array %}
{% for i in create_array %}
{{ forloop.counter }}
{% forend %}
If you want to do something when you hit a certain count in the forloop, then use the following code:
{% for i in create_array %}
{% if forloop.counter == 1 %}
Do something with {{ i }}.
{% endif %}
{% endfor %}
Here is the Django Documentation for the forloop counter:
https://docs.djangoproject.com/en/dev/ref/templates/builtins/#for

I'm afraid there's no simpler way than...
from django import template
from django.template.base import TemplateSyntaxError
register = template.Library()
#register.tag(name='create_array')
def create_array(parser, token):
class ArrayCreator(template.Node):
def __init__(self, var_name):
self.var_name = var_name # output variable
def render(self, context):
context[self.var_name] = [1, 2, 3, 4] # access to context
return ''
args = token.contents.split() # "create_array", "as", VAR_NAME
if len(args) != 3 or args[1] != 'as':
raise TemplateSyntaxError("'create_array' requires 'as variable' (got %r)" % args)
return ArrayCreator(args[2])
Usage in Template:
{{ load my_custom_tags }}
{% create_array as my_array %}
{% for item in my_array %}
{{ item }}
{% endfor %}
But maybe someone will come with something lighter and nicer?

Related

How can you loop through all of the children of a model?

In Django I am trying to loop through all of the children of my Todo model. But whenever I try to run it, it gives me an AttributeError that says "Manager isn't accessible via Todo instances". My code looks like this:
{% extends 'base.html' %}
{% block content %}
<h3>Tasks</h3>
{% for t in model.objects.all %} <!-- Error -->
<p>{{t.name}}</p>
{% endfor %}
{% endblock %}
{% block options %}
<li class="bg-light py-3 w-100 px-4 rounded text-nowrap fs-4">
<button class="text-decoration-none text-dark">Save</button>
</li>
{% endblock %}
I tried to just put the model in the context, and then I got an error in the HTML, so I figured out that it happened when I tried to reference 'model.objects.all'.
Inside of your view you need to specify what django should pass to the template. It does not serve the entire Database; therefore queries like in model.objects.all inside of your templates are not allowed.
Specify the queryset inside your views.py:
def todo_view(request):
context = {}
context['my_todos'] = my_todo_model.objects.all()
context['most_important_todo'] = my_todo_model.objects.get(pk=1)
# put your own logic inside the `.get` method above
return render(request, 'my_template.html' context)
And then access it inside of your template like so:
{% for t in my_todos %}
<p>{{ t.name }}</p>
{% endfor %}
{{ most_important_todo.name }}
Because we put a queryset inside of my_todos we can loop over it in the template. most_important_todo ist just a single object passed to the template, so we can access its properties (e.g. the name) directly.
Let me know how it goes

HubL / Twig: Not finding value from array

In HubSpot, I've created a custom module called footer. footer has five field types of menu:
I've created an array where all five of the above menu ID's are pushed to an array. I've done this via a template partial file which is included in my footer module.
<!-- creating array -->
{% set footer_id_array = [] %}
<!-- push menu id's to array -->
{% do footer_id_array.append(module.menus.menu_column_1) %}
{% do footer_id_array.append(module.menus.menu_column_2) %}
{% do footer_id_array.append(module.menus.menu_column_3) %}
{% do footer_id_array.append(module.menus.menu_column_4) %}
{% do footer_id_array.append(module.menus.menu_column_5) %}
Running {{ footer_id_array }} shows all the IDs in the array, i.e.
[29420054435, 29420223163, 29420054590, 29420158158, 29420071857]
So this is correct, the array contains the IDs.
Now, for each item in this array, I want to generate a nav, so in my footer custom module, I have the following:
{% set iterations = range(0, 5) %}
{% for i in iterations %}
<nav>
{% menu id="{{ footer_id_array[i] }}" %}
</nav>
{% endfor %}
However, on my page, this just prints HubSpots default menus, not the ones assigned to the ID's.
Why is this?

Wagtail Show latest blog posts on Homepage through a streamfield

I have 3 mains sections in my site, homepage, blog index, and blog specific. I am using the streamfield function in wagtail to order various sections in the homepage. One of those sections is for the latest three blog posts.
I have done this for the blog index page, but can't grab the latest blog posts in the streamfield.
My model looks like this
class CaseStudiesIndex(Page):
def casestudies(pages):
casestudies = CaseStudyPage.objects.all().order_by('-first_published_at')
return casestudies
intro = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('intro', classname="full")
]
class LatestPosts(blocks.StructBlock):
static = blocks.StaticBlock(admin_text='Latest posts: no configuration needed.',)
def casestudies(pages):
casestudies = CaseStudyPage.objects.all().order_by('-first_published_at')[:3]
return casestudies
class Meta:
icon = 'doc-full'
label = 'Latest Posts'
template = 'blocks/_latestPosts.html'
class HomePage(Page):
blocksbody = StreamField([
('lead_banner', LeadBanner()),
('latest_posts', LatestPosts()),
('team', Team())
],null=True,blank=True)
content_panels = Page.content_panels + [
StreamFieldPanel('blocksbody'),
]
In my block folder I am calling the file fine and it renders the wrapper fine but I can't grab any of the data, I have tried a bunch of ways but nothing returns.
{% load wagtailcore_tags wagtailimages_tags %}
{% load static %}
<section>
<div class="wrapper__inner">
<ul>
{% for case in self.casestudies %}
{{case.title}}
{% endfor %}
{% for case in self.case_studies %}
{{case.title}}
{% endfor %}
{% for case in self.latest_posts %}
{{case.title}}
{% endfor %}
{% for case in page.casestudies %}
{{case.title}}
{% endfor %}
{% for case in page.case_studies %}
{{case.title}}
{% endfor %}
{% for case in page.latest_posts %}
{{case.title}}
{% endfor %}
</ul>
</div>
</section>
For the Blog Index page that does work I do the following.
{% extends "inner_base.html" %}
{% load wagtailcore_tags %}
{% block body_class %}template-case-studies{% endblock %}
{% block content %}
<section>
<div class="wrapper__inner">
<h1>{{self.title}}</h1>
<ul>
{% include "blocks/CaseStudiesLatestBlock.html" %}
</ul>
</div>
</section>
{% endblock %}
And the CaseStudiesLatestBlock.html which works fine looks like
{% load wagtailcore_tags wagtailimages_tags %}
{% load static %}
{% for case in self.casestudies %}
<li>
<strong>{{ case.title }}</strong>
</li>
{% endfor %}
Defining your own methods on a StructBlock won't work - the self (or value) variable you receive on the template is just a plain dict, not the StructBlock object itself. (This might seem counter-intuitive, but it's consistent with how blocks work in general: just as a CharBlock gives you a string value to work with and not a CharBlock instance, StructBlock gives you a dict rather than a StructBlock instance.)
Instead, you can define a get_context method (as documented here) to provide additional variables to the template:
class LatestPosts(blocks.StructBlock):
static = blocks.StaticBlock(admin_text='Latest posts: no configuration needed.',)
def get_context(self, value, parent_context=None):
context = super(LatestPosts, self).get_context(value, parent_context=parent_context)
context['casestudies'] = CaseStudyPage.objects.all().order_by('-first_published_at')[:3]
return context
You can then access the casestudies variable in the template, e.g. {% for case in casestudies %}.

looping over jekyll collections

I have the following code
<div>
{% for note in site.regnotes %}
{% if note.regulationno == page.regulationno %}
<p>
{{ note.regulationno }} - {{ note.url }}
</p>
{% endif %}
{% endfor %}
</div>
This code loops over the regnotes collection in a jekyll site, checks if the current note regulationno is the same as the page regulationno and if so displays the regulationno and url - that is the url of the current page. How do I change this code to include the url of the previous page, the current page and the next page. I'm looking for three urls - previous, current and next? - The "page.previous.url" variable within jekyll does not appear to work in collections.
This is what it might look like in other code
for i=1 to number of items in the regnotes collection
if current note == page note
print page[i].url //current page url
print page[i-1].url //previous page url
print page[i+1].url //next page url
end if
end for
I suppose what I'm trying todo is reference the items in the collection by their array index. just can't seem to get the syntax correct.
Since you are a programmer, you just need to know that you need to use forloop.index0 to know where you are in the for loop (https://docs.shopify.com/themes/liquid-documentation/objects/for-loops#index0).
The code will be something like:
<div>
{% for note in site.regnotes %}
{% assign current_index = forloop.index0 }}
{% assign next_index = current_index | plus: 1 %}
{% assign prev_index = current_index | minus: 1 %}
{% if note.regulationno == page.regulationno %}
<p>
{{ note.regulationno }} - {{ note.url }}
</p>
{% if site.regnotes[prev_index] %}prev{% endif %}
{% if site.regnotes[next_index] %}next{% endif %}
{% endif %}
{% endfor %}
</div>

How to populate an array of array in Twig?

I have a problem with an array of arrays in Twig.
Here is the code I am struggling with :
{% set tabTmp = {0:{},1:{},2:{},3:{},4:{},5:{},6:{},7:{},8:{}} %}
{%for element in box.elements%}
{% set tab = tabTmp[element.category.id] %}
{% set elementId = element.id %}
{% set tab = tab | merge({elementId:element}) %}
{% endfor%}
{%for key, tmp in tabTmp %}
{% if tmp is iterable %}
{{ dump(tmp) }}
{% endif %}
{% endfor%}
box.elements and element exist, element.category.id and element.id are integer and element is the object I want to work with.
But I keep having Array(0) as a result of dump(tmp).
Any ideas ?
Everything looks fine, but if you want to merge a variable as key to an associative array you need to use ();
so try changing
{% set tab = tab | merge({elementId:element}) %}
To
{% set tab = tab | merge({(elementId):element}) %}

Resources