Extending Django Admin Templates - altering change list - django

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.

Related

How can I prevent duplicate db queries when using Django's inclusion tag?

So imagine if I have widget that I registered as an inclusion tag like this:
#register.inclusion_tag('tags/widget.html', takes_context=True)
def widget(context):
return {'qs': Foo.objects.all(),
'request': context['request'],}
In the widget template I loop over some entries in the Foo object. I include the widget in my Mega Menu and my Footer. These templates are obviously split up. And included in base.html
However right now when I load the main page I hit the database twice for the same widget (Menu and Footer) so I get a duplicate query. How can I prevent something like this?
I like the idea of the inclusion tag but I don't want duplicate queries.
You can call your tag in base.html and save the data that it returns and then pass it to other sub templates that might need that data.
But i don't think you can use inclusion_tag anymore. You probably need to use something like simple_tag. Here is an example:
simple_tag:
#register.simple_tag
def widget(some_data):
return Foo.objects.all()
base template:
# You can access the result as foo_objs
{% widget some_data as foo_objs %}
# pass the data to other templates:
{% include "menu.html" with foo_objs=foo_objs %}
{% include "footer.html" with foo_objs=foo_objs %}
Now the widget function called once and data can be passed around.
Django docs on include

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 %}

Correct way to include a sidebar across many templates?

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.

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.

Django: How can I identify the calling view from a template?

Short version:
Is there a simple, built-in way to identify the calling view in a Django template, without passing extra context variables?
Long (original) version:
One of my Django apps has several different views, each with its own named URL pattern, that all render the same template. There's a very small amount of template code that needs to change depending on the called view, too small to be worth the overhead of setting up separate templates for each view, so ideally I need to find a way to identify the calling view in the template.
I've tried setting up the views to pass in extra context variables (e.g. "view_name") to identify the calling view, and I've also tried using {% ifequal request.path "/some/path/" %} comparisons, but neither of these solutions seems particularly elegant. Is there a better way to identify the calling view from the template? Is there a way to access to the view's name, or the name of the URL pattern?
Update 1: Regarding the comment that this is simply a case of me misunderstanding MVC, I understand MVC, but Django's not really an MVC framework. I believe the way my app is set up is consistent with Django's take on MVC: the views describe which data is presented, and the templates describe how the data is presented. It just happens that I have a number of views that prepare different data, but that all use the same template because the data is presented the same way for all the views. I'm just looking for a simple way to identify the calling view from the template, if this exists.
Update 2: Thanks for all the answers. I think the question is being overthought -- as mentioned in my original question, I've already considered and tried all of the suggested solutions -- so I've distilled it down to a "short version" now at the top of the question. And right now it seems that if someone were to simply post "No", it'd be the most correct answer :)
Update 3: Carl Meyer posted "No" :) Thanks again, everyone.
Since Django 1.5, the url_name is accessible using:
request.resolver_match.url_name
Before that, you can use a Middleware for that :
from django.core.urlresolvers import resolve
class ViewNameMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
url_name = resolve(request.path).url_name
request.url_name = url_name
Then adding this in MIDDLEWARE_CLASSES, and in templates I have this:
{% if request.url_name == "url_name" %} ... {% endif %}
considering a RequestContext(request) is always passed to the render function. I prefer using url_name for urls, but one can use resolve().app_name and resolve().func.name, but this doesn't work with decorators - the decorator function name is returned instead.
No, and it would be a bad idea. To directly refer to a view function name from the template introduces overly tight coupling between the view layer and the template layer.
A much better solution here is Django's template inheritance system. Define a common parent template, with a block for the (small) area that needs to change in each view's version. Then define each view's template to extend from the parent and define that block appropriately.
If your naming is consistent in your urls.py and views.py, which it should be, then this will return the view name:
{{ request.resolver_match.url_name }}
Be sure to apply some context to it when you call it in the template. For example, I use it here to remove the delete button from my detail view, but in my update view the delete button will still appear!
{% if request.resolver_match.url_name != 'employee_detail' %}
Since Django 1.5 you can access an instance of ResolverMatch through request.resolver_match.
The ResolverMatch gives you the resolved url name, namespace, etc.
one simple solution is :
def view1(req):
viewname = "view1"
and pass this viewname to the template context
def view2(req):
viewname = "view2"
and pass this viewname to the template context
in template access the viewname as
{{viewname}}
and also you can use this in comparisons.
I'm working on this for a help-page system where I wanted each view to correspond to a help-page in my cms with a default page shown if no help page was defined for that view. I stumbled upon this blog where they use a template context processor and some python inspect magic to deduce the view name and populate the context with it.
This sounds like the perfect example of a generic view that you can set up.
See the following resources:
Django Book - Chapter 11: Generic Views
Django Docs -Tutorial: Chapter 4
Django Docs - Generic Views
These links should help you simplify your views and your templates accordingly.
If you're using Class Based Views, you most likely have a view variable you can access.
You can use several methods from that to determine which view has been called or which template is being rendered.
e.g.
{% if view.template_name == 'foo.html' %}
# do something
{% else %}
# other thing
{% endif %}
Another option is to take out the piece of the template where you need something to change and make it into a snippet and then use {% include 'my_snippet.html' with button_type = 'bold' %} in your templates, sending arbitrary values to the snippet so it can determine what to show / how to style itself.
Most generic views — if not all — inherits the ContextMixin which adds a view context variable that points to the View instance.
In your template, you can access the current view instance like this:
{{ view }}
Define class_name method in your view
class ExampleView(FormView):
...
def class_name(self):
return self.__class__.__name__
You can get the class name of the current view in a template like this:
{{ view.class_name }}
{% if view.class_name == "ExampleView" %} ... {% endif %}
Why not trying setting up a session cookie, then read the cookie from your template.
on your views set cookies
def view1(request):
...
#set cookie
request.session["param"]="view1"
def view2(request):
request.session["param"]="view2"
then in your ONE template check something like..
{% ifequal request.session.param "view1" %}
... do stuff related to view1
{% endifequal %}
{% ifequal request.session.param "view2" %}
... do stuff related to "view2"
{% endifequal %}
Gath