Changeset - d2de0c2f02cd
[Not reviewed]
beta
0 7 2
Marcin Kuzminski - 14 years ago 2011-11-11 19:41:53
marcin@python-works.com
#77 code review
- initial very simple version of changeset comments
- RST parsed
9 files changed with 393 insertions and 2 deletions:
0 comments (0 inline, 0 general)
rhodecode/config/routing.py
Show inline comments
 
@@ -343,12 +343,20 @@ def make_map(config):
 
                conditions=dict(function=check_group))
 

	
 
    rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
 
                controller='changeset', revision='tip',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changeset_comment', '/{repo_name:.*}/changeset/{revision}/comment',
 
                controller='changeset', revision='tip', action='comment',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changeset_comment_delete', '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
 
                controller='changeset', action='delete_comment',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('raw_changeset_home',
 
                 '/{repo_name:.*}/raw-changeset/{revision}',
 
                 controller='changeset', action='raw_changeset',
 
                 revision='tip', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('summary_home', '/{repo_name:.*}/summary',
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -26,18 +26,22 @@
 
import logging
 
import traceback
 

	
 
from pylons import tmpl_context as c, url, request, response
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
from pylons.decorators import jsonify
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
 
    NotAnonymous
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.utils import EmptyChangeset
 
from rhodecode.lib.compat import OrderedDict
 
from rhodecode.model.db import ChangesetComment
 
from rhodecode.model.comment import ChangesetCommentsModel
 

	
 
from vcs.exceptions import RepositoryError, ChangesetError, \
 
ChangesetDoesNotExistError
 
from vcs.nodes import FileNode
 
from vcs.utils import diffs as differ
 

	
 
@@ -89,12 +93,20 @@ class ChangesetController(BaseRepoContro
 
        c.sum_added = 0
 
        c.sum_removed = 0
 
        c.lines_added = 0
 
        c.lines_deleted = 0
 
        c.cut_off = False  # defines if cut off limit is reached
 

	
 
        c.comments = []
 
        for cs in c.cs_ranges:
 
            c.comments.extend(ChangesetComment.query()\
 
                              .filter(ChangesetComment.repo_id == c.rhodecode_db_repo.repo_id)\
 
                              .filter(ChangesetComment.commit_id == cs.raw_id)\
 
                              .filter(ChangesetComment.line_no == None)\
 
                              .filter(ChangesetComment.f_path == None).all())
 

	
 
        # Iterate over ranges (default changeset view is always one changeset)
 
        for changeset in c.cs_ranges:
 
            c.changes[changeset.raw_id] = []
 
            try:
 
                changeset_parent = changeset.parents[0]
 
            except IndexError:
 
@@ -249,6 +261,25 @@ class ChangesetController(BaseRepoContro
 

	
 
        c.diffs = ''
 
        for x in c.changes:
 
            c.diffs += x[2]
 

	
 
        return render('changeset/raw_changeset.html')
 

	
 
    def comment(self, repo_name, revision):
 
        ccmodel = ChangesetCommentsModel()
 

	
 
        ccmodel.create(text=request.POST.get('text'),
 
                       repo_id=c.rhodecode_db_repo.repo_id, 
 
                       user_id=c.rhodecode_user.user_id, 
 
                       commit_id=revision, f_path=request.POST.get('f_path'), 
 
                       line_no = request.POST.get('line'))
 

	
 
        return redirect(h.url('changeset_home', repo_name=repo_name,
 
                              revision=revision))
 

	
 
    @jsonify
 
    @HasRepoPermissionAnyDecorator('hg.admin', 'repository.admin')
 
    def delete_comment(self, comment_id):
 
        ccmodel = ChangesetCommentsModel()
 
        ccmodel.delete(comment_id=comment_id)
 
        return True
rhodecode/lib/helpers.py
Show inline comments
 
@@ -36,12 +36,14 @@ from webhelpers.html.tags import _set_in
 
    convert_boolean_attrs, NotGiven
 

	
 
from vcs.utils.annotate import annotate_highlight
 
from rhodecode.lib.utils import repo_name_slug
 
from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
 

	
 
from rhodecode.lib.markup_renderer import MarkupRenderer
 

	
 
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
 
    """
 
    Reset button
 
    """
 
    _set_input_attrs(attrs, type, name, value)
 
    _set_id_attr(attrs, id, name)
 
@@ -665,6 +667,9 @@ def urlify_text(text):
 
    def url_func(match_obj):
 
        url_full = match_obj.groups()[0]
 
        return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
 

	
 
    return literal(url_pat.sub(url_func, text))
 

	
 

	
 
def rst(source):
 
    return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
rhodecode/model/comment.py
Show inline comments
 
new file 100644
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.model.comment
 
    ~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    comments model for RhodeCode
 
    
 
    :created_on: Nov 11, 2011
 
    :author: marcink
 
    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 

	
 
import logging
 
import traceback
 

	
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import ChangesetComment
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetCommentsModel(BaseModel):
 

	
 

	
 
    def create(self, text, repo_id, user_id, commit_id, f_path=None,
 
               line_no=None):
 
        """
 
        Creates new comment for changeset
 
        
 
        :param text:
 
        :param repo_id:
 
        :param user_id:
 
        :param commit_id:
 
        :param f_path:
 
        :param line_no:
 
        """
 

	
 
        comment = ChangesetComment()
 
        comment.repo_id = repo_id
 
        comment.user_id = user_id
 
        comment.commit_id = commit_id
 
        comment.text = text
 
        comment.f_path = f_path
 
        comment.line_no = line_no
 

	
 
        self.sa.add(comment)
 
        self.sa.commit()
 
        return comment
 

	
 
    def delete(self, comment_id):
 
        """
 
        Deletes given comment
 
        
 
        :param comment_id:
 
        """
 
        comment = ChangesetComment.get(comment_id)
 
        self.sa.delete(comment)
 
        self.sa.commit()
 
        return comment
rhodecode/model/db.py
Show inline comments
 
@@ -1092,12 +1092,28 @@ class CacheInvalidation(Base, BaseModel)
 
            .filter(CacheInvalidation.cache_key == key).scalar()
 
        inv_obj.cache_active = True
 
        Session.add(inv_obj)
 
        Session.commit()
 

	
 

	
 
class ChangesetComment(Base, BaseModel):
 
    __tablename__ = 'changeset_comments'
 
    __table_args__ = ({'extend_existing':True},)
 
    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
 
    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    commit_id = Column('commit_id', String(100), nullable=False)
 
    line_no = Column('line_no', Integer(), nullable=True)
 
    f_path = Column('f_path', String(1000), nullable=True)
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
 
    text = Column('text', String(25000), nullable=False)
 
    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 

	
 
    author = relationship('User')
 
    repo = relationship('Repository')
 

	
 

	
 
class DbMigrateVersion(Base, BaseModel):
 
    __tablename__ = 'db_migrate_version'
 
    __table_args__ = {'extend_existing':True}
 
    repository_id = Column('repository_id', String(250), primary_key=True)
 
    repository_path = Column('repository_path', Text)
 
    version = Column('version', Integer)
rhodecode/public/css/diff.css
Show inline comments
 
@@ -5,12 +5,17 @@ div.diffblock {
 
    background: #f8f8f8;
 
    font-size: 100%;
 
    line-height: 100%;
 
    /* new */
 
    line-height: 125%;
 
}
 

	
 
div.diffblock.margined{
 
	margin: 0px 20px 0px 20px;
 
}
 

	
 
div.diffblock .code-header{
 
	border-bottom: 1px solid #CCCCCC;
 
	background: #EEEEEE;
 
	padding:10px 0 10px 0;
 
}
 
div.diffblock .code-header div{
rhodecode/public/css/style.css
Show inline comments
 
@@ -3132,6 +3132,204 @@ div.readme .readme_box pre {
 
	overflow: auto;
 
	-webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
 
	-webkit-border-radius: 3px;
 
	-moz-border-radius: 3px;
 
	border-radius: 3px;
 
}
 
 
 
/** RST STYLE **/
 
 
 
div.rst-block {
 
    padding:0px;
 
}
 
 
div.rst-block h2 {
 
    font-weight: normal;
 
}
 
 
div.rst-block  {
 
    background-color: #fafafa;
 
}
 
 
div.rst-block  {
 
clear:both;
 
overflow:hidden;
 
margin:0;
 
padding:0 20px 10px;
 
}
 
 
div.rst-block  h1, div.rst-block  h2, div.rst-block  h3, div.rst-block  h4, div.rst-block  h5, div.rst-block  h6 {
 
border-bottom: 0 !important;
 
margin: 0 !important;
 
padding: 0 !important;
 
line-height: 1.5em !important;
 
}
 
 
 
div.rst-block  h1:first-child {
 
padding-top: .25em !important;
 
}
 
 
div.rst-block  h2, div.rst-block  h3 {
 
margin: 1em 0 !important;
 
}
 
 
div.rst-block  h2 {
 
margin-top: 1.5em !important;
 
border-top: 4px solid #e0e0e0 !important;
 
padding-top: .5em !important;
 
}
 
 
div.rst-block  p {
 
color: black !important;
 
margin: 1em 0 !important;
 
line-height: 1.5em !important;
 
}
 
 
div.rst-block  ul {
 
list-style: disc !important;
 
margin: 1em 0 1em 2em !important;
 
}
 
 
div.rst-block  ol {
 
list-style: decimal;
 
margin: 1em 0 1em 2em !important;
 
}
 
 
div.rst-block  pre, code {
 
font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
 
}
 
 
div.rst-block  code {
 
    font-size: 12px !important;
 
    background-color: ghostWhite !important;
 
    color: #444 !important;
 
    padding: 0 .2em !important;
 
    border: 1px solid #dedede !important;
 
}
 
 
div.rst-block  pre code {
 
    padding: 0 !important;
 
    font-size: 12px !important;
 
    background-color: #eee !important;
 
    border: none !important;
 
}
 
 
div.rst-block  pre {
 
    margin: 1em 0;
 
    font-size: 12px;
 
    background-color: #eee;
 
    border: 1px solid #ddd;
 
    padding: 5px;
 
    color: #444;
 
    overflow: auto;
 
    -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
 
    -webkit-border-radius: 3px;
 
    -moz-border-radius: 3px;
 
    border-radius: 3px;
 
}
 
 
 
 
.comments {
 
    padding:10px 20px;
 
}
 
 
.comments .comment {
 
    border: 1px solid #ddd;
 
    margin-top: 10px;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;    
 
}
 
 
.comments .comment .meta {
 
    background: #f8f8f8;
 
    padding: 6px;
 
    border-bottom: 1px solid #ddd;
 
}
 
 
.comments .comment .meta img {
 
    vertical-align: middle;
 
}
 
 
.comments .comment .meta .user {
 
    font-weight: bold;
 
}
 
 
.comments .comment .meta .date {
 
    float: right;
 
}
 
 
.comments .comment .text {
 
    margin-top: 7px;
 
    padding: 6px;
 
    padding-bottom: 13px;
 
}
 
 
.comments .comments-number{
 
	padding:0px 0px 10px 0px;
 
	font-weight: bold;
 
}
 
 
.comment-form .clearfix{
 
	background: #EEE;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    padding: 10px;
 
}
 
 
div.comment-form {
 
    margin-top: 20px;
 
}
 
 
.comment-form strong {
 
    display: block;
 
    margin-bottom: 15px;
 
}
 
 
.comment-form textarea {
 
    width: 100%;
 
    height: 100px;
 
    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
 
}
 
 
form.comment-form {
 
    margin-top: 10px;
 
    margin-left: 10px;
 
}
 
 
.comment-form-submit {
 
    margin-top: 5px;
 
    margin-left: 525px;
 
}
 
 
.file-comments {
 
    display: none;
 
}
 
 
.comment-form .comment {
 
    margin-left: 10px;
 
}
 
 
.comment-form .comment-help{
 
    padding: 0px 0px 5px 0px;
 
    color: #666;
 
}
 
 
.comment-form .comment-button{
 
	padding-top:5px;
 
}
 
 
.add-another-button {
 
    margin-left: 10px;
 
    margin-top: 10px;
 
    margin-bottom: 10px;
 
}
 
 
.comment .buttons {
 
    float: right;
 
    display: none;
 
}
rhodecode/templates/changeset/changeset.html
Show inline comments
 
@@ -105,13 +105,13 @@
 
	    
 
    </div>
 
    	
 
	%for change,filenode,diff,cs1,cs2,stat in c.changes:
 
		%if change !='removed':
 
		<div style="clear:both;height:10px"></div>
 
		<div class="diffblock">
 
		<div class="diffblock  margined">
 
			<div id="${h.repo_name_slug('C%s' % h.safe_unicode(filenode.path))}" class="code-header">
 
				<div class="changeset_header">
 
					<span class="changeset_file">
 
						${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
 
						revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
 
					</span>
 
@@ -132,8 +132,32 @@
 
						${_('No changes in this file')}
 
					%endif
 
			</div>
 
		</div>
 
		%endif
 
	%endfor 
 
  
 
    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
    
 
    <div class="comments">
 
        <div class="comments-number">${len(c.comments)} comment(s)</div>
 
        %for co in c.comments:
 
            ${comment.comment_block(co)}
 
        %endfor
 
        %if c.rhodecode_user.username != 'default':
 
        <div class="comment-form">
 
            ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id))}
 
            <strong>Leave a comment</strong>
 
            <div class="clearfix">
 
                <div class="comment-help">${_('Comments parsed using RST syntax')}</div>
 
                    ${h.textarea('text')}
 
            </div>
 
            <div class="comment-button">
 
            ${h.submit('save', _('Comment'), class_='ui-button')}
 
            </div>
 
            ${h.end_form()}
 
        </div>
 
        %endif
 
    </div>
 
    
 
    </div>	
 
</%def>
rhodecode/templates/changeset/changeset_file_comment.html
Show inline comments
 
new file 100644
 
##usage:
 
## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
## ${comment.comment_block(co)}
 
##
 
<%def name="comment_block(co)">
 
  <div class="comment" id="comment-${co.comment_id}">
 
  	<div class="meta">
 
  		<span class="user">
 
  			<img src="${h.gravatar_url(co.author.email, 20)}" />
 
  			${co.author.username}
 
  		</span>
 
  		<a href="${h.url.current(anchor='comment-%s' % co.comment_id)}"> ${_('commented on')} </a>
 
  		${h.short_id(co.commit_id)}
 
  		%if co.f_path:
 
  			${_(' in file ')}
 
  			${co.f_path}:L${co.line_no}
 
  		%endif
 
  		<span class="date">
 
  			${h.age(co.modified_at)}
 
  		</span>
 
  	</div>
 
  	<div class="text">
 
  		%if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
 
  			<div class="buttons">
 
  				<a href="javascript:void(0);" onClick="deleteComment(${co.comment_id})" class="">${_('Delete')}</a>
 
  			</div>
 
  		%endif
 
  		${h.rst(co.text)|n}
 
  	</div>
 
  </div>
 
</%def>
 
\ No newline at end of file
0 comments (0 inline, 0 general)