Correct way to include a sidebar across many templates? - flask

I'm trying to figure out the best way to include some sidebar code that will be used on almost every one of my templates. For example, my index.html is
{% extends "base.html" %}
Hello, ...
{% include 'sidebar.html' %}
{% endblock %}
The view is:
#app.route('/')
#app.route('/index')
def index():
# some stuff for my index page...
form_sidebar = sidebar()
return render_template('index.html', indexdata=indexdata, form_sidebar=form_sidebar)
My sidebar form is created with
def sidebar():
form_sidebar = MySideBarForm() # defined in forms.py
# do some sql stuff to initialize the form ...
return form_sidebar
My sidebar.html uses the data received from sidebar().
If I continue in this manner then every one of my view functions will have to call sidebar(), they will all have to pass along the varialbe form_sidebar, and every template will need to {% include 'sidebar.html' %}. Is there a better way to do this? I think ideally my base.html would {% include 'sidebar.html, but then I don't know how to provide it with the form_sidebar data.

You could include your form_sidebar in the global g which is implicitly available in templates if I am not mistaken.
To feed g you can do it globally in a #before_request decorated function and including the sidebar template in the base template should finish the job.

Related

django - Count objects and sum values in a field

I want to include some basic statistics about a model in a stats.html file. The variables don't show in the html. What am I doing wrong?
from django.shortcuts import render, get_object_or_404, redirect
from django.db.models import Avg, Sum, Count
from .models import Production
def statistics(request):
nr_of_plays = Production.objects.count()
nr_of_actors = Production.objects.aggregate(num_actors=Sum('nr_actors'))
nr_of_audience = Production.objects.aggregate(num_audience=Sum('est_audience'))
context = {
'nr_of_plays': nr_of_plays,
'nr_of_actors': nr_of_actors['num_actors'],
'nr_of_audience': nr_of_audience['num_audience'],
'test':'abc'
}
return render(request, 'stats.html', context)
The model:
class Production(models.Model):
title = models.CharField(max_length=200)
nr_actors = models.IntegerField(default=0)
est_audience = models.IntegerField(default=0)
...
urls.py:
path('stats/', views.statistics, name='stats'),
the relevant section of base.html:
<copyright class="text-muted">
<div class="container text-center">
<p>© One World Theatre - {% now "Y" %} {% include 'stats.html' with test=test %} </p>
</div>
</copyright>
And the stats.html template:
{% load static %}
{{ test }} - Stats: {{ nr_of_plays }} plays produced, involving {{ nr_of_actors }} actors, seen by {{ nr_of_audience }} people.
the output:
© One World Theatre - 2020 - Stats: plays produced, involving actors, seen by people.
EDIT:
I didn't mention that I'm using my template stats.html in my base.html template like this {% include 'stats.html' %}. When I add with test=test to the include tag, the test text shows. But when adding with nr_of_plays=nr_of_plays nothing happens :-/.
I ended up forgetting about trying to {% include 'stats.html' %} in my base template and just added those variables where I need them, works great. Not DRY, but what to do... .
EDIT 2:
I was too quick to cry victory. Edited the question with the latest code. Passing the variables in the view that handles the main content block works, but that means I would have to add them in every single view (not DRY). Still not getting what doesn't work with my setup. example.com/stats.html renders exactly what I want, but doesn't show the variables when I include it in my base.html. with test=test doesn't do anything. Clueless (and thankful for the help sofar).
Aggregate returns a dictionary.
You need to access its value via the key
context = {
'nr_of_plays': nr_of_plays,
'nr_of_actors': nr_of_actors['nr_actors_sum'],
'nr_of_audience': nr_of_audience['est_audience_sum']
}
Alternatively you can specify a custom key name instead of the default composite one:
nr_of_actors = Production.objects.aggregate(num_actors=Sum('nr_actors'))
nr_of_audience = Production.objects.aggregate(num_audience=Sum('est_audience'))
Note: .all() is redundant and can be removed
Base on your latest confession and symptoms, you don't seem to be going to your statistics view.
Looks like the url is rendering another view, which also extends base.html confuses you that you are in the right view.
One way to test it is to put a print statement in your statistics view and see if it prints anything in the console:
def statistics(request):
print(111111111111111111111111111111)
...
return render(request, 'stats.html', context)
Second thing is, if your base.html includes stats.html, you shouldn't be rendering the stats.html directly, you should pass the context to a template that extends base.html.
Third thing is, refer to Pynchia's answer to properly get the count of aggregated queryset.

Can't use django variable in include tag

I'm trying to include a .html using
{% include "paintings/experiments/points/{{substance.name}}.html" %}
This however leads to the error TemplateDoesNotExist.
If I hardcode the name of the .html file, it does work.
{% include "paintings/experiments/points/fabric.html" %}
And, in fact, I can use {{substance.name}} inside the included html, were it does indeed get substituted for fabric. Why can I not use a django variable when using an include tag?
I doit with the add templateTag.
{% include "paintings/experiments/points/"|add:substance.name %}
Notice that substance.name should have .html . I'm using this approach to use dynamic Templates. So in a context_processor I set the variable value and use it normally, like this:
{% include ""|add:paginationTemplatePath with page=page_obj %}
In this case, I change the paginationTemplatePath given certain conditions on the context_processor.
I'm exposing this example in order to enrich the answer for other cases, as use include with variable page.
include template tag was designed to accept either string or variable. If you try to use the above, it's just going to be string. But you can manipulate strings with template filters and tags.
You can create custom template tag that creates variable and then use that newly created variable in the include tag. If you check the documentation on Custom template tags and filter you'll see how they work and what are the requirements for them to work.
First you must create a directory inside your app named templatetags
myapp/
__init__.py
models.py
templatetags/
__init__.py
custom_tags.py
views.py
Below is a possible code to create variable for template path:
from django import template
register = template.Library()
#register.simple_tag
def build_template_path(base, name):
return base.format(name)
base in your case would be "paintings/experiments/points/{}.html" and name would be source.name.
Now in the template you first have to load these custom tags with:
{% load custom_tags %}
and then you use this template tag in the template:
{% for source in sources %}
{% build_template_path "paintings/experiments/points/{}.html" source.name as template_path %}
{% include template_path %}
{% endfor %}
With build_template_path you create custom variable template_path which you then use in the include tag.
Overall, template tags are very powerful. You can create or do pretty much anything with them, while filters are a bit more limited, but you could have done that with filters as well. Maybe something like this:
#register.filter
def replace_value(value, name):
return value.replace('**', name)
{% include "paintings/experiments/points/**.html"|replace_value:source.name %}

Include template displaying a form

What I want to do is include a form from a separate template at the bottom of a given page, lets say; "example.com/listdataandform/".
The form-template "form.html" displays the form as it should when the view is included in the URLConf. So I can view with "example.com/form/"
What I have so far goes something like this:
{% extends "base/base.html" %}
{% block title %} page title {% endblock %}
{% block content %}
<h2>some "scene" data</h2>
<ul>
{% for scene in scenes %}
<li>{{ scene.scene }} - {{ scene.date }}</li>
{% endfor %}
</ul>
{% include "tasks/form.html"%}
{% endblock %}
The code inside "block content" works as it should, since it is defined with it's corresponding view for the url "example.com/listdataandform/".
{% include "tasks/form.html"%}: This only displays the submit button from form.html, as expected. I realize by only doing this: {% include "tasks/form.html"%}, the corresponding view method is never executed to provide the "form"-template with data.
Is there any way to this without having to define the view to a specific pattern in urls.py, so that the form can be used without going to the that specified URL..?
So I guess the more general question is; how to include templates and provide them with data generated from a view?
Thanks.
For occasions like this, where I have something that needs to be included on every (or almost every) page, I use a custom context processor, which I then add to the TEMPLATE_CONTEXT_PROCESSORS in settings.py. You can add your form to the context by using this method.
Example:
common.py (this goes in the same folder as settings.py)
from myapp.forms import MyForm
def context(request):
c = {}
c['myform'] = MyForm()
return c
You can also do any processing required for the form here.
Then add it in your settings.py file:
settings.py
.
.
TEMPLATE_CONTEXT_PROCESSORS = (
'''
All the processors that are already there
'''
"myproject.common.context",
)
.
.
I realize by only doing this: {% include "tasks/form.html"%}, the corresponding view method is never executed to provide the "form"-template with data.
Indeed. You included a template, and it really means "included" - ie: "execute in the current context". The template knows nothing about your views, not even what a "view" is.
How does this help me executing the view for the included template to provide it with form data?
It doesn't. A Django "view" is not "a fraction of a template", it's really a request handler, iow a piece of code that takes an HTTP request and returns an HTTP response.
Your have to provide the form to the context one way or another. The possible places are:
in the view
in a context processor (if using a RequestContext)
in a middleware if using a TemplateResponse AND the TemplateResponse has not been rendered yet
in a custom template tag
In all cases this will just insert the form in your template's context - you'll still have to take care of the form processing when it's posted. There are different ways to address this problem but from what I guess of your use case (adding the same form and processing to a couple differents views of your own app), using a custom TemplateResponse subclass taking care of the form's initialisation and processing might just be the ticket.

Extending Django Admin Templates - altering change list

A (not so) quick question about extending django admin templates.
I'm trying to change the result list (change list in django lingo) of a specific model by adding an intermediary row between the result rows (row1 and row2 classes) that contains some objects related to that object.
I searched the code but haven't found a way to do this. Any pointers are very much appreciated. Code will also help too.
PS: I know I should be designing my own interface, but this is an internal project and I don't have that much time to spare. Also, the django interface is really nice.
Thank you in advance.
To expand on Yuji's answer, here are some specifics on overriding change_list_results.html ...
Override changelist_view as described above in step 1, and also described here at djangoproject. Or auto-override by placing in the appropriate directory as in step 2 above. (Note that the step 2 path shown above is model-specific. App-specific would be /admin/<MyAppName>/change_list.html under any directory defined in the TEMPLATE_DIRS tuple.)
Or (perhaps easier) simply specify ModelAdmin.change_list_template as explained here with any discoverable template filename. (Although, if you retain the name change_list.html, be sure not to deposit directly into the /admin folder, else the extends tag will cause a recursion.)
class MyModelAdmin(admin.ModelAdmin):
change_list_template = 'change_list.html' # definitely not 'admin/change_list.html'
# ...
In your change_list.html template, have at a minimum
{% extends "admin/change_list.html" %}
{% load i18n admin_static admin_list %}
{% load myapptags %}
{% block result_list %}
{% if action_form and actions_on_top and cl.full_result_count %}{% admin_actions %}{% endif %}
{% result_list cl %}
{% if action_form and actions_on_bottom and cl.full_result_count %}{% admin_actions %}{% endif %}
{% endblock %}
Create a /<MyAppName>/templatetags package (a directory containing __init__.py) with a file corresponding to the load tag above
# MyAppName/templatetags/myapptags.py
from django import template
from django.contrib.admin.templatetags.admin_list import result_list
register = template.Library()
register.inclusion_tag('my_change_list_results.html')(result_list)
Copy and edit Django's change_list_results.html (as e.g. my_change_list_results.html above) to use your added functionality.
Note that these steps do not include extra context for the template, but can easily be expanded as such. (My reason for doing this was to add classes for CSS and a leading <tbody> that was not sorted with the results list.)
ADDITIONAL:
To include extra context, change your templatetags module as follows:
# MyAppName/templatetags/myapptags.py
from django import template
from django.contrib.admin.templatetags.admin_list import result_list as admin_list_result_list
def result_list(cl):
mycl = {'myextracontext': 'something extra'}
mycl.update(foo_extra())
mycl.update(admin_list_result_list(cl))
return mycl
register = template.Library()
register.inclusion_tag('my_change_list_results.html')(result_list)
Then, the value of myextracontext or whatever foo_extra returns can be included in your results template (as e.g. {{ myextracontext }})
Step 1: Overriding changelist view:
You'll have to override a template as opposed to specifying one like you can with add_view / change_view.
First things first, override
def changelist_view(self, request, extra_context=None): in your ModelAdmin. Remember to call super(foo, self).changelist_view(request, extra_context) and to return that.
Step 2: Overriding templates:
Next, override the app-specific changelist template at templates/admin/my_app/my_model/change_list.html (or not.. you can use a global changelist override too if you'd like).
Step 3: Copy result list functionality
I think you can either copy result_list functionality (define a new template tag) or fake it by copying and pasting the result_list function and template include into your view.
# django.contrib.admin.templatetags.admin_list
def result_list(cl):
"""
Displays the headers and data list together
"""
return {'cl': cl,
'result_hidden_fields': list(result_hidden_fields(cl)),
'result_headers': list(result_headers(cl)),
'results': list(results(cl))}
result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
You can see the admin uses this admin/change_list_results.html template to render individual columns so you'll need to use one of the methods to replace this template tag.
Since it's looking for a global template, I wouldn't override it.
Either define a new tag w/ new template specifically for your view, or send result_list(cl) to your template directly and adopt the admin/change_list_results.html for use directly in your change_list.html template.
If you want a complete override of the admin_list template tag, here is my post in another thread. https://stackoverflow.com/a/55597294/11335685
I know it is not directly related to the change_list template, but the logic for it lays on the functions inside the admin_list template tag.
A disadvantage of that approach is i didn't find a way of overriding only specific function and get use of the other ones.
Still that was the only approach that was allowing me to not mess with the html templates and only alter the behavior of result_list function.
Hope that helps anyone.

Django: Figure out which item in a menu that has been selected

I'm sure I've seen this question on Stack Overflow before, but I couldn't find it by my life, so here goes nothing.
I have a normal Django menu which uses the {% url %} tag and static names for the menu items. Now I want to have a different style for the menu item which has been selected. But the menu is being rendered in the base template, so how do I figure out which menu item it is?
You could surely do this with some ugly template code, but a better more globally known way is to use a CSS selector. This lets CSS do all of the work automatically for you.
Here's how it works:
You simply put an id in your body depending on which page you are on.
Then in css you do something like this:
#section-aboutme #nav-aboutme,
#section-contact #nav-contact
/* ... put one of these per body/menu item ... */
{
font-color: red;
}
You put the nav-aboutme, and nav-contact ids on each of your menu items.
The style will automatically be selected by CSS depending on which body id they are inside of.
I normally do it the way Brian suggested, but to accommodate for a template which a designer gave me which used the more common class="selected" method, I wrote a {% nav %} template tag.
Your HTML navigation template will look something like:
{% block nav %}
<ul class="nav">
<li{% if nav.home %} class="selected"{% endif %}>Home</li>
<li{% if nav.about %} class="selected"{% endif %}>About</li>
</ul>
{% endblock %}
To set the navigation in a child template, do:
{% include "base.html" %}
{% load nav %}
{% block nav %}
{% nav "about" %}
{{ block.super }}
{% endblock %}
How about a custom tag which you use to generate your nav item?
The following takes the name of the url for which a nav item should be generated and the text it should display. It generates a li tag with a class of "selected" if the named url's path is the same as the current url (requires 'django.core.context_processors.request' in your TEMPLATE_CONTEXT_PROCESSORS). Within the li, it generates an a tag with the path of the url specified by the url_name. It has the contents specified by contents.
Obviously, this could be tweaked to generate different markup for the nav item, as required.
The rest can be done using CSS.
Advantages:
Easy to use
Little code required
DRY
Could be made to be more flexible
Disadvantages:
Requires 'django.core.context_processors.request'
Requires urls to be named e.g. urlpatterns = patterns('django.views.generic.simple',
...
(r'^$', 'direct_to_template', {'template': 'index.html'}, 'index'),
...
). This could potentially be done differently (e.g. pass in url).
Doesn't cope with pages not exactly equal to the specified and therefore will not apply the selected class to the li when on a page lower in the url heirarchy. For example, if I'm on /products/, it will highlight the nav item directing to /products/. If I'm on /products/myProduct/, it will not highlight the /products/ link. This could be coded around, but it would force people to use sensible urls. For example, change the additionalAttrs assignment to additionalAttrs = ' class=selected' if (context['request'].path.startswith(path) and path != '/') or (context['request'].path == path) else ''.
Code:
from django import template
from django.core.urlresolvers import reverse
register = template.Library()
class NavNode(template.Node):
def __init__(self, url_name, contents):
self.url_name = url_name
self.contents = contents
def render(self, context):
path = reverse(self.url_name)
additionalAttrs = ' class=selected' if path == context['request'].path else ''
return '<li'+additionalAttrs+'>'+self.contents+'</li>'
#register.tag
def nav_link(parser, token):
bits = token.split_contents()
if len(bits) == 3:
contents = bits.pop()
url_name = bits.pop()
else:
raise template.TemplateSyntaxError, "%r tag requires a single argument" % bits[0]
if contents[0] == contents[-1] and contents[0] in ('"', "'"):
contents = contents[1:-1]
return NavNode(url_name, contents)
You can pass request.path to your template
from django.shortcuts import render_to_response
from django.template import RequestContext
return render_to_response('templ.html', {'page':request.path}, context_instance=RequestContext(request))
then use an ugly if template tag to add a CSS class to your menu item
Let's say one has an app named "stackoverflow" and inside of that app folder one has a templates folder with a stackoverflow.html file that extends from the base template.
One way to achieve it is by defining a variable in the views.py of ones stackoverflow app, like
def stackoverflow(request):
return render(request,
'stackoverflow/stackoverflow.html',
{'section': 'stackoverflow'})
Then, in the base template
<li {% if section == "stackoverflow" %} class="selected" {% endif %}>
StackOverflow
</li>
Essentially having the variable section allows to figure the section. Note that one needs a space between section and ==... if one doesn't respect that, then one will get a Django Template Error saying
Could not parse the remainder.