How to inject vars into the dict used for template by a decorator in TurboGears2?

124 Views Asked by At

When a method is exposed, it can return a dict used by a template :

class RootController(TGController):
    @expose('myapp.templates.index')
    def index(self):
        self.mykey = "foo"
        self.mymenu = ["item1", "item2", "item3"]
        self.selected = "item1"
        return dict( mykey=self.mykey, mymenu=self.mymenu, selected=self.selected)

This code works fine. Now I would like to encapsulate the menu boiler plate into a decorator like this :

class RootController(TGController):
    @expose('myapp.templates.index')
    @menu()
    def index(self):
        self.mykey = "foo"
        self.mymenu = ["item1", "item2", "item3"]
        self.selected = "item1"
        return dict( mykey=self.mykey)

But I don't know how to write this menu decorator. If I use :

def before_render_cb(remainder, params, output):
    return output.update( dict(mymenu=["item1", "item2", "item3"], selected="item1")) 
    
class RootController(TGController):
    @expose('myapp.templates.index')
    @before_render(before_render_cb)
    def index(self):
        self.mykey = "foo"
        self.mymenu = ["item1", "item2", "item3"]
        self.selected = "item1"
        return dict( mykey=self.mykey)

It will add mymenu and selected into the dict but I don't have access to the instance attribute of the controller (self.mymenu and self.selected)

If I use a decorator :

class menus(object):
    def __call__(self, func):
        deco = Decoration.get_decoration(func)
        return func

I can have access to the decoration but not to the expose object neither to the controller.

How can I do this?

2

There are 2 best solutions below

0
On

Here is my example for understanding "before_render" decorator:

#---=== read this section second: ===---

def make_menu_from_controller(remainder, params, output):
    controller = output.pop('controller') #Here we remove RootController sent by "index" and write it to "controller" variable.
                                          #Only {'page':'index'} left inside "output" variable
    return output.update(dict(mykey=controller.mykey, mymenu=controller.mymenu, selected=controller.selected)) #here we update dict adding "mykey", "mymenu" and "selected" keys. 

#---=== read this section first: ===---

class RootController(TGController):

    def __init__(self): #here we initiate variables, which are not changed during surfing
        self.mykey = "foo"
        self.mymenu = ["item1", "item2", "item3"]

    @expose('myapp.templates.index')
    @before_render(make_menu_from_controller)
    def index(self):
        self.selected = "item1"
        return dict(page='index', controller=self) #RootController sends itself

As You can see, "before_render" decorator intercepts sent data, works on it and returns another data. "output" is a local variable for "before_render". We changed and updated it's consistence, then sent it as new data.

0
On

You can always access the controller object and method that are handling the current request using tg.request.controller_state.controller and tg.request.controller_state.method

This should make possible to access the controller variables from the @before_render decorator.

Keep in mind that setting variables inside the controller object is not suggested as it is shared between multiple threads. The suggested way to go is to use tg.tmpl_context to store temporary variables that are request specific.

You might also be interested in giving a look at tgext.menu to generate application menus