Correct way to call a controller action from another controller's action

218 views
Skip to first unread message

Konstantin Alexandrov

unread,
Jan 12, 2008, 2:28:14 PM1/12/08
to pylons-...@googlegroups.com
Hello all!
Please advise on the subject.

--
http://perceivingreality.com

wolf

unread,
Jan 12, 2008, 5:51:26 PM1/12/08
to pylons-discuss
hi,

my guess is that in the general case you probably don't want call one
controller's action method from another controller's method. if what
you want is make FooController.baz() a synonym of BarController.gnu()
(that is, visiting /foo/baz has the same effect as visiting /bar/gnu)
then a lot of time you'd just say either

return redirect_to( '/bar/gnu' )

in FooController.baz() or else

return redirect_to( '/foo/baz' )

in BarController.gnu(); this will cause an HTTP 304 with the new
location to be sent to the browser who will promptly issue a new
request for the altered location. you may consider setting a flag in
your session or maybe inspect HTTP request headers if it should be
important to you that the client had called the alternative action and
got redirected (either from /foo/baz to /bar/gnu or vice versa).

if what you want is to take advantage of the functionality that is
encapsulated in a piece of code that happened to end up in another
controller chances are that this piece of code ended up in the wrong
place. you could move that piece of code either into (1) a library
module in application/lib/myLibrary.py, into (2) application/lib/
app_globals.py to make it globally available using the global g
object, or else into (3) application/lib/base.py to make it a common
method to all controllers. it would even be possible to set up a
specialized class of controllers by inheriting from base.py/
BaseController and use that altered base controller as the base class
of some of your controller classes.

intriguing video, btw

_wolf
http://ontonym.net


On Jan 12, 8:28 pm, "Konstantin Alexandrov" <iwuvjh...@gmail.com>
wrote:

Alec

unread,
Jan 13, 2008, 1:43:35 AM1/13/08
to pylons-...@googlegroups.com
this is my function, that can call a controller action from another
controller

#####
import inspect
from pylons import config

def transfer(controller = None, action = None, url = None, **kwargs):
if (url != None):
route_map = config['routes.map']
match_route= route_map.match(url)
if (match_route == None):
raise(Exception("no route matched url '%s'" % url))
# if
controller = match_route["controller"].replace("/", ".")
action = match_route["action"]
del(match_route["controller"])
del(match_route["action"])
kwargs.update(match_route)
else:
if (controller == None):
route_map = config['routes.map']
match_route = route_map.match("/")
if (match_route == None):
raise(Exception("no route matched url '%s'" % url))
# if
controller = match_route["controller"].replace("/", ".")
if (action == None):
action = match_route["action"]
# if
del(match_route["controller"])
del(match_route["action"])
kwargs.update(match_route)
else:
controller = controller.replace("/", ".")
if (action == None):
action = "index"
# if
# if
# if
full_module_name = config['pylons.package'] + '.controllers.' + controller
__traceback_hide__ = 'before_and_this'
try:
__import__(full_module_name)
except ImportError, e:
raise(NotImplementedError("'%s' not found: %s" % (controller, e)))
# try
module_name = controller.split('.')[-1]
class_name = class_name_from_module_name(module_name) + 'Controller'
controller_class = getattr(sys.modules[full_module_name], class_name)
controller_inst = controller_class()
if (hasattr(controller_inst, action)):
action_method = getattr(controller_inst, action, None)
if (not isinstance(action_method, types.MethodType)):
raise(NotImplementedError("action '%s' not found in '%s'" % (action,
controller)))
# if
if (hasattr(controller_inst, "__before__")):
before_method = getattr(controller_inst, "__before__", None)
if (isinstance(before_method, types.MethodType)):
before_method(action)
# if
# if
action_args_name, action_args, action_kargs, action_defaults =
inspect.getargspec(action_method)
del(action_args_name[0])
call_kargs = {}
for k, v in kwargs.iteritems():
if (k in action_args_name):
call_kargs[k] = v
# if
# for
result = action_method(**call_kargs)
if (hasattr(controller_inst, "__after__")):
after_method = getattr(controller_inst, "__after__", None)
if (isinstance(after_method, types.MethodType)):
after_method(action)
# if
# if
return(result)
else:
raise(NotImplementedError("action '%s' not found in '%s'" % (action,
controller)))
# if
# def

Konstantin Alexandrov 写道:

iwuv...@gmail.com

unread,
Jan 14, 2008, 1:56:35 PM1/14/08
to pylons-discuss
tabs missed :-(
can you insert that into pastebin?

alec

unread,
Jan 15, 2008, 10:36:43 AM1/15/08
to pylons-discuss
import inspect, types
from pylons import config

def transfer(controller = None, action = None, url = None, **kwargs):

"""usage:
1. result = transfer(url = "/someurl/someaction")
2. result = transfer(controller = "/controller1/sub_controller2",
action = "test") # kwargs will pass to action.

wolf

unread,
Jan 16, 2008, 4:29:59 AM1/16/08
to pylons-discuss

not wanting to discourage other authors, but don't you think that the
solution posted above is quite heavy and almost a bit black-magical?
don't you think it is quite hard to read & relies a bit too heavily on
implementation details? when all that this code achieves (assuming it
does) is something you are IMHO much better off *avoiding* to do in
the first place? see my original response to the OP for considerations
about how to avoid the issue of 'cross-calling controller code'
altogether.

in short: you shouldn't be doing that, it's not the way to go. calling
controller A from controller B is * too hard to do in pylons, * not
especially useful, * of doubtful (architectural) merit.

you can do what you want without allowing another brittle code beast
into your application by doing a simple refatoring of code. maybe you
can argue it should be posssible, maybe other frameworks make it
painless. but should you resort to throwing a hog like the one posted
at this problem? i doubt it.

_wolf



programmer.py

unread,
Jan 16, 2008, 10:17:53 AM1/16/08
to pylons-discuss
If someone absolutely, positively, did not want to redirect, then I
agree with you. It probably should be refactored. But no one should
be afraid to post code. We all wanna help :-).

The easiest thing to do in this case, is to have your action be a
proxy to some other method that does it's magic. If your method isn't
too coupled to your controller, this is easy.

# Before ...
class MyController(BaseController):

def double(self, x):
return '%d' % int(x) * 2

# After ...

def double(x):
return '%d' % int(x) * 2

class MyController(BaseController):
def double(self, x):
return double(x)

But, if you're going to do something this crafty, you could also just
write a mixin class.

class MyMixin:
def double(self, x):
return '%d' % int(x) * 2

class MyController(BaseController, MyMixin):
...

Alternatively, you could just write a class that is a controller and
derive from it.

class MyController(BaseController):
def double(self, x):
return '%d' % int(x) * 2

class MyDerivedController(MyController):
'''Yay. This class and other derived classes have a double method
now.'''
pass


So, in a nutshell you can:

* decouple your method from your controller, making your original
action a proxy to the new decoupled function.
* mixin
* inheritance

Good luck.

jw

wolf

unread,
Jan 16, 2008, 2:25:16 PM1/16/08
to pylons-discuss
exactly. see, only standard methodology. don't act strange when going
straight gets you there.

iwuv...@gmail.com

unread,
Jan 21, 2008, 1:54:05 PM1/21/08
to pylons-discuss
Great!
Thank you a lot!
Reply all
Reply to author
Forward
0 new messages