source: tracformsplugin/trunk/tracforms/api.py

Last change on this file was 16954, checked in by Ryan J Ollos, 6 years ago

TracForms 0.5dev: Show traceback on exception

Refs #13319.

File size: 9.3 KB
Line 
1# -*- coding: utf-8 -*-
2
3from urllib import unquote_plus
4
5from pkg_resources import resource_filename
6
7from trac.core import Component, ExtensionPoint, Interface, implements
8from trac.perm import IPermissionRequestor
9from trac.resource import IResourceManager, ResourceNotFound, \
10                          get_resource_name, get_resource_shortname, \
11                          get_resource_url
12from trac.util.html import html as tag
13from trac.util.text import exception_to_unicode
14from trac.util.translation import domain_functions
15from trac.web import IRequestHandler
16from trac.web.api import HTTPBadRequest
17
18try:
19    from acct_mgr.api import IPasswordStore
20except ImportError:
21    can_check_user = False
22else:
23    can_check_user = True
24from compat import json
25
26add_domain, _, tag_ = \
27    domain_functions('tracforms', ('add_domain', '_', 'tag_'))
28dgettext = None
29
30
31class IFormChangeListener(Interface):
32    """Extension point interface for components that require notification
33    when TracForms forms are created, modified or deleted.
34    """
35
36    def form_created(form):
37        """Called when a form is created."""
38
39    def form_changed(form, author, old_state):
40        """Called when a form is modified.
41
42        `old_state` is a dictionary containing the previous values of the
43        fields that have changed.
44        """
45
46    def form_deleted(form):
47        """Called when a form is deleted."""
48        # DEVEL: not implemented yet
49
50
51class IFormDBObserver(Interface):
52
53    def get_tracform_ids(self, src):
54        pass
55
56    def get_tracform_meta(self, src):
57        pass
58
59    def get_tracform_state(self, src):
60        pass
61
62    def get_tracform_history(self, src):
63        pass
64
65    def save_tracform_allowed(self, path_or_realm, resource_id):
66        pass
67
68    def save_tracform(self, src, state, updater,
69                      base_version=None, keep_history=False,
70                      track_fields=False):
71        pass
72
73    def get_tracform_fields(self, src):
74        pass
75
76    def get_tracfrom_fieldinfo(self, src, field):
77        pass
78
79    def reset_tracform(self, src, field=None, author=None, step=0):
80        pass
81
82    def search_tracforms(self, terms):
83        pass
84
85
86def tracob_first(fn=None, default=None):
87    if fn is None:
88        def builder(fn):
89            return tracob_first(fn, default)
90
91        return builder
92    else:
93        def wrapper(self, *_args, **_kw):
94            observers = fn(self, *_args, **_kw)
95            for observer in observers:
96                result = getattr(observer, fn.__name__)(*_args, **_kw)
97                if result is not default:
98                    return result
99            else:
100                return default
101
102        wrapper.__name__ = fn.__name__
103        wrapper.__doc__ = fn.__doc__
104        return wrapper
105
106
107class FormDBUser(Component):
108    tracformdb_observers = ExtensionPoint(IFormDBObserver)
109
110    @tracob_first
111    def save_tracform(self, *_args, **_kw):
112        return self.tracformdb_observers
113
114    @tracob_first
115    def save_tracform_allowed(self, *_args, **_kw):
116        return self.tracformdb_observers
117
118    @tracob_first
119    def get_tracform_ids(self, *_args, **_kw):
120        return self.tracformdb_observers
121
122    @tracob_first
123    def get_tracform_meta(self, *_args, **_kw):
124        return self.tracformdb_observers
125
126    @tracob_first
127    def get_tracform_state(self, *_args, **_kw):
128        return self.tracformdb_observers
129
130    @tracob_first
131    def get_tracform_history(self, *_args, **_kw):
132        return self.tracformdb_observers
133
134    @tracob_first
135    def get_tracform_fields(self, *_args, **_kw):
136        return self.tracformdb_observers
137
138    @tracob_first
139    def get_tracform_fieldinfo(self, *_args, **_kw):
140        return self.tracformdb_observers
141
142    @tracob_first
143    def reset_tracform(self, *_args, **_kw):
144        return self.tracformdb_observers
145
146    @tracob_first
147    def search_tracforms(self, *_args, **_kw):
148        return self.tracformdb_observers
149
150
151class FormBase(Component):
152    """Provides i18n support for TracForms."""
153
154    def __init__(self):
155        # bind 'tracforms' catalog to specified locale directory
156        locale_dir = resource_filename(__name__, 'locale')
157        add_domain(self.env.path, locale_dir)
158
159
160class FormSystem(FormBase, FormDBUser):
161    """Provides permissions and access to TracForms as resource 'form'."""
162
163    implements(IPermissionRequestor, IResourceManager)
164
165    # IPermissionRequestor method
166
167    def get_permission_actions(self):
168        action = ['FORM_VIEW', 'FORM_EDIT_VAL', 'FORM_RESET']
169        actions = [action[0], (action[1], [action[0]]),
170                   (action[2], [action[0]]), ('FORM_ADMIN', action)]
171        return actions
172
173    # IResourceManager methods
174
175    def get_resource_realms(self):
176        yield 'form'
177
178    def get_resource_description(self, resource, format=None, **kwargs):
179        env = self.env
180        href = kwargs.get('href')
181        if resource.parent is None:
182            return _("Unparented form %(id)s", id=resource.id)
183        parent_name = get_resource_name(env, resource.parent)
184        parent_url = href and \
185                     get_resource_url(env, resource.parent, href) or None
186        parent = parent_url is not None and \
187                 tag.a(parent_name, href=parent_url) or parent_name
188        # DEVEL: resource description not implemented yet
189        # if format == 'summary':
190        #    return Form(self.env, resource).description
191        if resource.id:
192            if format == 'compact':
193                return _("Form %(form_id)s (%(parent)s)", form_id=resource.id,
194                         parent=get_resource_shortname(env, resource.parent))
195            # TRANSLATOR: Most verbose title, i.e. for form history page
196            return tag_("Form %(form_id)s (in %(parent)s)",
197                        form_id=resource.id, parent=parent)
198        else:
199            # TRANSLATOR: Title printed i.e. in form select page
200            if format == 'compact':
201                return tag_("Forms (%(parent)s)", parent=parent)
202            return tag_("Forms in %(parent)s", parent=parent)
203
204    def get_resource_url(self, resource, href, **kwargs):
205        # use parent's url instead
206        return get_resource_url(self.env, resource.parent, href)
207
208    def resource_exists(self, resource):
209        try:
210            meta = self.get_tracform_meta(resource.id)
211        except ResourceNotFound:
212            return False
213        else:
214            if meta[2] is not None:
215                return True
216
217
218class PasswordStoreUser(Component):
219    if can_check_user:
220        passwordstore_observers = ExtensionPoint(IPasswordStore)
221
222        @tracob_first
223        def has_user(self, *_args, **_kw):
224            return self.passwordstore_observers
225    else:
226        def has_user(self, *_args, **_kw):
227            """Stub, if AccountManagerPlugin isn't installed."""
228            return False
229
230
231class FormUpdater(FormDBUser, PasswordStoreUser):
232    """Update request handler for TracForms form commits."""
233
234    implements(IRequestHandler)
235
236    def match_request(self, req):
237        return req.path_info.endswith('/form/update')
238
239    def process_request(self, req):
240        req.perm.require('FORM_EDIT_VAL')
241        try:
242            self.log.debug('UPDATE ARGS:' + str(req.args))
243            args = dict(req.args)
244            backpath = args.pop('__backpath__', None)
245            context = json.loads(unquote_plus(
246                args.pop('__context__', '[null, null, null]')))
247            if None in context:
248                # TRANSLATOR: HTTP error message
249                raise HTTPBadRequest(_("__context__ is required"))
250            basever = args.pop('__basever__', None)
251            keep_history = args.pop('__keep_history__', None)
252            track_fields = args.pop('__track_fields__', None)
253            args.pop('__FORM_TOKEN', None)  # Ignore.
254            # wipe not JSON serializable arguments
255            rejargs = []
256            for key, value in args.iteritems():
257                try:
258                    len(value)
259                except AttributeError:
260                    rejargs.append(key)
261                    pass
262            for key in rejargs:
263                args.pop(key)
264            who = req.authname
265            result = json.dumps(args, separators=(',', ':'))
266            self.save_tracform(context, result, who, basever,
267                               keep_history=keep_history,
268                               track_fields=track_fields)
269            buffer = 'OK'
270            if backpath is not None:
271                req.send_response(302)
272                req.send_header('Content-Type', 'text/plain')
273                req.send_header('Location', backpath)
274                req.send_header('Content-Length', str(len(buffer)))
275                req.end_headers()
276                req.write(buffer)
277            else:
278                req.send_response(200)
279                req.send_header('Content-Type', 'text/plain')
280                req.send_header('Content-Length', str(len(buffer)))
281                req.end_headers()
282                req.write(buffer)
283        except Exception, e:
284            buffer = exception_to_unicode(e, traceback=True).encode('utf-8')
285            req.send_response(500)
286            req.send_header('Content-type', 'text/plain')
287            req.send_header('Content-Length', str(len(buffer)))
288            req.end_headers()
289            self.log.warning("Failed processing request for %s: "
290                             "%s", req.path_info, buffer)
291            req.write(buffer)
Note: See TracBrowser for help on using the repository browser.