views:

87

answers:

4

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:

  1. Register it's content in site-wide search. Luckily django-haystack has this built-in, so it's not a problem.

  2. Register cron jobs. django-cron does exactly that. Not a problem.

  3. 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 about 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.

A: 

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

Dmitry Shevchenko
Interesting. But doesn't solve my problem. I doesn't give me a way to automatically list them all in my template.
Ludwik Trammer
technically you can iterate on tags libraries (django.template.builtins) and look into `tags` attribute to list all templatetag functions. But that's not very convenient :)
Dmitry Shevchenko
A: 

Do you know about Django-block ?

Pierre-Jean Coudert
A: 

I'd write template tag that will populize list of inclusion tags in a variable. Ask django.template.Library register for them.

Almad
+2  A: 

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 :

  1. How to have a dynamic list of all widget functions for all installed apps, available in the template?
  2. 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.
Clément