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

Last change on this file was 15518, checked in by Cinc-th, 7 years ago

PeerReviewPlugin: refactoring and PEP8 changes.

File size: 14.5 KB
Line 
1#
2# Copyright (C) 2005-2006 Team5
3# Copyright (C) 2016 Cinc
4#
5# All rights reserved.
6#
7# This software is licensed as described in the file COPYING.txt, which
8# you should have received as part of this distribution.
9#
10# Author: Team5
11#
12
13
14import os
15import shutil
16import sys
17import time
18import unicodedata
19import urllib
20import json
21from genshi.template.markup import MarkupTemplate
22from trac import util
23from trac.core import *
24from trac.mimeview import Context
25from trac.util import Markup
26from trac.web.main import IRequestHandler
27from trac.wiki import format_to_html
28from dbBackend import *
29from model import Comment, PeerReviewModel, ReviewDataModel, ReviewFileModel
30from util import get_review_for_file, not_allowed_to_comment, review_is_finished, review_is_locked
31
32
33def writeJSONResponse(rq, data, httperror=200):
34    writeResponse(rq, json.dumps(data), httperror)
35
36
37def writeResponse(req, data, httperror=200):
38    data=data.encode('utf-8')
39    req.send_response(httperror)
40    req.send_header('Content-Type', 'text/plain; charset=utf-8')
41    req.send_header('Content-Length', len(data))
42    req.end_headers()
43    req.write(data)
44
45class PeerReviewCommentHandler(Component):
46    implements(IRequestHandler)
47
48    # IRequestHandler methods
49    def match_request(self, req):
50        return req.path_info == '/peerReviewCommentCallback'
51
52    #This page should never be called directly.  It should only be called
53    #by JavaScript HTTPRequest calls.
54    def process_request(self, req):
55        req.perm.require('CODE_REVIEW_DEV')
56
57        data = {}
58
59        if not (req.perm.has_permission('CODE_REVIEW_MGR') or
60                req.perm.has_permission('CODE_REVIEW_DEV')):
61
62            data['invalid'] = 4
63            return 'peerReviewCommentCallback.html', data, None
64
65        data['invalid'] = 0
66        data['trac.href.peerReviewCommentCallback'] = self.env.href.peerReviewCommentCallback()
67
68        if req.method == 'POST':
69            if req.args.get('addcomment'):
70                # This shouldn't happen but still...
71                review = get_review_for_file(self.env, req.args.get('fileid'))
72                if not_allowed_to_comment(self.env, review, req.perm, req.authname):
73                    writeResponse(req, "", 403)
74                    return
75                # We shouldn't end here but in case just drop out.
76                if self.review_is_closed(req):
77                    data['invalid'] = 'closed'
78                    return 'peerReviewCommentCallback.html', data, None
79                txt = req.args.get('comment')
80                comment = Comment(self.env)
81                comment.file_id = data['fileid'] = req.args.get('fileid')
82                comment.parent_id = data['parentid'] = req.args.get('parentid')
83                comment.comment = txt
84                comment.line_num = data['line'] = req.args.get('line')
85                comment.author = req.authname
86                if txt and txt.strip():
87                    comment.insert()
88                writeJSONResponse(req, data)
89                return
90            elif req.args.get('markread'):
91                data['fileid'] = req.args.get('fileid')
92                data['line'] = req.args.get('line')
93                if req.args.get('markread') == 'read':
94                    rev_dat = ReviewDataModel(self.env)
95                    rev_dat['file_id'] = data['fileid']
96                    rev_dat['comment_id'] = req.args.get('commentid')
97                    rev_dat['review_id'] = req.args.get('reviewid')
98                    rev_dat['owner'] = req.authname
99                    rev_dat['type'] = 'read'
100                    rev_dat['data'] = 'read'
101                    rev_dat.insert()
102                else:
103                    rev_dat = ReviewDataModel(self.env)
104                    rev_dat['file_id'] = data['fileid']
105                    rev_dat['comment_id'] = req.args.get('commentid')
106                    rev_dat['owner'] = req.authname
107                    for rev in rev_dat.list_matching_objects():
108                        rev.delete()
109                writeJSONResponse(req, data)
110                return
111
112        actionType = req.args.get('actionType')
113
114        if actionType == 'getCommentTree':
115            self.get_comment_tree(req, data)
116
117        elif actionType == 'getCommentFile':
118            self.getCommentFile(req, data)
119
120        else:
121            data['invalid'] = 5
122
123        return 'peerReviewCommentCallback.html', data, None
124
125    def review_is_closed(self, req):
126        fileid = req.args.get('IDFile')
127        if not fileid:
128            fileid = req.args.get('fileid')
129        r_file = ReviewFileModel(self.env, fileid)
130        review = PeerReviewModel(self.env, r_file['review_id'])
131        if review['status'] == 'closed':
132            return True
133        return False
134
135    # Used to send a file that is attached to a comment
136    def getCommentFile(self, req, data):
137        data['invalid'] = 6
138        short_path = req.args.get('fileName')
139        fileid = req.args.get('IDFile')
140        if not fileid or not short_path:
141            return
142
143        short_path = urllib.unquote(short_path)
144        self.path = os.path.join(self.env.path, 'attachments', 'CodeReview',
145                                 urllib.quote(fileid))
146        self.path = os.path.normpath(self.path)
147        attachments_dir = os.path.join(os.path.normpath(self.env.path),
148                                       'attachments')
149        commonprefix = os.path.commonprefix([attachments_dir, self.path])
150        assert commonprefix == attachments_dir
151        full_path = os.path.join(self.path, short_path)
152        req.send_header('Content-Disposition', 'attachment; filename=' + short_path)
153        req.send_file(full_path)
154
155    #Creates a comment based on the values from the request
156    def createComment(self, req, data):
157        data['invalid'] = 5
158        struct = ReviewCommentStruct(None)
159        struct.IDParent = req.args.get('IDParent')
160        struct.IDFile = req.args.get('IDFile')
161        struct.LineNum = req.args.get('LineNum')
162        struct.Author = util.get_reporter_id(req)
163        struct.Text = req.args.get('Text')
164        struct.DateCreate = int(time.time())
165
166        if struct.IDFile is None or struct.LineNum is None or \
167                struct.Author is None or struct.Text is None:
168            return
169
170        if struct.IDFile == "" or struct.LineNum == "" or struct.Author == "":
171            return
172
173        if struct.Text == "":
174            return
175
176        if struct.IDParent is None or struct.IDParent == "":
177            struct.IDParent = "-1"
178
179        # If there was a file uploaded with the comment, place it in the correct spot
180        # The basic parts of this code were taken from the file upload portion of
181        # the trac wiki code
182
183        if 'FileUp' in req.args:
184            upload = req.args['FileUp']
185            if upload and upload.filename:
186                self.path = \
187                    os.path.join(self.env.path, 'attachments',
188                                 'CodeReview', urllib.quote(struct.IDFile))
189                self.path = os.path.normpath(self.path)
190                if hasattr(upload.file, 'fileno'):
191                    size = os.fstat(upload.file.fileno())[6]
192                else:
193                    size = upload.file.len
194                if size != 0:
195                    filename = urllib.unquote(upload.filename)
196                    filename = filename.replace('\\', '/').replace(':', '/')
197                    filename = os.path.basename(filename)
198                    if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 3):
199                        filename = unicodedata.normalize('NFC', unicode(filename, 'utf-8')).encode('utf-8')
200                    attachments_dir = os.path.join(os.path.normpath(self.env.path), 'attachments')
201                    commonprefix = os.path.commonprefix([attachments_dir, self.path])
202                    assert commonprefix == attachments_dir
203                    if not os.access(self.path, os.F_OK):
204                        os.makedirs(self.path)
205                    path, targetfile = util.create_unique_file(os.path.join(self.path, filename))
206                    try:
207                        shutil.copyfileobj(upload.file, targetfile)
208                        struct.AttachmentPath = os.path.basename(path)
209                    finally:
210                        targetfile.close()
211        struct.save(self.env.get_db_cnx())
212
213    # Returns a comment tree for the requested line number
214    # in the requested file
215    def get_comment_tree(self, req, data):
216        fileid = req.args.get('IDFile')
217        linenum = req.args.get('LineNum')
218
219        if not fileid or not linenum:
220            data['invalid'] = 1
221            return
222
223        db = self.env.get_read_db()
224        dbBack = dbBackend(db)
225        comments = dbBack.getCommentsByFileIDAndLine(fileid, linenum)
226
227        my_comment_data = ReviewDataModel.comments_for_file_and_owner(self.env, fileid, req.authname)
228        data['read_comments'] = [c_id for c_id, t, dat in my_comment_data if t == 'read']
229
230        rfile = ReviewFileModel(self.env, fileid)
231        review = PeerReviewModel(self.env, rfile['review_id'])
232        data['review'] = review
233        data['context'] = Context.from_request(req)
234        # A finished review can't be changed anymore except by a manager
235        data['is_finished'] = review_is_finished(self.env.config, review)
236        # A user can't change his voting for a reviewed review
237        data['review_locked'] = review_is_locked(self.env.config, review, req.authname)
238        data['not_allowed'] = not_allowed_to_comment(self.env, review, req.perm, req.authname)
239
240        comment_html = ""
241        first = True
242        keys = comments.keys()
243        keys.sort()
244        for key in keys:
245            if comments[key].IDParent not in comments:
246                comment_html += self.build_comment_html(comments[key], 0, linenum, fileid, first, data)
247                first = False
248        comment_html = comment_html.strip()
249        if not comment_html:
250            comment_html = "No Comments on this Line"
251        data['lineNum'] = linenum
252        data['fileID'] = fileid
253        data['commentHTML'] = Markup(comment_html)
254
255
256    comment_template = u"""
257            <table xmlns:py="http://genshi.edgewall.org/"
258                   style="width:400px"
259                   py:attrs="{'class': 'comment-table'} if comment.IDComment in read_comments else {'class': 'comment-table comment-notread'}"
260                   id="${comment.IDParent}:${comment.IDComment}" data-child-of="$comment.IDParent">
261            <tbody>
262            <tr>
263                <td style="width:${width}px"></td>
264                <td colspan="3" style="width:${400-width}px"
265                class="border-col"></td>
266            </tr>
267            <tr>
268                <td style="width:${width}px"></td>
269                <td colspan="2" class="comment-author">Author: $comment.Author
270                <a py:if="comment.IDComment not in read_comments"
271                   href="javascript:markCommentRead($line, $fileid, $comment.IDComment, ${review['review_id']})">Mark read</a>
272                <a py:if="comment.IDComment in read_comments"
273                   href="javascript:markCommentNotread($line, $fileid, $comment.IDComment, ${review['review_id']})">Mark unread</a>
274                </td>
275                <td style="width:100px" class="comment-date">$date</td>
276            </tr>
277            <tr>
278                <td style="width:${width}px"></td>
279                <td valign="top" style="width:${factor}px" id="${comment.IDComment}TreeButton">
280                    <img py:if="childrenHTML" src="${href.chrome('hw/images/minus.gif')}" id="${comment.IDComment}collapse"
281                         onclick="collapseComments($comment.IDComment);" style="cursor: pointer;" />
282                    <img py:if="childrenHTML" src="${href.chrome('hw/images/plus.gif')}" style="display: none;cursor:pointer;"
283                         id="${comment.IDComment}expand"
284                         onclick="expandComments($comment.IDComment);" />
285                </td>
286                <td colspan="2">
287                <div class="comment-text">
288                    $text
289                </div>
290                </td>
291            </tr>
292            <tr>
293                <td></td>
294                <td></td>
295                <td>
296                    <!--! Attachment -->
297                    <a py:if="comment.AttachmentPath" border="0" alt="Code Attachment"
298                       href="${callback}?actionType=getCommentFile&amp;fileName=${comment.AttachmentPath}&amp;IDFile=$fileid">
299                        <img src="${href.chrome('hw/images/paper_clip.gif')}" /> $comment.AttachmentPath
300                    </a>
301                </td>
302                <td class="comment-reply">
303                   <a py:if="not is_locked" href="javascript:addComment($line, $fileid, $comment.IDComment)">Reply</a>
304                </td>
305            </tr>
306            </tbody>
307            </table>
308        """
309
310    #Recursively builds the comment html to send back.
311    def build_comment_html(self, comment, nodesIn, linenum, fiileid, first, data):
312        if nodesIn > 50:
313            return ""
314
315        children_html = ""
316        keys = comment.Children.keys()
317        keys.sort()
318        for key in keys:
319            child = comment.Children[key]
320            children_html += self.build_comment_html(child, nodesIn + 1, linenum, fiileid, False, data)
321
322        factor = 15
323        width = 5 + nodesIn * factor
324
325        tdata = {'width': width,
326                 'text': format_to_html(self.env, data['context'], comment.Text,
327                                        escape_newlines=True),
328                 'comment': comment,
329                 'first': first,
330                 'date': util.format_date(comment.DateCreate),
331                 'factor': factor,
332                 'childrenHTML': children_html != '' or False,
333                 'href': self.env.href,
334                 'line': linenum,
335                 'fileid': fiileid,
336                 'callback': self.env.href.peerReviewCommentCallback(),
337                 'review': data['review'],
338                 'is_locked': data['is_finished'] or data['review_locked'] or data.get('not_allowed', False),
339                 'read_comments': data['read_comments']
340                 }
341
342        tbl = MarkupTemplate(self.comment_template, lookup='lenient')
343        return tbl.generate(**tdata).render(encoding=None) + children_html
Note: See TracBrowser for help on using the repository browser.