changeset 1670:d2de0c2f02cd beta

#77 code review - initial very simple version of changeset comments - RST parsed
author Marcin Kuzminski <marcin@python-works.com>
date Fri, 11 Nov 2011 20:41:53 +0200
parents f522f4d3bf93
children 428c0e42d25d
files rhodecode/config/routing.py rhodecode/controllers/changeset.py rhodecode/lib/helpers.py rhodecode/model/comment.py rhodecode/model/db.py rhodecode/public/css/diff.css rhodecode/public/css/style.css rhodecode/templates/changeset/changeset.html rhodecode/templates/changeset/changeset_file_comment.html
diffstat 9 files changed, 398 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/config/routing.py	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/config/routing.py	Fri Nov 11 20:41:53 2011 +0200
@@ -346,6 +346,14 @@
                 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',
--- a/rhodecode/controllers/changeset.py	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/controllers/changeset.py	Fri Nov 11 20:41:53 2011 +0200
@@ -29,15 +29,19 @@
 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
+    ChangesetDoesNotExistError
 from vcs.nodes import FileNode
 from vcs.utils import diffs as differ
 
@@ -66,7 +70,7 @@
 
         #get ranges of revisions if preset
         rev_range = revision.split('...')[:2]
-        
+
         try:
             if len(rev_range) == 2:
                 rev_start = rev_range[0]
@@ -92,6 +96,14 @@
         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] = []
@@ -252,3 +264,22 @@
             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
--- a/rhodecode/lib/helpers.py	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/lib/helpers.py	Fri Nov 11 20:41:53 2011 +0200
@@ -39,6 +39,8 @@
 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
@@ -531,7 +533,7 @@
             self.last_item = ((self.item_count - 1) - items_per_page *
                               (self.page - 1))
 
-            self.items = list(self.collection[self.first_item:self.last_item+1])
+            self.items = list(self.collection[self.first_item:self.last_item + 1])
 
 
             # Links to previous and next page
@@ -668,3 +670,6 @@
 
     return literal(url_pat.sub(url_func, text))
 
+
+def rst(source):
+    return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/model/comment.py	Fri Nov 11 20:41:53 2011 +0200
@@ -0,0 +1,73 @@
+# -*- 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
--- a/rhodecode/model/db.py	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/model/db.py	Fri Nov 11 20:41:53 2011 +0200
@@ -1095,6 +1095,22 @@
         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}
--- a/rhodecode/public/css/diff.css	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/public/css/diff.css	Fri Nov 11 20:41:53 2011 +0200
@@ -8,6 +8,11 @@
     /* new */
     line-height: 125%;
 }
+
+div.diffblock.margined{
+	margin: 0px 20px 0px 20px;
+}
+
 div.diffblock .code-header{
 	border-bottom: 1px solid #CCCCCC;
 	background: #EEEEEE;
--- a/rhodecode/public/css/style.css	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/public/css/style.css	Fri Nov 11 20:41:53 2011 +0200
@@ -3135,3 +3135,201 @@
 	-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;
+}
--- a/rhodecode/templates/changeset/changeset.html	Fri Nov 11 19:42:10 2011 +0200
+++ b/rhodecode/templates/changeset/changeset.html	Fri Nov 11 20:41:53 2011 +0200
@@ -108,7 +108,7 @@
 	%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">
@@ -134,6 +134,30 @@
 			</div>
 		</div>
 		%endif
-	%endfor 
-    </div>	
+	%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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/templates/changeset/changeset_file_comment.html	Fri Nov 11 20:41:53 2011 +0200
@@ -0,0 +1,31 @@
+##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