0001from paste import httpexceptions
0002import event
0003
0004__all__ = ['public', 'ActionDispatch', 'PathDispatch']
0005
0006def public(func):
0007    func.public = True
0008    return func
0009
0010class MethodDispatch(object):
0011
0012    """
0013    This is an *abstract* class.  It implements generic dispatching
0014    to servlet methods.  See ``ActionDispatch`` and ``PathDispatch``
0015    for implementations.
0016
0017    Methods are considered public if their ``public`` attribute is
0018    true (you can use the ``@public`` decorator to set this) or if the
0019    method name is prefixed appropriately.  (E.g., if ``prefix`` is
0020    ``action_`` then ``action_meth()`` will be considered the public
0021    method by the name ``meth`` -- the prefix is added automatically!)
0022    """
0023
0024    prefix = None
0025
0026    def __addtoclass__(self, attr, cls):
0027        if self.__class__ is MethodDispatch:
0028            raise NotImplementedError(
0029                "MethodDispatch is an abstract class, and cannot be "
0030                "used directly (use one of its subclasses)")
0031        cls.listeners.append(self.respond_event)
0032
0033    def respond_event(self, name, servlet, *args, **kw):
0034        if name == 'end_awake':
0035            result = self.find_method(servlet, *args, **kw)
0036            if result is None:
0037                return event.Continue
0038            else:
0039                return result
0040        return event.Continue
0041
0042    def get_method(self, servlet, action):
0043        if self.prefix:
0044            try:
0045                return (self.prefix + action,
0046                        getattr(servlet, self.prefix + action))
0047            except AttributeError:
0048                pass
0049        try:
0050            return action, getattr(servlet, action)
0051        except AttributeError:
0052            pass
0053        return None, None
0054
0055    def valid_method(self, name, method):
0056        if getattr(method, 'public', False):
0057            return True
0058        if self.prefix and name.startswith(self.prefix):
0059            return True
0060        return False
0061
0062class ActionDispatch(MethodDispatch):
0063
0064    """
0065    This dispatches to a method based on a URL variable (GET or POST
0066    -- remember that you can also include GET variables in your form's
0067    ``action`` even if the form is being POSTed).
0068
0069    The URL variable indicates a method to run; if no variable is
0070    found then ``default_action`` is assumed (if you pass in a default
0071    action).
0072
0073    The URL variable is given with ``action_name`` and defaults to
0074    ``'_action_'``.  You can pass in the action either as
0075    ``_action_=method_name`` or ``_action_method_name=anything``
0076    (useful with submit buttons, where the value is part of the UI).
0077    """
0078
0079    prefix = 'action_'
0080
0081    def __init__(self, action_name='_action_', default_action=None):
0082        self.action_name = action_name
0083        self.default_action = default_action
0084
0085    def find_method(self, servlet, ret_value, **kw):
0086        possible_actions = []
0087        for name, value in servlet.fields.items():
0088            if name == self.action_name:
0089                possible_actions.append(value)
0090            elif name.startswith(self.action_name):
0091                possible_actions.append(name[len(self.action_name):])
0092        if not possible_actions:
0093            if self.default_action:
0094                possible_actions = [self.default_action]
0095            else:
0096                return event.Continue
0097        if len(possible_actions) > 1:
0098            raise httpexceptions.HTTPBadRequest(
0099                "More than one action received: %s"
0100                % ', '.join(map(repr, possible_actions)))
0101        action = possible_actions[0]
0102        name, method = self.get_method(servlet, action)
0103        if name is None:
0104            raise httpexceptions.HTTPForbidden(
0105                "Action method not found: %r" % action)
0106        if not self.valid_method(name, method):
0107            raise httpexceptions.HTTPForbidden(
0108                "Method not allowed: %r" % action)
0109        return method()
0110
0111class PathDispatch(MethodDispatch):
0112
0113    """
0114    This dispatches to a method based on the ``PATH_INFO``.  Thus
0115    ``/path/to/servlet/meth`` will call the ``meth`` method.
0116
0117    @@: This should probably adjust SCRIPT_NAME and PATH_INFO
0118    @@: Should these all use the same prefix?
0119    """
0120
0121    prefix = 'path_'
0122
0123    def find_method(self, servlet, ret_value, **kw):
0124        parts = servlet.path_parts
0125        if not parts:
0126            action = 'index'
0127        else:
0128            action = parts[0]
0129            servlet.path_parts = parts[1:]
0130        name, method = self.get_method(servlet, action)
0131        if name is None:
0132            raise httpexceptions.HTTPForbidden(
0133                "Method not found: %r" % action)
0134        if not self.valid_method(name, method):
0135            raise httpexceptions.HTTPForbidden(
0136                "Method not allowed: %r" % action)
0137        return method()