I have a site where different projects/programs are listed, the list is dynamic so once a user clicks on a specific program e.g. /program/health the template should load one that has a "health" specific header (mast head). How can I load a base template based on the program selected?
Whenever you pass the path to a template to a function, you may substitute the hard-coded string for some variable of your choosing. If you do this it is extremele important to make sure it is a sensible value. To be fair, the template system will escape dangerous stuff and will not go outside the specified template dir, but trying to load a non-existing template will cause the view to crash and you don't want that.
If you have a model for the program, then get the model and use the slug as a template name, like this:
p = get_object_or_404(Program, slug = slug_from_url_or_whatever)
template = "program_%s.html" % p.slug
It is also possible to pass a list of templates to the loaders. If it doesn't find the first one, it will try the next one, etc. For example:
render_to_response([template, "default_program_template.html"], ...)
You could certainly use a custom Context Processor and some CSS to get the job done.
settings.py:
TEMPLATE_CONTEXT_PROCESSORS = ("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"yourproject.context_processors.header_context")
context_processors.py:
def header_context(request):
values = {'body_class': "default",}
if request.path == "/program/health/":
## Don't really use an if block, it's just nasty.
values['body_class'] = "health"
return values
views.py:
from django.shortcuts import render_to_response
from django.template import RequestContext
def view(request):
## Do magic stuff!
return render_to_response("template.html",
{},
context_instance=RequestContext(request))
template.html:
<body class="{{ body_class }}">
<!-- Whatever -->
</body>
Have you considered using a combination of CSS class names from a template variable and include template tags to accomplish this?
Something like:
<body class="{{ program }}">
<div>{% include program_specific_template %}</div>
<div>Common stuff...</div>
</body>
Related
In a Django project, I have a mini navbar that is common in ~30% of my templates. Instead of including it in my global base.html, I decided to take a different route.
I first wrote a separate view for this:
from django.template.loader import render_to_string
def navbar(origin=None):
if origin == '1':
locations = get_approved_loc(withscores=True)
else:
locations = get_approved_loc()
obj_count = get_all_obj_count()
return render_to_string("mini_navbar.html",{'top_3_locs':locations[:3],\
'other_cities':len(locations[3:]),'obj_count':obj_count})
I next added it in the templates it needed to be in via:
{% include "mini_navbar.html" with origin='1' %}
When I run this code, I get a NoReverseMatch error. It seems the view function navbar never runs. So the context variables it was sending in (e.g. top_3_locs or other_cities etc) are never populated. Hence NoReverseMatch.
What's wrong with this pattern, and what's the fix for it? An illustrative example would do the trick.
Rather than including a template directly, you should write a custom template tag - specifically, an inclusion tag that renders the template with the custom context. The code that you have put in that separate view goes in that template tag instead.
Here's an illustrative example of Daniel's suggestion:
I created an 'inclusion' template tag like so:
from django import template
from redis_modules import get_approved_loc, get_all_obj_coun
register = template.Library()
#register.inclusion_tag(file_name='mini_navbar.html')
def mini_navbar(origin=None):
locations = get_approved_loc(withscores=True)
obj_count = get_all_obj_count()
return {'top_3_locs':locations[:3],'origin':origin,'other_cities':len(locations[3:]),'obj_count':obj_count}
Next I included this in the relevants templates like so:
{% load get_mini_navbar %} <!-- name of the module -->
And finally, I called it within the template like so:
{% mini_navbar origin %}
Where origin is the parameter passed into the tag.
With the following renderer...
from django.shortcuts import render
...
return render(request, 'template.html', {'context':context,})
Is it possible to override the render classe's methods so that I can in certain circumstances interpret template tags myself for example if I find a tag consisting of a certain format such as...
{% url 'website' page.slug %}
I could point it to...
/theme/theme-1/page.html
or
/theme/theme-2/page.html
depending on extranious settings.
The render method is just a shortcut for:
template = loader.get_template(''template.html')
context = {
...,
}
return HttpResponse(template.render(context, request))
Therefore, it is not the correct place to try to change the behaviour of the url tag.
For the example you have given, it looks like the website should be a variable that holds theme-1 or theme-2. You can then pass the variable to the url tag instead of the string 'website'.
{% url website page.slug %}
If that is not possible, you could create a custom template tag my_url that returns the correct url depending on your settings.
So I often have to use something like the following in my Django templates:
{% include "conname.html" %}
I'd like to be able to instead just have my own tag and only have to type something like
{% conname %}
Is there an easy way to setup some sort of alias so that when ever the template engine sees the conname tag it knows that should actually be a specific include tag?
Pretty simple, in a module just add the following code (mine are usually called custom_tags.py:
from django import template
register = template.Library()
#register.simple_tag
def conname():
return '''{% include "conname.html" %}'''
#to make it available everywhere (i generally add this to my urls.py
from django.template.loader import add_to_builtins
#this will be aded to all templates
add_to_builtins('custom_tags')
In your template you would just use {% conname %}
Or you could make it more complicated and programatically call IncludeNode but that is overkill.
In my base template, I want to include a search form.
I already created it, but I'm wondering if there is a better option than passing the form to all my templates that extend the base?
Yea, this is what template context processors are useful for. They allow you to pass a variable to all your templates without having to specify.
settings.py
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
...
'some_app.context_processors.search_form',
)
context_processors.py (you place this in one of your apps, or in the main directory if you prefer)
from my_forms import MySearchForm
def search_form(request):
return {
'search_form' : MySearchForm()
}
Now you can use the {{ search_form }} in all of you templates
You could make it into a filter that returns the form, assuming it's static. It would then look something like this:
<body>
...
{% import_form_template %}
...
</body>
Or something along those lines. You can also make it so it takes arguments if you needed it to be a bit more dynamic:
{% import_form_template arg1 arg2 arg3 %}
https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#writing-custom-template-tags
Why not using a custom context processor?
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.