source: pullrequestsplugin/trunk/pullrequests/web_ui.py

Last change on this file was 17475, checked in by lucid, 4 years ago

PullRequestsPlugin: Support Trac 1.5.1+ using Jinja templates.
Bump version to 1.2
Refs #13594.

File size: 9.2 KB
RevLine 
[14675]1# -*- coding: utf-8 -*-
2
3import re
4
5from datetime import datetime
6
7from trac.admin import *
8from trac.config import ListOption
9from trac.core import *
10from trac.resource import Resource
11from trac.ticket.api import ITicketManipulator
[14804]12from trac.util import get_reporter_id
[14675]13from trac.util.datefmt import format_datetime, utc
[17475]14from trac.util.html import tag
[14675]15from trac.util.presentation import Paginator
[16118]16from trac.web.api import IRequestFilter
[14675]17from trac.web.chrome import Chrome, add_link, add_script, add_stylesheet, add_notice, add_warning, web_context
18from trac.wiki.formatter import format_to_oneliner
19from trac.wiki.macros import WikiMacroBase
20from trac.wiki.api import parse_args
21
22from pullrequests.model import PullRequest
23
24
25class PullRequestsModule(Component):
26
[16118]27    implements(IAdminPanelProvider, IRequestFilter, ITicketManipulator)
[14675]28
29    create_commands = ListOption('pullrequests', 'create_commands', 'open')
30    update_commands = ListOption('pullrequests', 'update_commands', 'reviewed, closed')
31
32    # IAdminPanelProvider methods
33
34    def get_admin_panels(self, req):
35        if 'PULL_REQUEST' in req.perm:
36            yield ('pr', "Pull Requests", 'list', "List")
37
38    def render_admin_panel(self, req, category, panel, path_info):
39        if panel == 'list':
40            req.perm.require('PULL_REQUEST')
41            return self.render_pr_list_panel(req, category, panel, path_info)
42
43    def render_pr_list_panel(self, req, category, panel, path_info):
44        def format_datetime_utc(t):
45            return format_datetime(t, tzinfo=utc, locale=getattr(req, 'lc_time', None))
46
47        def format_wikilink(pr):
48            resource = Resource('ticket', pr.ticket)
49            context = web_context(req, resource)
50            return format_to_oneliner(self.env, context, pr.wikilink)
51
52        # Detail view?
53        if path_info:
54            id = path_info
55            pr = PullRequest.select_by_id(self.env, id)
56            if not pr:
57                raise TracError("Pull request does not exist!")
58            if req.method == 'POST':
59                if req.args.get('save'):
60                    pr.status = req.args.get('status')
61                    pr.add_reviewer(req.authname)
62
63                    PullRequest.update_status_and_reviewers(self.env, pr)
64                    add_notice(req, 'Your changes have been saved.')
65                    req.redirect(req.href.admin(category, panel))
66                elif req.args.get('cancel'):
67                    req.redirect(req.href.admin(category, panel))
68
69            Chrome(self.env).add_wiki_toolbars(req)
70            data = {'view': 'detail',
71                    'pr': pr,
72                    'statuses': self.create_commands + self.update_commands,
73                    'format_datetime_utc': format_datetime_utc,
74            }
75
76        else:
77            #Pagination
78            page = int(req.args.get('page', 1))
79            max_per_page = int(req.args.get('max', 10))
80
81            prs = PullRequest.select_all_paginated(self.env, page, max_per_page)
82            total_count = PullRequest.count_all(self.env)
83
84            paginator = Paginator(prs, page - 1, max_per_page, total_count)
85            if paginator.has_next_page:
86                next_href = req.href.admin(category, panel, max=max_per_page, page=page + 1)
87                add_link(req, 'next', next_href, 'Next Page')
88            if paginator.has_previous_page:
89                prev_href = req.href.admin(category, panel, max=max_per_page, page=page - 1)
90                add_link(req, 'prev', prev_href, 'Previous Page')
91   
92            pagedata = []
93            shown_pages = paginator.get_shown_pages(21)
94            for page in shown_pages:
95                pagedata.append([req.href.admin(category, panel, max=max_per_page, page=page), None,
96                                str(page), 'Page %d' % (page,)])
97            paginator.shown_pages = [dict(zip(['href', 'class', 'string', 'title'], p)) for p in pagedata]
98            paginator.current_page = {'href': None, 'class': 'current',
99                                    'string': str(paginator.page + 1),
100                                    'title':None}
101            data = {'view': 'list',
102                    'paginator': paginator,
103                    'max_per_page': max_per_page,
104                    'prs': prs,
105                    'format_datetime_utc': format_datetime_utc,
106                    'format_wikilink': format_wikilink,
107            }
108
[17475]109        return 'pullrequests.html', data
[14675]110
[16118]111    # IRequestFilter methods
112
113    def pre_process_request(self, req, handler):
114        return handler
115
116    def post_process_request(self, req, template, data, content_type):
117        path = req.path_info
118        if path.startswith('/ticket/'):
[16122]119            if data and 'ticket' in data and 'fields' in data:
[16118]120                self._append_pr_links(req, data)
121        return template, data, content_type
122
123    def _append_pr_links(self, req, data):
[16123]124        rendered = ''
[16118]125        ticket = data['ticket']
126        items = PullRequest.select(self.env, ticket=str(ticket.id))
[16123]127        if items:
128            ticket.values['PRs'] = True # Activates field
129            results = []
130            for pr in reversed(items):
131                label = 'PR:%s (%s)' % (pr.id, pr.status)
132                href = req.href.ticket(pr.ticket) + '#comment:%s' % (pr.comment,)
133                link = tag.a(label, href=href)
134                results.append(link)
135            rendered = tag.span(*[e for pair in zip(results, [' '] * len(results)) for e in pair][:-1])
[16118]136        data['fields'].append({
137            'name': 'PRs',
138            'rendered': rendered,
139            'type': 'textarea', # Full row
140        })
141
[14675]142    # ITicketManipulator methods
143
[14804]144    command_pr_url = r'(?P<command>[A-Za-z]+) PR: (?P<wikilink>\S+)'
[14675]145    command_pr_id = r'(?P<command>[A-Za-z]+) PR:(?P<id>[0-9]+)'
146
147    command_pr_url_re = re.compile(command_pr_url)
148    command_pr_id_re = re.compile(command_pr_id)   
149
150    def prepare_ticket(self, req, ticket, fields, actions):
151        pass
152
153    def validate_ticket(self, req, ticket):
[14804]154        if 'preview' in req.args:
155            # During preview: Do nothing
156            return []
157
[14675]158        if 'PULL_REQUEST' in req.perm:
159            comment = req.args.get('comment')
[14804]160            author = get_reporter_id(req, 'author')
[14675]161            if comment:
162                req.args['comment'] = self._handle_comment(req, ticket, comment, author)
163        return []
164
165    def _handle_comment(self, req, ticket, comment, author):
166        def create_pr_and_inline_id(m):
167            command = m.group('command')
168            wikilink = m.group('wikilink')
169            if command in self.create_commands:
170                id = None
171                status = command
172                reviewers = ''
173                opened = modified = datetime.now(utc)
[14804]174                comment_number = (ticket.get_comment_number(ticket['changetime']) or 0) + 1
[14675]175                pr = PullRequest(id, status, author, reviewers, opened, modified, ticket.id, comment_number, wikilink)
176                PullRequest.add(self.env, pr)
177                add_notice(req, 'A new pull request has been created.')
178
179                return u'%s PR:%s %s' % (command, pr.id, wikilink)
180            else:
181                return m.group(0)
182            return u'[%s %s]' % (('/hours/%s' % ticket.id), match.group())
183        comment = self.command_pr_url_re.sub(create_pr_and_inline_id, comment)
184
185        for m in self.command_pr_id_re.finditer(comment):
186            command = m.group('command')
187            id = m.group('id')
188            if command in self.update_commands:
189                pr = PullRequest.select_by_id(self.env, id)
[17366]190                if pr is None:
191                    add_warning(req, 'Pull request %s was not found.' % (id,))
192                    continue
[14675]193                pr.status = command
194                pr.add_reviewer(author)
195                PullRequest.update_status_and_reviewers(self.env, pr)
196                add_notice(req, 'Pull request %s has been updated.' % (id,))
197
198        return comment
199
200
201class PRQueryMacro(WikiMacroBase):
202    """List all matching pull requests.
203   
204    Example:
205    {{{
206        [[PRQuery(status=reviewed,author=joe)]]
207    }}}
208    """
209
210    def expand_macro(self, formatter, name, content):
211        args, kw = parse_args(content)
212        status = kw.get('status')
213        author = kw.get('author')
214        items = PullRequest.select(self.env, status=status, author=author)
215
216        rows = [tag.tr(
217                    tag.td(pr.id),
218                    tag.td(pr.status),
219                    tag.td(tag.a('#%s' % (pr.ticket,), href=formatter.href.ticket(pr.ticket) + '#comment:%s' % (pr.comment,))),
220                    tag.td(format_to_oneliner(self.env, formatter.context, pr.wikilink)),
221                    tag.td(pr.author),
222                    tag.td(pr.reviewers),
223                    class_='odd' if idx % 2 else 'even')
224                for idx, pr in enumerate(items)]
225        if not rows:
226            rows = [tag.tr(tag.td('No pull requests found', colspan=6, class_='even'))]
227
228        return tag.table(
229            tag.thead(
230                tag.tr(
231                    tag.th('PR'),
232                    tag.th('Status'),
233                    tag.th('Ticket'),
234                    tag.th('Changes'),
235                    tag.th('Author'),
236                    tag.th('Reviewers'),
237                    class_='trac-columns')),
238            tag.tbody(rows),
239            class_='listing')
Note: See TracBrowser for help on using the repository browser.