I'm not sure using inclusion tag is actually your best choice... There's no easy way, AFAIK, to call template tags dynamically from a template (and that's not the point of a template tag anyway :-).
I suppose I can make the following assumptions on your widgets, correct me if I'm wrong :
- a widget rendering function doesn't need parameters from the template context
- at most, it will eventually need the current
request
object (so it can access user
, session
, etc...)
With this, you can think of your widgets as mini-views, returning a string instead of a response:
def my_widget(request):
...
# Here too, the function control both presentation and content
return render_to_string('my_widget.html', {'foo': bar})
Now there's two issues to adress :
- How to have a dynamic list of all widget functions for all installed apps, available in the template?
- How to have these widget functions called in the template?
First point:
An easy way is to rely on a convention. Have a uniformly named list of functions in a template_widgets.py
module in all your applications, eg:
## myapp/template_widgets.py
def shopping_cart(request):
# do something with the session/user
...
return render_to_string('myapp/widgets/cart.html', {'cart': cart})
# Another widget func, not defined here
from myapp.views import another_widget_func
# The list of widget functions declared by this app
widgets = (shopping_cart, another_widget_func,)
Then, you can load a global list of widgets by looking at INSTALLED_APPS
, and have it automatically available in all your templates (using a context processor). Of course, it's better to load this list lazily to be sure not to waste CPU cycles on building it if you're not gonna use it.
## myproject/context_processors.py
from django.utils.importlib import import_module
from django.utils.functional import lazy
def widgets(request):
def get_all_widgets(request):
from django.conf import settings
widgets_list = []
for app in settings.INSTALLED_APPS:
try:
mod = import_module(app+'.template_widgets')
widgets_list.extend(mod.widgets)
except ImportError:
pass
except AttributeError:
# Couldn't find a widgets variable in app.template_widgets module,
# probably better to raise a custom exception
raise
return widgets_list
return {'widgets': lazy(get_all_widgets, list)(request)}
Second point:
Now, you have the list of widgets available, and lazily loaded, in every templates. A convenient syntax for using it would be something like:
## home.html
...
<div id="widgets">
{% for widget in widgets %}
<div class="widget">
{{ widget }}
</div>
{% endfor %}
</div>
But this will not work, {[widget}}
is here a callable, that needs a request
parameter. Djano doesn't allow you to call callables with parameters from within a template, so you have to modify a bit the context processor to return a list of (lazily) evaluated widget functions.
## myproject/context_processors.py
...
# replace `return widgets_list` with the following
return map(lambda w: lazy(w, str)(request), widgets_list)
...
And voilà, the above template code should now be working.
Notes:
- The order of the widgets in the list depends on the orders of the apps in
INSTALLED_APPS
and in each widgets
lists. Up to you to choose the right ordering method for you (using weighting for example, using a dict to access widgets function by names, etc..)
- Don't forget to load the context processor, and to always use a
RequestContext
.