Trying to understand Django's communication between view and template - python

(edited)
I come from a web2py background and find Django a much more complicated framework to learn and use than web2py.
After the first answer, I have adapted the description of my problem.
In my view I have:
def team(request):
hr = dict(name="Some Name", photo="/static/images/HR.jpg", url="http://some.website.com/?page_id=3602")
js = dict(name="Some Name2", photo="/static/images/JS.jpg", url="http://some.website.com/?page_id=3608")
context = {team:[hr,js]}
return render(request, "wos_2017_2/team.html", context)
In the template I have
<ul>
{% for person in context.team %}
<li> {{ person.name }} {{ person.photo }} {{ person.url }} </li>
{% endfor %}
</ul>
There is absolutely no output.
This works in normal python:
hr = dict(name="Some Name", photo="/static/images/HR.jpg", url="http://some.website.com/?page_id=3602")
js = dict(name="Some Name2", photo="/static/images/JS.jpg", url="http://some.website.com/?page_id=3608")
context = dict(team = [hr,js])
for i in context['team']:
print(i['name'], i['photo'], i['url'])
With output
Some Name /static/images/HR.jpg http://some.website.com/?page_id=3602
Some Name2 /static/images/JS.jpg http://some.website.com/?page_id=3608
Why am I not getting any result in Django?

Your first example is correct. Sadly you have a small typo in your first line of code:
hr = dict(name="Some Name, ...),
The line ends with a comma ,. Now hr is becomes a tuple with a single element: the dict. Without the comma this works:
{{ team.0.name }}
{{ team.1.name }}
With your updated answer you need to change context.team to team in your template:
{% for person in team %}
The context dictionary is 'unpacked' in the template.

I was not able to comment so I had to post an answer.
Only immutable data types can be used as keys, i.e. no lists or dictionaries can be used If you use a mutable data type as a key, you get an error message.Tuple as keys are okay.
With what i can tell is the problem is in your view code:
This
context = {team:[hr,js]}
should be this:
context = {"team":[hr,js]}
or
context = dict(team=[hr,js])

<ul>
{% for person in team %}
<li> {{ person.name }} {{ person.photo }} {{ person.url }} </li>
{% endfor %}
</ul>
Was the correct way to read the dictionary in the template.

Related

Adding related column to query results?

I have a setup in SQLAlchemy ORM (using Flask-SQLAlchemy) with Book and BookSubject classes, the latter being a many-to-many relationship (there is, of course, a Subject class, but it's not relevant to this question). I have a working query to return all books based on the date the subject was added to the database (there's a reason for this):
records = Book.query.\
filter(BookSubject.book_id == Book.id).\
filter(BookSubject.subject_id.in_([1, 12, 17])).\
filter(BookSubject.created > '2021-01-01').\
order_by(db.desc(BookSubject.created)).\
paginate(page, 50, True)
I then pass records to a Jinja2 template and do assorted stuff to display it; it works perfectly.
I'd now like to do the obvious thing and actually display the creation date (i.e. BookSubject.created, from the order_by clause), but I can't figure out how to add this to the query. Putting in add_columns((BookSubject.created).label("added")) isn't the answer; that throws an error when I try to use one of the record objects "'sqlalchemy.util._collections.result object' has no attribute 'id'". The template code that generates this is (roughly):
{% for book in records.items %}
<tr><td>{{ book.title }}</td></tr>
{% endfor %}
This should be obvious; how am I meant to add this to the result?
By using add_columns
Book.query.add_columns((BookSubject.created).label("added"))
it will return a named tuple with fields Book and added, so to access book fields you'd need something like book.Book.id
{% for book in records.items %}
<tr><td>
{{ book.Book.title }} {{ book.added }}
</td></tr>
{% endfor %}
or iterate by pairs:
{% for book, added in records.items %}
<tr><td>
{{ book.title }} {{ added }}
</td></tr>
{% endfor %}
If you want a flat structure (and without add_columns), then you can use
session.query(
Book.id,
Book.title,
BookSubject.created.label('added')
).filter(BookSubject.book_id == Book.id)...
then results will have a named tuple with fields id, title and added, so you can print directly book.id:
{% for book in records.items %}
<tr><td>
{{ book.title }} {{ book.added }}
</td></tr>
{% endfor %}

grouping fields in django form

I want to render form grouping fields. Form actually is created dynamically according to incoming dictionary
for f in settings.FORM_BIG_FIELDS:
self.fields[f['id']] = eval(f['type'])(label=f['label'], required=f.get('required', True))
self.fields[f['id']].groupp = f.get('group', 1)
groupp attribute means appropriate group, then I try to render it like
{% regroup form.fields.values by groupp as field_group %}
{% for group in field_group %}
<div class="group_{{ group.grouper }}">
{% for field in group.list %}
<p>
{{ field.all }}
{{ field }}
</p>
{% endfor %}
</div>
{% endfor %}
But as output I get the following
<django.forms.fields.CharField object at 0xb527388c>
<django.forms.fields.IntegerField object at 0xb52738ec>
<django.forms.fields.ChoiceField object at 0xb527394c>
I have read that these are not the same as BoundField object. How to render fields or is there any other better approaches to group fields?
If you do not want use any additional libraries, then the most easy solution is to render them manually, i would say. Otherwise you will just spend alot of time repeating the functionality of the library i copied as comment to your post.
There is always the case that things should be DRY. But we build websites for the users and user cares little about how the form rendering in template is done. For this reason we have often created form templates manually like this:
<div class="something">
{{ form.fieldname.label_tag }}{{ form.fieldname }}
</div>
Easyest way to organise it saving you some time. And in my opinion it is not that bad either, since this is not very common when you need fields organised by fieldsets.
I know this question is rather old, but I am sure there are still people who can benefit from a simple solution:
Say you have a group name and list of members. You can define a self.fieldset in your form's init to be a dictionary of {'group_1': ['member_1', 'member_2', ... ], ... }. Once you attach this to the form, you can pass it to views and from there to the template:
In forms.py:
class MyForm:
def __init__(self, current_user, *args, **kwargs):
super(YourForm, self).__init__(*args, **kwargs)
self.field['group'].queryset = Group.objects.filter(user = current_user)
...
In views.py:
form = self.Form(current_user)
the_fieldsets = form.fieldset
c = {'form': search_form,
'fieldsets': the_fieldsets }
In your template:
{% for field in form %}
<tr>
<td>{{field.label_tag}}</td>
{% if field.name == 'group' %}
<td>
<select id='{{field.id}}' name='{{field.name}}'>
{% for k,v in fieldsets.items %}
<optgroup label = {{k.name}}>
{% for val in v %}
<option name='{{val}} value = {{val.id}}> {{val.name}} </option> # Note that the select needs to return 'id', so value has to be {{val.id}}
{% endfor %}
</optgroup>
{% endfor %}
</select>
</td>
{% else %}
<td>{{field}}</td>
{% endif %}
<td>{{field.help_text}}</td>
<td>{{field.errors}}</td>
</tr>
{% endfor %}

sort in jinja2 when attribute is not available

I have a set of articles (using pelican for generating static sites) which includes a category called hotels. I'd like to sort these hotels. The problem is that only the hotels have an attribute called 'city' while the other articles do not and this obviously leads to the following error:
Caught exception "'pelican.contents.Article object' has no attribute 'city'".
Here is the code I am using:
{% for article in articles|sort(attribute='city') %}
{% if article.category == 'hotels' %}
<a href="hotels/{{ article.slug }}.html">
<p>{{ article.title }}</p>
</a>
{% endif %}
{% endfor %}
Is there a way to check to see if the attribute exists and provide some default value so that it does not cause an error?
You may be able to move your if statement into your for loop as a filter:
for article in articles if article.category == 'hotels' | sort(attribute='city')
If you want to show only entries that have a 'city' attribute, and have that list sorted by 'city', do:
for article in articles|selectattr("city")|sort(attribute="city")
If you want to iterate over only the hotels, see Sean Vieira's answer. If you want to iterate over all articles, but have the hotels sorted while the rest are in arbitrary order, you can do it by using macros:
{% macro my_macro(article) %}
...
{% endmacro %}
{% for a in articles if a.category == 'hotels' | sort(attribute='city') %}
{{ my_macro(a) }}
{% endfor %}
{% for a in articles if a.category != 'hotels' %}
{{ my_macro(a) }}
{% endfor %}
This will include everything you defined in my_macro first for each hotel, in the desired order, then for each article that is not a hotel.
I found this page when was looking for a similar solution.
Eventually, I solved it a bit differently and it might be helpful for someone else.
In one of my templates for Pelican I added statistics collected by 'post_stats' plugin about approximate time to read. It looked like
~{{ article.stats['read_mins']|default("0") }} min read
But if the plugin is not loaded then the 'article' object doesn't have the 'stats' attribute and rendering fails.
Jinja has the builtin test for testing if a variable is defined.
So, I came up with this solution
~{{ article.stats['read_mins'] if article.stats is defined else "0" }} min read

flask and wtf - how can I direct tag attributes for fields?

After fiddling with wtforms, fields use widgets to actually render them to html. I wrote some custom field/widget to draw html in a way that I'd more like to. But here's a question:
suppose I want to render them with pre-defined css class or give actual details myself.
How can I achieve this? and on what phase of handling requests(at Form class declaration? or when setting attributes to give Form some Fields? or when I'm actually calling them in jinja2 templates) I should do that?
I use a Jinja macro something like this:
{% macro field_with_errors(field) %}
{% set css_class=kwargs.pop('class', '') %}
{% if field.type in ('DateField', 'DateTimeField') %}
{{ field(class='date ' + css_class, **kwargs) }}
{% elif field.type == 'IntegerField' %}
{{ field(class='number ' + css_class, **kwargs) }}
{% else %}
{{ field(class=css_class, **kwargs) }}
{% endif %}
{% if field.errors %}
<ul class="errors">{% for error in field.errors %}<li>{{ error|e }}</li>{% endfor %}</ul>
{% endif %}
{% endmacro %}
usage is something like:
{{ field_with_errors(form.foo, placeholder='bar') }}
This lets me avoid boilerplate, but also lets me keep the display decisions in the template space.
Have a look at the rendering fields section.
Alternatively, you can add attributes to be rendered in the Jinja2 (etc.) template:
<div class="input-prepend">
{{ form.address(placeholder="example.com", id="address", autofocus="autofocus", required="required") }}
</div>
There's nothing to prevent you from using a variable for the ID value above, instead of address, then rendering the template with a keyword argument to populate it.

Jinja2 and Json

I have for example a JSON File
{
"Google":{
"Web":"www.web.de",
"Apps":{
"Drive": "DriveLink",
"Dropbox": "DropboxLink"
},
"Google Main":"http://mail.google.com",
"G+":"http://plus.google.com"
},
"Social":{
"Facebook":"http://www.facebook.de",
"G+":"https://plus.google.com",
"Xing":"http://www.xing.de",
"LinkedIn":"http://www.linkedin.com",
"Tumblr":"http://www.tumblr.com"
},
"Fun":{
"Reddit":"http://www.reddit.com"
}
}
As you can see I have under the section Google a Nested Section named Apps
With CherryPy I hand over this JSON Object as following with the name linksList:
#cherrypy.expose
def index(self):
linksFile = open('links.json', 'r')
linksList = json.load(linksFile)
template = jinjaEnv.get_template('index.html')
return template.render(linksList=linksList)
What I want is to render following:
Google
Web (as a link)
Google Main
G+
Apps
Drive
Dropbox
Social
Facebook
G+
Xing
and so on
What I don't understand is to do is to render this nested Objects like "Apps" recursively
The documentation reads:
It is possible to use loops recursively. This is useful if you are
dealing with recursive data such as sitemaps. To use loops recursively
you basically have to add the recursive modifier to the loop
definition and call the loop variable with the new iterable where you
want to recurse.
In your case this would be accomplished with the following:
<ul>
{% for key, value in linksList.items() recursive %}
<li>
{% if value is string %}
{{ key }}
{% else %}
{{ key }}
<ul>{{ loop(value.items()) }}</ul>
{% endif %}
</li>
{% endfor %}
</ul>
My 2 cents, just if someone comes here looking for rendering a JSON using Jinja, and complementing the response from #Ryon Sherman :)
Since JSON may have int values as well as strings, you can use if value is mapping (and flip the if-condition)
If your output must feel like a JSON you can use {{key|indent(width)}} + loop.depth
In my case, the template was for an email and key|indent() didn't work as expected so I ended up using an auxiliar {% for %} loop:
<div>
<code>{
{% for key, value in dic.items() recursive %}
{% if value is mapping %}
<p>
{% for it in range(loop.depth) %} {% endfor %}{{key}}: {
</p>
{{ loop(value.items()) }}
<p>
{% for it in range(loop.depth) %} {% endfor %}}
</p>
{% else %}
<p>
{% for it in range(loop.depth) %} {% endfor %}{{key}}: {{value}},
</p>
{% endif %}
{% endfor %}
}</code>
</div>

Categories