Show More
@@ -0,0 +1,43 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | import logging | |||
|
4 | from sqlalchemy import * | |||
|
5 | ||||
|
6 | from alembic.migration import MigrationContext | |||
|
7 | from alembic.operations import Operations | |||
|
8 | from sqlalchemy import BigInteger | |||
|
9 | ||||
|
10 | from rhodecode.lib.dbmigrate.versions import _reset_base | |||
|
11 | from rhodecode.model import init_model_encryption | |||
|
12 | ||||
|
13 | ||||
|
14 | log = logging.getLogger(__name__) | |||
|
15 | ||||
|
16 | ||||
|
17 | def upgrade(migrate_engine): | |||
|
18 | """ | |||
|
19 | Upgrade operations go here. | |||
|
20 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
21 | """ | |||
|
22 | _reset_base(migrate_engine) | |||
|
23 | from rhodecode.lib.dbmigrate.schema import db_4_18_0_1 as db | |||
|
24 | ||||
|
25 | init_model_encryption(db) | |||
|
26 | ||||
|
27 | context = MigrationContext.configure(migrate_engine.connect()) | |||
|
28 | op = Operations(context) | |||
|
29 | ||||
|
30 | comments = db.ChangesetComment.__table__ | |||
|
31 | ||||
|
32 | with op.batch_alter_table(comments.name) as batch_op: | |||
|
33 | new_column = Column('immutable_state', Unicode(128), nullable=True) | |||
|
34 | batch_op.add_column(new_column) | |||
|
35 | ||||
|
36 | ||||
|
37 | def downgrade(migrate_engine): | |||
|
38 | meta = MetaData() | |||
|
39 | meta.bind = migrate_engine | |||
|
40 | ||||
|
41 | ||||
|
42 | def fixups(models, _SESSION): | |||
|
43 | pass |
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}' | |||||
48 | EXTENSIONS = {} |
|
48 | EXTENSIONS = {} | |
49 |
|
49 | |||
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) | |
51 |
__dbversion__ = 10 |
|
51 | __dbversion__ = 106 # defines current db version for migrations | |
52 | __platform__ = platform.system() |
|
52 | __platform__ = platform.system() | |
53 | __license__ = 'AGPLv3, and Commercial License' |
|
53 | __license__ = 'AGPLv3, and Commercial License' | |
54 | __author__ = 'RhodeCode GmbH' |
|
54 | __author__ = 'RhodeCode GmbH' |
@@ -220,7 +220,7 b' def db():' | |||||
220 | 'pr_comment_url': 'http://comment-url', |
|
220 | 'pr_comment_url': 'http://comment-url', | |
221 | 'pr_comment_reply_url': 'http://comment-url#reply', |
|
221 | 'pr_comment_reply_url': 'http://comment-url#reply', | |
222 |
|
222 | |||
223 |
'comment_file': 'rhodecode/model/ |
|
223 | 'comment_file': 'rhodecode/model/get_flow_commits', | |
224 | 'comment_line': 'o1210', |
|
224 | 'comment_line': 'o1210', | |
225 | 'comment_type': 'todo', |
|
225 | 'comment_type': 'todo', | |
226 | 'comment_body': ''' |
|
226 | 'comment_body': ''' |
@@ -22,8 +22,7 b' import pytest' | |||||
22 |
|
22 | |||
23 | from rhodecode.tests import TestController |
|
23 | from rhodecode.tests import TestController | |
24 |
|
24 | |||
25 |
from rhodecode.model.db import |
|
25 | from rhodecode.model.db import ChangesetComment, Notification | |
26 | ChangesetComment, Notification, UserNotification) |
|
|||
27 | from rhodecode.model.meta import Session |
|
26 | from rhodecode.model.meta import Session | |
28 | from rhodecode.lib import helpers as h |
|
27 | from rhodecode.lib import helpers as h | |
29 |
|
28 | |||
@@ -269,7 +268,36 b' class TestRepoCommitCommentsView(TestCon' | |||||
269 | repo_name=backend.repo_name, commit_id=commit_id)) |
|
268 | repo_name=backend.repo_name, commit_id=commit_id)) | |
270 | assert_comment_links(response, 0, 0) |
|
269 | assert_comment_links(response, 0, 0) | |
271 |
|
270 | |||
272 | @pytest.mark.parametrize('renderer, input, output', [ |
|
271 | def test_delete_forbidden_for_immutable_comments(self, backend): | |
|
272 | self.log_user() | |||
|
273 | commit_id = backend.repo.get_commit('300').raw_id | |||
|
274 | text = u'CommentOnCommit' | |||
|
275 | ||||
|
276 | params = {'text': text, 'csrf_token': self.csrf_token} | |||
|
277 | self.app.post( | |||
|
278 | route_path( | |||
|
279 | 'repo_commit_comment_create', | |||
|
280 | repo_name=backend.repo_name, commit_id=commit_id), | |||
|
281 | params=params) | |||
|
282 | ||||
|
283 | comments = ChangesetComment.query().all() | |||
|
284 | assert len(comments) == 1 | |||
|
285 | comment_id = comments[0].comment_id | |||
|
286 | ||||
|
287 | comment = ChangesetComment.get(comment_id) | |||
|
288 | comment.immutable_state = ChangesetComment.OP_IMMUTABLE | |||
|
289 | Session().add(comment) | |||
|
290 | Session().commit() | |||
|
291 | ||||
|
292 | self.app.post( | |||
|
293 | route_path('repo_commit_comment_delete', | |||
|
294 | repo_name=backend.repo_name, | |||
|
295 | commit_id=commit_id, | |||
|
296 | comment_id=comment_id), | |||
|
297 | params={'csrf_token': self.csrf_token}, | |||
|
298 | status=403) | |||
|
299 | ||||
|
300 | @pytest.mark.parametrize('renderer, text_input, output', [ | |||
273 | ('rst', 'plain text', '<p>plain text</p>'), |
|
301 | ('rst', 'plain text', '<p>plain text</p>'), | |
274 | ('rst', 'header\n======', '<h1 class="title">header</h1>'), |
|
302 | ('rst', 'header\n======', '<h1 class="title">header</h1>'), | |
275 | ('rst', '*italics*', '<em>italics</em>'), |
|
303 | ('rst', '*italics*', '<em>italics</em>'), | |
@@ -280,11 +308,11 b' class TestRepoCommitCommentsView(TestCon' | |||||
280 | ('markdown', '**bold**', '<strong>bold</strong>'), |
|
308 | ('markdown', '**bold**', '<strong>bold</strong>'), | |
281 | ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', |
|
309 | ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain', | |
282 | 'md-header', 'md-italics', 'md-bold', ]) |
|
310 | 'md-header', 'md-italics', 'md-bold', ]) | |
283 | def test_preview(self, renderer, input, output, backend, xhr_header): |
|
311 | def test_preview(self, renderer, text_input, output, backend, xhr_header): | |
284 | self.log_user() |
|
312 | self.log_user() | |
285 | params = { |
|
313 | params = { | |
286 | 'renderer': renderer, |
|
314 | 'renderer': renderer, | |
287 | 'text': input, |
|
315 | 'text': text_input, | |
288 | 'csrf_token': self.csrf_token |
|
316 | 'csrf_token': self.csrf_token | |
289 | } |
|
317 | } | |
290 | commit_id = '0' * 16 # fake this for tests |
|
318 | commit_id = '0' * 16 # fake this for tests |
@@ -22,7 +22,7 b'' | |||||
22 | import logging |
|
22 | import logging | |
23 | import collections |
|
23 | import collections | |
24 |
|
24 | |||
25 | from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound |
|
25 | from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden | |
26 | from pyramid.view import view_config |
|
26 | from pyramid.view import view_config | |
27 | from pyramid.renderers import render |
|
27 | from pyramid.renderers import render | |
28 | from pyramid.response import Response |
|
28 | from pyramid.response import Response | |
@@ -538,6 +538,10 b' class RepoCommitsView(RepoAppView):' | |||||
538 | # comment already deleted in another call probably |
|
538 | # comment already deleted in another call probably | |
539 | return True |
|
539 | return True | |
540 |
|
540 | |||
|
541 | if comment.immutable: | |||
|
542 | # don't allow deleting comments that are immutable | |||
|
543 | raise HTTPForbidden() | |||
|
544 | ||||
541 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) |
|
545 | is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name) | |
542 | super_admin = h.HasPermissionAny('hg.admin')() |
|
546 | super_admin = h.HasPermissionAny('hg.admin')() | |
543 | comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id) |
|
547 | comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id) |
@@ -1473,6 +1473,10 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1473 | self.request.matchdict['comment_id']) |
|
1473 | self.request.matchdict['comment_id']) | |
1474 | comment_id = comment.comment_id |
|
1474 | comment_id = comment.comment_id | |
1475 |
|
1475 | |||
|
1476 | if comment.immutable: | |||
|
1477 | # don't allow deleting comments that are immutable | |||
|
1478 | raise HTTPForbidden() | |||
|
1479 | ||||
1476 | if pull_request.is_closed(): |
|
1480 | if pull_request.is_closed(): | |
1477 | log.debug('comment: forbidden because pull request is closed') |
|
1481 | log.debug('comment: forbidden because pull request is closed') | |
1478 | raise HTTPForbidden() |
|
1482 | raise HTTPForbidden() |
@@ -3711,6 +3711,9 b' class ChangesetComment(Base, BaseModel):' | |||||
3711 | COMMENT_TYPE_TODO = u'todo' |
|
3711 | COMMENT_TYPE_TODO = u'todo' | |
3712 | COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO] |
|
3712 | COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO] | |
3713 |
|
3713 | |||
|
3714 | OP_IMMUTABLE = u'immutable' | |||
|
3715 | OP_CHANGEABLE = u'changeable' | |||
|
3716 | ||||
3714 | comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) |
|
3717 | comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) | |
3715 | repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) |
|
3718 | repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
3716 | revision = Column('revision', String(40), nullable=True) |
|
3719 | revision = Column('revision', String(40), nullable=True) | |
@@ -3725,6 +3728,7 b' class ChangesetComment(Base, BaseModel):' | |||||
3725 | modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) |
|
3728 | modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
3726 | renderer = Column('renderer', Unicode(64), nullable=True) |
|
3729 | renderer = Column('renderer', Unicode(64), nullable=True) | |
3727 | display_state = Column('display_state', Unicode(128), nullable=True) |
|
3730 | display_state = Column('display_state', Unicode(128), nullable=True) | |
|
3731 | immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE) | |||
3728 |
|
3732 | |||
3729 | comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE) |
|
3733 | comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE) | |
3730 | resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True) |
|
3734 | resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True) | |
@@ -3767,6 +3771,10 b' class ChangesetComment(Base, BaseModel):' | |||||
3767 | def outdated(self): |
|
3771 | def outdated(self): | |
3768 | return self.display_state == self.COMMENT_OUTDATED |
|
3772 | return self.display_state == self.COMMENT_OUTDATED | |
3769 |
|
3773 | |||
|
3774 | @property | |||
|
3775 | def immutable(self): | |||
|
3776 | return self.immutable_state == self.OP_IMMUTABLE | |||
|
3777 | ||||
3770 | def outdated_at_version(self, version): |
|
3778 | def outdated_at_version(self, version): | |
3771 | """ |
|
3779 | """ | |
3772 | Checks if comment is outdated for given pull request version |
|
3780 | Checks if comment is outdated for given pull request version |
@@ -135,7 +135,7 b'' | |||||
135 | ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated |
|
135 | ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated | |
136 | %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())): |
|
136 | %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())): | |
137 | ## permissions to delete |
|
137 | ## permissions to delete | |
138 | %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id: |
|
138 | %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id): | |
139 | ## TODO: dan: add edit comment here |
|
139 | ## TODO: dan: add edit comment here | |
140 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a> |
|
140 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a> | |
141 | %else: |
|
141 | %else: |
General Comments 0
You need to be logged in to leave comments.
Login now