##// END OF EJS Templates
issue-tracker: use stored issueTracker patterns for repo instead of initializing model every time.
bart -
r4201:ecf6f84c stable
parent child Browse files
Show More
@@ -1,802 +1,803 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 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 time
22 22 import logging
23 23 import operator
24 24
25 25 from pyramid import compat
26 26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27 27
28 28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 29 from rhodecode.lib.utils2 import (
30 30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
32 32 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
34 34 from rhodecode.model import repo
35 35 from rhodecode.model import repo_group
36 36 from rhodecode.model import user_group
37 37 from rhodecode.model import user
38 38 from rhodecode.model.db import User
39 39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.settings import VcsSettingsModel
40 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
41 41 from rhodecode.model.repo import ReadmeFinder
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 ADMIN_PREFIX = '/_admin'
47 47 STATIC_FILE_PREFIX = '/_static'
48 48
49 49 URL_NAME_REQUIREMENTS = {
50 50 # group name can have a slash in them, but they must not end with a slash
51 51 'group_name': r'.*?[^/]',
52 52 'repo_group_name': r'.*?[^/]',
53 53 # repo names can have a slash in them, but they must not end with a slash
54 54 'repo_name': r'.*?[^/]',
55 55 # file path eats up everything at the end
56 56 'f_path': r'.*',
57 57 # reference types
58 58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
59 59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
60 60 }
61 61
62 62
63 63 def add_route_with_slash(config,name, pattern, **kw):
64 64 config.add_route(name, pattern, **kw)
65 65 if not pattern.endswith('/'):
66 66 config.add_route(name + '_slash', pattern + '/', **kw)
67 67
68 68
69 69 def add_route_requirements(route_path, requirements=None):
70 70 """
71 71 Adds regex requirements to pyramid routes using a mapping dict
72 72 e.g::
73 73 add_route_requirements('{repo_name}/settings')
74 74 """
75 75 requirements = requirements or URL_NAME_REQUIREMENTS
76 76 for key, regex in requirements.items():
77 77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
78 78 return route_path
79 79
80 80
81 81 def get_format_ref_id(repo):
82 82 """Returns a `repo` specific reference formatter function"""
83 83 if h.is_svn(repo):
84 84 return _format_ref_id_svn
85 85 else:
86 86 return _format_ref_id
87 87
88 88
89 89 def _format_ref_id(name, raw_id):
90 90 """Default formatting of a given reference `name`"""
91 91 return name
92 92
93 93
94 94 def _format_ref_id_svn(name, raw_id):
95 95 """Special way of formatting a reference for Subversion including path"""
96 96 return '%s@%s' % (name, raw_id)
97 97
98 98
99 99 class TemplateArgs(StrictAttributeDict):
100 100 pass
101 101
102 102
103 103 class BaseAppView(object):
104 104
105 105 def __init__(self, context, request):
106 106 self.request = request
107 107 self.context = context
108 108 self.session = request.session
109 109 if not hasattr(request, 'user'):
110 110 # NOTE(marcink): edge case, we ended up in matched route
111 111 # but probably of web-app context, e.g API CALL/VCS CALL
112 112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
113 113 log.warning('Unable to process request `%s` in this scope', request)
114 114 raise HTTPBadRequest()
115 115
116 116 self._rhodecode_user = request.user # auth user
117 117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
118 118 self._maybe_needs_password_change(
119 119 request.matched_route.name, self._rhodecode_db_user)
120 120
121 121 def _maybe_needs_password_change(self, view_name, user_obj):
122 122 log.debug('Checking if user %s needs password change on view %s',
123 123 user_obj, view_name)
124 124 skip_user_views = [
125 125 'logout', 'login',
126 126 'my_account_password', 'my_account_password_update'
127 127 ]
128 128
129 129 if not user_obj:
130 130 return
131 131
132 132 if user_obj.username == User.DEFAULT_USER:
133 133 return
134 134
135 135 now = time.time()
136 136 should_change = user_obj.user_data.get('force_password_change')
137 137 change_after = safe_int(should_change) or 0
138 138 if should_change and now > change_after:
139 139 log.debug('User %s requires password change', user_obj)
140 140 h.flash('You are required to change your password', 'warning',
141 141 ignore_duplicate=True)
142 142
143 143 if view_name not in skip_user_views:
144 144 raise HTTPFound(
145 145 self.request.route_path('my_account_password'))
146 146
147 147 def _log_creation_exception(self, e, repo_name):
148 148 _ = self.request.translate
149 149 reason = None
150 150 if len(e.args) == 2:
151 151 reason = e.args[1]
152 152
153 153 if reason == 'INVALID_CERTIFICATE':
154 154 log.exception(
155 155 'Exception creating a repository: invalid certificate')
156 156 msg = (_('Error creating repository %s: invalid certificate')
157 157 % repo_name)
158 158 else:
159 159 log.exception("Exception creating a repository")
160 160 msg = (_('Error creating repository %s')
161 161 % repo_name)
162 162 return msg
163 163
164 164 def _get_local_tmpl_context(self, include_app_defaults=True):
165 165 c = TemplateArgs()
166 166 c.auth_user = self.request.user
167 167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
168 168 c.rhodecode_user = self.request.user
169 169
170 170 if include_app_defaults:
171 171 from rhodecode.lib.base import attach_context_attributes
172 172 attach_context_attributes(c, self.request, self.request.user.user_id)
173 173
174 174 c.is_super_admin = c.auth_user.is_admin
175 175
176 176 c.can_create_repo = c.is_super_admin
177 177 c.can_create_repo_group = c.is_super_admin
178 178 c.can_create_user_group = c.is_super_admin
179 179
180 180 c.is_delegated_admin = False
181 181
182 182 if not c.auth_user.is_default and not c.is_super_admin:
183 183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
184 184 user=self.request.user)
185 185 repositories = c.auth_user.repositories_admin or c.can_create_repo
186 186
187 187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
188 188 user=self.request.user)
189 189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
190 190
191 191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
192 192 user=self.request.user)
193 193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
194 194 # delegated admin can create, or manage some objects
195 195 c.is_delegated_admin = repositories or repository_groups or user_groups
196 196 return c
197 197
198 198 def _get_template_context(self, tmpl_args, **kwargs):
199 199
200 200 local_tmpl_args = {
201 201 'defaults': {},
202 202 'errors': {},
203 203 'c': tmpl_args
204 204 }
205 205 local_tmpl_args.update(kwargs)
206 206 return local_tmpl_args
207 207
208 208 def load_default_context(self):
209 209 """
210 210 example:
211 211
212 212 def load_default_context(self):
213 213 c = self._get_local_tmpl_context()
214 214 c.custom_var = 'foobar'
215 215
216 216 return c
217 217 """
218 218 raise NotImplementedError('Needs implementation in view class')
219 219
220 220
221 221 class RepoAppView(BaseAppView):
222 222
223 223 def __init__(self, context, request):
224 224 super(RepoAppView, self).__init__(context, request)
225 225 self.db_repo = request.db_repo
226 226 self.db_repo_name = self.db_repo.repo_name
227 227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
228 228 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
229 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
229 230
230 231 def _handle_missing_requirements(self, error):
231 232 log.error(
232 233 'Requirements are missing for repository %s: %s',
233 234 self.db_repo_name, safe_unicode(error))
234 235
235 236 def _get_local_tmpl_context(self, include_app_defaults=True):
236 237 _ = self.request.translate
237 238 c = super(RepoAppView, self)._get_local_tmpl_context(
238 239 include_app_defaults=include_app_defaults)
239 240
240 241 # register common vars for this type of view
241 242 c.rhodecode_db_repo = self.db_repo
242 243 c.repo_name = self.db_repo_name
243 244 c.repository_pull_requests = self.db_repo_pull_requests
244 245 c.repository_artifacts = self.db_repo_artifacts
245 246 c.repository_is_user_following = ScmModel().is_following_repo(
246 247 self.db_repo_name, self._rhodecode_user.user_id)
247 248 self.path_filter = PathFilter(None)
248 249
249 250 c.repository_requirements_missing = {}
250 251 try:
251 252 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
252 253 # NOTE(marcink):
253 254 # comparison to None since if it's an object __bool__ is expensive to
254 255 # calculate
255 256 if self.rhodecode_vcs_repo is not None:
256 257 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
257 258 c.auth_user.username)
258 259 self.path_filter = PathFilter(path_perms)
259 260 except RepositoryRequirementError as e:
260 261 c.repository_requirements_missing = {'error': str(e)}
261 262 self._handle_missing_requirements(e)
262 263 self.rhodecode_vcs_repo = None
263 264
264 265 c.path_filter = self.path_filter # used by atom_feed_entry.mako
265 266
266 267 if self.rhodecode_vcs_repo is None:
267 268 # unable to fetch this repo as vcs instance, report back to user
268 269 h.flash(_(
269 270 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
270 271 "Please check if it exist, or is not damaged.") %
271 272 {'repo_name': c.repo_name},
272 273 category='error', ignore_duplicate=True)
273 274 if c.repository_requirements_missing:
274 275 route = self.request.matched_route.name
275 276 if route.startswith(('edit_repo', 'repo_summary')):
276 277 # allow summary and edit repo on missing requirements
277 278 return c
278 279
279 280 raise HTTPFound(
280 281 h.route_path('repo_summary', repo_name=self.db_repo_name))
281 282
282 283 else: # redirect if we don't show missing requirements
283 284 raise HTTPFound(h.route_path('home'))
284 285
285 286 c.has_origin_repo_read_perm = False
286 287 if self.db_repo.fork:
287 288 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
288 289 'repository.write', 'repository.read', 'repository.admin')(
289 290 self.db_repo.fork.repo_name, 'summary fork link')
290 291
291 292 return c
292 293
293 294 def _get_f_path_unchecked(self, matchdict, default=None):
294 295 """
295 296 Should only be used by redirects, everything else should call _get_f_path
296 297 """
297 298 f_path = matchdict.get('f_path')
298 299 if f_path:
299 300 # fix for multiple initial slashes that causes errors for GIT
300 301 return f_path.lstrip('/')
301 302
302 303 return default
303 304
304 305 def _get_f_path(self, matchdict, default=None):
305 306 f_path_match = self._get_f_path_unchecked(matchdict, default)
306 307 return self.path_filter.assert_path_permissions(f_path_match)
307 308
308 309 def _get_general_setting(self, target_repo, settings_key, default=False):
309 310 settings_model = VcsSettingsModel(repo=target_repo)
310 311 settings = settings_model.get_general_settings()
311 312 return settings.get(settings_key, default)
312 313
313 314 def _get_repo_setting(self, target_repo, settings_key, default=False):
314 315 settings_model = VcsSettingsModel(repo=target_repo)
315 316 settings = settings_model.get_repo_settings_inherited()
316 317 return settings.get(settings_key, default)
317 318
318 319 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
319 320 log.debug('Looking for README file at path %s', path)
320 321 if commit_id:
321 322 landing_commit_id = commit_id
322 323 else:
323 324 landing_commit = db_repo.get_landing_commit()
324 325 if isinstance(landing_commit, EmptyCommit):
325 326 return None, None
326 327 landing_commit_id = landing_commit.raw_id
327 328
328 329 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
329 330 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
330 331 start = time.time()
331 332
332 333 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
333 334 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
334 335 readme_data = None
335 336 readme_filename = None
336 337
337 338 commit = db_repo.get_commit(_commit_id)
338 339 log.debug("Searching for a README file at commit %s.", _commit_id)
339 340 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
340 341
341 342 if readme_node:
342 343 log.debug('Found README node: %s', readme_node)
343 344 relative_urls = {
344 345 'raw': h.route_path(
345 346 'repo_file_raw', repo_name=_repo_name,
346 347 commit_id=commit.raw_id, f_path=readme_node.path),
347 348 'standard': h.route_path(
348 349 'repo_files', repo_name=_repo_name,
349 350 commit_id=commit.raw_id, f_path=readme_node.path),
350 351 }
351 352 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
352 353 readme_filename = readme_node.unicode_path
353 354
354 355 return readme_data, readme_filename
355 356
356 357 readme_data, readme_filename = generate_repo_readme(
357 358 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
358 359 compute_time = time.time() - start
359 360 log.debug('Repo README for path %s generated and computed in %.4fs',
360 361 path, compute_time)
361 362 return readme_data, readme_filename
362 363
363 364 def _render_readme_or_none(self, commit, readme_node, relative_urls):
364 365 log.debug('Found README file `%s` rendering...', readme_node.path)
365 366 renderer = MarkupRenderer()
366 367 try:
367 368 html_source = renderer.render(
368 369 readme_node.content, filename=readme_node.path)
369 370 if relative_urls:
370 371 return relative_links(html_source, relative_urls)
371 372 return html_source
372 373 except Exception:
373 374 log.exception(
374 375 "Exception while trying to render the README")
375 376
376 377 def get_recache_flag(self):
377 378 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
378 379 flag_val = self.request.GET.get(flag_name)
379 380 if str2bool(flag_val):
380 381 return True
381 382 return False
382 383
383 384
384 385 class PathFilter(object):
385 386
386 387 # Expects and instance of BasePathPermissionChecker or None
387 388 def __init__(self, permission_checker):
388 389 self.permission_checker = permission_checker
389 390
390 391 def assert_path_permissions(self, path):
391 392 if self.path_access_allowed(path):
392 393 return path
393 394 raise HTTPForbidden()
394 395
395 396 def path_access_allowed(self, path):
396 397 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
397 398 if self.permission_checker:
398 399 return path and self.permission_checker.has_access(path)
399 400 return True
400 401
401 402 def filter_patchset(self, patchset):
402 403 if not self.permission_checker or not patchset:
403 404 return patchset, False
404 405 had_filtered = False
405 406 filtered_patchset = []
406 407 for patch in patchset:
407 408 filename = patch.get('filename', None)
408 409 if not filename or self.permission_checker.has_access(filename):
409 410 filtered_patchset.append(patch)
410 411 else:
411 412 had_filtered = True
412 413 if had_filtered:
413 414 if isinstance(patchset, diffs.LimitedDiffContainer):
414 415 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
415 416 return filtered_patchset, True
416 417 else:
417 418 return patchset, False
418 419
419 420 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
420 421 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
421 422 result = diffset.render_patchset(
422 423 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
423 424 result.has_hidden_changes = has_hidden_changes
424 425 return result
425 426
426 427 def get_raw_patch(self, diff_processor):
427 428 if self.permission_checker is None:
428 429 return diff_processor.as_raw()
429 430 elif self.permission_checker.has_full_access:
430 431 return diff_processor.as_raw()
431 432 else:
432 433 return '# Repository has user-specific filters, raw patch generation is disabled.'
433 434
434 435 @property
435 436 def is_enabled(self):
436 437 return self.permission_checker is not None
437 438
438 439
439 440 class RepoGroupAppView(BaseAppView):
440 441 def __init__(self, context, request):
441 442 super(RepoGroupAppView, self).__init__(context, request)
442 443 self.db_repo_group = request.db_repo_group
443 444 self.db_repo_group_name = self.db_repo_group.group_name
444 445
445 446 def _get_local_tmpl_context(self, include_app_defaults=True):
446 447 _ = self.request.translate
447 448 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
448 449 include_app_defaults=include_app_defaults)
449 450 c.repo_group = self.db_repo_group
450 451 return c
451 452
452 453 def _revoke_perms_on_yourself(self, form_result):
453 454 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
454 455 form_result['perm_updates'])
455 456 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
456 457 form_result['perm_additions'])
457 458 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
458 459 form_result['perm_deletions'])
459 460 admin_perm = 'group.admin'
460 461 if _updates and _updates[0][1] != admin_perm or \
461 462 _additions and _additions[0][1] != admin_perm or \
462 463 _deletions and _deletions[0][1] != admin_perm:
463 464 return True
464 465 return False
465 466
466 467
467 468 class UserGroupAppView(BaseAppView):
468 469 def __init__(self, context, request):
469 470 super(UserGroupAppView, self).__init__(context, request)
470 471 self.db_user_group = request.db_user_group
471 472 self.db_user_group_name = self.db_user_group.users_group_name
472 473
473 474
474 475 class UserAppView(BaseAppView):
475 476 def __init__(self, context, request):
476 477 super(UserAppView, self).__init__(context, request)
477 478 self.db_user = request.db_user
478 479 self.db_user_id = self.db_user.user_id
479 480
480 481 _ = self.request.translate
481 482 if not request.db_user_supports_default:
482 483 if self.db_user.username == User.DEFAULT_USER:
483 484 h.flash(_("Editing user `{}` is disabled.".format(
484 485 User.DEFAULT_USER)), category='warning')
485 486 raise HTTPFound(h.route_path('users'))
486 487
487 488
488 489 class DataGridAppView(object):
489 490 """
490 491 Common class to have re-usable grid rendering components
491 492 """
492 493
493 494 def _extract_ordering(self, request, column_map=None):
494 495 column_map = column_map or {}
495 496 column_index = safe_int(request.GET.get('order[0][column]'))
496 497 order_dir = request.GET.get(
497 498 'order[0][dir]', 'desc')
498 499 order_by = request.GET.get(
499 500 'columns[%s][data][sort]' % column_index, 'name_raw')
500 501
501 502 # translate datatable to DB columns
502 503 order_by = column_map.get(order_by) or order_by
503 504
504 505 search_q = request.GET.get('search[value]')
505 506 return search_q, order_by, order_dir
506 507
507 508 def _extract_chunk(self, request):
508 509 start = safe_int(request.GET.get('start'), 0)
509 510 length = safe_int(request.GET.get('length'), 25)
510 511 draw = safe_int(request.GET.get('draw'))
511 512 return draw, start, length
512 513
513 514 def _get_order_col(self, order_by, model):
514 515 if isinstance(order_by, compat.string_types):
515 516 try:
516 517 return operator.attrgetter(order_by)(model)
517 518 except AttributeError:
518 519 return None
519 520 else:
520 521 return order_by
521 522
522 523
523 524 class BaseReferencesView(RepoAppView):
524 525 """
525 526 Base for reference view for branches, tags and bookmarks.
526 527 """
527 528 def load_default_context(self):
528 529 c = self._get_local_tmpl_context()
529 530
530 531
531 532 return c
532 533
533 534 def load_refs_context(self, ref_items, partials_template):
534 535 _render = self.request.get_partial_renderer(partials_template)
535 536 pre_load = ["author", "date", "message", "parents"]
536 537
537 538 is_svn = h.is_svn(self.rhodecode_vcs_repo)
538 539 is_hg = h.is_hg(self.rhodecode_vcs_repo)
539 540
540 541 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
541 542
542 543 closed_refs = {}
543 544 if is_hg:
544 545 closed_refs = self.rhodecode_vcs_repo.branches_closed
545 546
546 547 data = []
547 548 for ref_name, commit_id in ref_items:
548 549 commit = self.rhodecode_vcs_repo.get_commit(
549 550 commit_id=commit_id, pre_load=pre_load)
550 551 closed = ref_name in closed_refs
551 552
552 553 # TODO: johbo: Unify generation of reference links
553 554 use_commit_id = '/' in ref_name or is_svn
554 555
555 556 if use_commit_id:
556 557 files_url = h.route_path(
557 558 'repo_files',
558 559 repo_name=self.db_repo_name,
559 560 f_path=ref_name if is_svn else '',
560 561 commit_id=commit_id)
561 562
562 563 else:
563 564 files_url = h.route_path(
564 565 'repo_files',
565 566 repo_name=self.db_repo_name,
566 567 f_path=ref_name if is_svn else '',
567 568 commit_id=ref_name,
568 569 _query=dict(at=ref_name))
569 570
570 571 data.append({
571 572 "name": _render('name', ref_name, files_url, closed),
572 573 "name_raw": ref_name,
573 574 "date": _render('date', commit.date),
574 575 "date_raw": datetime_to_time(commit.date),
575 576 "author": _render('author', commit.author),
576 577 "commit": _render(
577 578 'commit', commit.message, commit.raw_id, commit.idx),
578 579 "commit_raw": commit.idx,
579 580 "compare": _render(
580 581 'compare', format_ref_id(ref_name, commit.raw_id)),
581 582 })
582 583
583 584 return data
584 585
585 586
586 587 class RepoRoutePredicate(object):
587 588 def __init__(self, val, config):
588 589 self.val = val
589 590
590 591 def text(self):
591 592 return 'repo_route = %s' % self.val
592 593
593 594 phash = text
594 595
595 596 def __call__(self, info, request):
596 597 if hasattr(request, 'vcs_call'):
597 598 # skip vcs calls
598 599 return
599 600
600 601 repo_name = info['match']['repo_name']
601 602 repo_model = repo.RepoModel()
602 603
603 604 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
604 605
605 606 def redirect_if_creating(route_info, db_repo):
606 607 skip_views = ['edit_repo_advanced_delete']
607 608 route = route_info['route']
608 609 # we should skip delete view so we can actually "remove" repositories
609 610 # if they get stuck in creating state.
610 611 if route.name in skip_views:
611 612 return
612 613
613 614 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
614 615 repo_creating_url = request.route_path(
615 616 'repo_creating', repo_name=db_repo.repo_name)
616 617 raise HTTPFound(repo_creating_url)
617 618
618 619 if by_name_match:
619 620 # register this as request object we can re-use later
620 621 request.db_repo = by_name_match
621 622 redirect_if_creating(info, by_name_match)
622 623 return True
623 624
624 625 by_id_match = repo_model.get_repo_by_id(repo_name)
625 626 if by_id_match:
626 627 request.db_repo = by_id_match
627 628 redirect_if_creating(info, by_id_match)
628 629 return True
629 630
630 631 return False
631 632
632 633
633 634 class RepoForbidArchivedRoutePredicate(object):
634 635 def __init__(self, val, config):
635 636 self.val = val
636 637
637 638 def text(self):
638 639 return 'repo_forbid_archived = %s' % self.val
639 640
640 641 phash = text
641 642
642 643 def __call__(self, info, request):
643 644 _ = request.translate
644 645 rhodecode_db_repo = request.db_repo
645 646
646 647 log.debug(
647 648 '%s checking if archived flag for repo for %s',
648 649 self.__class__.__name__, rhodecode_db_repo.repo_name)
649 650
650 651 if rhodecode_db_repo.archived:
651 652 log.warning('Current view is not supported for archived repo:%s',
652 653 rhodecode_db_repo.repo_name)
653 654
654 655 h.flash(
655 656 h.literal(_('Action not supported for archived repository.')),
656 657 category='warning')
657 658 summary_url = request.route_path(
658 659 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
659 660 raise HTTPFound(summary_url)
660 661 return True
661 662
662 663
663 664 class RepoTypeRoutePredicate(object):
664 665 def __init__(self, val, config):
665 666 self.val = val or ['hg', 'git', 'svn']
666 667
667 668 def text(self):
668 669 return 'repo_accepted_type = %s' % self.val
669 670
670 671 phash = text
671 672
672 673 def __call__(self, info, request):
673 674 if hasattr(request, 'vcs_call'):
674 675 # skip vcs calls
675 676 return
676 677
677 678 rhodecode_db_repo = request.db_repo
678 679
679 680 log.debug(
680 681 '%s checking repo type for %s in %s',
681 682 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
682 683
683 684 if rhodecode_db_repo.repo_type in self.val:
684 685 return True
685 686 else:
686 687 log.warning('Current view is not supported for repo type:%s',
687 688 rhodecode_db_repo.repo_type)
688 689 return False
689 690
690 691
691 692 class RepoGroupRoutePredicate(object):
692 693 def __init__(self, val, config):
693 694 self.val = val
694 695
695 696 def text(self):
696 697 return 'repo_group_route = %s' % self.val
697 698
698 699 phash = text
699 700
700 701 def __call__(self, info, request):
701 702 if hasattr(request, 'vcs_call'):
702 703 # skip vcs calls
703 704 return
704 705
705 706 repo_group_name = info['match']['repo_group_name']
706 707 repo_group_model = repo_group.RepoGroupModel()
707 708 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
708 709
709 710 if by_name_match:
710 711 # register this as request object we can re-use later
711 712 request.db_repo_group = by_name_match
712 713 return True
713 714
714 715 return False
715 716
716 717
717 718 class UserGroupRoutePredicate(object):
718 719 def __init__(self, val, config):
719 720 self.val = val
720 721
721 722 def text(self):
722 723 return 'user_group_route = %s' % self.val
723 724
724 725 phash = text
725 726
726 727 def __call__(self, info, request):
727 728 if hasattr(request, 'vcs_call'):
728 729 # skip vcs calls
729 730 return
730 731
731 732 user_group_id = info['match']['user_group_id']
732 733 user_group_model = user_group.UserGroup()
733 734 by_id_match = user_group_model.get(user_group_id, cache=False)
734 735
735 736 if by_id_match:
736 737 # register this as request object we can re-use later
737 738 request.db_user_group = by_id_match
738 739 return True
739 740
740 741 return False
741 742
742 743
743 744 class UserRoutePredicateBase(object):
744 745 supports_default = None
745 746
746 747 def __init__(self, val, config):
747 748 self.val = val
748 749
749 750 def text(self):
750 751 raise NotImplementedError()
751 752
752 753 def __call__(self, info, request):
753 754 if hasattr(request, 'vcs_call'):
754 755 # skip vcs calls
755 756 return
756 757
757 758 user_id = info['match']['user_id']
758 759 user_model = user.User()
759 760 by_id_match = user_model.get(user_id, cache=False)
760 761
761 762 if by_id_match:
762 763 # register this as request object we can re-use later
763 764 request.db_user = by_id_match
764 765 request.db_user_supports_default = self.supports_default
765 766 return True
766 767
767 768 return False
768 769
769 770
770 771 class UserRoutePredicate(UserRoutePredicateBase):
771 772 supports_default = False
772 773
773 774 def text(self):
774 775 return 'user_route = %s' % self.val
775 776
776 777 phash = text
777 778
778 779
779 780 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
780 781 supports_default = True
781 782
782 783 def text(self):
783 784 return 'user_with_default_route = %s' % self.val
784 785
785 786 phash = text
786 787
787 788
788 789 def includeme(config):
789 790 config.add_route_predicate(
790 791 'repo_route', RepoRoutePredicate)
791 792 config.add_route_predicate(
792 793 'repo_accepted_types', RepoTypeRoutePredicate)
793 794 config.add_route_predicate(
794 795 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
795 796 config.add_route_predicate(
796 797 'repo_group_route', RepoGroupRoutePredicate)
797 798 config.add_route_predicate(
798 799 'user_group_route', UserGroupRoutePredicate)
799 800 config.add_route_predicate(
800 801 'user_route_with_default', UserRouteWithDefaultPredicate)
801 802 config.add_route_predicate(
802 803 'user_route', UserRoutePredicate)
@@ -1,139 +1,139 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2019 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
23 23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 24 from pyramid.view import view_config
25 25 import formencode
26 26
27 27 from rhodecode.apps._base import RepoAppView
28 28 from rhodecode.lib import audit_logger
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.auth import (
31 31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 32 from rhodecode.model.forms import IssueTrackerPatternsForm
33 33 from rhodecode.model.meta import Session
34 from rhodecode.model.settings import IssueTrackerSettingsModel, SettingsModel
34 from rhodecode.model.settings import SettingsModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class RepoSettingsIssueTrackersView(RepoAppView):
40 40 def load_default_context(self):
41 41 c = self._get_local_tmpl_context()
42 42
43 43
44 44 return c
45 45
46 46 @LoginRequired()
47 47 @HasRepoPermissionAnyDecorator('repository.admin')
48 48 @view_config(
49 49 route_name='edit_repo_issuetracker', request_method='GET',
50 50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 51 def repo_issuetracker(self):
52 52 c = self.load_default_context()
53 53 c.active = 'issuetracker'
54 54 c.data = 'data'
55 55
56 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
56 c.settings_model = self.db_repo_patterns
57 57 c.global_patterns = c.settings_model.get_global_settings()
58 58 c.repo_patterns = c.settings_model.get_repo_settings()
59 59
60 60 return self._get_template_context(c)
61 61
62 62 @LoginRequired()
63 63 @HasRepoPermissionAnyDecorator('repository.admin')
64 64 @CSRFRequired()
65 65 @view_config(
66 66 route_name='edit_repo_issuetracker_test', request_method='POST',
67 67 renderer='string', xhr=True)
68 68 def repo_issuetracker_test(self):
69 69 return h.urlify_commit_message(
70 70 self.request.POST.get('test_text', ''),
71 71 self.db_repo_name)
72 72
73 73 @LoginRequired()
74 74 @HasRepoPermissionAnyDecorator('repository.admin')
75 75 @CSRFRequired()
76 76 @view_config(
77 77 route_name='edit_repo_issuetracker_delete', request_method='POST',
78 78 renderer='json_ext', xhr=True)
79 79 def repo_issuetracker_delete(self):
80 80 _ = self.request.translate
81 81 uid = self.request.POST.get('uid')
82 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
82 repo_settings = self.db_repo_patterns
83 83 try:
84 84 repo_settings.delete_entries(uid)
85 85 except Exception:
86 86 h.flash(_('Error occurred during deleting issue tracker entry'),
87 87 category='error')
88 88 raise HTTPNotFound()
89 89
90 90 SettingsModel().invalidate_settings_cache()
91 91 h.flash(_('Removed issue tracker entry.'), category='success')
92 92
93 93 return {'deleted': uid}
94 94
95 95 def _update_patterns(self, form, repo_settings):
96 96 for uid in form['delete_patterns']:
97 97 repo_settings.delete_entries(uid)
98 98
99 99 for pattern_data in form['patterns']:
100 100 for setting_key, pattern, type_ in pattern_data:
101 101 sett = repo_settings.create_or_update_setting(
102 102 setting_key, pattern.strip(), type_)
103 103 Session().add(sett)
104 104
105 105 Session().commit()
106 106
107 107 @LoginRequired()
108 108 @HasRepoPermissionAnyDecorator('repository.admin')
109 109 @CSRFRequired()
110 110 @view_config(
111 111 route_name='edit_repo_issuetracker_update', request_method='POST',
112 112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 113 def repo_issuetracker_update(self):
114 114 _ = self.request.translate
115 115 # Save inheritance
116 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
116 repo_settings = self.db_repo_patterns
117 117 inherited = (
118 118 self.request.POST.get('inherit_global_issuetracker') == "inherited")
119 119 repo_settings.inherit_global_settings = inherited
120 120 Session().commit()
121 121
122 122 try:
123 123 form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST)
124 124 except formencode.Invalid as errors:
125 125 log.exception('Failed to add new pattern')
126 126 error = errors
127 127 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
128 128 category='error')
129 129 raise HTTPFound(
130 130 h.route_path('edit_repo_issuetracker',
131 131 repo_name=self.db_repo_name))
132 132
133 133 if form:
134 134 self._update_patterns(form, repo_settings)
135 135
136 136 h.flash(_('Updated issue tracker entries'), category='success')
137 137 raise HTTPFound(
138 138 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
139 139
General Comments 0
You need to be logged in to leave comments. Login now