source: tracformsplugin/trunk/tracforms/web_ui.py

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

TracForms 0.5dev: Revise r16384 from compatibility with Trac < 1.0.2

Refs #13319.

File size: 9.6 KB
Line 
1# -*- coding: utf-8 -*-
2
3import re
4from pkg_resources import resource_filename
5
6from trac.core import implements
7from trac.resource import get_resource_description, \
8                          get_resource_shortname, get_resource_url
9from trac.search.api import ISearchSource, shorten_result
10from trac.util.datefmt import to_datetime
11from trac.util.html import html as tag
12from trac.web.api import IRequestFilter, IRequestHandler
13from trac.web.chrome import ITemplateProvider, add_ctxtnav, add_stylesheet
14
15from api import FormDBUser, _, dgettext, tag_
16from compat import json
17from formdb import format_author
18from model import Form
19from util import parse_history, resource_from_page
20
21tf_page_re = re.compile('/form(/\d+|$)')
22
23
24class FormUI(FormDBUser):
25    """Provides form views for reviewing and managing TracForm data.
26
27    Extensions for the Trac web user interface display saved field values,
28    metadata and history. TracSearch support for TracForms is included here
29    and administrative actions to revert form changes are embedded as well.
30    """
31
32    implements(IRequestFilter, IRequestHandler, ISearchSource,
33               ITemplateProvider)
34
35    # IRequestFilter methods
36
37    def pre_process_request(self, req, handler):
38        return handler
39
40    def post_process_request(self, req, template, data, content_type):
41        env = self.env
42        page = req.path_info
43        realm, resource_id = resource_from_page(env, page)
44        # break (recursive) search for form in forms realm
45        if tf_page_re.match(page) is None and resource_id is not None:
46            if page == '/wiki' or page == '/wiki/':
47                page = '/wiki/WikiStart'
48            form = Form(env, realm, resource_id)
49            if 'FORM_VIEW' in req.perm(form.resource):
50                if len(form.siblings) == 0:
51                    # no form record found for this parent resource
52                    return template, data, content_type
53                elif form.resource.id is not None:
54                    # single form record found
55                    href = req.href.form(form.resource.id)
56                else:
57                    # multiple form records found
58                    href = req.href.form(action='select', realm=realm,
59                                         resource_id=resource_id)
60                add_ctxtnav(req, _("Form details"), href=href,
61                            title=_("Review form data"))
62        elif page.startswith('/form') and not resource_id == '':
63            form = Form(env, form_id=resource_id)
64            parent = form.resource.parent
65            if len(form.siblings) > 1:
66                href = req.href.form(action='select', realm=parent.realm,
67                                     resource_id=parent.id)
68                add_ctxtnav(req, _("Back to forms list"), href=href)
69        return template, data, content_type
70
71    # ITemplateProvider methods
72
73    def get_htdocs_dirs(self):
74        """Return static resources for TracForms."""
75        return [('tracforms', resource_filename(__name__, 'htdocs'))]
76
77    def get_templates_dirs(self):
78        """Return template directory for TracForms."""
79        return [resource_filename(__name__, 'templates')]
80
81    # IRequestHandler methods
82
83    def match_request(self, req):
84        if req.path_info == '/form':
85            return True
86        match = re.match('/form/(\d+)$', req.path_info)
87        if match:
88            if match.group(1):
89                req.args['id'] = match.group(1)
90            return True
91
92    def process_request(self, req):
93        env = self.env
94        id_hint = req.args.get('id')
95        if id_hint is not None and Form.id_is_valid(id_hint):
96            form_id = int(id_hint)
97        else:
98            form_id = None
99        if form_id is not None:
100            form = Form(env, form_id=form_id)
101            if req.method == 'POST':
102                req.perm(form.resource).require('FORM_RESET')
103                return self._do_reset(env, req, form)
104
105            req.perm(form.resource).require('FORM_VIEW')
106            return self._do_view(env, req, form)
107
108        if req.args.get('action') == 'select':
109            realm = req.args.get('realm')
110            resource_id = req.args.get('resource_id')
111            if realm is not None and resource_id is not None:
112                form = Form(env, realm, resource_id)
113                req.perm(form.resource).require('FORM_VIEW')
114                return self._do_switch(env, req, form)
115
116    def _do_view(self, env, req, form):
117        data = {'_dgettext': dgettext}
118        form_id = form.resource.id
119        data['page_title'] = get_resource_description(env, form.resource,
120                                                      href=req.href)
121        data['title'] = get_resource_shortname(env, form.resource)
122        # prime list with current state
123        subcontext, author, time = self.get_tracform_meta(form_id)[3:6]
124        author = format_author(self.env, req, author, 'change')
125        if not subcontext == '':
126            data['subcontext'] = subcontext
127        state = self.get_tracform_state(form_id)
128        data['fields'] = self._render_fields(req, form_id, state)
129        history = [{'author': author, 'time': time,
130                    'old_state': state}]
131        # add recorded old_state
132        records = self.get_tracform_history(form_id)
133        for author, time, old_state in records:
134            author = format_author(self.env, req, author, 'change')
135            history.append({'author': author, 'time': time,
136                            'old_state': old_state})
137        data['history'] = parse_history(history)
138        # show reset button in case of existing data and proper permission
139        data['allow_reset'] = req.perm(form.resource) \
140                                  .has_permission(
141            'FORM_RESET') and form.has_data
142        add_stylesheet(req, 'tracforms/tracforms.css')
143        return 'form.html', data, None
144
145    def _do_switch(self, env, req, form):
146        data = {
147            '_dgettext': dgettext,
148            'page_title': get_resource_description(env, form.resource,
149                                                   href=req.href),
150            'title': get_resource_shortname(env, form.resource),
151            'siblings': []
152        }
153        for sibling in form.siblings:
154            form_id = tag.strong(tag.a(
155                _("Form %(form_id)s", form_id=sibling[0]),
156                href=req.href.form(sibling[0])))
157            if sibling[1] == '':
158                data['siblings'].append(form_id)
159            else:
160                # TRANSLATOR: Form list entry for form select page
161                data['siblings'].append(tag_(
162                    "%(form_id)s (subcontext = '%(subcontext)s')",
163                    form_id=form_id, subcontext=sibling[1]))
164        add_stylesheet(req, 'tracforms/tracforms.css')
165        return 'switch.html', data, None
166
167    def _do_reset(self, env, req, form):
168        author = req.authname
169        step = None
170        if 'rewind' in req.args:
171            step = -1
172        elif 'reset' in req.args:
173            step = 0
174        if form.resource.id is not None:
175            self.reset_tracform(form.resource.id, author=author, step=step)
176        else:
177            self.reset_tracform(tuple([form.parent.realm, form.parent.id]),
178                                author=author, step=step)
179        return self._do_view(env, req, form)
180
181    def _render_fields(self, req, form_id, state):
182        fields = json.loads(state is not None and state or '{}')
183        rendered = []
184        for name, value in fields.iteritems():
185            if value == 'on':
186                value = _("checked (checkbox)")
187            elif value == '':
188                value = _("empty (text field)")
189            elif isinstance(value, basestring):
190                value = "'".join(['', value, ''])
191            else:
192                # Still try to display something useful instead of corrupting
193                #   parent page beyond hope of recovery through the web_ui.
194                value = "'".join(['', repr(value), ''])
195            author, time = self.get_tracform_fieldinfo(form_id, name)
196            author = format_author(self.env, req, author, 'value')
197            rendered.append(
198                {'name': name, 'value': value,
199                 'author': tag.span(tag(_("by %(author)s", author=author)),
200                                    class_='author'),
201                 'time': time})
202        return rendered
203
204    # ISearchSource methods
205
206    def get_search_filters(self, req):
207        if 'FORM_VIEW' in req.perm:
208            # TRANSLATOR: The realm name used as TracSearch filter label
209            yield ('form', _("Forms"))
210
211    def get_search_results(self, req, terms, filters):
212        if 'form' not in filters:
213            return
214        env = self.env
215        results = self.search_tracforms(terms)
216
217        for id_, realm, parent, subctxt, state, author, updated_on in results:
218            # DEVEL: support for handling form revisions not implemented yet
219            # form = Form(env, realm, parent, subctxt, id, version)
220            form = Form(env, realm, parent, subctxt, id_)
221            if 'FORM_VIEW' in req.perm(form.resource):
222                form = form.resource
223                # build a more human-readable form values representation,
224                # especially with unicode character escapes removed
225                state = _render_values(state)
226                yield (get_resource_url(env, form, req.href),
227                       get_resource_description(env, form),
228                       to_datetime(updated_on), author,
229                       shorten_result(state, terms))
230
231
232def _render_values(state, delimiter=': '):
233    fields = []
234    for name, value in json.loads(state or '{}').iteritems():
235        fields.append(''.join([name, delimiter, value]))
236    return '; '.join(fields)
Note: See TracBrowser for help on using the repository browser.