source: peerreviewplugin/trunk/codereview/peerReviewCommentCallback.py

Last change on this file was 18281, checked in by Cinc-th, 2 years ago

PeerReviewPlugin: fixed problem for Trac 1.2 where a root comment couldn't be created if there was already one on a line. Removed obsolete code for attachment file handling.

File size: 13.9 KB
RevLine 
[13497]1#
2# Copyright (C) 2005-2006 Team5
[18050]3# Copyright (C) 2016-2021 Cinc
[15448]4#
[13497]5# All rights reserved.
6#
[15228]7# This software is licensed as described in the file COPYING.txt, which
[13497]8# you should have received as part of this distribution.
9#
[717]10# Author: Team5
11#
12
[15242]13import json
[17441]14from codereview.model import ReviewCommentModel, PeerReviewModel, ReviewDataModel, ReviewFileModel
15from codereview.util import get_review_for_file, not_allowed_to_comment, review_is_finished, review_is_locked
[18269]16from functools import partial
[18244]17try:
18    from genshi.template.markup import MarkupTemplate
19except ImportError:
20    pass  # We are Trac 1.4 and use Jinja2
[717]21from trac.core import *
[18248]22from trac.util.datefmt import format_date, to_datetime, user_time
[18249]23from trac.util.html import Markup
[18244]24from trac.web.chrome import Chrome, web_context
[717]25from trac.web.main import IRequestHandler
[15518]26from trac.wiki import format_to_html
[717]27
[15517]28
[15242]29def writeJSONResponse(rq, data, httperror=200):
[18263]30    writeResponse(rq, json.dumps(data), httperror, content_type='application/json; charset=utf-8')
[15242]31
32
[18263]33def writeResponse(req, data, httperror=200, content_type='text/plain; charset=utf-8'):
[17448]34    data = data.encode('utf-8')
[15242]35    req.send_response(httperror)
[18263]36    req.send_header('Content-Type', content_type)
[15242]37    req.send_header('Content-Length', len(data))
38    req.end_headers()
39    req.write(data)
40
[17448]41
[15228]42class PeerReviewCommentHandler(Component):
[18280]43    """Handling of comments for reviews. This component is responsible for creating comments and
44    building comment trees for display.
45    """
[15228]46    implements(IRequestHandler)
47
[717]48    # IRequestHandler methods
[17448]49
[717]50    def match_request(self, req):
[18263]51        if req.path_info == '/peercomment':
52            return True
[717]53        return req.path_info == '/peerReviewCommentCallback'
54
[17448]55    # This page should never be called directly.  It should only be called
56    # by JavaScript HTTPRequest calls.
[717]57    def process_request(self, req):
[17448]58        data = {'invalid': 0}
[717]59
[18265]60        if not 'CODE_REVIEW_VIEW' in req.perm:
[18264]61            writeResponse(req, "", 403)
[3488]62
[15242]63        if req.method == 'POST':
[18265]64            if not 'CODE_REVIEW_DEV' in req.perm:
65                writeResponse(req, "", 403)
[15242]66            if req.args.get('addcomment'):
[18263]67                fileid = req.args.get('fileid')
[15517]68                # This shouldn't happen but still...
[18263]69                review = get_review_for_file(self.env, fileid)
[15517]70                if not_allowed_to_comment(self.env, review, req.perm, req.authname):
71                    writeResponse(req, "", 403)
72                    return
[15253]73                # We shouldn't end here but in case just drop out.
[15242]74                if self.review_is_closed(req):
75                    data['invalid'] = 'closed'
[18247]76                    if hasattr(Chrome, 'jenv'):
[18281]77                        return 'peerreview_comment_jinja.html', data
[18247]78                    else:
[18281]79                        return 'peerreview_comment.html', data, None
[18246]80
[18263]81                rfile = ReviewFileModel(self.env, fileid)
[18274]82                data['path'] = rfile['path'].lstrip('/')  # Trac path doesn't start with '/'. Db path does.
[15249]83                txt = req.args.get('comment')
[17441]84                comment = ReviewCommentModel(self.env)
85                comment['file_id'] = data['fileid'] = req.args.get('fileid')
86                comment['parent_id'] = data['parentid'] = req.args.get('parentid')
87                comment['comment'] = txt
88                comment['line_num'] = data['line'] = req.args.get('line')
89                comment['author'] = req.authname
[15249]90                if txt and txt.strip():
91                    comment.insert()
[15242]92                writeJSONResponse(req, data)
[15253]93                return
[15448]94            elif req.args.get('markread'):
95                data['fileid'] = req.args.get('fileid')
96                data['line'] = req.args.get('line')
[18263]97                rfile = ReviewFileModel(self.env, data['fileid'])
98                data['path'] = rfile['path']
[15448]99                if req.args.get('markread') == 'read':
100                    rev_dat = ReviewDataModel(self.env)
101                    rev_dat['file_id'] = data['fileid']
102                    rev_dat['comment_id'] = req.args.get('commentid')
103                    rev_dat['review_id'] = req.args.get('reviewid')
104                    rev_dat['owner'] = req.authname
105                    rev_dat['type'] = 'read'
106                    rev_dat['data'] = 'read'
107                    rev_dat.insert()
108                else:
109                    rev_dat = ReviewDataModel(self.env)
110                    rev_dat['file_id'] = data['fileid']
111                    rev_dat['comment_id'] = req.args.get('commentid')
112                    rev_dat['owner'] = req.authname
113                    for rev in rev_dat.list_matching_objects():
114                        rev.delete()
115                writeJSONResponse(req, data)
116                return
[15242]117
[18263]118        if req.args.get('action') == 'commenttree':
119            self.get_comment_tree(req, data)
120            data['path'] = req.args.get('path', '')
121        elif req.args.get('action') == 'addcommentdlg':
122            data['create_add_comment_dlg'] = True
123            data['form_token'] = req.form_token
[18281]124        else:
125            data['invalid'] = 'error'  # We shouldn't end here
[18263]126
[18247]127        if hasattr(Chrome, 'jenv'):
[18281]128            return 'peerreview_comment_jinja.html', data
[18247]129        else:
[18281]130            return 'peerreview_comment.html', data, None
[717]131
[18281]132
[15208]133    def review_is_closed(self, req):
[15242]134        fileid = req.args.get('IDFile')
135        if not fileid:
136            fileid = req.args.get('fileid')
[17448]137        review = get_review_for_file(self.env, fileid)
[15294]138        if review['status'] == 'closed':
[15208]139            return True
140        return False
141
[15330]142    # Returns a comment tree for the requested line number
143    # in the requested file
[15288]144    def get_comment_tree(self, req, data):
[18263]145        fileid = req.args.get('IDFile') or req.args.get('fileid')
146        linenum = req.args.get('LineNum') or req.args.get('line')
[15249]147
[15288]148        if not fileid or not linenum:
[18281]149            data['invalid'] = 'valueerror'
[717]150            return
[15249]151
[18049]152        with self.env.db_query as db:
[18280]153            comments = ReviewCommentModel.create_comment_tree(self.env, fileid, linenum)
[15448]154        my_comment_data = ReviewDataModel.comments_for_file_and_owner(self.env, fileid, req.authname)
155        data['read_comments'] = [c_id for c_id, t, dat in my_comment_data if t == 'read']
156
[15511]157        rfile = ReviewFileModel(self.env, fileid)
158        review = PeerReviewModel(self.env, rfile['review_id'])
[15253]159        data['review'] = review
[15312]160        # A finished review can't be changed anymore except by a manager
[15328]161        data['is_finished'] = review_is_finished(self.env.config, review)
[15511]162        # A user can't change his voting for a reviewed review
[15328]163        data['review_locked'] = review_is_locked(self.env.config, review, req.authname)
[15517]164        data['not_allowed'] = not_allowed_to_comment(self.env, review, req.perm, req.authname)
[15253]165
[15288]166        comment_html = ""
[18252]167        keys = sorted(comments.keys())
[717]168        for key in keys:
[18280]169            if comments[key].parent_id == -1:
170                comment_html += self.build_comment_html(req, comments[key], 0, linenum, fileid, data)
[15288]171        comment_html = comment_html.strip()
172        if not comment_html:
173            comment_html = "No Comments on this Line"
174        data['lineNum'] = linenum
175        data['fileID'] = fileid
176        data['commentHTML'] = Markup(comment_html)
[717]177
[15250]178    comment_template = u"""
179            <table xmlns:py="http://genshi.edgewall.org/"
[18266]180                   style="width:450px"
[18280]181                   py:attrs="{'class': 'comment-table'} if comment.comment_id in read_comments else {'class': 'comment-table comment-notread'}"
182                   id="c-${comment.comment_id}" data-child-of="$comment.parent_id">
[15252]183            <tbody>
[15448]184            <tr>
185                <td style="width:${width}px"></td>
[18266]186                <td colspan="3" style="width:${450-width}px"
[15448]187                class="border-col"></td>
[15250]188            </tr>
189            <tr>
[15448]190                <td style="width:${width}px"></td>
[18280]191                <td colspan="2" class="comment-author">Author: ${authorinfo(comment.author)}
192                <a py:if="comment.comment_id not in read_comments"
193                   href="javascript:markCommentRead($line, $fileid, $comment.comment_ic, ${review['review_id']})">Mark read</a>
194                <a py:if="comment.comment_ic in read_comments"
195                   href="javascript:markCommentNotread($line, $fileid, $comment.comment_id, ${review['review_id']})">Mark unread</a>
[15448]196                </td>
197                <td style="width:100px" class="comment-date">$date</td>
[15250]198            </tr>
199            <tr>
[15448]200                <td style="width:${width}px"></td>
[18280]201                <td valign="top" style="width:${factor}px" id="${comment.comment_id}TreeButton">
202                    <img py:if="childrenHTML" src="${href.chrome('hw/images/minus.gif')}" id="${comment.comment_id}collapse"
203                         onclick="collapseComments(${comment.comment_id});" style="cursor: pointer;" />
[15461]204                    <img py:if="childrenHTML" src="${href.chrome('hw/images/plus.gif')}" style="display: none;cursor:pointer;"
[18280]205                         id="${comment.comment_id}expand"
206                         onclick="expandComments(${comment.comment_id});" />
[15250]207                </td>
[15448]208                <td colspan="2">
209                <div class="comment-text">
[15256]210                    $text
[15448]211                </div>
[15250]212                </td>
213            </tr>
214            <tr>
[15448]215                <td></td>
216                <td></td>
[18281]217                <td></td>
[15448]218                <td class="comment-reply">
[18280]219                   <a py:if="not is_locked" href="javascript:addComment($line, $fileid, $comment.comment_id)">Reply</a>
[15250]220                </td>
221            </tr>
[15252]222            </tbody>
[15250]223            </table>
224        """
[18244]225    comment_template_jinja = u"""
[18266]226            <table style="width:450px"
[18280]227                   class="${'comment-table' if comment.comment_id in read_comments else 'comment-table comment-notread'}"
228                   id="c-${comment.comment_id}" data-child-of="${comment.parent_id}">
[18244]229            <tbody>
230            <tr>
231                <td style="width:${width}px"></td>
[18266]232                <td colspan="3" style="width:${450-width}px"
[18244]233                class="border-col"></td>
234            </tr>
235            <tr>
236                <td style="width:${width}px"></td>
[18280]237                <td colspan="2" class="comment-author">Author: ${authorinfo(comment.author)}
238                # if comment.comment_id not in read_comments:
239                <a href="javascript:markCommentRead(${line}, ${fileid}, ${comment.comment_id}, ${review['review_id']})">Mark read</a>
[18244]240                # else:
[18280]241                <a href="javascript:markCommentNotread(${line}, ${fileid}, ${comment.comment_id}, ${review['review_id']})">Mark unread</a>
[18244]242                # endif
243                </td>
244                <td style="width:100px" class="comment-date">${date}</td>
245            </tr>
246            <tr>
247                <td style="width:${width}px"></td>
[18280]248                <td valign="top" style="width:${factor}px" id="${comment.comment_id}TreeButton">
[18244]249                    # if childrenHTML:
[18280]250                    <img src="${href.chrome('hw/images/minus.gif')}" id="${comment.comment_id}collapse"
251                         onclick="collapseComments(${comment.comment_id});" style="cursor: pointer;" />
[18244]252                    <img src="${href.chrome('hw/images/plus.gif')}" style="display: none;cursor:pointer;"
[18280]253                         id="${comment.comment_id}expand"
254                         onclick="expandComments(${comment.comment_id});" />
[18244]255                    # endif
256                </td>
257                <td colspan="2">
258                <div class="comment-text">
259                    ${text}
260                </div>
261                </td>
262            </tr>
263            <tr>
264                <td></td>
265                <td></td>
[18281]266                <td></td>
[18244]267                <td class="comment-reply">
268                   # if not is_locked:
[18280]269                   <a href="javascript:addComment(${line}, ${fileid}, ${comment.comment_id})">Reply</a>
[18244]270                   # endif
271                </td>
272            </tr>
273            </tbody>
274            </table>
275        """
[15250]276
[17448]277    # Recursively builds the comment html to send back.
[18280]278    def build_comment_html(self, req, comment, indent, linenum, fileid, data):
[18263]279        if indent > 50:
[717]280            return ""
281
[15252]282        children_html = ""
[18280]283        keys = sorted(comment.children.keys())
[717]284        for key in keys:
[18280]285            child = comment.children[key]
286            children_html += self.build_comment_html(req, child, indent + 1, linenum, fileid, data)
[15228]287
[717]288        factor = 15
[18263]289        width = 5 + indent * factor
[15228]290
[18269]291        chrome = Chrome(self.env)
[18050]292        context = web_context(req)
[15250]293        tdata = {'width': width,
[18280]294                 'text': format_to_html(self.env, context, comment.comment,
[15256]295                                        escape_newlines=True),
[15250]296                 'comment': comment,
[18280]297                 'date': user_time(req, format_date, to_datetime(comment.created)),
[15250]298                 'factor': factor,
[15252]299                 'childrenHTML': children_html != '' or False,
[17448]300                 'href': req.href,
[15252]301                 'line': linenum,
[17448]302                 'fileid': fileid,
303                 'callback': req.href.peerReviewCommentCallback(),  # this is for attachments
[15312]304                 'review': data['review'],
[15517]305                 'is_locked': data['is_finished'] or data['review_locked'] or data.get('not_allowed', False),
[18269]306                 'read_comments': data['read_comments'],
307                 'authorinfo': partial(chrome.authorinfo, req)
[15250]308                 }
[15249]309
[18244]310        if hasattr(Chrome, 'jenv'):
311            template = chrome.jenv.from_string(self.comment_template_jinja)
[18247]312            return chrome.render_template_string(template, tdata, True) + children_html
[18244]313        else:
314            tbl = MarkupTemplate(self.comment_template, lookup='lenient')
315            return tbl.generate(**tdata).render(encoding=None) + children_html
Note: See TracBrowser for help on using the repository browser.