Structuring jinja/flask templates with multiple child templates? - python

I'm looking to design a site that provides different views of data from a database. I'm new to jinja/flask and templating in general, so it's quite likely I am going about this the wrong way and I'm looking for some feedback or "lordy lord no, you don't ever do that" feedback works too. :)
The basic structure of the page is: a header menu with quick links, a navbar menu pointing to different data sources, another navbar below that providing different views for the data. This 2nd navbar of the views would differ per option selected in the initial navbar.
I envisioned the structure of this to be something like the following:
|-base.html (header menu)
|--navbar1.html (extends base.html, first navbar)
|---navbar2.html (and 3 and 4, etc. - extends navbar1)
|----data_view1.html (and 2, 3, etc. - the returned query, extends navbar2)
This would treat each html as basically a frame and in my mind seems like the right way to do it, and use variables to track which navbar option to highlight through else blocks.
It seems messy though, what would be the best method to achieve the above? Would it be just one html file with a series of elif that would show the navbar2/3/4 and view1/2/3 pages based on what the user selects?
Many thanks in advance.

You certainly can do it this way, but it gets complicated really quickly. The better solution is almost certainly to include all of your navigation in one include:
<nav class="top-navigation">
<ul class="nav nav-first-level">
{{ nav_entry("1st_ds", "1st Data Source", page, sub_page) }}
{{ nav_entry("2nd_ds", "2nd Data Source", page, sub_page) }}
{{ nav_entry("3rd_ds", "3rd Data Source", page, sub_page) }}
</ul>
</nav>
{% macro nav_entry(identifier, title, page_id, sub_page_id) %}
{% set attrs = {"class": "nav-selected"} if identifier == page_id else {} %}
<li{{ attrs | xmlattrs }}>{{ title }}
<ul class="nav nav-second-level">
<li{{ set_if_selected('1st_sub_entry', sub_page_id) }}>1st Sub-Entry</li>
{# Other set_if_selected calls left off for clarity) #}
<li>2nd Sub-Entry</li>
<li>3rd Sub-Entry</li>
</ul>
</li>
{% endmacro %}
{% macro set_if_selected(identifier, to_match) %}
{{ {"class": "nav-selected"} if identifier == to_match else {} | xmlattrs }}
{% endmacro %}
If all of these navigation entries are dynamic, then it gets even easier:
<nav class="top-navigation">
<ul class="nav nav-first-level">
{% for nav_item in navigation %}
{{ nav_entry(nav_item) }}
{% endfor %}
</ul>
</nav>
navigation could be as simple as a list of tuples:
# Tuples are in the form
# (Link Title, URL, Is Selected?, Sub Menu Items)
# And sub menu items are tuples in the form
# (Link Title, URL, Is Selected?)
navigation = [("1st Data Source", "/some/url", true, []),
("2nd Data Source", "/some/url/2", false, [("A", "/a", false)]),
("3rd Data Source", "/some/url/3", false, [("B", "/b", false),("C", "/c", false)])]
The updates to nav_entry are left as an exercise for the reader :wink:

Related

Flask's Jinja nesting some rendered elements in 'strong' elements

I'm experiencing a weird behavior with Jinja. I made a dynamic flask route and so I made a jinja modular template, it's just a for loop to create an element for each article present in some data (in a dict) I give to Jinja, the template looks like this :
{% for theme in article_data %}
{% for article in theme["article"] %}
{% if article["main"] == 1 %}
<div style="background-image: url('{{article['content']['image1']}}');" class="theme-item-bg frow space-between">
{% endif %}
{% endfor %}
<div class="wrapper-row space-between pinkfilter">
<div class="uB theme-item-text">{{theme["name"]}}</div>
<div class="pageChanger waves-effect waves-light btn uL primaryB" page="/nos-articles/{{theme['name']}}" title="{{theme['name']}}">Voir plus d'articles</div>
</div>
</div>
{% endfor %}
It does work correctly for most of my pages but for one, it have a really weird behavior, Jinja render one of the article correctly and nest the others in a strong element.
The data used to render the page have the same structure and is correctly parsed.
Is there a way to prevent Jinja from nesting stuff in a strongelement?
There must be either some html inside theme["name"] (fix it by escaping it with theme["name"]|escape), or a <strong> tag not closed in one your templates.
Jinja doesn’t insert random html tags, but the browsers do when trying to parse and fix a broken html code

Difficulty with pagination and row formatting in Django template

I'm trying to implement pagination. I'm mainly following django documentation for pagination,https://docs.djangoproject.com/en/1.8/topics/pagination/...I'm not sure what i did wrong but the pagination effect is not being activated: When I set page to only have three posts, it still shows nine posts. I didn't do anything special, I just followed the documentation.
def category_detail(request, slug):
obj = NewsCategory.objects.get(slug=slug)
newsInCat = obj.news_set.all() #for the list of news
paginator = Paginator(newsInCat, 3) # Show 25 contacts per page
page = request.GET.get('page')
try:
news_set = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
news_set = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
news_set = paginator.page(paginator.num_pages)
bestInCat = obj.news_set.get_bestInCat()
specialInCat = obj.news_set.get_special()
mustSeeInCat = obj.news_set.get_mustSeeInCat()
recommend = obj.news_set.get_recommend()
ad2 = Sponsored.objects.get_ad2()
context = {
"obj":obj,
"news_set":news_set,
"newsInCat":newsInCat,
"bestInCat":bestInCat,
"specialInCat":specialInCat,
"mustSeeInCat":mustSeeInCat,
"recommend":recommend,
"ad2":ad2
}
and the below is my html...beside pagination, I'm having one more issue. When the title of the post becomes too long that it breaks another line, the format of my page gets messed up. It looks like this
<div class="row">
<article>
{% for news in newsInCat %}
<div class='col-sm-4'>
<div class="content">
<figure class="story-image">
</figure>
<div id="forever "style="margin-bottom:30px;">
<a href='{{news.get_absolute_url }}' style="text-decoration:none; color:#282E5C;"><h4 style="font-size: 18px;
font-weight: 400;">{{news.title}}</h4></a>
</div>
</div>
</div>
{% endfor %}
</article>
</div>
<div class="pagination">
<span class="step-links">
<!-- {% if news_set.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ news_set.number }} of {{ news_set.paginator.num_pages }}.
</span> -->
{% if news_set.has_next %}
Load More
{% endif %}
</span>
</div>
There are two separate problems at work here which have been indicated in the above answers:
You are not using Bootstrap correctly: although you can append multiple <div class="col-sm-4"> together, you will see the irregular collapsing behavior in your screenshot if they are different heights. The purpose of <div class="row"> is to ensure that your columns will appear in separate rows. See Must Bootstrap container elements include row elements? for more information.
You can resolve this with code like the following in your for-loop to add a new row every third item:
{% if forloop.counter|divisibleby:3 %}
</div>
<div class="row">
{% endif %}
You are not using the correct context object in your template: The paginator object is passed as news_set in your context object but the template uses another context object: newsInCat, which is not paginated. If you follow #Sayse's suggestion of using the news_set object, you should be in good shape:
{% for news in news_set %}
As a final suggestion, the <article> tag does not seem to be doing anything besides giving semantic value. Why not just use it instead of the div, so that you have <article class="col-sm-4">?
And as a final note, camelCase is generally frowned on in Python. Try using_underscores_with_lowercase, like you've already done with news_set.
Adding all these suggestions, you would only need to amend your template to something like this:
<div class="row">
{% for news in news_set %}
<article class="col-sm-4">
<!-- add your article content here...and clean it up! You have unnecessary spaces, inconsistent use of single and double quotes, and inline styles that (probably) should be defined in an external stylesheet. -->
</article>
{% if forloop.counter|divisibleby:3 %}
</div>
<div class="row">
{% endif %}
{% endfor %}
</div>
I can speak to your HTML/CSS formatting problem. col-sm-4 tells me you're using Bootstrap, which defines layouts in terms of 12 column width. Bootstrap will always attempt to make the with of all rows equal 12.
Right now, you're looping over all the objects and adding n columns each of width col-sm-4. Booststrap is trying to make sure each takes up a third of the row, but you're adding more that 3 divs, which is more than 12 total column width. Once the column is full, (starting with the 4th news item), Bootstrap moves the divs as closely as it can to the top of the row while still obeying the rule that width can only be 12.
In the case you pictured, Bootstrap offsets the 4th and 5th of divs because the offset allows them to be closer to the top of the row.
To fix this, you'd need to have each set of three news items in it's own row, so that your 3 col-sm-4 divs total a width of 12.
Judging based off the template, you must be still including the full list as well as the paginated set
In your template you are iterating over newsInCat instead of news_set
{% for news in newsInCat %}
should be
{% for news in news_set %}

Flask & Bootstrap Multiple collapsibles, but they each only open the first

I have an HTML page displaying a database populated by emails. I have them displayed in a collapsible, and for each post the timestamp of it is what toggles it and the innards are the email itself. The HTML page is structured like this:
{% extends "base.html" %}
{% block content %}
{% for email in emails %}
<div><button class="btn" data-toggle="collapse" data-target="#demo">{{ email.timestamp }}</button>
<div id="demo" class="collapse">
{{ email.body }}
</div>
{% endfor %}
{% endblock %}
relevant portion of views.py
#app.route('/cruz')
def cruz():
u = Politician.query.get(1)
emails = u.emails.all()
return render_template('cruz.html',title='Ted Cruz',emails=emails)
which produces a webpage that looks like this: http://imgur.com/noqC40E
The problem is that no matter which of those timestamps I click, only the first collapsible opens and closes. I've tried a number of things to fix it, mostly messing around with the HTML page and the for blocks and where I place the {{ email.body }}, but nothing I do seems to work. Can anyone see where this is going wrong?
You are generating the same id attribute for your div each time:
<div id="demo" class="collapse">
You almost certainly need to generate unique ids. You could generate unique ids by adding the loop index perhaps:
<div id="demo-{{loop.index}}" class="collapse">

Grouping of search results in a django template

I have a search results page in django. The results list a video title, video description and some meta data. It also includes the topic the video is in.
Instead of returning a full list of videos, I would like to try grouping my results by topic. Then, within each group, only show the top 1-3, with a link to "view all x videos in y topic" The determining factor of whether to show 1, 2, or 3 in the group would be a calculation based on the total number. So if there are under 10, only show 1 video in that grouping. If there are between 10-30 in the group, show 2 videos. If 30 or more in that grouping, show 3. (Determining 1-3 might not be necessary, I would be ok doing top 3)
My code is currently pretty simple to just return results. But I can't figure out how to get the groupings set up within django's template language. I guess I could do this in my view but hoping there's some simple filters I'm not aware of to help and do it in the template. Thanks for any advice.. Here's my current code:
<h4>Search Results</h4>
<p>{{ res.hits.total }} matches found for <span id="qresult">{% if q %}'{{ q }}'{% else %}all{% endif %}</span></p>
</div>
<ul class="search-list" id="search-results">
{% if res.hits.total > 0 %}
{% for h in reshh %}
{% with h|get_underscore_value:"_source" as source %}
<li><a href='/video/{{ source.slug }}'>{{ source.title }}</a>
<p>{{ source.description }}</p>
<span class="sr-meta" style="">metadata</span>
<p>(topic: {{ source.topic_name }})</p></li>
{% endwith %}
{% endfor %}
{% endif %}
</ul>
btw it's pulling from elasticsearch. res.hits.hits._source is where my results are contained. The "get_underscore_value" is just a custom filter to get around django complaining that my variable begins with an underscore...
Check out the regroup templatetag: https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup

How do I avoid duplicating strings in my Django templates?

I'm wondering how to duplicate a few strings in my templates. Specifically, I'm looking to create a table of contents sort of navigation at the top of my pages with anchor links to content farther down (like http://www.google.com/transparencyreport/faq/). I want the links to have the same text as the section headers farther down.
I've thought about using {% with %}, but it seems unwieldy to have to nest everything inside my {% with %} block.
Similar to Whats the best way to duplicate data in a django template?, but I am not inheriting this template anywhere so using {% block %} is not really an option.
This seems like a situation for just using a template variable that you've passed from a view (e.g. {{ link_name }}).
You could use also possibly use template inclusion tag that includes another template with your duplicate information.
In your view, you could potentially break your content up so that the headers are individually accessible as template variables. You might store the information associated with each header as a list of dicts:
page_content = [
{
'id':'header1',
'header': 'Text for Header 1'
'content' : 'Content Beneath header 1'
},
]
Then, in your templates, you could generate your table on contents with something like this:
{% for d in page_content %}
{{ d.header }}
{% endfor %}
While the content of your page would look something like this:
{% for d in page_content %}
<h1 id="#{{ d.id }}">{{ d.header }}</h1><p>{{ d.content }}</p>
{% endfor %}

Categories