##// END OF EJS Templates
pyramid: turn on inclusion of app defaults. This will be required...
marcink -
r2332:332b2dd3 default
parent child Browse files
Show More
@@ -1,584 +1,582 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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.httpexceptions import HTTPFound
26 26
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
29 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 30 from rhodecode.model import repo
31 31 from rhodecode.model import repo_group
32 32 from rhodecode.model import user_group
33 33 from rhodecode.model import user
34 34 from rhodecode.model.db import User
35 35 from rhodecode.model.scm import ScmModel
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 ADMIN_PREFIX = '/_admin'
41 41 STATIC_FILE_PREFIX = '/_static'
42 42
43 43 URL_NAME_REQUIREMENTS = {
44 44 # group name can have a slash in them, but they must not end with a slash
45 45 'group_name': r'.*?[^/]',
46 46 'repo_group_name': r'.*?[^/]',
47 47 # repo names can have a slash in them, but they must not end with a slash
48 48 'repo_name': r'.*?[^/]',
49 49 # file path eats up everything at the end
50 50 'f_path': r'.*',
51 51 # reference types
52 52 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
53 53 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
54 54 }
55 55
56 56
57 57 def add_route_with_slash(config,name, pattern, **kw):
58 58 config.add_route(name, pattern, **kw)
59 59 if not pattern.endswith('/'):
60 60 config.add_route(name + '_slash', pattern + '/', **kw)
61 61
62 62
63 63 def add_route_requirements(route_path, requirements=URL_NAME_REQUIREMENTS):
64 64 """
65 65 Adds regex requirements to pyramid routes using a mapping dict
66 66 e.g::
67 67 add_route_requirements('{repo_name}/settings')
68 68 """
69 69 for key, regex in requirements.items():
70 70 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
71 71 return route_path
72 72
73 73
74 74 def get_format_ref_id(repo):
75 75 """Returns a `repo` specific reference formatter function"""
76 76 if h.is_svn(repo):
77 77 return _format_ref_id_svn
78 78 else:
79 79 return _format_ref_id
80 80
81 81
82 82 def _format_ref_id(name, raw_id):
83 83 """Default formatting of a given reference `name`"""
84 84 return name
85 85
86 86
87 87 def _format_ref_id_svn(name, raw_id):
88 88 """Special way of formatting a reference for Subversion including path"""
89 89 return '%s@%s' % (name, raw_id)
90 90
91 91
92 92 class TemplateArgs(StrictAttributeDict):
93 93 pass
94 94
95 95
96 96 class BaseAppView(object):
97 97
98 98 def __init__(self, context, request):
99 99 self.request = request
100 100 self.context = context
101 101 self.session = request.session
102 102 self._rhodecode_user = request.user # auth user
103 103 self._rhodecode_db_user = self._rhodecode_user.get_instance()
104 104 self._maybe_needs_password_change(
105 105 request.matched_route.name, self._rhodecode_db_user)
106 106
107 107 def _maybe_needs_password_change(self, view_name, user_obj):
108 108 log.debug('Checking if user %s needs password change on view %s',
109 109 user_obj, view_name)
110 110 skip_user_views = [
111 111 'logout', 'login',
112 112 'my_account_password', 'my_account_password_update'
113 113 ]
114 114
115 115 if not user_obj:
116 116 return
117 117
118 118 if user_obj.username == User.DEFAULT_USER:
119 119 return
120 120
121 121 now = time.time()
122 122 should_change = user_obj.user_data.get('force_password_change')
123 123 change_after = safe_int(should_change) or 0
124 124 if should_change and now > change_after:
125 125 log.debug('User %s requires password change', user_obj)
126 126 h.flash('You are required to change your password', 'warning',
127 127 ignore_duplicate=True)
128 128
129 129 if view_name not in skip_user_views:
130 130 raise HTTPFound(
131 131 self.request.route_path('my_account_password'))
132 132
133 133 def _log_creation_exception(self, e, repo_name):
134 134 _ = self.request.translate
135 135 reason = None
136 136 if len(e.args) == 2:
137 137 reason = e.args[1]
138 138
139 139 if reason == 'INVALID_CERTIFICATE':
140 140 log.exception(
141 141 'Exception creating a repository: invalid certificate')
142 142 msg = (_('Error creating repository %s: invalid certificate')
143 143 % repo_name)
144 144 else:
145 145 log.exception("Exception creating a repository")
146 146 msg = (_('Error creating repository %s')
147 147 % repo_name)
148 148 return msg
149 149
150 def _get_local_tmpl_context(self, include_app_defaults=False):
150 def _get_local_tmpl_context(self, include_app_defaults=True):
151 151 c = TemplateArgs()
152 152 c.auth_user = self.request.user
153 153 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
154 154 c.rhodecode_user = self.request.user
155 155
156 156 if include_app_defaults:
157 # NOTE(marcink): after full pyramid migration include_app_defaults
158 # should be turned on by default
159 157 from rhodecode.lib.base import attach_context_attributes
160 158 attach_context_attributes(c, self.request, self.request.user.user_id)
161 159
162 160 return c
163 161
164 162 def _register_global_c(self, tmpl_args):
165 163 """
166 164 Registers attributes to pylons global `c`
167 165 """
168 166
169 167 # TODO(marcink): remove once pyramid migration is finished
170 168 from pylons import tmpl_context as c
171 169 try:
172 170 for k, v in tmpl_args.items():
173 171 setattr(c, k, v)
174 172 except TypeError:
175 173 log.exception('Failed to register pylons C')
176 174 pass
177 175
178 176 def _get_template_context(self, tmpl_args):
179 177 self._register_global_c(tmpl_args)
180 178
181 179 local_tmpl_args = {
182 180 'defaults': {},
183 181 'errors': {},
184 182 # register a fake 'c' to be used in templates instead of global
185 183 # pylons c, after migration to pyramid we should rename it to 'c'
186 184 # make sure we replace usage of _c in templates too
187 185 '_c': tmpl_args
188 186 }
189 187 local_tmpl_args.update(tmpl_args)
190 188 return local_tmpl_args
191 189
192 190 def load_default_context(self):
193 191 """
194 192 example:
195 193
196 194 def load_default_context(self):
197 195 c = self._get_local_tmpl_context()
198 196 c.custom_var = 'foobar'
199 197 self._register_global_c(c)
200 198 return c
201 199 """
202 200 raise NotImplementedError('Needs implementation in view class')
203 201
204 202
205 203 class RepoAppView(BaseAppView):
206 204
207 205 def __init__(self, context, request):
208 206 super(RepoAppView, self).__init__(context, request)
209 207 self.db_repo = request.db_repo
210 208 self.db_repo_name = self.db_repo.repo_name
211 209 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
212 210
213 211 def _handle_missing_requirements(self, error):
214 212 log.error(
215 213 'Requirements are missing for repository %s: %s',
216 214 self.db_repo_name, error.message)
217 215
218 216 def _get_local_tmpl_context(self, include_app_defaults=False):
219 217 _ = self.request.translate
220 218 c = super(RepoAppView, self)._get_local_tmpl_context(
221 219 include_app_defaults=include_app_defaults)
222 220
223 221 # register common vars for this type of view
224 222 c.rhodecode_db_repo = self.db_repo
225 223 c.repo_name = self.db_repo_name
226 224 c.repository_pull_requests = self.db_repo_pull_requests
227 225
228 226 c.repository_requirements_missing = False
229 227 try:
230 228 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
231 229 except RepositoryRequirementError as e:
232 230 c.repository_requirements_missing = True
233 231 self._handle_missing_requirements(e)
234 232 self.rhodecode_vcs_repo = None
235 233
236 234 if (not c.repository_requirements_missing
237 235 and self.rhodecode_vcs_repo is None):
238 236 # unable to fetch this repo as vcs instance, report back to user
239 237 h.flash(_(
240 238 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
241 239 "Please check if it exist, or is not damaged.") %
242 240 {'repo_name': c.repo_name},
243 241 category='error', ignore_duplicate=True)
244 242 raise HTTPFound(h.route_path('home'))
245 243
246 244 return c
247 245
248 246 def _get_f_path(self, matchdict, default=None):
249 247 f_path = matchdict.get('f_path')
250 248 if f_path:
251 249 # fix for multiple initial slashes that causes errors for GIT
252 250 return f_path.lstrip('/')
253 251
254 252 return default
255 253
256 254
257 255 class RepoGroupAppView(BaseAppView):
258 256 def __init__(self, context, request):
259 257 super(RepoGroupAppView, self).__init__(context, request)
260 258 self.db_repo_group = request.db_repo_group
261 259 self.db_repo_group_name = self.db_repo_group.group_name
262 260
263 261 def _revoke_perms_on_yourself(self, form_result):
264 262 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
265 263 form_result['perm_updates'])
266 264 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
267 265 form_result['perm_additions'])
268 266 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
269 267 form_result['perm_deletions'])
270 268 admin_perm = 'group.admin'
271 269 if _updates and _updates[0][1] != admin_perm or \
272 270 _additions and _additions[0][1] != admin_perm or \
273 271 _deletions and _deletions[0][1] != admin_perm:
274 272 return True
275 273 return False
276 274
277 275
278 276 class UserGroupAppView(BaseAppView):
279 277 def __init__(self, context, request):
280 278 super(UserGroupAppView, self).__init__(context, request)
281 279 self.db_user_group = request.db_user_group
282 280 self.db_user_group_name = self.db_user_group.users_group_name
283 281
284 282
285 283 class UserAppView(BaseAppView):
286 284 def __init__(self, context, request):
287 285 super(UserAppView, self).__init__(context, request)
288 286 self.db_user = request.db_user
289 287 self.db_user_id = self.db_user.user_id
290 288
291 289 _ = self.request.translate
292 290 if not request.db_user_supports_default:
293 291 if self.db_user.username == User.DEFAULT_USER:
294 292 h.flash(_("Editing user `{}` is disabled.".format(
295 293 User.DEFAULT_USER)), category='warning')
296 294 raise HTTPFound(h.route_path('users'))
297 295
298 296
299 297 class DataGridAppView(object):
300 298 """
301 299 Common class to have re-usable grid rendering components
302 300 """
303 301
304 302 def _extract_ordering(self, request, column_map=None):
305 303 column_map = column_map or {}
306 304 column_index = safe_int(request.GET.get('order[0][column]'))
307 305 order_dir = request.GET.get(
308 306 'order[0][dir]', 'desc')
309 307 order_by = request.GET.get(
310 308 'columns[%s][data][sort]' % column_index, 'name_raw')
311 309
312 310 # translate datatable to DB columns
313 311 order_by = column_map.get(order_by) or order_by
314 312
315 313 search_q = request.GET.get('search[value]')
316 314 return search_q, order_by, order_dir
317 315
318 316 def _extract_chunk(self, request):
319 317 start = safe_int(request.GET.get('start'), 0)
320 318 length = safe_int(request.GET.get('length'), 25)
321 319 draw = safe_int(request.GET.get('draw'))
322 320 return draw, start, length
323 321
324 322 def _get_order_col(self, order_by, model):
325 323 if isinstance(order_by, basestring):
326 324 try:
327 325 return operator.attrgetter(order_by)(model)
328 326 except AttributeError:
329 327 return None
330 328 else:
331 329 return order_by
332 330
333 331
334 332 class BaseReferencesView(RepoAppView):
335 333 """
336 334 Base for reference view for branches, tags and bookmarks.
337 335 """
338 336 def load_default_context(self):
339 337 c = self._get_local_tmpl_context()
340 338
341 339 self._register_global_c(c)
342 340 return c
343 341
344 342 def load_refs_context(self, ref_items, partials_template):
345 343 _render = self.request.get_partial_renderer(partials_template)
346 344 pre_load = ["author", "date", "message"]
347 345
348 346 is_svn = h.is_svn(self.rhodecode_vcs_repo)
349 347 is_hg = h.is_hg(self.rhodecode_vcs_repo)
350 348
351 349 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
352 350
353 351 closed_refs = {}
354 352 if is_hg:
355 353 closed_refs = self.rhodecode_vcs_repo.branches_closed
356 354
357 355 data = []
358 356 for ref_name, commit_id in ref_items:
359 357 commit = self.rhodecode_vcs_repo.get_commit(
360 358 commit_id=commit_id, pre_load=pre_load)
361 359 closed = ref_name in closed_refs
362 360
363 361 # TODO: johbo: Unify generation of reference links
364 362 use_commit_id = '/' in ref_name or is_svn
365 363
366 364 if use_commit_id:
367 365 files_url = h.route_path(
368 366 'repo_files',
369 367 repo_name=self.db_repo_name,
370 368 f_path=ref_name if is_svn else '',
371 369 commit_id=commit_id)
372 370
373 371 else:
374 372 files_url = h.route_path(
375 373 'repo_files',
376 374 repo_name=self.db_repo_name,
377 375 f_path=ref_name if is_svn else '',
378 376 commit_id=ref_name,
379 377 _query=dict(at=ref_name))
380 378
381 379 data.append({
382 380 "name": _render('name', ref_name, files_url, closed),
383 381 "name_raw": ref_name,
384 382 "date": _render('date', commit.date),
385 383 "date_raw": datetime_to_time(commit.date),
386 384 "author": _render('author', commit.author),
387 385 "commit": _render(
388 386 'commit', commit.message, commit.raw_id, commit.idx),
389 387 "commit_raw": commit.idx,
390 388 "compare": _render(
391 389 'compare', format_ref_id(ref_name, commit.raw_id)),
392 390 })
393 391
394 392 return data
395 393
396 394
397 395 class RepoRoutePredicate(object):
398 396 def __init__(self, val, config):
399 397 self.val = val
400 398
401 399 def text(self):
402 400 return 'repo_route = %s' % self.val
403 401
404 402 phash = text
405 403
406 404 def __call__(self, info, request):
407 405
408 406 if hasattr(request, 'vcs_call'):
409 407 # skip vcs calls
410 408 return
411 409
412 410 repo_name = info['match']['repo_name']
413 411 repo_model = repo.RepoModel()
414 412 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
415 413
416 414 def redirect_if_creating(db_repo):
417 415 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
418 416 raise HTTPFound(
419 417 request.route_path('repo_creating',
420 418 repo_name=db_repo.repo_name))
421 419
422 420 if by_name_match:
423 421 # register this as request object we can re-use later
424 422 request.db_repo = by_name_match
425 423 redirect_if_creating(by_name_match)
426 424 return True
427 425
428 426 by_id_match = repo_model.get_repo_by_id(repo_name)
429 427 if by_id_match:
430 428 request.db_repo = by_id_match
431 429 redirect_if_creating(by_id_match)
432 430 return True
433 431
434 432 return False
435 433
436 434
437 435 class RepoTypeRoutePredicate(object):
438 436 def __init__(self, val, config):
439 437 self.val = val or ['hg', 'git', 'svn']
440 438
441 439 def text(self):
442 440 return 'repo_accepted_type = %s' % self.val
443 441
444 442 phash = text
445 443
446 444 def __call__(self, info, request):
447 445 if hasattr(request, 'vcs_call'):
448 446 # skip vcs calls
449 447 return
450 448
451 449 rhodecode_db_repo = request.db_repo
452 450
453 451 log.debug(
454 452 '%s checking repo type for %s in %s',
455 453 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
456 454
457 455 if rhodecode_db_repo.repo_type in self.val:
458 456 return True
459 457 else:
460 458 log.warning('Current view is not supported for repo type:%s',
461 459 rhodecode_db_repo.repo_type)
462 460 #
463 461 # h.flash(h.literal(
464 462 # _('Action not supported for %s.' % rhodecode_repo.alias)),
465 463 # category='warning')
466 464 # return redirect(
467 465 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
468 466
469 467 return False
470 468
471 469
472 470 class RepoGroupRoutePredicate(object):
473 471 def __init__(self, val, config):
474 472 self.val = val
475 473
476 474 def text(self):
477 475 return 'repo_group_route = %s' % self.val
478 476
479 477 phash = text
480 478
481 479 def __call__(self, info, request):
482 480 if hasattr(request, 'vcs_call'):
483 481 # skip vcs calls
484 482 return
485 483
486 484 repo_group_name = info['match']['repo_group_name']
487 485 repo_group_model = repo_group.RepoGroupModel()
488 486 by_name_match = repo_group_model.get_by_group_name(
489 487 repo_group_name, cache=True)
490 488
491 489 if by_name_match:
492 490 # register this as request object we can re-use later
493 491 request.db_repo_group = by_name_match
494 492 return True
495 493
496 494 return False
497 495
498 496
499 497 class UserGroupRoutePredicate(object):
500 498 def __init__(self, val, config):
501 499 self.val = val
502 500
503 501 def text(self):
504 502 return 'user_group_route = %s' % self.val
505 503
506 504 phash = text
507 505
508 506 def __call__(self, info, request):
509 507 if hasattr(request, 'vcs_call'):
510 508 # skip vcs calls
511 509 return
512 510
513 511 user_group_id = info['match']['user_group_id']
514 512 user_group_model = user_group.UserGroup()
515 513 by_id_match = user_group_model.get(
516 514 user_group_id, cache=True)
517 515
518 516 if by_id_match:
519 517 # register this as request object we can re-use later
520 518 request.db_user_group = by_id_match
521 519 return True
522 520
523 521 return False
524 522
525 523
526 524 class UserRoutePredicateBase(object):
527 525 supports_default = None
528 526
529 527 def __init__(self, val, config):
530 528 self.val = val
531 529
532 530 def text(self):
533 531 raise NotImplementedError()
534 532
535 533 def __call__(self, info, request):
536 534 if hasattr(request, 'vcs_call'):
537 535 # skip vcs calls
538 536 return
539 537
540 538 user_id = info['match']['user_id']
541 539 user_model = user.User()
542 540 by_id_match = user_model.get(
543 541 user_id, cache=True)
544 542
545 543 if by_id_match:
546 544 # register this as request object we can re-use later
547 545 request.db_user = by_id_match
548 546 request.db_user_supports_default = self.supports_default
549 547 return True
550 548
551 549 return False
552 550
553 551
554 552 class UserRoutePredicate(UserRoutePredicateBase):
555 553 supports_default = False
556 554
557 555 def text(self):
558 556 return 'user_route = %s' % self.val
559 557
560 558 phash = text
561 559
562 560
563 561 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
564 562 supports_default = True
565 563
566 564 def text(self):
567 565 return 'user_with_default_route = %s' % self.val
568 566
569 567 phash = text
570 568
571 569
572 570 def includeme(config):
573 571 config.add_route_predicate(
574 572 'repo_route', RepoRoutePredicate)
575 573 config.add_route_predicate(
576 574 'repo_accepted_types', RepoTypeRoutePredicate)
577 575 config.add_route_predicate(
578 576 'repo_group_route', RepoGroupRoutePredicate)
579 577 config.add_route_predicate(
580 578 'user_group_route', UserGroupRoutePredicate)
581 579 config.add_route_predicate(
582 580 'user_route_with_default', UserRouteWithDefaultPredicate)
583 581 config.add_route_predicate(
584 582 'user_route', UserRoutePredicate) No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now