source: milestonetemplateplugin/tags/milestonetemplate-0.1.2/milestonetemplate/web_ui.py

Last change on this file was 16172, checked in by Ryan J Ollos, 7 years ago

MilestoneTemplatePlugin 0.1.2: Fix incorrect import

Patch by komar.

Fixes #13027.

File size: 10.1 KB
Line 
1# -*- coding: utf-8 -*-
2
3__author__ = 'Cinc'
4
5from trac.core import implements, TracError
6from trac.resource import ResourceNotFound
7from trac.ticket.admin import MilestoneAdminPanel
8from trac.ticket.model import Milestone
9from trac.util.datefmt import parse_date
10from trac.util.translation import _
11from trac.web.api import IRequestFilter
12from trac.web.chrome import add_notice, add_script, add_script_data, add_stylesheet, \
13    ITemplateProvider, ITemplateStreamFilter
14from trac.wiki import WikiSystem, WikiPage
15from genshi.filters import Transformer
16from genshi.template.markup import MarkupTemplate
17from string import Template
18
19
20class MilestoneTemplatePlugin(MilestoneAdminPanel):
21    """Use templates when creating milestones.
22
23    Any wiki page with a name starting with ''!MilestoneTemplates/'' can be used as a template. This works in a
24    similar way as wiki templates work (see PageTemplates).
25
26    Examples for milestone templates:
27
28    * !MilestoneTemplates/MsTemplate
29    * !MilestoneTemplates/MyMilestoneTemplate
30
31    Milestone template may use the variable ''$MILESTONE'' for inserting the chosen milestone name into the
32    description. This may be useful if the template contains a TracQuery with milestone parameter.
33
34    {{{
35    [[TicketQuery(max=5,status=closed,milestone=$MILESTONE,format=table,col=resolution|summary|milestone)]]
36    }}}
37    """
38
39    implements(ITemplateStreamFilter, IRequestFilter, ITemplateProvider)
40
41    MILESTONE_TEMPLATES_PREFIX = u'MilestoneTemplates/'
42
43    admin_page_template = u"""
44        <div xmlns:py="http://genshi.edgewall.org/" class="field">
45            <label>Template:
46            <select id="ms-templates" name="template">
47                <option value="">(blank)</option>
48                <option py:for="tmpl in templates" value="$tmpl">
49                    ${tmpl}
50                </option>
51            </select>
52            <span class="hint">For Milestone description</span>
53            </label>
54        </div>
55        """
56    edit_page_template = u"""
57        <div xmlns:py="http://genshi.edgewall.org/" class="field">
58            <p>Or use a template for the description:</p>
59            <label>Template:
60            <select id="ms-templates" name="template">
61                <option value="" selected="${sel == None or None}">(Use given Description)</option>
62                <option py:for="tmpl in templates" value="$tmpl" selected="${sel == tmpl or None}">
63                    ${tmpl}
64                </option>
65            </select>
66            <span class="hint">The description will be replaced with the template contents on submit.</span>
67            </label>
68        </div>
69        """
70    preview_tmpl = u"""
71            <div id="mschange" class="ticketdraft" style="display: none">
72                <h3 id="ms-change-h3">Preview:</h3>
73                    <div class="notes-preview comment searchable">
74                    </div>
75            </div>
76    """
77    def __init__(self):
78        # Let our component handle the milestone stuff
79        if self.env.is_enabled(MilestoneAdminPanel):
80            self.env.disable_component(MilestoneAdminPanel)
81
82    def render_admin_panel(self, req, cat, page, version):
83        req.perm.require('MILESTONE_VIEW')
84
85        templ = req.args.get('template', '')
86        if req.method == 'POST' and templ:
87            if req.args.get('add') and req.args.get('name'):
88                req.perm.require('MILESTONE_CREATE')
89                name = req.args.get('name')
90                try:
91                    mil = Milestone(self.env, name=name)
92                except ResourceNotFound:
93                    mil = Milestone(self.env)
94                    mil.name = name
95                    mil.description = self.get_description_from_template(req, templ, name)
96                    if req.args.get('duedate'):
97                        mil.due = parse_date(req.args.get('duedate'),
98                                             req.tz, 'datetime')
99                    mil.insert()
100                    add_notice(req, _(u'The milestone "%(name)s" has been '
101                                      u'added.', name=name))
102                    req.redirect(req.href.admin(cat, page))
103                else:
104                    if mil.name is None:
105                        raise TracError(_(u'Invalid milestone name.'))
106                    raise TracError(_(u'Milestone %(name)s already exists.',
107                                      name=name))
108        return super(MilestoneAdminPanel, self).render_admin_panel(req, cat, page, version)
109
110    def get_description_from_template(self, req, template, ms_name):
111        """Get template text from wiki and replace $MILESTONE with given milestone name
112
113        :param req: Request object
114        :param template: template name to be used. This is a wiki page name.
115        :param ms_name: milestone name chosen by the user
116
117        :return
118        """
119        template_page = WikiPage(self.env, self.MILESTONE_TEMPLATES_PREFIX+template)
120        if template_page and template_page.exists and \
121           'WIKI_VIEW' in req.perm(template_page.resource):
122            return Template(template_page.text).safe_substitute(MILESTONE=ms_name)
123        return u""
124
125    # ITemplateStreamFilter methods
126
127    def filter_stream(self, req, method, filename, stream, data):
128        path = req.path_info.split('/')
129        if filename == 'admin_milestones.html':
130            # Milestone creation from admin page
131            if data:
132                if data.get('view') == 'list':
133                    templates = self.get_milestone_templates(req)
134                    if templates:
135                        filter_ = Transformer('//form[@id="addmilestone"]//div[@class="buttons"]')
136                        stream = stream | filter_.before(self.create_templ_select_ctrl(templates, self.admin_page_template))
137                elif data.get('view') == 'detail':
138                    # Add preview div
139                    tmpl = MarkupTemplate(self.preview_tmpl)
140                    self._add_preview(req, req.base_path+'/wiki_render')
141                    filter_ = Transformer('//form[@id="modifymilestone"]//div[@class="buttons"]')
142                    stream = stream | filter_.before(tmpl.generate())
143        elif req.args.get('action') == 'new' and len(path) > 1 and path[1] == 'milestone':
144            # Milestone creation from roadmap page
145            templates = self.get_milestone_templates(req)
146            self._add_preview(req, 'wiki_render')
147            filter_ = Transformer('//form[@id="edit"]//p')
148            if templates:
149                stream = stream | filter_.after(self.create_templ_select_ctrl(templates, self.edit_page_template))
150            tmpl = MarkupTemplate(self.preview_tmpl)
151            stream = stream | filter_.after(tmpl.generate())
152        elif filename == 'milestone_edit.html':
153            self._add_preview(req, req.base_path+'/wiki_render')
154            filter_ = Transformer('//form[@id="edit"]//p')
155            if req.method == "POST":
156                # Milestone creation from roadmap page. Duplicate name redirected to edit page.
157                templates = self.get_milestone_templates(req)
158                if templates:
159                    stream = stream | filter_.after(self.create_templ_select_ctrl(templates, self.edit_page_template,
160                                                                                  req.args.get('template', None)))
161            tmpl = MarkupTemplate(self.preview_tmpl)
162            stream = stream | filter_.after(tmpl.generate())
163
164        return stream
165
166    def _add_preview(self, req, render_url):
167        scr_data = {'auto_preview_timeout': self.env.config.get('trac', 'auto_preview_timeout', '2.0'),
168                    'form_token': req.form_token,
169                    'ms_preview_renderer': render_url}
170        add_script_data(req, scr_data)
171        add_script(req, 'common/js/auto_preview.js')
172        add_script(req, 'mstemplate/js/ms_preview.js')
173        add_stylesheet(req, 'common/css/ticket.css')
174        add_stylesheet(req, 'mstemplate/css/ms_preview.css')
175
176    def get_milestone_templates(self, req):
177        """Get milestone templates from wiki. You need WIKI_VIEW oermission to use templates"""
178        prefix = self.MILESTONE_TEMPLATES_PREFIX
179        ws = WikiSystem(self.env)
180        templates = [template[len(prefix):]
181                    for template in ws.get_pages(prefix)
182                    if 'WIKI_VIEW' in req.perm('wiki', template)]
183        return templates
184
185    def create_templ_select_ctrl(self, templates, tmpl, cur_sel=None):
186        """Create a selct control to be added to add milestone page or edit milestone page.
187
188        :param templates: list of templates (wikipage names)
189        :param tmpl: Genshi template to be used for creating the select control
190        :param cur_sel: tempalte selected by the user of None if using description
191
192        :return <div> tag holding a select control with label
193        """
194        sel = MarkupTemplate(tmpl)
195        return sel.generate(templates=templates, sel=cur_sel)
196
197    # IRequestFilter methods
198
199    # IRequestFilter is used to add the template contents as description when adding from the roadmap page
200    def pre_process_request(self, req, handler):
201
202        # Fill milestone description with contents of template.
203        if req.method == 'POST' and self._is_valid_request(req):
204            template = req.args.get('template')
205            if req.args.get('add') and template:
206                # The name of the milestone is given as a parameter to the template
207                req.args[u'description'] = self.get_description_from_template(req, template, req.args.get('name'))
208        return handler
209
210    @staticmethod
211    def _is_valid_request(req):
212        """Check request for correct path and valid form token"""
213        if req.path_info.startswith('/milestone') and req.args.get('__FORM_TOKEN') == req.form_token:
214            return True
215        return False
216
217    def post_process_request(self, req, template, data, content_type):
218        return template, data, content_type
219
220    # ITemplateProvider methods
221
222    def get_templates_dirs(self):
223        return []
224
225    def get_htdocs_dirs(self):
226        from pkg_resources import resource_filename
227        return [('mstemplate', resource_filename(__name__, 'htdocs'))]
Note: See TracBrowser for help on using the repository browser.