source: tracformsplugin/tags/tracforms-0.4.1/0.11/tracforms/api.py

Last change on this file was 10490, checked in by Steffen Hoffmann, 12 years ago

TracFormsPlugin: Release maintenance version 0.4.1 for compatibility with Trac 0.11, closes #9000.

These are changes cherry-picked from trunk and merged into tracforms-0.4 to
establish full compatibility with Trac 0.11 on level with plugin's own claims.

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