How Turning List into Class Object for Flask Templating - python

How to I turn a list of lists into a class that I can call for each object like foo.bar.spam?
list of lists:
information =[['BlueLake1','MO','North','98812'], ['BlueLake2','TX','West','65343'], ['BlueLake3','NY','sales','87645'],['RedLake1','NY','sales','58923'],['RedLake2','NY','sales','12644'],['RedLake3','KY','sales','32642']]
This would be to create variables for a very large html table using jinja2 templating in Flask.
I would want to be able to to do something like this:
{% for x in information %}
<tr>
<td>{{x.name}}</td>
<td>Via: {{x.location}} | Loop: {{x.region}}</td>
<td>{{x.idcode}}</td>
</tr>
{% endfor %}
There will be other uses then just this one template with this information, hence why I want it to be a callable class to use in other places.

Using collections.namedtuple:
>>> from collections import namedtuple
>>> Info = namedtuple('Info', ['name', 'location', 'region', 'idcode'])
>>>
>>> information =[
... ['BlueLake1','MO','North','98812'],
... ['BlueLake2','TX','West','65343'],
... ['BlueLake3','NY','sales','87645'],
... ['RedLake1','NY','sales','58923'],
... ['RedLake2','NY','sales','12644'],
... ['RedLake3','KY','sales','32642']
... ]
>>> [Info(*x) for x in information]
[Info(name='BlueLake1', location='MO', region='North', idcode='98812'),
Info(name='BlueLake2', location='TX', region='West', idcode='65343'),
Info(name='BlueLake3', location='NY', region='sales', idcode='87645'),
Info(name='RedLake1', location='NY', region='sales', idcode='58923'),
Info(name='RedLake2', location='NY', region='sales', idcode='12644'),
Info(name='RedLake3', location='KY', region='sales', idcode='32642')]

Probably the most common way is to put each of the records into a dict
info = []
for r in information:
record = dict(name=r[0], location=r[1], region=r[2], idcode=r[3])
info.append(record)
Jinja2 then allows you to use x.name etc to access the properties exactly as you do in your example.
{% for x in info %}
<tr>
<td>{{x.name}}</td>
<td>Via: {{x.location}} | Loop: {{x.region}}</td>
<td>{{x.idcode}}</td>
</tr>
{% endfor %}
NOTE this way of indexing into the data (x.name) is a jinja2 specific shortcut (though it's stolen from django templates, which probably stole it from something else).
Within python itself you'd have to do:
for x in info:
print(x['name'])
# x.name will throw an error since name isn't an 'attribute' within x
# x['name'] works because 'name' is a 'key' that we added to the dict

Related

Render variables from a file

There is a file api.yml containing a config for ansible:
config/application.properties:
server.port: 6081
system.default.lang: rus
api.pdd.url: "http://{{ stage['PDD'] }}"
api.policy.alias: "integration"
api.order.url: "http://{{ stage['Order'] }}
api.foo.url: "http://{{ stage['FOO'] }}
There is a stage.yml containing the key and the stage values:
default_node:
Order: '172.16.100.40:8811'
PDD: '172.16.100.41:8090'
FOO: '10.100.0.11:3165
In fact, these files are larger and the 'stage' variables are also many.
My task is to parse api.yml and turn it into properties-config. The problem is that I can not pull up the values ​​{{stage ['value']}} I'm trying to do it this way:
stream = yaml.load(open('api.yml'))
result={}
result.update(stream['config/application.properties'])
context= yaml.load(open('stage.yml'))
stage={}
stage.update(context['default_node'])
text = '{% for items in result | dictsort(true)%} {{ items[0] }} = {{
items[1] }} {%endfor%}'
template = Template(text)
properti = (template.render(result=result, stage=stage))
At the output I get this:
server.port = 6081
system.default.lang = rus
api.pdd.url = http://{{ stage['PDD'] }}
api.policy.alias = integration
api.order.url = http://{{ stage['Order'] }}
api.foo.url = http://{{ stage['FOO'] }}
And you need to get this:
server.port = 6081
system.default.lang = rus
api.pdd.url = 172.16.100.41:8090
api.policy.alias = "integration"
api.order.url = 172.16.100.40:8811
api.foo.url = 10.100.0.11:3165
Can I do it with jinja or ansible lib?
Sorry for my bad english
Following this approach, you would need to treat api.yml as a template itself and render it. Otherwise, jinja2 will treat it as a simple value of the property. Something like this would do:
import yaml
from jinja2 import Environment, Template
import json
stream = yaml.load(open('api.yml'))
result={}
result.update(stream['config/application.properties'])
context= yaml.load(open('stage.yml'))
stage={}
stage.update(context['default_node'])
text = """{% for items in result | dictsort(true)%} {{ items[0] }} = {{ items[1] }} {%endfor%}"""
#Then render the results dic as well
resultsTemplate = Template(json.dumps(result))
resultsRendered = json.loads( resultsTemplate.render(stage=stage) )
template = Template(text)
properti = (template.render(result=resultsRendered, stage=stage))
After this you will see the wanted values in the properti var:
' api.foo.url = http://10.100.0.11:3165 api.order.url = http://172.16.100.40:8811 api.pdd.url = http://172.16.100.41:8090 api.policy.alias = integration server.port = 6081 system.default.lang = rus'
It would be nice though if jinja2 was able to render recursively. Maybe spending some time working out with the globals and shared modes of the Environment this can be achieved.
Hope this helps.

Django template syntax for nested dictionary item using variable names

On a Django template page, I'm trying to access the value inside a nested dictionary.
books =
{
1: { 1: 'Alice', 2: 'Bob', 3: 'Marta' },
2: { 1: 'Alice', 3: 'Marta' },
3: { 1: 'Alice', 2: 'Bob' },
}
Somewhere on my page, I have these two variables
info.id = 1
detail.id = 2
What I want to do is print (if it exists) the item books[1][2], or in other words books[info.id][detail.id]. I ran into trouble because I couldn't access this nested variable. This got solved here. However, the solution proposed was to access nested dictionary items using the dot notation. But the problem is that this doesn't seem to work when using variables. Using that logic, I would do:
{{ books.info.id.detail.id }}
But this doesn't yield any result. How should I approach the situation when using variables to access the items in a dictionary? Do note that the actual item may or may not exist, which is why I run into trouble using books[info.id][detail.id]
You can't do this in the template directly. You'll need to use a custom template tag. This would work:
#register.simple_tag
def nested_get(dct, key1, key2):
return dct.get(key1, {}).get(key2)
Now you can use it in the template:
{% load my_tags_library %}
{% nested_get books item.id detail.id %}

Accessing nested YAML mappings with jinja2

I've recently started using YAML and jinja2. I'm having trouble understanding why I need to reference the entire structure of my YAML mapping in the jinja2 template.
I have the following YAML file
---
PROVIDERS:
PROV1:
int: ge-0/1/1
ipv4: 10.0.1.1/30
PROV2:
int: ge-0/1/2
ipv4: 10.0.1.2/30
and this is my jinja2 template
{%- for provider in PROVIDERS %}
{{ provider }}
{{ PROVIDERS[provider].int }} <-- why not provider.int
{{ PROVIDERS[provider].ipv4 }} <-- why not provider.ipv4
{%- endfor %}
Parsing with pyyaml gives me the (expected) output
PROV2
ge-0/1/2
10.0.1.2/30
PROV1
ge-0/1/1
10.0.1.1/30
However why must I use PROVIDERS[provider].int? provider.int doesn't work.
Additionally, I was wondering if I could make this a list of mappings instead of a nested mapping:
---
PROVIDERS:
- PROV1:
int: ge-0/1/1
ipv4: 10.0.1.1/30
- PROV2:
int: ge-0/1/2
ipv4: 10.0.1.2/30
I've tried to do so, but the jinja2 template no longer produced the desired output.
There are two things to consider here:
What Python data structure is constructed from your YAML document?
How can your template reference the elements of that data structure?
Answering point 1 is easy:
>>> import yaml
>>> from pprint import pprint
>>> p1 = yaml.load("""
... ---
... PROVIDERS:
... PROV1:
... int: ge-0/1/1
... ipv4: 10.0.1.1/30
... PROV2:
... int: ge-0/1/2
... ipv4: 10.0.1.2/30
... """)
>>> pprint(p1)
{'PROVIDERS': {'PROV1': {'int': 'ge-0/1/1', 'ipv4': '10.0.1.1/30'},
'PROV2': {'int': 'ge-0/1/2', 'ipv4': '10.0.1.2/30'}}}
You have a dictionary with a single item whose key is 'PROVIDERS', and whose value is a dictionary with the keys 'PROV1' and 'PROV2', each of whose values is a further dictionary. That's a more deeply nested structure than you need (more on which later), but now that we can see your data structure, we can work out what's going on with your template.
This line:
{%- for provider in PROVIDERS %}
iterates over the keys of PROVIDERS (which, given your output, is obviously the second-level nested dictionary which is the value for the key 'PROVIDERS' in your top-level dictionary). Since what you're iterating over are the keys, you then need to use those keys to get at the associated values:
{{ PROVIDERS[provider].int }}
{{ PROVIDERS[provider].ipv4 }}
A more straightforward YAML document for your purposes would be this:
---
- id: PROV1
int: ge-0/1/1
ipv4: 10.0.1.1/30
- id: PROV2
int: ge-0/1/2
ipv4: 10.0.1.2/30
Note that we've ditched the redundant single-item mapping, and replaced the second-level mapping of mappings with a list of mappings. Again, we can check that:
>>> p2 = yaml.load("""
... ---
... - id: PROV1
... int: ge-0/1/1
... ipv4: 10.0.1.1/30
... - id: PROV2
... int: ge-0/1/2
... ipv4: 10.0.1.2/30
... """)
>>> pprint(p2)
[{'int': 'ge-0/1/1', 'ipv4': '10.0.1.1/30', 'id': 'PROV1'},
{'int': 'ge-0/1/2', 'ipv4': '10.0.1.2/30', 'id': 'PROV2'}]
Here's how your template could use this data structure:
{%- for provider in PROVIDERS %}
{{ provider.id }}
{{ provider.int }}
{{ provider.ipv4 }}
{%- endfor %}
Obviously you'll need to modify the code which supplies PROVIDERS to the template, since it's now the top-level list represented by the entire YAML document, rather than a dictionary nested inside it.

Loop in dictionary in HTML

I have a Python script creating a dictionary and passing it to a html page to generate a report.
in Python:
data_query= {}
data_query["service1"] = "value1"
data_query["service2"] = "value2"
return data_query
in HTML:
% for name, count in data_query:
<tr>
<td>${name}</td>
<td>${count}</td>
</tr>
% endfor
it does not work, says that it does not return enough values.
I also tried (pointed out in a comment in the other question, that I deleted by mistake):
% for name, count in dict.iteritems():
It does not give any error, but does not work. Displays nothing.
${len(dict)}
gives the right dictionary length
${len(dict.iteritems())}
does not display anything and seem to have a weird effect on my table format.
Is there a way to iterate correctly a dictionart in HTMl to display both the key and value?
EDIT: How I transfer the dictionary to the html page.
from mako.lookup import TemplateLookup
from mako.runtime import Context
from mako.exceptions import text_error_template
html_lookup = TemplateLookup(directories=[os.path.join(self.dir_name)])
html_template = html_lookup.get_template('/templates/report.html')
html_data = { 'data_queries' : data_queries }
html_ctx = Context(html_file, **html_data)
try:
html_template.render_context(html_ctx)
except:
print text_error_template().render(full=False)
html_file.close()
return
html_file.close()
% for name, count in dict.items:
<tr>
<td>${name}</td>
<td>${count}</td>
</tr>
% endfor
should probably work ... typically you dont call the fn when you pass it to a templating language... alternatively
% for name in dict:
<tr>
<td>${name}</td>
<td>${dict[name]}</td>
</tr>
% endfor
would likely also work
as an aside ... dict is a terrible variable name as it shadows the builtin dict (which might be part of your problem if that is actually your variable name)

Multiple renders of jinja2 templates?

Is there any way to do this with jinja2?
template = Template("{{ var1 }}{{ var2 }}")
rendered1 = template.render(var1=5) # "5-{{ var2 }}"
rendered2 = Template(rendered1).render(var2=6) # "5-6"
basically, I want to be able to do multiple passes on a template. When the template engine finds a variable in the template that is not in the context, instead of replacing it with nothing, keep the template variable intact? If not jinja2, is there any other python template library that can do this?
You can use DebugUndefined, which keeps the failed lookups, as your Undefined Type for the undefined parameter of the Template environment:
>>> from jinja2 import Template, DebugUndefined
>>> template = Template("{{ var1 }}-{{ var2 }}", undefined=DebugUndefined)
>>> rendered1 = template.render(var1=5) # "5-{{ var2 }}"
>>> print(rendered1)
5-{{ var2 }}
>>> rendered2 = Template(rendered1).render(var2=6) # "5-6"
>>> print(rendered2)
5-6

Categories