summaryrefslogtreecommitdiff
path: root/paste/wareweb/dispatch.py
blob: 63b1aad58df9747939c4c79732cc90f7719f4c09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from paste import httpexceptions
import event

__all__ = ['public', 'ActionDispatch', 'PathDispatch']

def public(func):
    func.public = True
    return func

class MethodDispatch(object):

    """
    This is an *abstract* class.  It implements generic dispatching
    to servlet methods.  See ``ActionDispatch`` and ``PathDispatch``
    for implementations.

    Methods are considered public if their ``public`` attribute is
    true (you can use the ``@public`` decorator to set this) or if the
    method name is prefixed appropriately.  (E.g., if ``prefix`` is
    ``action_`` then ``action_meth()`` will be considered the public
    method by the name ``meth`` -- the prefix is added automatically!)
    """

    prefix = None

    def __addtoclass__(self, attr, cls):
        if self.__class__ is MethodDispatch:
            raise NotImplementedError(
                "MethodDispatch is an abstract class, and cannot be "
                "used directly")
        cls.listeners.append(self.respond_event)
    
    def respond_event(self, name, servlet, *args, **kw):
        if name == 'end_awake':
            result = self.find_method(servlet, *args, **kw)
            if result is None:
                return event.Continue
            else:
                return result
        return event.Continue

    def get_method(self, servlet, action):
        if self.prefix:
            try:
                return (self.prefix + action,
                        getattr(servlet, self.prefix + action))
            except AttributeError:
                pass
        try:
            return action, getattr(servlet, action)
        except AttributeError:
            pass
        return None, None

    def valid_method(self, name, method):
        if getattr(method, 'public', False):
            return True
        if self.prefix and name.startswith(self.prefix):
            return True
        return False
    
class ActionDispatch(MethodDispatch):

    """
    This dispatches to a method based on a URL variable (GET or POST
    -- remember that you can also include GET variables in your form's
    ``action`` even if the form is being POSTed).

    The URL variable indicates a method to run; if no variable is
    found then ``default_action`` is assumed (if you pass in a default
    action).

    The URL variable is given with ``action_name`` and defaults to
    ``'_action_'``.  You can pass in the action either as
    ``_action_=method_name`` or ``_action_method_name=anything``
    (useful with submit buttons, where the value is part of the UI).
    """

    prefix = 'action_'

    def __init__(self, action_name='_action_', default_action=None):
        self.action_name = action_name
        self.default_action = default_action

    def find_method(self, servlet, ret_value, **kw):
        possible_actions = []
        for name, value in servlet.fields.items():
            if name == self.action_name:
                possible_actions.append(value)
            elif name.startswith(self.action_name):
                possible_actions.append(name[len(self.action_name):])
        if not possible_actions:
            if self.default_action:
                possible_actions = [self.default_action]
            else:
                return event.Continue
        if len(possible_actions) > 1:
            raise httpexceptions.HTTPBadRequest(
                "More than one action received: %s"
                % ', '.join(map(repr, possible_actions)))
        action = possible_actions[0]
        name, method = self.get_method(servlet, action)
        if name is None:
            raise httpexceptions.HTTPForbidden(
                "Action method not found: %r" % action)
        if not self.valid_method(name, method):
            raise httpexceptions.HTTPForbidden(
                "Method not allowed: %r" % action)
        return method()

class PathDispatch(MethodDispatch):

    """
    This dispatches to a method based on the ``PATH_INFO``.  Thus
    ``/path/to/servlet/meth`` will call the ``meth`` method.

    @@: This should probably adjust SCRIPT_NAME and PATH_INFO
    @@: Should these all use the same prefix?
    """

    prefix = 'path_'

    def find_method(self, servlet, ret_value, **kw):
        parts = servlet.path_parts
        if not parts:
            action = 'index'
        else:
            action = parts[0]
            servlet.path_parts = parts[1:]
        name, method = self.get_method(servlet, action)
        if name is None:
            raise httpexceptions.HTTPForbidden(
                "Method not found: %r" % action)
        if not self.valid_method(name, method):
            raise httpexceptions.HTTPForbidden(
                "Method not allowed: %r" % action)
        return method()