source: peerreviewplugin/tags/0.12/3.1/codereview/peerreview_docx.py

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

TracCodeReview 3.1: Fix incorrect imports

Refs #13193.

File size: 9.9 KB
Line 
1# -*- coding: utf-8 -*-
2
3from string import Template
4from trac.admin import IAdminPanelProvider
5from trac.config import ListOption
6from trac.core import Component, implements
7from trac.mimeview.api import IContentConverter, Mimeview
8from trac.util.translation import _
9from trac.web.api import IRequestHandler
10from trac.web.chrome import add_notice, add_warning
11from model import PeerReviewModel, ReviewDataModel
12
13try:
14    from docx import Document
15    from docx_export import create_docx_for_review
16    docx_support = True
17except ImportError:
18    docx_support = False
19
20
21__author__ = 'Cinc'
22__copyright__ = "Copyright 2016"
23__license__ = "BSD"
24
25
26def escape_chars(txt):
27    repl = {u'ä': u'ae', u'ü': u'ue', u'ö': u'oe',
28            u'ß': u'ss', u'(': u'', u')': u'',
29            u'Ä': u'Ae', u'Ü': u'Ue', u'Ö': u'Oe'}
30    for key in repl:
31        txt = txt.replace(key, repl[key])
32    return txt
33
34
35class PeerReviewDocx(Component):
36    """Export reviews as a document in Word 2010 format (docx).
37
38    [[BR]]
39    == Overview
40    When enabled a download link (''Download in other formats'') for a Word 2010 document is added to
41    review pages.
42    The document holds all the information from the review page and the file content of each file.
43    File comments are printed inline.
44
45    It is possible to provide a default template document for new environments by providing the path
46    in ''trac.ini'':
47    [[TracIni(peerreview, review.docx)]]
48
49    The path must be readable by Trac. It will be used only on first start to populate the database and is
50    meant to make automated deploying easier.
51    You may use the admin page to change it later on.
52
53    == Template document format
54    Markers are used to signify the position where to add information to the document.
55
56    The following is added to predefined tables:
57    * Review info
58    * Reviewer info
59    * File info
60
61    File contents with inline comments is added as text.
62
63    === Review info table
64    The table must have the following format.
65
66    ||= Name =|| $REVIEWNAME$ ||
67    ||= Status =|| $STATUS$ ||
68    ||= ID =|| $ID$ ||
69    ||= Project =|| $PROJECT$ ||
70    ||= Author =|| $AUTHOR$ ||
71    ||= Date =|| $DATE$ ||
72    ||= Followup from =|| $FOLLOWUP$ ||
73    [[BR]]
74    Any formatting will be preserved. Note that the order of rows is not important. You may also omit
75    rows.
76
77    === Reviewer info table
78    The table must have the following format.
79
80    || $REVIEWER$ || ||
81
82    You may add a header row:
83
84    ||= Reviewer =||= Status =||
85    || $REVIEWER$ || ||
86    [[BR]]
87    Formatting for headers will be preserved. Note that a predefined text style is used for the information
88    added.
89
90    === File info table
91
92    ||= ID =||= Path =||= Hash =||= Revision =||= Comments =||= Status =||
93    ||  || $FILEPATH$ ||  ||  ||  ||  ||
94    [[BR]]
95    === File content marker
96    File content is added at the position marked by the paragraph ''$FILECONTENT$''.
97
98    For each file a heading ''Heading 2'' with the file path is added.
99
100    === Defining styles
101    The plugin uses different paragraph styles when adding file contents with inline comments. If the styles are not
102    yet defined in the template document they will be added using some defaults. You may use your own style definitions
103    by defining a style in the document.
104
105    The following styles are used:
106    * ''Code'': for printing file contents
107    * ''Reviewcomment'': for comments printed inline
108    * ''Reviewcommentinfo'': information like author, date about an inline comment
109
110    == Prerequisite
111    The python package ''python-docx'' (https://python-docx.readthedocs.org/en/latest/index.html) must
112    be installed. If it isn't available the feature will be silently disabled.
113    """
114    implements(IAdminPanelProvider, IContentConverter, IRequestHandler,)
115
116    ListOption('peerreview', 'review.docx', doc=u"Path to template document in ''docx'' format used for generating "
117                                                 u"review documents.")
118    def __init__(self):
119        if not docx_support:
120            self.env.log.info("PeerReviewPlugin: python-docx is not installed. Review report creation as docx is not "
121                              "available.")
122        else:
123            # Create default database entries
124            defaults = ['reviewreport.title', 'reviewreport.subject', 'reviewreport.template']
125            rdm = ReviewDataModel(self.env)
126            rdm.clear_props()
127            rdm['type'] = "reviewreport.%"
128            keys = [item['type'] for item in rdm.list_matching_objects(False)]
129            for d in defaults:
130                if d not in keys:
131                    if d == 'reviewreport.template':
132                        # Admins may set this value in trac.ini to specify a default which will be used on first
133                        # start.
134                        data = self.env.config.get('peerreview', 'review.docx', '')
135                    else:
136                        data = u""
137                    rdm = ReviewDataModel(self.env)
138                    rdm['type'] = d
139                    rdm['data'] = data
140                    rdm.insert()
141                    self.env.log.info("PeerReviewPlugin: added '%s' with value '%s' to 'peerreviewdata' table",
142                                      d, data)
143
144    # IAdminPanelProvider methods
145
146    def get_admin_panels(self, req):
147        if docx_support and 'CODE_REVIEW_MGR' in req.perm:
148            yield ('codereview', 'Code review', 'reviewreport', 'Review Report')
149
150    def render_admin_panel(self, req, cat, page, path_info):
151        req.perm.require('CODE_REVIEW_MGR')
152
153        report_data = self.get_report_defaults()
154
155        if req.method=='POST':
156            save = req.args.get('save', '')
157            if save:
158                report_data['reviewreport.title']['data'] = req.args.get('title', u'')
159                report_data['reviewreport.title'].save_changes()
160                report_data['reviewreport.subject']['data'] = req.args.get('subject', u'')
161                report_data['reviewreport.subject'].save_changes()
162                report_data['reviewreport.template']['data'] = req.args.get('template', u'')
163                report_data['reviewreport.template'].save_changes()
164                add_notice(req, _("Your changes have been saved."))
165            req.redirect(req.href.admin(cat, page))
166
167        data = {'title': report_data['reviewreport.title']['data'],
168                'subject': report_data['reviewreport.subject']['data'],
169                'template': report_data['reviewreport.template']['data']}
170        return 'admin_review_report.html', data
171
172    def get_report_defaults(self):
173        """
174        @return: dict with default values. Key: one of [reviewreport.title, reviewreport.subject], value: unicode
175        """
176        rdm = ReviewDataModel(self.env)
177        rdm.clear_props()
178        rdm['type'] = "reviewreport%"
179        d = {}
180        for item in rdm.list_matching_objects(False):
181            d[item['type']] = item
182        return d
183
184    # IRequestHandler methods
185
186    def match_request(self, req):
187        if not docx_support:
188            return False
189        return req.path_info == '/peerreview'
190
191    def process_request(self, req):
192
193        format_arg = req.args.get('format')
194        review_id = req.args.get('reviewid', None)
195        referrer=req.get_header("Referer")
196        if review_id and format_arg == 'docx':
197            review = PeerReviewModel(self.env, review_id)
198            if review:
199                def proj_name():
200                    return review['project'] + u'_' if review['project'] and review['project'].upper() != 'MC000000' \
201                        else u''
202                def review_name():
203                    return escape_chars(review['name'].replace(' ', '_'))
204                doc_name = u"%sSRC-REV_%s_Review_%s_V1.0" % (proj_name(), review_name(), review_id)
205            else:
206                doc_name = u"Review %s" % review_id
207            content_info = {'review_id': review_id,
208                            'review': review}
209            Mimeview(self.env).send_converted(req,
210                                              'text/x-trac-peerreview',
211                                              content_info, format_arg, doc_name)
212
213        self.env.log.info("PeerReviewPlugin: Export of Review data in format 'docx' failed because of missing "
214                          "parameters. Review id is '%s'. Format is '%s'.", review_id, format_arg)
215        req.redirect(referrer)
216
217    # IContentConverter methods
218
219    def get_supported_conversions(self):
220        if docx_support:
221            yield ('docx', 'MS-Word 2010', 'docx', 'text/x-trac-peerreview',
222                   'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 4)
223
224    def convert_content(self, req, mimetype, content, key):
225        """
226        @param content: This is the review id
227        """
228        if mimetype == 'text/x-trac-peerreview':
229            report_data = self.get_report_defaults()
230            template = report_data['reviewreport.template']['data']
231            review = content['review']
232            # Data for title and subject templates
233            tdata = {'reviewid': content['review_id'],
234                     'review_name': review['name'],
235                     'review_name_escaped': escape_chars(review['name'])}
236
237            info = {'review_id': content['review_id'],
238                    'review': review,
239                    'author': review['owner'],
240                    'title': Template(report_data['reviewreport.title']['data']).safe_substitute(tdata),
241                    'subject': Template(report_data['reviewreport.subject']['data']).safe_substitute(tdata)}
242            data = create_docx_for_review(self.env, info, template)
243            return data, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
244
245        return None # This will cause a Trac error displayed to the user
Note: See TracBrowser for help on using the repository browser.