##// END OF EJS Templates
drafts: fixes in draft comments for non-pr view
milka -
r4550:647f7e13 default
parent child Browse files
Show More
@@ -1,795 +1,802 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import collections
23 23
24 24 from pyramid.httpexceptions import (
25 25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.apps.file_store import utils as store_utils
32 32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
33 33
34 34 from rhodecode.lib import diffs, codeblocks, channelstream
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.compat import OrderedDict
39 39 from rhodecode.lib.diffs import (
40 40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 41 get_diff_whitespace_flag)
42 42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
43 43 import rhodecode.lib.helpers as h
44 44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
45 45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 46 from rhodecode.lib.vcs.exceptions import (
47 47 RepositoryError, CommitDoesNotExistError)
48 48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
49 49 ChangesetCommentHistory
50 50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 51 from rhodecode.model.comment import CommentsModel
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def _update_with_GET(params, request):
59 59 for k in ['diff1', 'diff2', 'diff']:
60 60 params[k] += request.GET.getall(k)
61 61
62 62
63 63 class RepoCommitsView(RepoAppView):
64 64 def load_default_context(self):
65 65 c = self._get_local_tmpl_context(include_app_defaults=True)
66 66 c.rhodecode_repo = self.rhodecode_vcs_repo
67 67
68 68 return c
69 69
70 70 def _is_diff_cache_enabled(self, target_repo):
71 71 caching_enabled = self._get_general_setting(
72 72 target_repo, 'rhodecode_diff_cache')
73 73 log.debug('Diff caching enabled: %s', caching_enabled)
74 74 return caching_enabled
75 75
76 76 def _commit(self, commit_id_range, method):
77 77 _ = self.request.translate
78 78 c = self.load_default_context()
79 79 c.fulldiff = self.request.GET.get('fulldiff')
80 80
81 81 # fetch global flags of ignore ws or context lines
82 82 diff_context = get_diff_context(self.request)
83 83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
84 84
85 85 # diff_limit will cut off the whole diff if the limit is applied
86 86 # otherwise it will just hide the big files from the front-end
87 87 diff_limit = c.visual.cut_off_limit_diff
88 88 file_limit = c.visual.cut_off_limit_file
89 89
90 90 # get ranges of commit ids if preset
91 91 commit_range = commit_id_range.split('...')[:2]
92 92
93 93 try:
94 94 pre_load = ['affected_files', 'author', 'branch', 'date',
95 95 'message', 'parents']
96 96 if self.rhodecode_vcs_repo.alias == 'hg':
97 97 pre_load += ['hidden', 'obsolete', 'phase']
98 98
99 99 if len(commit_range) == 2:
100 100 commits = self.rhodecode_vcs_repo.get_commits(
101 101 start_id=commit_range[0], end_id=commit_range[1],
102 102 pre_load=pre_load, translate_tags=False)
103 103 commits = list(commits)
104 104 else:
105 105 commits = [self.rhodecode_vcs_repo.get_commit(
106 106 commit_id=commit_id_range, pre_load=pre_load)]
107 107
108 108 c.commit_ranges = commits
109 109 if not c.commit_ranges:
110 110 raise RepositoryError('The commit range returned an empty result')
111 111 except CommitDoesNotExistError as e:
112 112 msg = _('No such commit exists. Org exception: `{}`').format(e)
113 113 h.flash(msg, category='error')
114 114 raise HTTPNotFound()
115 115 except Exception:
116 116 log.exception("General failure")
117 117 raise HTTPNotFound()
118 118 single_commit = len(c.commit_ranges) == 1
119 119
120 120 c.changes = OrderedDict()
121 121 c.lines_added = 0
122 122 c.lines_deleted = 0
123 123
124 124 # auto collapse if we have more than limit
125 125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
126 126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
127 127
128 128 c.commit_statuses = ChangesetStatus.STATUSES
129 129 c.inline_comments = []
130 130 c.files = []
131 131
132 132 c.comments = []
133 133 c.unresolved_comments = []
134 134 c.resolved_comments = []
135 135
136 136 # Single commit
137 137 if single_commit:
138 138 commit = c.commit_ranges[0]
139 139 c.comments = CommentsModel().get_comments(
140 140 self.db_repo.repo_id,
141 141 revision=commit.raw_id)
142 142
143 143 # comments from PR
144 144 statuses = ChangesetStatusModel().get_statuses(
145 145 self.db_repo.repo_id, commit.raw_id,
146 146 with_revisions=True)
147 147
148 148 prs = set()
149 149 reviewers = list()
150 150 reviewers_duplicates = set() # to not have duplicates from multiple votes
151 151 for c_status in statuses:
152 152
153 153 # extract associated pull-requests from votes
154 154 if c_status.pull_request:
155 155 prs.add(c_status.pull_request)
156 156
157 157 # extract reviewers
158 158 _user_id = c_status.author.user_id
159 159 if _user_id not in reviewers_duplicates:
160 160 reviewers.append(
161 161 StrictAttributeDict({
162 162 'user': c_status.author,
163 163
164 164 # fake attributed for commit, page that we don't have
165 165 # but we share the display with PR page
166 166 'mandatory': False,
167 167 'reasons': [],
168 168 'rule_user_group_data': lambda: None
169 169 })
170 170 )
171 171 reviewers_duplicates.add(_user_id)
172 172
173 173 c.reviewers_count = len(reviewers)
174 174 c.observers_count = 0
175 175
176 176 # from associated statuses, check the pull requests, and
177 177 # show comments from them
178 178 for pr in prs:
179 179 c.comments.extend(pr.comments)
180 180
181 181 c.unresolved_comments = CommentsModel()\
182 182 .get_commit_unresolved_todos(commit.raw_id)
183 183 c.resolved_comments = CommentsModel()\
184 184 .get_commit_resolved_todos(commit.raw_id)
185 185
186 186 c.inline_comments_flat = CommentsModel()\
187 187 .get_commit_inline_comments(commit.raw_id)
188 188
189 189 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
190 190 statuses, reviewers)
191 191
192 192 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
193 193
194 194 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
195 195
196 196 for review_obj, member, reasons, mandatory, status in review_statuses:
197 197 member_reviewer = h.reviewer_as_json(
198 198 member, reasons=reasons, mandatory=mandatory, role=None,
199 199 user_group=None
200 200 )
201 201
202 202 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
203 203 member_reviewer['review_status'] = current_review_status
204 204 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
205 205 member_reviewer['allowed_to_update'] = False
206 206 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
207 207
208 208 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
209 209
210 210 # NOTE(marcink): this uses the same voting logic as in pull-requests
211 211 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
212 212 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
213 213
214 214 diff = None
215 215 # Iterate over ranges (default commit view is always one commit)
216 216 for commit in c.commit_ranges:
217 217 c.changes[commit.raw_id] = []
218 218
219 219 commit2 = commit
220 220 commit1 = commit.first_parent
221 221
222 222 if method == 'show':
223 223 inline_comments = CommentsModel().get_inline_comments(
224 224 self.db_repo.repo_id, revision=commit.raw_id)
225 225 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
226 226 inline_comments))
227 227 c.inline_comments = inline_comments
228 228
229 229 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
230 230 self.db_repo)
231 231 cache_file_path = diff_cache_exist(
232 232 cache_path, 'diff', commit.raw_id,
233 233 hide_whitespace_changes, diff_context, c.fulldiff)
234 234
235 235 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
236 236 force_recache = str2bool(self.request.GET.get('force_recache'))
237 237
238 238 cached_diff = None
239 239 if caching_enabled:
240 240 cached_diff = load_cached_diff(cache_file_path)
241 241
242 242 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
243 243 if not force_recache and has_proper_diff_cache:
244 244 diffset = cached_diff['diff']
245 245 else:
246 246 vcs_diff = self.rhodecode_vcs_repo.get_diff(
247 247 commit1, commit2,
248 248 ignore_whitespace=hide_whitespace_changes,
249 249 context=diff_context)
250 250
251 251 diff_processor = diffs.DiffProcessor(
252 252 vcs_diff, format='newdiff', diff_limit=diff_limit,
253 253 file_limit=file_limit, show_full_diff=c.fulldiff)
254 254
255 255 _parsed = diff_processor.prepare()
256 256
257 257 diffset = codeblocks.DiffSet(
258 258 repo_name=self.db_repo_name,
259 259 source_node_getter=codeblocks.diffset_node_getter(commit1),
260 260 target_node_getter=codeblocks.diffset_node_getter(commit2))
261 261
262 262 diffset = self.path_filter.render_patchset_filtered(
263 263 diffset, _parsed, commit1.raw_id, commit2.raw_id)
264 264
265 265 # save cached diff
266 266 if caching_enabled:
267 267 cache_diff(cache_file_path, diffset, None)
268 268
269 269 c.limited_diff = diffset.limited_diff
270 270 c.changes[commit.raw_id] = diffset
271 271 else:
272 272 # TODO(marcink): no cache usage here...
273 273 _diff = self.rhodecode_vcs_repo.get_diff(
274 274 commit1, commit2,
275 275 ignore_whitespace=hide_whitespace_changes, context=diff_context)
276 276 diff_processor = diffs.DiffProcessor(
277 277 _diff, format='newdiff', diff_limit=diff_limit,
278 278 file_limit=file_limit, show_full_diff=c.fulldiff)
279 279 # downloads/raw we only need RAW diff nothing else
280 280 diff = self.path_filter.get_raw_patch(diff_processor)
281 281 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
282 282
283 283 # sort comments by how they were generated
284 284 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
285 285 c.at_version_num = None
286 286
287 287 if len(c.commit_ranges) == 1:
288 288 c.commit = c.commit_ranges[0]
289 289 c.parent_tmpl = ''.join(
290 290 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
291 291
292 292 if method == 'download':
293 293 response = Response(diff)
294 294 response.content_type = 'text/plain'
295 295 response.content_disposition = (
296 296 'attachment; filename=%s.diff' % commit_id_range[:12])
297 297 return response
298 298 elif method == 'patch':
299 299 c.diff = safe_unicode(diff)
300 300 patch = render(
301 301 'rhodecode:templates/changeset/patch_changeset.mako',
302 302 self._get_template_context(c), self.request)
303 303 response = Response(patch)
304 304 response.content_type = 'text/plain'
305 305 return response
306 306 elif method == 'raw':
307 307 response = Response(diff)
308 308 response.content_type = 'text/plain'
309 309 return response
310 310 elif method == 'show':
311 311 if len(c.commit_ranges) == 1:
312 312 html = render(
313 313 'rhodecode:templates/changeset/changeset.mako',
314 314 self._get_template_context(c), self.request)
315 315 return Response(html)
316 316 else:
317 317 c.ancestor = None
318 318 c.target_repo = self.db_repo
319 319 html = render(
320 320 'rhodecode:templates/changeset/changeset_range.mako',
321 321 self._get_template_context(c), self.request)
322 322 return Response(html)
323 323
324 324 raise HTTPBadRequest()
325 325
326 326 @LoginRequired()
327 327 @HasRepoPermissionAnyDecorator(
328 328 'repository.read', 'repository.write', 'repository.admin')
329 329 @view_config(
330 330 route_name='repo_commit', request_method='GET',
331 331 renderer=None)
332 332 def repo_commit_show(self):
333 333 commit_id = self.request.matchdict['commit_id']
334 334 return self._commit(commit_id, method='show')
335 335
336 336 @LoginRequired()
337 337 @HasRepoPermissionAnyDecorator(
338 338 'repository.read', 'repository.write', 'repository.admin')
339 339 @view_config(
340 340 route_name='repo_commit_raw', request_method='GET',
341 341 renderer=None)
342 342 @view_config(
343 343 route_name='repo_commit_raw_deprecated', request_method='GET',
344 344 renderer=None)
345 345 def repo_commit_raw(self):
346 346 commit_id = self.request.matchdict['commit_id']
347 347 return self._commit(commit_id, method='raw')
348 348
349 349 @LoginRequired()
350 350 @HasRepoPermissionAnyDecorator(
351 351 'repository.read', 'repository.write', 'repository.admin')
352 352 @view_config(
353 353 route_name='repo_commit_patch', request_method='GET',
354 354 renderer=None)
355 355 def repo_commit_patch(self):
356 356 commit_id = self.request.matchdict['commit_id']
357 357 return self._commit(commit_id, method='patch')
358 358
359 359 @LoginRequired()
360 360 @HasRepoPermissionAnyDecorator(
361 361 'repository.read', 'repository.write', 'repository.admin')
362 362 @view_config(
363 363 route_name='repo_commit_download', request_method='GET',
364 364 renderer=None)
365 365 def repo_commit_download(self):
366 366 commit_id = self.request.matchdict['commit_id']
367 367 return self._commit(commit_id, method='download')
368 368
369 369 @LoginRequired()
370 370 @NotAnonymous()
371 371 @HasRepoPermissionAnyDecorator(
372 372 'repository.read', 'repository.write', 'repository.admin')
373 373 @CSRFRequired()
374 374 @view_config(
375 375 route_name='repo_commit_comment_create', request_method='POST',
376 376 renderer='json_ext')
377 377 def repo_commit_comment_create(self):
378 378 _ = self.request.translate
379 379 commit_id = self.request.matchdict['commit_id']
380 380
381 381 c = self.load_default_context()
382 382 status = self.request.POST.get('changeset_status', None)
383 is_draft = str2bool(self.request.POST.get('draft'))
383 384 text = self.request.POST.get('text')
384 385 comment_type = self.request.POST.get('comment_type')
385 386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 387 f_path = self.request.POST.get('f_path')
387 388 line_no = self.request.POST.get('line')
388 389 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
389 390
390 391 if status:
391 392 text = text or (_('Status change %(transition_icon)s %(status)s')
392 393 % {'transition_icon': '>',
393 394 'status': ChangesetStatus.get_status_lbl(status)})
394 395
395 396 multi_commit_ids = []
396 397 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
397 398 if _commit_id not in ['', None, EmptyCommit.raw_id]:
398 399 if _commit_id not in multi_commit_ids:
399 400 multi_commit_ids.append(_commit_id)
400 401
401 402 commit_ids = multi_commit_ids or [commit_id]
402 403
403 comment = None
404 data = {}
405 # Multiple comments for each passed commit id
404 406 for current_id in filter(None, commit_ids):
405 407 comment = CommentsModel().create(
406 408 text=text,
407 409 repo=self.db_repo.repo_id,
408 410 user=self._rhodecode_db_user.user_id,
409 411 commit_id=current_id,
410 412 f_path=f_path,
411 413 line_no=line_no,
412 414 status_change=(ChangesetStatus.get_status_lbl(status)
413 415 if status else None),
414 416 status_change_type=status,
415 417 comment_type=comment_type,
418 is_draft=is_draft,
416 419 resolves_comment_id=resolves_comment_id,
417 auth_user=self._rhodecode_user
420 auth_user=self._rhodecode_user,
421 send_email=not is_draft, # skip notification for draft comments
418 422 )
419 423 is_inline = comment.is_inline
420 424
421 425 # get status if set !
422 426 if status:
423 427 # if latest status was from pull request and it's closed
424 428 # disallow changing status !
425 429 # dont_allow_on_closed_pull_request = True !
426 430
427 431 try:
428 432 ChangesetStatusModel().set_status(
429 433 self.db_repo.repo_id,
430 434 status,
431 435 self._rhodecode_db_user.user_id,
432 436 comment,
433 437 revision=current_id,
434 438 dont_allow_on_closed_pull_request=True
435 439 )
436 440 except StatusChangeOnClosedPullRequestError:
437 441 msg = _('Changing the status of a commit associated with '
438 442 'a closed pull request is not allowed')
439 443 log.exception(msg)
440 444 h.flash(msg, category='warning')
441 445 raise HTTPFound(h.route_path(
442 446 'repo_commit', repo_name=self.db_repo_name,
443 447 commit_id=current_id))
444 448
445 commit = self.db_repo.get_commit(current_id)
446 CommentsModel().trigger_commit_comment_hook(
447 self.db_repo, self._rhodecode_user, 'create',
448 data={'comment': comment, 'commit': commit})
449 # skip notifications for drafts
450 if not is_draft:
451 commit = self.db_repo.get_commit(current_id)
452 CommentsModel().trigger_commit_comment_hook(
453 self.db_repo, self._rhodecode_user, 'create',
454 data={'comment': comment, 'commit': commit})
449 455
450 # finalize, commit and redirect
451 Session().commit()
452
453 data = {}
454 if comment:
455 456 comment_id = comment.comment_id
456 457 data[comment_id] = {
457 458 'target_id': target_elem_id
458 459 }
459 460 c.co = comment
460 461 c.at_version_num = 0
462 c.is_new = True
461 463 rendered_comment = render(
462 464 'rhodecode:templates/changeset/changeset_comment_block.mako',
463 465 self._get_template_context(c), self.request)
464 466
465 467 data[comment_id].update(comment.get_dict())
466 468 data[comment_id].update({'rendered_text': rendered_comment})
467 469
468 comment_broadcast_channel = channelstream.comment_channel(
469 self.db_repo_name, commit_obj=commit)
470 # skip channelstream for draft comments
471 if not is_draft:
472 comment_broadcast_channel = channelstream.comment_channel(
473 self.db_repo_name, commit_obj=commit)
470 474
471 comment_data = data
472 comment_type = 'inline' if is_inline else 'general'
473 channelstream.comment_channelstream_push(
474 self.request, comment_broadcast_channel, self._rhodecode_user,
475 _('posted a new {} comment').format(comment_type),
476 comment_data=comment_data)
475 comment_data = data
476 comment_type = 'inline' if is_inline else 'general'
477 channelstream.comment_channelstream_push(
478 self.request, comment_broadcast_channel, self._rhodecode_user,
479 _('posted a new {} comment').format(comment_type),
480 comment_data=comment_data)
481
482 # finalize, commit and redirect
483 Session().commit()
477 484
478 485 return data
479 486
480 487 @LoginRequired()
481 488 @NotAnonymous()
482 489 @HasRepoPermissionAnyDecorator(
483 490 'repository.read', 'repository.write', 'repository.admin')
484 491 @CSRFRequired()
485 492 @view_config(
486 493 route_name='repo_commit_comment_preview', request_method='POST',
487 494 renderer='string', xhr=True)
488 495 def repo_commit_comment_preview(self):
489 496 # Technically a CSRF token is not needed as no state changes with this
490 497 # call. However, as this is a POST is better to have it, so automated
491 498 # tools don't flag it as potential CSRF.
492 499 # Post is required because the payload could be bigger than the maximum
493 500 # allowed by GET.
494 501
495 502 text = self.request.POST.get('text')
496 503 renderer = self.request.POST.get('renderer') or 'rst'
497 504 if text:
498 505 return h.render(text, renderer=renderer, mentions=True,
499 506 repo_name=self.db_repo_name)
500 507 return ''
501 508
502 509 @LoginRequired()
503 510 @HasRepoPermissionAnyDecorator(
504 511 'repository.read', 'repository.write', 'repository.admin')
505 512 @CSRFRequired()
506 513 @view_config(
507 514 route_name='repo_commit_comment_history_view', request_method='POST',
508 515 renderer='string', xhr=True)
509 516 def repo_commit_comment_history_view(self):
510 517 c = self.load_default_context()
511 518
512 519 comment_history_id = self.request.matchdict['comment_history_id']
513 520 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
514 521 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
515 522
516 523 if is_repo_comment:
517 524 c.comment_history = comment_history
518 525
519 526 rendered_comment = render(
520 527 'rhodecode:templates/changeset/comment_history.mako',
521 528 self._get_template_context(c)
522 529 , self.request)
523 530 return rendered_comment
524 531 else:
525 532 log.warning('No permissions for user %s to show comment_history_id: %s',
526 533 self._rhodecode_db_user, comment_history_id)
527 534 raise HTTPNotFound()
528 535
529 536 @LoginRequired()
530 537 @NotAnonymous()
531 538 @HasRepoPermissionAnyDecorator(
532 539 'repository.read', 'repository.write', 'repository.admin')
533 540 @CSRFRequired()
534 541 @view_config(
535 542 route_name='repo_commit_comment_attachment_upload', request_method='POST',
536 543 renderer='json_ext', xhr=True)
537 544 def repo_commit_comment_attachment_upload(self):
538 545 c = self.load_default_context()
539 546 upload_key = 'attachment'
540 547
541 548 file_obj = self.request.POST.get(upload_key)
542 549
543 550 if file_obj is None:
544 551 self.request.response.status = 400
545 552 return {'store_fid': None,
546 553 'access_path': None,
547 554 'error': '{} data field is missing'.format(upload_key)}
548 555
549 556 if not hasattr(file_obj, 'filename'):
550 557 self.request.response.status = 400
551 558 return {'store_fid': None,
552 559 'access_path': None,
553 560 'error': 'filename cannot be read from the data field'}
554 561
555 562 filename = file_obj.filename
556 563 file_display_name = filename
557 564
558 565 metadata = {
559 566 'user_uploaded': {'username': self._rhodecode_user.username,
560 567 'user_id': self._rhodecode_user.user_id,
561 568 'ip': self._rhodecode_user.ip_addr}}
562 569
563 570 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
564 571 allowed_extensions = [
565 572 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
566 573 '.pptx', '.txt', '.xlsx', '.zip']
567 574 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
568 575
569 576 try:
570 577 storage = store_utils.get_file_storage(self.request.registry.settings)
571 578 store_uid, metadata = storage.save_file(
572 579 file_obj.file, filename, extra_metadata=metadata,
573 580 extensions=allowed_extensions, max_filesize=max_file_size)
574 581 except FileNotAllowedException:
575 582 self.request.response.status = 400
576 583 permitted_extensions = ', '.join(allowed_extensions)
577 584 error_msg = 'File `{}` is not allowed. ' \
578 585 'Only following extensions are permitted: {}'.format(
579 586 filename, permitted_extensions)
580 587 return {'store_fid': None,
581 588 'access_path': None,
582 589 'error': error_msg}
583 590 except FileOverSizeException:
584 591 self.request.response.status = 400
585 592 limit_mb = h.format_byte_size_binary(max_file_size)
586 593 return {'store_fid': None,
587 594 'access_path': None,
588 595 'error': 'File {} is exceeding allowed limit of {}.'.format(
589 596 filename, limit_mb)}
590 597
591 598 try:
592 599 entry = FileStore.create(
593 600 file_uid=store_uid, filename=metadata["filename"],
594 601 file_hash=metadata["sha256"], file_size=metadata["size"],
595 602 file_display_name=file_display_name,
596 603 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
597 604 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
598 605 scope_repo_id=self.db_repo.repo_id
599 606 )
600 607 Session().add(entry)
601 608 Session().commit()
602 609 log.debug('Stored upload in DB as %s', entry)
603 610 except Exception:
604 611 log.exception('Failed to store file %s', filename)
605 612 self.request.response.status = 400
606 613 return {'store_fid': None,
607 614 'access_path': None,
608 615 'error': 'File {} failed to store in DB.'.format(filename)}
609 616
610 617 Session().commit()
611 618
612 619 return {
613 620 'store_fid': store_uid,
614 621 'access_path': h.route_path(
615 622 'download_file', fid=store_uid),
616 623 'fqn_access_path': h.route_url(
617 624 'download_file', fid=store_uid),
618 625 'repo_access_path': h.route_path(
619 626 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
620 627 'repo_fqn_access_path': h.route_url(
621 628 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
622 629 }
623 630
624 631 @LoginRequired()
625 632 @NotAnonymous()
626 633 @HasRepoPermissionAnyDecorator(
627 634 'repository.read', 'repository.write', 'repository.admin')
628 635 @CSRFRequired()
629 636 @view_config(
630 637 route_name='repo_commit_comment_delete', request_method='POST',
631 638 renderer='json_ext')
632 639 def repo_commit_comment_delete(self):
633 640 commit_id = self.request.matchdict['commit_id']
634 641 comment_id = self.request.matchdict['comment_id']
635 642
636 643 comment = ChangesetComment.get_or_404(comment_id)
637 644 if not comment:
638 645 log.debug('Comment with id:%s not found, skipping', comment_id)
639 646 # comment already deleted in another call probably
640 647 return True
641 648
642 649 if comment.immutable:
643 650 # don't allow deleting comments that are immutable
644 651 raise HTTPForbidden()
645 652
646 653 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
647 654 super_admin = h.HasPermissionAny('hg.admin')()
648 655 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
649 656 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
650 657 comment_repo_admin = is_repo_admin and is_repo_comment
651 658
652 659 if super_admin or comment_owner or comment_repo_admin:
653 660 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
654 661 Session().commit()
655 662 return True
656 663 else:
657 664 log.warning('No permissions for user %s to delete comment_id: %s',
658 665 self._rhodecode_db_user, comment_id)
659 666 raise HTTPNotFound()
660 667
661 668 @LoginRequired()
662 669 @NotAnonymous()
663 670 @HasRepoPermissionAnyDecorator(
664 671 'repository.read', 'repository.write', 'repository.admin')
665 672 @CSRFRequired()
666 673 @view_config(
667 674 route_name='repo_commit_comment_edit', request_method='POST',
668 675 renderer='json_ext')
669 676 def repo_commit_comment_edit(self):
670 677 self.load_default_context()
671 678
672 679 comment_id = self.request.matchdict['comment_id']
673 680 comment = ChangesetComment.get_or_404(comment_id)
674 681
675 682 if comment.immutable:
676 683 # don't allow deleting comments that are immutable
677 684 raise HTTPForbidden()
678 685
679 686 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
680 687 super_admin = h.HasPermissionAny('hg.admin')()
681 688 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
682 689 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
683 690 comment_repo_admin = is_repo_admin and is_repo_comment
684 691
685 692 if super_admin or comment_owner or comment_repo_admin:
686 693 text = self.request.POST.get('text')
687 694 version = self.request.POST.get('version')
688 695 if text == comment.text:
689 696 log.warning(
690 697 'Comment(repo): '
691 698 'Trying to create new version '
692 699 'with the same comment body {}'.format(
693 700 comment_id,
694 701 )
695 702 )
696 703 raise HTTPNotFound()
697 704
698 705 if version.isdigit():
699 706 version = int(version)
700 707 else:
701 708 log.warning(
702 709 'Comment(repo): Wrong version type {} {} '
703 710 'for comment {}'.format(
704 711 version,
705 712 type(version),
706 713 comment_id,
707 714 )
708 715 )
709 716 raise HTTPNotFound()
710 717
711 718 try:
712 719 comment_history = CommentsModel().edit(
713 720 comment_id=comment_id,
714 721 text=text,
715 722 auth_user=self._rhodecode_user,
716 723 version=version,
717 724 )
718 725 except CommentVersionMismatch:
719 726 raise HTTPConflict()
720 727
721 728 if not comment_history:
722 729 raise HTTPNotFound()
723 730
724 731 commit_id = self.request.matchdict['commit_id']
725 732 commit = self.db_repo.get_commit(commit_id)
726 733 CommentsModel().trigger_commit_comment_hook(
727 734 self.db_repo, self._rhodecode_user, 'edit',
728 735 data={'comment': comment, 'commit': commit})
729 736
730 737 Session().commit()
731 738 return {
732 739 'comment_history_id': comment_history.comment_history_id,
733 740 'comment_id': comment.comment_id,
734 741 'comment_version': comment_history.version,
735 742 'comment_author_username': comment_history.author.username,
736 743 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
737 744 'comment_created_on': h.age_component(comment_history.created_on,
738 745 time_is_local=True),
739 746 }
740 747 else:
741 748 log.warning('No permissions for user %s to edit comment_id: %s',
742 749 self._rhodecode_db_user, comment_id)
743 750 raise HTTPNotFound()
744 751
745 752 @LoginRequired()
746 753 @HasRepoPermissionAnyDecorator(
747 754 'repository.read', 'repository.write', 'repository.admin')
748 755 @view_config(
749 756 route_name='repo_commit_data', request_method='GET',
750 757 renderer='json_ext', xhr=True)
751 758 def repo_commit_data(self):
752 759 commit_id = self.request.matchdict['commit_id']
753 760 self.load_default_context()
754 761
755 762 try:
756 763 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
757 764 except CommitDoesNotExistError as e:
758 765 return EmptyCommit(message=str(e))
759 766
760 767 @LoginRequired()
761 768 @HasRepoPermissionAnyDecorator(
762 769 'repository.read', 'repository.write', 'repository.admin')
763 770 @view_config(
764 771 route_name='repo_commit_children', request_method='GET',
765 772 renderer='json_ext', xhr=True)
766 773 def repo_commit_children(self):
767 774 commit_id = self.request.matchdict['commit_id']
768 775 self.load_default_context()
769 776
770 777 try:
771 778 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
772 779 children = commit.children
773 780 except CommitDoesNotExistError:
774 781 children = []
775 782
776 783 result = {"results": children}
777 784 return result
778 785
779 786 @LoginRequired()
780 787 @HasRepoPermissionAnyDecorator(
781 788 'repository.read', 'repository.write', 'repository.admin')
782 789 @view_config(
783 790 route_name='repo_commit_parents', request_method='GET',
784 791 renderer='json_ext')
785 792 def repo_commit_parents(self):
786 793 commit_id = self.request.matchdict['commit_id']
787 794 self.load_default_context()
788 795
789 796 try:
790 797 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
791 798 parents = commit.parents
792 799 except CommitDoesNotExistError:
793 800 parents = []
794 801 result = {"results": parents}
795 802 return result
General Comments 0
You need to be logged in to leave comments. Login now