Autodiscover widgets for Django apps registered in settins.py
The title may be a little confusing, but I don't know how else to call it.
I would like to create a Django project with a large set of applications you could arbitrary turn on or off using INSTALLED_APPS
option in settings.py (you would obviously also need to edit urls.py and run syncdb
). After being turned on an app should be able to automatically:
Register it's content in site-wide search. Luckily django-haystack has this built-in, so it's not a problem.
Register cron jobs. django-cron does exactly that. Not a problem.
Register a widget that should be displayed on the homepage. The homepage should include a list of boxes with widgets form different applications.
I thought abo开发者_开发技巧ut inclusion tags, because you can put them anywhere on a page and they control both content and presentation. The problem is I don't know how to automatically get a list of inclusion tags provided by my applications, and display them one by one on a homepage. I need a way to register them somehow, and then display all registered tags.
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 accessuser
,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 eachwidgets
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
.
Do you know about Django-block ?
in your app __init__.py
file you can this lines:
from django.template import add_to_builtins
add_to_builtins('python.path.to.mytags')
this way, when app module is imported, tag library gets loaded into "builtins" - tags that are available everywhere without {% load %}
I'd write template tag that will populize list of inclusion tags in a variable. Ask django.template.Library
register for them.
精彩评论