##// END OF EJS Templates
Show cache keys in admin settings of repository
marcink -
r2809:070d2eac beta
parent child Browse files
Show More
@@ -1,673 +1,674 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.repos_group import ReposGroupModel
57 57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 58 from rhodecode.lib.vcs.utils.fakemod import create_module
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63 63
64 64
65 65 def recursive_replace(str_, replace=' '):
66 66 """
67 67 Recursive replace of given sign to just one instance
68 68
69 69 :param str_: given string
70 70 :param replace: char to find and replace multiple instances
71 71
72 72 Examples::
73 73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 74 'Mighty-Mighty-Bo-sstones'
75 75 """
76 76
77 77 if str_.find(replace * 2) == -1:
78 78 return str_
79 79 else:
80 80 str_ = str_.replace(replace * 2, replace)
81 81 return recursive_replace(str_, replace)
82 82
83 83
84 84 def repo_name_slug(value):
85 85 """
86 86 Return slug of name of repository
87 87 This function is called on each creation/modification
88 88 of repository to prevent bad names in repo
89 89 """
90 90
91 91 slug = remove_formatting(value)
92 92 slug = strip_tags(slug)
93 93
94 94 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 95 slug = slug.replace(c, '-')
96 96 slug = recursive_replace(slug, '-')
97 97 slug = collapse(slug, '-')
98 98 return slug
99 99
100 100
101 101 def get_repo_slug(request):
102 102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 103 if _repo:
104 104 _repo = _repo.rstrip('/')
105 105 return _repo
106 106
107 107
108 108 def get_repos_group_slug(request):
109 109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 110 if _group:
111 111 _group = _group.rstrip('/')
112 112 return _group
113 113
114 114
115 115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 116 """
117 117 Action logger for various actions made by users
118 118
119 119 :param user: user that made this action, can be a unique username string or
120 120 object containing user_id attribute
121 121 :param action: action to log, should be on of predefined unique actions for
122 122 easy translations
123 123 :param repo: string name of repository or object containing repo_id,
124 124 that action was made on
125 125 :param ipaddr: optional ip address from what the action was made
126 126 :param sa: optional sqlalchemy session
127 127
128 128 """
129 129
130 130 if not sa:
131 131 sa = meta.Session()
132 132
133 133 try:
134 134 if hasattr(user, 'user_id'):
135 135 user_obj = user
136 136 elif isinstance(user, basestring):
137 137 user_obj = User.get_by_username(user)
138 138 else:
139 139 raise Exception('You have to provide user object or username')
140 140
141 141 if hasattr(repo, 'repo_id'):
142 142 repo_obj = Repository.get(repo.repo_id)
143 143 repo_name = repo_obj.repo_name
144 144 elif isinstance(repo, basestring):
145 145 repo_name = repo.lstrip('/')
146 146 repo_obj = Repository.get_by_repo_name(repo_name)
147 147 else:
148 148 repo_obj = None
149 149 repo_name = ''
150 150
151 151 user_log = UserLog()
152 152 user_log.user_id = user_obj.user_id
153 153 user_log.action = safe_unicode(action)
154 154
155 155 user_log.repository = repo_obj
156 156 user_log.repository_name = repo_name
157 157
158 158 user_log.action_date = datetime.datetime.now()
159 159 user_log.user_ip = ipaddr
160 160 sa.add(user_log)
161 161
162 162 log.info(
163 163 'Adding user %s, action %s on %s' % (user_obj, action,
164 164 safe_unicode(repo))
165 165 )
166 166 if commit:
167 167 sa.commit()
168 168 except:
169 169 log.error(traceback.format_exc())
170 170 raise
171 171
172 172
173 173 def get_repos(path, recursive=False):
174 174 """
175 175 Scans given path for repos and return (name,(type,path)) tuple
176 176
177 177 :param path: path to scan for repositories
178 178 :param recursive: recursive search and return names with subdirs in front
179 179 """
180 180
181 181 # remove ending slash for better results
182 182 path = path.rstrip(os.sep)
183 183
184 184 def _get_repos(p):
185 185 if not os.access(p, os.W_OK):
186 186 return
187 187 for dirpath in os.listdir(p):
188 188 if os.path.isfile(os.path.join(p, dirpath)):
189 189 continue
190 190 cur_path = os.path.join(p, dirpath)
191 191 try:
192 192 scm_info = get_scm(cur_path)
193 193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 194 except VCSError:
195 195 if not recursive:
196 196 continue
197 197 #check if this dir containts other repos for recursive scan
198 198 rec_path = os.path.join(p, dirpath)
199 199 if os.path.isdir(rec_path):
200 200 for inner_scm in _get_repos(rec_path):
201 201 yield inner_scm
202 202
203 203 return _get_repos(path)
204 204
205 205
206 206 def is_valid_repo(repo_name, base_path, scm=None):
207 207 """
208 208 Returns True if given path is a valid repository False otherwise.
209 209 If scm param is given also compare if given scm is the same as expected
210 210 from scm parameter
211 211
212 212 :param repo_name:
213 213 :param base_path:
214 214 :param scm:
215 215
216 216 :return True: if given path is a valid repository
217 217 """
218 218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219 219
220 220 try:
221 221 scm_ = get_scm(full_path)
222 222 if scm:
223 223 return scm_[0] == scm
224 224 return True
225 225 except VCSError:
226 226 return False
227 227
228 228
229 229 def is_valid_repos_group(repos_group_name, base_path):
230 230 """
231 231 Returns True if given path is a repos group False otherwise
232 232
233 233 :param repo_name:
234 234 :param base_path:
235 235 """
236 236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237 237
238 238 # check if it's not a repo
239 239 if is_valid_repo(repos_group_name, base_path):
240 240 return False
241 241
242 242 try:
243 243 # we need to check bare git repos at higher level
244 244 # since we might match branches/hooks/info/objects or possible
245 245 # other things inside bare git repo
246 246 get_scm(os.path.dirname(full_path))
247 247 return False
248 248 except VCSError:
249 249 pass
250 250
251 251 # check if it's a valid path
252 252 if os.path.isdir(full_path):
253 253 return True
254 254
255 255 return False
256 256
257 257
258 258 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
259 259 while True:
260 260 ok = raw_input(prompt)
261 261 if ok in ('y', 'ye', 'yes'):
262 262 return True
263 263 if ok in ('n', 'no', 'nop', 'nope'):
264 264 return False
265 265 retries = retries - 1
266 266 if retries < 0:
267 267 raise IOError
268 268 print complaint
269 269
270 270 #propagated from mercurial documentation
271 271 ui_sections = ['alias', 'auth',
272 272 'decode/encode', 'defaults',
273 273 'diff', 'email',
274 274 'extensions', 'format',
275 275 'merge-patterns', 'merge-tools',
276 276 'hooks', 'http_proxy',
277 277 'smtp', 'patch',
278 278 'paths', 'profiling',
279 279 'server', 'trusted',
280 280 'ui', 'web', ]
281 281
282 282
283 283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 284 """
285 285 A function that will read python rc files or database
286 286 and make an mercurial ui object from read options
287 287
288 288 :param path: path to mercurial config file
289 289 :param checkpaths: check the path
290 290 :param read_from: read from 'file' or 'db'
291 291 """
292 292
293 293 baseui = ui.ui()
294 294
295 295 # clean the baseui object
296 296 baseui._ocfg = config.config()
297 297 baseui._ucfg = config.config()
298 298 baseui._tcfg = config.config()
299 299
300 300 if read_from == 'file':
301 301 if not os.path.isfile(path):
302 302 log.debug('hgrc file is not present at %s skipping...' % path)
303 303 return False
304 304 log.debug('reading hgrc from %s' % path)
305 305 cfg = config.config()
306 306 cfg.read(path)
307 307 for section in ui_sections:
308 308 for k, v in cfg.items(section):
309 309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
310 310 baseui.setconfig(section, k, v)
311 311
312 312 elif read_from == 'db':
313 313 sa = meta.Session()
314 314 ret = sa.query(RhodeCodeUi)\
315 315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 316 .all()
317 317
318 318 hg_ui = ret
319 319 for ui_ in hg_ui:
320 320 if ui_.ui_active:
321 321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
322 322 ui_.ui_key, ui_.ui_value)
323 323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
324 324 if ui_.ui_key == 'push_ssl':
325 325 # force set push_ssl requirement to False, rhodecode
326 326 # handles that
327 327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
328 328 if clear_session:
329 329 meta.Session.remove()
330 330 return baseui
331 331
332 332
333 333 def set_rhodecode_config(config):
334 334 """
335 335 Updates pylons config with new settings from database
336 336
337 337 :param config:
338 338 """
339 339 hgsettings = RhodeCodeSetting.get_app_settings()
340 340
341 341 for k, v in hgsettings.items():
342 342 config[k] = v
343 343
344 344
345 345 def invalidate_cache(cache_key, *args):
346 346 """
347 347 Puts cache invalidation task into db for
348 348 further global cache invalidation
349 349 """
350 350
351 351 from rhodecode.model.scm import ScmModel
352 352
353 353 if cache_key.startswith('get_repo_cached_'):
354 354 name = cache_key.split('get_repo_cached_')[-1]
355 355 ScmModel().mark_for_invalidation(name)
356 356
357 357
358 358 def map_groups(path):
359 359 """
360 360 Given a full path to a repository, create all nested groups that this
361 361 repo is inside. This function creates parent-child relationships between
362 362 groups and creates default perms for all new groups.
363 363
364 364 :param paths: full path to repository
365 365 """
366 366 sa = meta.Session()
367 367 groups = path.split(Repository.url_sep())
368 368 parent = None
369 369 group = None
370 370
371 371 # last element is repo in nested groups structure
372 372 groups = groups[:-1]
373 373 rgm = ReposGroupModel(sa)
374 374 for lvl, group_name in enumerate(groups):
375 375 group_name = '/'.join(groups[:lvl] + [group_name])
376 376 group = RepoGroup.get_by_group_name(group_name)
377 377 desc = '%s group' % group_name
378 378
379 379 # skip folders that are now removed repos
380 380 if REMOVED_REPO_PAT.match(group_name):
381 381 break
382 382
383 383 if group is None:
384 384 log.debug('creating group level: %s group_name: %s' % (lvl,
385 385 group_name))
386 386 group = RepoGroup(group_name, parent)
387 387 group.group_description = desc
388 388 sa.add(group)
389 389 rgm._create_default_perms(group)
390 390 sa.flush()
391 391 parent = group
392 392 return group
393 393
394 394
395 395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
396 396 install_git_hook=False):
397 397 """
398 398 maps all repos given in initial_repo_list, non existing repositories
399 399 are created, if remove_obsolete is True it also check for db entries
400 400 that are not in initial_repo_list and removes them.
401 401
402 402 :param initial_repo_list: list of repositories found by scanning methods
403 403 :param remove_obsolete: check for obsolete entries in database
404 404 :param install_git_hook: if this is True, also check and install githook
405 405 for a repo if missing
406 406 """
407 407 from rhodecode.model.repo import RepoModel
408 408 from rhodecode.model.scm import ScmModel
409 409 sa = meta.Session()
410 410 rm = RepoModel()
411 411 user = sa.query(User).filter(User.admin == True).first()
412 412 if user is None:
413 413 raise Exception('Missing administrative account !')
414 414 added = []
415 415
416 416 # # clear cache keys
417 417 # log.debug("Clearing cache keys now...")
418 418 # CacheInvalidation.clear_cache()
419 419 # sa.commit()
420 420
421 421 for name, repo in initial_repo_list.items():
422 422 group = map_groups(name)
423 423 db_repo = rm.get_by_repo_name(name)
424 424 # found repo that is on filesystem not in RhodeCode database
425 425 if not db_repo:
426 426 log.info('repository %s not found creating now' % name)
427 427 added.append(name)
428 428 desc = (repo.description
429 429 if repo.description != 'unknown'
430 430 else '%s repository' % name)
431 431 new_repo = rm.create_repo(
432 432 repo_name=name,
433 433 repo_type=repo.alias,
434 434 description=desc,
435 435 repos_group=getattr(group, 'group_id', None),
436 436 owner=user,
437 437 just_db=True
438 438 )
439 439 # we added that repo just now, and make sure it has githook
440 440 # installed
441 441 if new_repo.repo_type == 'git':
442 442 ScmModel().install_git_hook(new_repo.scm_instance)
443 443 elif install_git_hook:
444 444 if db_repo.repo_type == 'git':
445 445 ScmModel().install_git_hook(db_repo.scm_instance)
446 446 # during starting install all cache keys for all repositories in the
447 447 # system, this will register all repos and multiple instances
448 448 key, _prefix, _org_key = CacheInvalidation._get_key(name)
449 log.debug("Creating cache key for %s instance_id:`%s`" % (name, _prefix))
449 450 CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False)
450 451 sa.commit()
451 452 removed = []
452 453 if remove_obsolete:
453 454 # remove from database those repositories that are not in the filesystem
454 455 for repo in sa.query(Repository).all():
455 456 if repo.repo_name not in initial_repo_list.keys():
456 457 log.debug("Removing non existing repository found in db `%s`" %
457 458 repo.repo_name)
458 459 try:
459 460 sa.delete(repo)
460 461 sa.commit()
461 462 removed.append(repo.repo_name)
462 463 except:
463 464 #don't hold further removals on error
464 465 log.error(traceback.format_exc())
465 466 sa.rollback()
466 467
467 468 return added, removed
468 469
469 470
470 471 # set cache regions for beaker so celery can utilise it
471 472 def add_cache(settings):
472 473 cache_settings = {'regions': None}
473 474 for key in settings.keys():
474 475 for prefix in ['beaker.cache.', 'cache.']:
475 476 if key.startswith(prefix):
476 477 name = key.split(prefix)[1].strip()
477 478 cache_settings[name] = settings[key].strip()
478 479 if cache_settings['regions']:
479 480 for region in cache_settings['regions'].split(','):
480 481 region = region.strip()
481 482 region_settings = {}
482 483 for key, value in cache_settings.items():
483 484 if key.startswith(region):
484 485 region_settings[key.split('.')[1]] = value
485 486 region_settings['expire'] = int(region_settings.get('expire',
486 487 60))
487 488 region_settings.setdefault('lock_dir',
488 489 cache_settings.get('lock_dir'))
489 490 region_settings.setdefault('data_dir',
490 491 cache_settings.get('data_dir'))
491 492
492 493 if 'type' not in region_settings:
493 494 region_settings['type'] = cache_settings.get('type',
494 495 'memory')
495 496 beaker.cache.cache_regions[region] = region_settings
496 497
497 498
498 499 def load_rcextensions(root_path):
499 500 import rhodecode
500 501 from rhodecode.config import conf
501 502
502 503 path = os.path.join(root_path, 'rcextensions', '__init__.py')
503 504 if os.path.isfile(path):
504 505 rcext = create_module('rc', path)
505 506 EXT = rhodecode.EXTENSIONS = rcext
506 507 log.debug('Found rcextensions now loading %s...' % rcext)
507 508
508 509 # Additional mappings that are not present in the pygments lexers
509 510 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
510 511
511 512 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
512 513
513 514 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
514 515 log.debug('settings custom INDEX_EXTENSIONS')
515 516 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
516 517
517 518 #ADDITIONAL MAPPINGS
518 519 log.debug('adding extra into INDEX_EXTENSIONS')
519 520 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
520 521
521 522
522 523 #==============================================================================
523 524 # TEST FUNCTIONS AND CREATORS
524 525 #==============================================================================
525 526 def create_test_index(repo_location, config, full_index):
526 527 """
527 528 Makes default test index
528 529
529 530 :param config: test config
530 531 :param full_index:
531 532 """
532 533
533 534 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
534 535 from rhodecode.lib.pidlock import DaemonLock, LockHeld
535 536
536 537 repo_location = repo_location
537 538
538 539 index_location = os.path.join(config['app_conf']['index_dir'])
539 540 if not os.path.exists(index_location):
540 541 os.makedirs(index_location)
541 542
542 543 try:
543 544 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
544 545 WhooshIndexingDaemon(index_location=index_location,
545 546 repo_location=repo_location)\
546 547 .run(full_index=full_index)
547 548 l.release()
548 549 except LockHeld:
549 550 pass
550 551
551 552
552 553 def create_test_env(repos_test_path, config):
553 554 """
554 555 Makes a fresh database and
555 556 install test repository into tmp dir
556 557 """
557 558 from rhodecode.lib.db_manage import DbManage
558 559 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
559 560
560 561 # PART ONE create db
561 562 dbconf = config['sqlalchemy.db1.url']
562 563 log.debug('making test db %s' % dbconf)
563 564
564 565 # create test dir if it doesn't exist
565 566 if not os.path.isdir(repos_test_path):
566 567 log.debug('Creating testdir %s' % repos_test_path)
567 568 os.makedirs(repos_test_path)
568 569
569 570 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
570 571 tests=True)
571 572 dbmanage.create_tables(override=True)
572 573 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
573 574 dbmanage.create_default_user()
574 575 dbmanage.admin_prompt()
575 576 dbmanage.create_permissions()
576 577 dbmanage.populate_default_permissions()
577 578 Session().commit()
578 579 # PART TWO make test repo
579 580 log.debug('making test vcs repositories')
580 581
581 582 idx_path = config['app_conf']['index_dir']
582 583 data_path = config['app_conf']['cache_dir']
583 584
584 585 #clean index and data
585 586 if idx_path and os.path.exists(idx_path):
586 587 log.debug('remove %s' % idx_path)
587 588 shutil.rmtree(idx_path)
588 589
589 590 if data_path and os.path.exists(data_path):
590 591 log.debug('remove %s' % data_path)
591 592 shutil.rmtree(data_path)
592 593
593 594 #CREATE DEFAULT TEST REPOS
594 595 cur_dir = dn(dn(abspath(__file__)))
595 596 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
596 597 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
597 598 tar.close()
598 599
599 600 cur_dir = dn(dn(abspath(__file__)))
600 601 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
601 602 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
602 603 tar.close()
603 604
604 605 #LOAD VCS test stuff
605 606 from rhodecode.tests.vcs import setup_package
606 607 setup_package()
607 608
608 609
609 610 #==============================================================================
610 611 # PASTER COMMANDS
611 612 #==============================================================================
612 613 class BasePasterCommand(Command):
613 614 """
614 615 Abstract Base Class for paster commands.
615 616
616 617 The celery commands are somewhat aggressive about loading
617 618 celery.conf, and since our module sets the `CELERY_LOADER`
618 619 environment variable to our loader, we have to bootstrap a bit and
619 620 make sure we've had a chance to load the pylons config off of the
620 621 command line, otherwise everything fails.
621 622 """
622 623 min_args = 1
623 624 min_args_error = "Please provide a paster config file as an argument."
624 625 takes_config_file = 1
625 626 requires_config_file = True
626 627
627 628 def notify_msg(self, msg, log=False):
628 629 """Make a notification to user, additionally if logger is passed
629 630 it logs this action using given logger
630 631
631 632 :param msg: message that will be printed to user
632 633 :param log: logging instance, to use to additionally log this message
633 634
634 635 """
635 636 if log and isinstance(log, logging):
636 637 log(msg)
637 638
638 639 def run(self, args):
639 640 """
640 641 Overrides Command.run
641 642
642 643 Checks for a config file argument and loads it.
643 644 """
644 645 if len(args) < self.min_args:
645 646 raise BadCommand(
646 647 self.min_args_error % {'min_args': self.min_args,
647 648 'actual_args': len(args)})
648 649
649 650 # Decrement because we're going to lob off the first argument.
650 651 # @@ This is hacky
651 652 self.min_args -= 1
652 653 self.bootstrap_config(args[0])
653 654 self.update_parser()
654 655 return super(BasePasterCommand, self).run(args[1:])
655 656
656 657 def update_parser(self):
657 658 """
658 659 Abstract method. Allows for the class's parser to be updated
659 660 before the superclass's `run` method is called. Necessary to
660 661 allow options/arguments to be passed through to the underlying
661 662 celery command.
662 663 """
663 664 raise NotImplementedError("Abstract Method.")
664 665
665 666 def bootstrap_config(self, conf):
666 667 """
667 668 Loads the pylons configuration.
668 669 """
669 670 from pylons import config as pylonsconfig
670 671
671 672 self.path_to_ini_file = os.path.realpath(conf)
672 673 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
673 674 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,1775 +1,1792 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 if id_:
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 return cls.query().all()
130 130
131 131 @classmethod
132 132 def delete(cls, id_):
133 133 obj = cls.query().get(id_)
134 134 Session().delete(obj)
135 135
136 136 def __repr__(self):
137 137 if hasattr(self, '__unicode__'):
138 138 # python repr needs to return str
139 139 return safe_str(self.__unicode__())
140 140 return '<DB:%s>' % (self.__class__.__name__)
141 141
142 142
143 143 class RhodeCodeSetting(Base, BaseModel):
144 144 __tablename__ = 'rhodecode_settings'
145 145 __table_args__ = (
146 146 UniqueConstraint('app_settings_name'),
147 147 {'extend_existing': True, 'mysql_engine': 'InnoDB',
148 148 'mysql_charset': 'utf8'}
149 149 )
150 150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
151 151 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153 153
154 154 def __init__(self, k='', v=''):
155 155 self.app_settings_name = k
156 156 self.app_settings_value = v
157 157
158 158 @validates('_app_settings_value')
159 159 def validate_settings_value(self, key, val):
160 160 assert type(val) == unicode
161 161 return val
162 162
163 163 @hybrid_property
164 164 def app_settings_value(self):
165 165 v = self._app_settings_value
166 166 if self.app_settings_name == 'ldap_active':
167 167 v = str2bool(v)
168 168 return v
169 169
170 170 @app_settings_value.setter
171 171 def app_settings_value(self, val):
172 172 """
173 173 Setter that will always make sure we use unicode in app_settings_value
174 174
175 175 :param val:
176 176 """
177 177 self._app_settings_value = safe_unicode(val)
178 178
179 179 def __unicode__(self):
180 180 return u"<%s('%s:%s')>" % (
181 181 self.__class__.__name__,
182 182 self.app_settings_name, self.app_settings_value
183 183 )
184 184
185 185 @classmethod
186 186 def get_by_name(cls, key):
187 187 return cls.query()\
188 188 .filter(cls.app_settings_name == key).scalar()
189 189
190 190 @classmethod
191 191 def get_by_name_or_create(cls, key):
192 192 res = cls.get_by_name(key)
193 193 if not res:
194 194 res = cls(key)
195 195 return res
196 196
197 197 @classmethod
198 198 def get_app_settings(cls, cache=False):
199 199
200 200 ret = cls.query()
201 201
202 202 if cache:
203 203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
204 204
205 205 if not ret:
206 206 raise Exception('Could not get application settings !')
207 207 settings = {}
208 208 for each in ret:
209 209 settings['rhodecode_' + each.app_settings_name] = \
210 210 each.app_settings_value
211 211
212 212 return settings
213 213
214 214 @classmethod
215 215 def get_ldap_settings(cls, cache=False):
216 216 ret = cls.query()\
217 217 .filter(cls.app_settings_name.startswith('ldap_')).all()
218 218 fd = {}
219 219 for row in ret:
220 220 fd.update({row.app_settings_name: row.app_settings_value})
221 221
222 222 return fd
223 223
224 224
225 225 class RhodeCodeUi(Base, BaseModel):
226 226 __tablename__ = 'rhodecode_ui'
227 227 __table_args__ = (
228 228 UniqueConstraint('ui_key'),
229 229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
230 230 'mysql_charset': 'utf8'}
231 231 )
232 232
233 233 HOOK_UPDATE = 'changegroup.update'
234 234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 235 HOOK_PUSH = 'changegroup.push_logger'
236 236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
237 237 HOOK_PULL = 'outgoing.pull_logger'
238 238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
239 239
240 240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
241 241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 242 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 243 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 244 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
245 245
246 246 @classmethod
247 247 def get_by_key(cls, key):
248 248 return cls.query().filter(cls.ui_key == key).scalar()
249 249
250 250 @classmethod
251 251 def get_builtin_hooks(cls):
252 252 q = cls.query()
253 253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
254 254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
255 255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
256 256 return q.all()
257 257
258 258 @classmethod
259 259 def get_custom_hooks(cls):
260 260 q = cls.query()
261 261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
262 262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
263 263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
264 264 q = q.filter(cls.ui_section == 'hooks')
265 265 return q.all()
266 266
267 267 @classmethod
268 268 def get_repos_location(cls):
269 269 return cls.get_by_key('/').ui_value
270 270
271 271 @classmethod
272 272 def create_or_update_hook(cls, key, val):
273 273 new_ui = cls.get_by_key(key) or cls()
274 274 new_ui.ui_section = 'hooks'
275 275 new_ui.ui_active = True
276 276 new_ui.ui_key = key
277 277 new_ui.ui_value = val
278 278
279 279 Session().add(new_ui)
280 280
281 281
282 282 class User(Base, BaseModel):
283 283 __tablename__ = 'users'
284 284 __table_args__ = (
285 285 UniqueConstraint('username'), UniqueConstraint('email'),
286 286 Index('u_username_idx', 'username'),
287 287 Index('u_email_idx', 'email'),
288 288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
289 289 'mysql_charset': 'utf8'}
290 290 )
291 291 DEFAULT_USER = 'default'
292 292 DEFAULT_PERMISSIONS = [
293 293 'hg.register.manual_activate', 'hg.create.repository',
294 294 'hg.fork.repository', 'repository.read'
295 295 ]
296 296 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
297 297 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 298 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 299 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
300 300 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
301 301 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
302 302 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 303 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 304 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
305 305 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 306 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 307 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
308 308
309 309 user_log = relationship('UserLog', cascade='all')
310 310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
311 311
312 312 repositories = relationship('Repository')
313 313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
314 314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
315 315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
316 316
317 317 group_member = relationship('UsersGroupMember', cascade='all')
318 318
319 319 notifications = relationship('UserNotification', cascade='all')
320 320 # notifications assigned to this user
321 321 user_created_notifications = relationship('Notification', cascade='all')
322 322 # comments created by this user
323 323 user_comments = relationship('ChangesetComment', cascade='all')
324 324 #extra emails for this user
325 325 user_emails = relationship('UserEmailMap', cascade='all')
326 326
327 327 @hybrid_property
328 328 def email(self):
329 329 return self._email
330 330
331 331 @email.setter
332 332 def email(self, val):
333 333 self._email = val.lower() if val else None
334 334
335 335 @property
336 336 def firstname(self):
337 337 # alias for future
338 338 return self.name
339 339
340 340 @property
341 341 def emails(self):
342 342 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
343 343 return [self.email] + [x.email for x in other]
344 344
345 345 @property
346 346 def username_and_name(self):
347 347 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
348 348
349 349 @property
350 350 def full_name(self):
351 351 return '%s %s' % (self.firstname, self.lastname)
352 352
353 353 @property
354 354 def full_name_or_username(self):
355 355 return ('%s %s' % (self.firstname, self.lastname)
356 356 if (self.firstname and self.lastname) else self.username)
357 357
358 358 @property
359 359 def full_contact(self):
360 360 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
361 361
362 362 @property
363 363 def short_contact(self):
364 364 return '%s %s' % (self.firstname, self.lastname)
365 365
366 366 @property
367 367 def is_admin(self):
368 368 return self.admin
369 369
370 370 def __unicode__(self):
371 371 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
372 372 self.user_id, self.username)
373 373
374 374 @classmethod
375 375 def get_by_username(cls, username, case_insensitive=False, cache=False):
376 376 if case_insensitive:
377 377 q = cls.query().filter(cls.username.ilike(username))
378 378 else:
379 379 q = cls.query().filter(cls.username == username)
380 380
381 381 if cache:
382 382 q = q.options(FromCache(
383 383 "sql_cache_short",
384 384 "get_user_%s" % _hash_key(username)
385 385 )
386 386 )
387 387 return q.scalar()
388 388
389 389 @classmethod
390 390 def get_by_api_key(cls, api_key, cache=False):
391 391 q = cls.query().filter(cls.api_key == api_key)
392 392
393 393 if cache:
394 394 q = q.options(FromCache("sql_cache_short",
395 395 "get_api_key_%s" % api_key))
396 396 return q.scalar()
397 397
398 398 @classmethod
399 399 def get_by_email(cls, email, case_insensitive=False, cache=False):
400 400 if case_insensitive:
401 401 q = cls.query().filter(cls.email.ilike(email))
402 402 else:
403 403 q = cls.query().filter(cls.email == email)
404 404
405 405 if cache:
406 406 q = q.options(FromCache("sql_cache_short",
407 407 "get_email_key_%s" % email))
408 408
409 409 ret = q.scalar()
410 410 if ret is None:
411 411 q = UserEmailMap.query()
412 412 # try fetching in alternate email map
413 413 if case_insensitive:
414 414 q = q.filter(UserEmailMap.email.ilike(email))
415 415 else:
416 416 q = q.filter(UserEmailMap.email == email)
417 417 q = q.options(joinedload(UserEmailMap.user))
418 418 if cache:
419 419 q = q.options(FromCache("sql_cache_short",
420 420 "get_email_map_key_%s" % email))
421 421 ret = getattr(q.scalar(), 'user', None)
422 422
423 423 return ret
424 424
425 425 def update_lastlogin(self):
426 426 """Update user lastlogin"""
427 427 self.last_login = datetime.datetime.now()
428 428 Session().add(self)
429 429 log.debug('updated user %s lastlogin' % self.username)
430 430
431 431 def get_api_data(self):
432 432 """
433 433 Common function for generating user related data for API
434 434 """
435 435 user = self
436 436 data = dict(
437 437 user_id=user.user_id,
438 438 username=user.username,
439 439 firstname=user.name,
440 440 lastname=user.lastname,
441 441 email=user.email,
442 442 emails=user.emails,
443 443 api_key=user.api_key,
444 444 active=user.active,
445 445 admin=user.admin,
446 446 ldap_dn=user.ldap_dn,
447 447 last_login=user.last_login,
448 448 )
449 449 return data
450 450
451 451 def __json__(self):
452 452 data = dict(
453 453 full_name=self.full_name,
454 454 full_name_or_username=self.full_name_or_username,
455 455 short_contact=self.short_contact,
456 456 full_contact=self.full_contact
457 457 )
458 458 data.update(self.get_api_data())
459 459 return data
460 460
461 461
462 462 class UserEmailMap(Base, BaseModel):
463 463 __tablename__ = 'user_email_map'
464 464 __table_args__ = (
465 465 Index('uem_email_idx', 'email'),
466 466 UniqueConstraint('email'),
467 467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 468 'mysql_charset': 'utf8'}
469 469 )
470 470 __mapper_args__ = {}
471 471
472 472 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
473 473 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
474 474 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
475 475 user = relationship('User', lazy='joined')
476 476
477 477 @validates('_email')
478 478 def validate_email(self, key, email):
479 479 # check if this email is not main one
480 480 main_email = Session().query(User).filter(User.email == email).scalar()
481 481 if main_email is not None:
482 482 raise AttributeError('email %s is present is user table' % email)
483 483 return email
484 484
485 485 @hybrid_property
486 486 def email(self):
487 487 return self._email
488 488
489 489 @email.setter
490 490 def email(self, val):
491 491 self._email = val.lower() if val else None
492 492
493 493
494 494 class UserLog(Base, BaseModel):
495 495 __tablename__ = 'user_logs'
496 496 __table_args__ = (
497 497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 498 'mysql_charset': 'utf8'},
499 499 )
500 500 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
502 502 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
503 503 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 504 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 505 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
506 506 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
507 507
508 508 @property
509 509 def action_as_day(self):
510 510 return datetime.date(*self.action_date.timetuple()[:3])
511 511
512 512 user = relationship('User')
513 513 repository = relationship('Repository', cascade='')
514 514
515 515
516 516 class UsersGroup(Base, BaseModel):
517 517 __tablename__ = 'users_groups'
518 518 __table_args__ = (
519 519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
520 520 'mysql_charset': 'utf8'},
521 521 )
522 522
523 523 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
524 524 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
525 525 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
526 526 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
527 527
528 528 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
529 529 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
530 530 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
531 531
532 532 def __unicode__(self):
533 533 return u'<userGroup(%s)>' % (self.users_group_name)
534 534
535 535 @classmethod
536 536 def get_by_group_name(cls, group_name, cache=False,
537 537 case_insensitive=False):
538 538 if case_insensitive:
539 539 q = cls.query().filter(cls.users_group_name.ilike(group_name))
540 540 else:
541 541 q = cls.query().filter(cls.users_group_name == group_name)
542 542 if cache:
543 543 q = q.options(FromCache(
544 544 "sql_cache_short",
545 545 "get_user_%s" % _hash_key(group_name)
546 546 )
547 547 )
548 548 return q.scalar()
549 549
550 550 @classmethod
551 551 def get(cls, users_group_id, cache=False):
552 552 users_group = cls.query()
553 553 if cache:
554 554 users_group = users_group.options(FromCache("sql_cache_short",
555 555 "get_users_group_%s" % users_group_id))
556 556 return users_group.get(users_group_id)
557 557
558 558 def get_api_data(self):
559 559 users_group = self
560 560
561 561 data = dict(
562 562 users_group_id=users_group.users_group_id,
563 563 group_name=users_group.users_group_name,
564 564 active=users_group.users_group_active,
565 565 )
566 566
567 567 return data
568 568
569 569
570 570 class UsersGroupMember(Base, BaseModel):
571 571 __tablename__ = 'users_groups_members'
572 572 __table_args__ = (
573 573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
574 574 'mysql_charset': 'utf8'},
575 575 )
576 576
577 577 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
578 578 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
579 579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
580 580
581 581 user = relationship('User', lazy='joined')
582 582 users_group = relationship('UsersGroup')
583 583
584 584 def __init__(self, gr_id='', u_id=''):
585 585 self.users_group_id = gr_id
586 586 self.user_id = u_id
587 587
588 588
589 589 class Repository(Base, BaseModel):
590 590 __tablename__ = 'repositories'
591 591 __table_args__ = (
592 592 UniqueConstraint('repo_name'),
593 593 Index('r_repo_name_idx', 'repo_name'),
594 594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
595 595 'mysql_charset': 'utf8'},
596 596 )
597 597
598 598 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
599 599 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
600 600 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
601 601 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
602 602 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
603 603 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
604 604 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
605 605 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
606 606 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
607 607 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
608 608 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
609 609 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
610 610 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
611 611 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
612 612
613 613 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
614 614 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
615 615
616 616 user = relationship('User')
617 617 fork = relationship('Repository', remote_side=repo_id)
618 618 group = relationship('RepoGroup')
619 619 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
620 620 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
621 621 stats = relationship('Statistics', cascade='all', uselist=False)
622 622
623 623 followers = relationship('UserFollowing',
624 624 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
625 625 cascade='all')
626 626
627 627 logs = relationship('UserLog')
628 628 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
629 629
630 630 pull_requests_org = relationship('PullRequest',
631 631 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
632 632 cascade="all, delete, delete-orphan")
633 633
634 634 pull_requests_other = relationship('PullRequest',
635 635 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
636 636 cascade="all, delete, delete-orphan")
637 637
638 638 def __unicode__(self):
639 639 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
640 640 self.repo_name)
641 641
642 642 @hybrid_property
643 643 def locked(self):
644 644 # always should return [user_id, timelocked]
645 645 if self._locked:
646 646 _lock_info = self._locked.split(':')
647 647 return int(_lock_info[0]), _lock_info[1]
648 648 return [None, None]
649 649
650 650 @locked.setter
651 651 def locked(self, val):
652 652 if val and isinstance(val, (list, tuple)):
653 653 self._locked = ':'.join(map(str, val))
654 654 else:
655 655 self._locked = None
656 656
657 657 @classmethod
658 658 def url_sep(cls):
659 659 return URL_SEP
660 660
661 661 @classmethod
662 662 def get_by_repo_name(cls, repo_name):
663 663 q = Session().query(cls).filter(cls.repo_name == repo_name)
664 664 q = q.options(joinedload(Repository.fork))\
665 665 .options(joinedload(Repository.user))\
666 666 .options(joinedload(Repository.group))
667 667 return q.scalar()
668 668
669 669 @classmethod
670 670 def get_by_full_path(cls, repo_full_path):
671 671 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
672 672 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
673 673
674 674 @classmethod
675 675 def get_repo_forks(cls, repo_id):
676 676 return cls.query().filter(Repository.fork_id == repo_id)
677 677
678 678 @classmethod
679 679 def base_path(cls):
680 680 """
681 681 Returns base path when all repos are stored
682 682
683 683 :param cls:
684 684 """
685 685 q = Session().query(RhodeCodeUi)\
686 686 .filter(RhodeCodeUi.ui_key == cls.url_sep())
687 687 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
688 688 return q.one().ui_value
689 689
690 690 @property
691 691 def forks(self):
692 692 """
693 693 Return forks of this repo
694 694 """
695 695 return Repository.get_repo_forks(self.repo_id)
696 696
697 697 @property
698 698 def parent(self):
699 699 """
700 700 Returns fork parent
701 701 """
702 702 return self.fork
703 703
704 704 @property
705 705 def just_name(self):
706 706 return self.repo_name.split(Repository.url_sep())[-1]
707 707
708 708 @property
709 709 def groups_with_parents(self):
710 710 groups = []
711 711 if self.group is None:
712 712 return groups
713 713
714 714 cur_gr = self.group
715 715 groups.insert(0, cur_gr)
716 716 while 1:
717 717 gr = getattr(cur_gr, 'parent_group', None)
718 718 cur_gr = cur_gr.parent_group
719 719 if gr is None:
720 720 break
721 721 groups.insert(0, gr)
722 722
723 723 return groups
724 724
725 725 @property
726 726 def groups_and_repo(self):
727 727 return self.groups_with_parents, self.just_name
728 728
729 729 @LazyProperty
730 730 def repo_path(self):
731 731 """
732 732 Returns base full path for that repository means where it actually
733 733 exists on a filesystem
734 734 """
735 735 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
736 736 Repository.url_sep())
737 737 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
738 738 return q.one().ui_value
739 739
740 740 @property
741 741 def repo_full_path(self):
742 742 p = [self.repo_path]
743 743 # we need to split the name by / since this is how we store the
744 744 # names in the database, but that eventually needs to be converted
745 745 # into a valid system path
746 746 p += self.repo_name.split(Repository.url_sep())
747 747 return os.path.join(*p)
748 748
749 @property
750 def cache_keys(self):
751 """
752 Returns associated cache keys for that repo
753 """
754 return CacheInvalidation.query()\
755 .filter(CacheInvalidation.cache_args == self.repo_name)\
756 .order_by(CacheInvalidation.cache_key)\
757 .all()
758
749 759 def get_new_name(self, repo_name):
750 760 """
751 761 returns new full repository name based on assigned group and new new
752 762
753 763 :param group_name:
754 764 """
755 765 path_prefix = self.group.full_path_splitted if self.group else []
756 766 return Repository.url_sep().join(path_prefix + [repo_name])
757 767
758 768 @property
759 769 def _ui(self):
760 770 """
761 771 Creates an db based ui object for this repository
762 772 """
763 773 from mercurial import ui
764 774 from mercurial import config
765 775 baseui = ui.ui()
766 776
767 777 #clean the baseui object
768 778 baseui._ocfg = config.config()
769 779 baseui._ucfg = config.config()
770 780 baseui._tcfg = config.config()
771 781
772 782 ret = RhodeCodeUi.query()\
773 783 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
774 784
775 785 hg_ui = ret
776 786 for ui_ in hg_ui:
777 787 if ui_.ui_active:
778 788 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
779 789 ui_.ui_key, ui_.ui_value)
780 790 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
781 791 if ui_.ui_key == 'push_ssl':
782 792 # force set push_ssl requirement to False, rhodecode
783 793 # handles that
784 794 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
785 795
786 796 return baseui
787 797
788 798 @classmethod
789 799 def inject_ui(cls, repo, extras={}):
790 800 from rhodecode.lib.vcs.backends.hg import MercurialRepository
791 801 from rhodecode.lib.vcs.backends.git import GitRepository
792 802 required = (MercurialRepository, GitRepository)
793 803 if not isinstance(repo, required):
794 804 raise Exception('repo must be instance of %s' % required)
795 805
796 806 # inject ui extra param to log this action via push logger
797 807 for k, v in extras.items():
798 808 repo._repo.ui.setconfig('rhodecode_extras', k, v)
799 809
800 810 @classmethod
801 811 def is_valid(cls, repo_name):
802 812 """
803 813 returns True if given repo name is a valid filesystem repository
804 814
805 815 :param cls:
806 816 :param repo_name:
807 817 """
808 818 from rhodecode.lib.utils import is_valid_repo
809 819
810 820 return is_valid_repo(repo_name, cls.base_path())
811 821
812 822 def get_api_data(self):
813 823 """
814 824 Common function for generating repo api data
815 825
816 826 """
817 827 repo = self
818 828 data = dict(
819 829 repo_id=repo.repo_id,
820 830 repo_name=repo.repo_name,
821 831 repo_type=repo.repo_type,
822 832 clone_uri=repo.clone_uri,
823 833 private=repo.private,
824 834 created_on=repo.created_on,
825 835 description=repo.description,
826 836 landing_rev=repo.landing_rev,
827 837 owner=repo.user.username,
828 838 fork_of=repo.fork.repo_name if repo.fork else None
829 839 )
830 840
831 841 return data
832 842
833 843 @classmethod
834 844 def lock(cls, repo, user_id):
835 845 repo.locked = [user_id, time.time()]
836 846 Session().add(repo)
837 847 Session().commit()
838 848
839 849 @classmethod
840 850 def unlock(cls, repo):
841 851 repo.locked = None
842 852 Session().add(repo)
843 853 Session().commit()
844 854
845 855 #==========================================================================
846 856 # SCM PROPERTIES
847 857 #==========================================================================
848 858
849 859 def get_changeset(self, rev=None):
850 860 return get_changeset_safe(self.scm_instance, rev)
851 861
852 862 def get_landing_changeset(self):
853 863 """
854 864 Returns landing changeset, or if that doesn't exist returns the tip
855 865 """
856 866 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
857 867 return cs
858 868
859 869 @property
860 870 def tip(self):
861 871 return self.get_changeset('tip')
862 872
863 873 @property
864 874 def author(self):
865 875 return self.tip.author
866 876
867 877 @property
868 878 def last_change(self):
869 879 return self.scm_instance.last_change
870 880
871 881 def get_comments(self, revisions=None):
872 882 """
873 883 Returns comments for this repository grouped by revisions
874 884
875 885 :param revisions: filter query by revisions only
876 886 """
877 887 cmts = ChangesetComment.query()\
878 888 .filter(ChangesetComment.repo == self)
879 889 if revisions:
880 890 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
881 891 grouped = defaultdict(list)
882 892 for cmt in cmts.all():
883 893 grouped[cmt.revision].append(cmt)
884 894 return grouped
885 895
886 896 def statuses(self, revisions=None):
887 897 """
888 898 Returns statuses for this repository
889 899
890 900 :param revisions: list of revisions to get statuses for
891 901 :type revisions: list
892 902 """
893 903
894 904 statuses = ChangesetStatus.query()\
895 905 .filter(ChangesetStatus.repo == self)\
896 906 .filter(ChangesetStatus.version == 0)
897 907 if revisions:
898 908 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
899 909 grouped = {}
900 910
901 911 #maybe we have open new pullrequest without a status ?
902 912 stat = ChangesetStatus.STATUS_UNDER_REVIEW
903 913 status_lbl = ChangesetStatus.get_status_lbl(stat)
904 914 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
905 915 for rev in pr.revisions:
906 916 pr_id = pr.pull_request_id
907 917 pr_repo = pr.other_repo.repo_name
908 918 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
909 919
910 920 for stat in statuses.all():
911 921 pr_id = pr_repo = None
912 922 if stat.pull_request:
913 923 pr_id = stat.pull_request.pull_request_id
914 924 pr_repo = stat.pull_request.other_repo.repo_name
915 925 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
916 926 pr_id, pr_repo]
917 927 return grouped
918 928
919 929 #==========================================================================
920 930 # SCM CACHE INSTANCE
921 931 #==========================================================================
922 932
923 933 @property
924 934 def invalidate(self):
925 935 return CacheInvalidation.invalidate(self.repo_name)
926 936
927 937 def set_invalidate(self):
928 938 """
929 939 set a cache for invalidation for this instance
930 940 """
931 941 CacheInvalidation.set_invalidate(self.repo_name)
932 942
933 943 @LazyProperty
934 944 def scm_instance(self):
935 945 return self.__get_instance()
936 946
937 947 def scm_instance_cached(self, cache_map=None):
938 948 @cache_region('long_term')
939 949 def _c(repo_name):
940 950 return self.__get_instance()
941 951 rn = self.repo_name
942 952 log.debug('Getting cached instance of repo')
943 953
944 954 if cache_map:
945 955 # get using prefilled cache_map
946 956 invalidate_repo = cache_map[self.repo_name]
947 957 if invalidate_repo:
948 958 invalidate_repo = (None if invalidate_repo.cache_active
949 959 else invalidate_repo)
950 960 else:
951 961 # get from invalidate
952 962 invalidate_repo = self.invalidate
953 963
954 964 if invalidate_repo is not None:
955 965 region_invalidate(_c, None, rn)
956 966 # update our cache
957 967 CacheInvalidation.set_valid(invalidate_repo.cache_key)
958 968 return _c(rn)
959 969
960 970 def __get_instance(self):
961 971 repo_full_path = self.repo_full_path
962 972 try:
963 973 alias = get_scm(repo_full_path)[0]
964 974 log.debug('Creating instance of %s repository' % alias)
965 975 backend = get_backend(alias)
966 976 except VCSError:
967 977 log.error(traceback.format_exc())
968 978 log.error('Perhaps this repository is in db and not in '
969 979 'filesystem run rescan repositories with '
970 980 '"destroy old data " option from admin panel')
971 981 return
972 982
973 983 if alias == 'hg':
974 984
975 985 repo = backend(safe_str(repo_full_path), create=False,
976 986 baseui=self._ui)
977 987 # skip hidden web repository
978 988 if repo._get_hidden():
979 989 return
980 990 else:
981 991 repo = backend(repo_full_path, create=False)
982 992
983 993 return repo
984 994
985 995
986 996 class RepoGroup(Base, BaseModel):
987 997 __tablename__ = 'groups'
988 998 __table_args__ = (
989 999 UniqueConstraint('group_name', 'group_parent_id'),
990 1000 CheckConstraint('group_id != group_parent_id'),
991 1001 {'extend_existing': True, 'mysql_engine': 'InnoDB',
992 1002 'mysql_charset': 'utf8'},
993 1003 )
994 1004 __mapper_args__ = {'order_by': 'group_name'}
995 1005
996 1006 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 1007 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
998 1008 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
999 1009 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1000 1010 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1001 1011
1002 1012 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1003 1013 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1004 1014
1005 1015 parent_group = relationship('RepoGroup', remote_side=group_id)
1006 1016
1007 1017 def __init__(self, group_name='', parent_group=None):
1008 1018 self.group_name = group_name
1009 1019 self.parent_group = parent_group
1010 1020
1011 1021 def __unicode__(self):
1012 1022 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1013 1023 self.group_name)
1014 1024
1015 1025 @classmethod
1016 1026 def groups_choices(cls):
1017 1027 from webhelpers.html import literal as _literal
1018 1028 repo_groups = [('', '')]
1019 1029 sep = ' &raquo; '
1020 1030 _name = lambda k: _literal(sep.join(k))
1021 1031
1022 1032 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1023 1033 for x in cls.query().all()])
1024 1034
1025 1035 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1026 1036 return repo_groups
1027 1037
1028 1038 @classmethod
1029 1039 def url_sep(cls):
1030 1040 return URL_SEP
1031 1041
1032 1042 @classmethod
1033 1043 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1034 1044 if case_insensitive:
1035 1045 gr = cls.query()\
1036 1046 .filter(cls.group_name.ilike(group_name))
1037 1047 else:
1038 1048 gr = cls.query()\
1039 1049 .filter(cls.group_name == group_name)
1040 1050 if cache:
1041 1051 gr = gr.options(FromCache(
1042 1052 "sql_cache_short",
1043 1053 "get_group_%s" % _hash_key(group_name)
1044 1054 )
1045 1055 )
1046 1056 return gr.scalar()
1047 1057
1048 1058 @property
1049 1059 def parents(self):
1050 1060 parents_recursion_limit = 5
1051 1061 groups = []
1052 1062 if self.parent_group is None:
1053 1063 return groups
1054 1064 cur_gr = self.parent_group
1055 1065 groups.insert(0, cur_gr)
1056 1066 cnt = 0
1057 1067 while 1:
1058 1068 cnt += 1
1059 1069 gr = getattr(cur_gr, 'parent_group', None)
1060 1070 cur_gr = cur_gr.parent_group
1061 1071 if gr is None:
1062 1072 break
1063 1073 if cnt == parents_recursion_limit:
1064 1074 # this will prevent accidental infinit loops
1065 1075 log.error('group nested more than %s' %
1066 1076 parents_recursion_limit)
1067 1077 break
1068 1078
1069 1079 groups.insert(0, gr)
1070 1080 return groups
1071 1081
1072 1082 @property
1073 1083 def children(self):
1074 1084 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1075 1085
1076 1086 @property
1077 1087 def name(self):
1078 1088 return self.group_name.split(RepoGroup.url_sep())[-1]
1079 1089
1080 1090 @property
1081 1091 def full_path(self):
1082 1092 return self.group_name
1083 1093
1084 1094 @property
1085 1095 def full_path_splitted(self):
1086 1096 return self.group_name.split(RepoGroup.url_sep())
1087 1097
1088 1098 @property
1089 1099 def repositories(self):
1090 1100 return Repository.query()\
1091 1101 .filter(Repository.group == self)\
1092 1102 .order_by(Repository.repo_name)
1093 1103
1094 1104 @property
1095 1105 def repositories_recursive_count(self):
1096 1106 cnt = self.repositories.count()
1097 1107
1098 1108 def children_count(group):
1099 1109 cnt = 0
1100 1110 for child in group.children:
1101 1111 cnt += child.repositories.count()
1102 1112 cnt += children_count(child)
1103 1113 return cnt
1104 1114
1105 1115 return cnt + children_count(self)
1106 1116
1107 1117 def recursive_groups_and_repos(self):
1108 1118 """
1109 1119 Recursive return all groups, with repositories in those groups
1110 1120 """
1111 1121 all_ = []
1112 1122
1113 1123 def _get_members(root_gr):
1114 1124 for r in root_gr.repositories:
1115 1125 all_.append(r)
1116 1126 childs = root_gr.children.all()
1117 1127 if childs:
1118 1128 for gr in childs:
1119 1129 all_.append(gr)
1120 1130 _get_members(gr)
1121 1131
1122 1132 _get_members(self)
1123 1133 return [self] + all_
1124 1134
1125 1135 def get_new_name(self, group_name):
1126 1136 """
1127 1137 returns new full group name based on parent and new name
1128 1138
1129 1139 :param group_name:
1130 1140 """
1131 1141 path_prefix = (self.parent_group.full_path_splitted if
1132 1142 self.parent_group else [])
1133 1143 return RepoGroup.url_sep().join(path_prefix + [group_name])
1134 1144
1135 1145
1136 1146 class Permission(Base, BaseModel):
1137 1147 __tablename__ = 'permissions'
1138 1148 __table_args__ = (
1139 1149 Index('p_perm_name_idx', 'permission_name'),
1140 1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 1151 'mysql_charset': 'utf8'},
1142 1152 )
1143 1153 PERMS = [
1144 1154 ('repository.none', _('Repository no access')),
1145 1155 ('repository.read', _('Repository read access')),
1146 1156 ('repository.write', _('Repository write access')),
1147 1157 ('repository.admin', _('Repository admin access')),
1148 1158
1149 1159 ('group.none', _('Repositories Group no access')),
1150 1160 ('group.read', _('Repositories Group read access')),
1151 1161 ('group.write', _('Repositories Group write access')),
1152 1162 ('group.admin', _('Repositories Group admin access')),
1153 1163
1154 1164 ('hg.admin', _('RhodeCode Administrator')),
1155 1165 ('hg.create.none', _('Repository creation disabled')),
1156 1166 ('hg.create.repository', _('Repository creation enabled')),
1157 1167 ('hg.fork.none', _('Repository forking disabled')),
1158 1168 ('hg.fork.repository', _('Repository forking enabled')),
1159 1169 ('hg.register.none', _('Register disabled')),
1160 1170 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1161 1171 'with manual activation')),
1162 1172
1163 1173 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1164 1174 'with auto activation')),
1165 1175 ]
1166 1176
1167 1177 # defines which permissions are more important higher the more important
1168 1178 PERM_WEIGHTS = {
1169 1179 'repository.none': 0,
1170 1180 'repository.read': 1,
1171 1181 'repository.write': 3,
1172 1182 'repository.admin': 4,
1173 1183
1174 1184 'group.none': 0,
1175 1185 'group.read': 1,
1176 1186 'group.write': 3,
1177 1187 'group.admin': 4,
1178 1188
1179 1189 'hg.fork.none': 0,
1180 1190 'hg.fork.repository': 1,
1181 1191 'hg.create.none': 0,
1182 1192 'hg.create.repository':1
1183 1193 }
1184 1194
1185 1195 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 1196 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1187 1197 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1188 1198
1189 1199 def __unicode__(self):
1190 1200 return u"<%s('%s:%s')>" % (
1191 1201 self.__class__.__name__, self.permission_id, self.permission_name
1192 1202 )
1193 1203
1194 1204 @classmethod
1195 1205 def get_by_key(cls, key):
1196 1206 return cls.query().filter(cls.permission_name == key).scalar()
1197 1207
1198 1208 @classmethod
1199 1209 def get_default_perms(cls, default_user_id):
1200 1210 q = Session().query(UserRepoToPerm, Repository, cls)\
1201 1211 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1202 1212 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1203 1213 .filter(UserRepoToPerm.user_id == default_user_id)
1204 1214
1205 1215 return q.all()
1206 1216
1207 1217 @classmethod
1208 1218 def get_default_group_perms(cls, default_user_id):
1209 1219 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1210 1220 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1211 1221 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1212 1222 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1213 1223
1214 1224 return q.all()
1215 1225
1216 1226
1217 1227 class UserRepoToPerm(Base, BaseModel):
1218 1228 __tablename__ = 'repo_to_perm'
1219 1229 __table_args__ = (
1220 1230 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1221 1231 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1222 1232 'mysql_charset': 'utf8'}
1223 1233 )
1224 1234 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1225 1235 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1226 1236 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1227 1237 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1228 1238
1229 1239 user = relationship('User')
1230 1240 repository = relationship('Repository')
1231 1241 permission = relationship('Permission')
1232 1242
1233 1243 @classmethod
1234 1244 def create(cls, user, repository, permission):
1235 1245 n = cls()
1236 1246 n.user = user
1237 1247 n.repository = repository
1238 1248 n.permission = permission
1239 1249 Session().add(n)
1240 1250 return n
1241 1251
1242 1252 def __unicode__(self):
1243 1253 return u'<user:%s => %s >' % (self.user, self.repository)
1244 1254
1245 1255
1246 1256 class UserToPerm(Base, BaseModel):
1247 1257 __tablename__ = 'user_to_perm'
1248 1258 __table_args__ = (
1249 1259 UniqueConstraint('user_id', 'permission_id'),
1250 1260 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1251 1261 'mysql_charset': 'utf8'}
1252 1262 )
1253 1263 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1254 1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1255 1265 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1256 1266
1257 1267 user = relationship('User')
1258 1268 permission = relationship('Permission', lazy='joined')
1259 1269
1260 1270
1261 1271 class UsersGroupRepoToPerm(Base, BaseModel):
1262 1272 __tablename__ = 'users_group_repo_to_perm'
1263 1273 __table_args__ = (
1264 1274 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1265 1275 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1266 1276 'mysql_charset': 'utf8'}
1267 1277 )
1268 1278 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1269 1279 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1270 1280 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1271 1281 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1272 1282
1273 1283 users_group = relationship('UsersGroup')
1274 1284 permission = relationship('Permission')
1275 1285 repository = relationship('Repository')
1276 1286
1277 1287 @classmethod
1278 1288 def create(cls, users_group, repository, permission):
1279 1289 n = cls()
1280 1290 n.users_group = users_group
1281 1291 n.repository = repository
1282 1292 n.permission = permission
1283 1293 Session().add(n)
1284 1294 return n
1285 1295
1286 1296 def __unicode__(self):
1287 1297 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1288 1298
1289 1299
1290 1300 class UsersGroupToPerm(Base, BaseModel):
1291 1301 __tablename__ = 'users_group_to_perm'
1292 1302 __table_args__ = (
1293 1303 UniqueConstraint('users_group_id', 'permission_id',),
1294 1304 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1295 1305 'mysql_charset': 'utf8'}
1296 1306 )
1297 1307 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 1308 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1299 1309 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1300 1310
1301 1311 users_group = relationship('UsersGroup')
1302 1312 permission = relationship('Permission')
1303 1313
1304 1314
1305 1315 class UserRepoGroupToPerm(Base, BaseModel):
1306 1316 __tablename__ = 'user_repo_group_to_perm'
1307 1317 __table_args__ = (
1308 1318 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1309 1319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1310 1320 'mysql_charset': 'utf8'}
1311 1321 )
1312 1322
1313 1323 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1314 1324 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1315 1325 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1316 1326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1317 1327
1318 1328 user = relationship('User')
1319 1329 group = relationship('RepoGroup')
1320 1330 permission = relationship('Permission')
1321 1331
1322 1332
1323 1333 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1324 1334 __tablename__ = 'users_group_repo_group_to_perm'
1325 1335 __table_args__ = (
1326 1336 UniqueConstraint('users_group_id', 'group_id'),
1327 1337 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1328 1338 'mysql_charset': 'utf8'}
1329 1339 )
1330 1340
1331 1341 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1332 1342 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1333 1343 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1334 1344 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1335 1345
1336 1346 users_group = relationship('UsersGroup')
1337 1347 permission = relationship('Permission')
1338 1348 group = relationship('RepoGroup')
1339 1349
1340 1350
1341 1351 class Statistics(Base, BaseModel):
1342 1352 __tablename__ = 'statistics'
1343 1353 __table_args__ = (
1344 1354 UniqueConstraint('repository_id'),
1345 1355 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1346 1356 'mysql_charset': 'utf8'}
1347 1357 )
1348 1358 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1349 1359 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1350 1360 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1351 1361 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1352 1362 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1353 1363 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1354 1364
1355 1365 repository = relationship('Repository', single_parent=True)
1356 1366
1357 1367
1358 1368 class UserFollowing(Base, BaseModel):
1359 1369 __tablename__ = 'user_followings'
1360 1370 __table_args__ = (
1361 1371 UniqueConstraint('user_id', 'follows_repository_id'),
1362 1372 UniqueConstraint('user_id', 'follows_user_id'),
1363 1373 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1364 1374 'mysql_charset': 'utf8'}
1365 1375 )
1366 1376
1367 1377 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1368 1378 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1369 1379 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1370 1380 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1371 1381 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1372 1382
1373 1383 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1374 1384
1375 1385 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1376 1386 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1377 1387
1378 1388 @classmethod
1379 1389 def get_repo_followers(cls, repo_id):
1380 1390 return cls.query().filter(cls.follows_repo_id == repo_id)
1381 1391
1382 1392
1383 1393 class CacheInvalidation(Base, BaseModel):
1384 1394 __tablename__ = 'cache_invalidation'
1385 1395 __table_args__ = (
1386 1396 UniqueConstraint('cache_key'),
1387 1397 Index('key_idx', 'cache_key'),
1388 1398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1389 1399 'mysql_charset': 'utf8'},
1390 1400 )
1391 1401 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1392 1402 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1393 1403 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1394 1404 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1395 1405
1396 1406 def __init__(self, cache_key, cache_args=''):
1397 1407 self.cache_key = cache_key
1398 1408 self.cache_args = cache_args
1399 1409 self.cache_active = False
1400 1410
1401 1411 def __unicode__(self):
1402 1412 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1403 1413 self.cache_id, self.cache_key)
1404 1414
1415 @property
1416 def prefix(self):
1417 _split = self.cache_key.split(self.cache_args, 1)
1418 if _split and len(_split) == 2:
1419 return _split[0]
1420 return ''
1421
1405 1422 @classmethod
1406 1423 def clear_cache(cls):
1407 1424 cls.query().delete()
1408 1425
1409 1426 @classmethod
1410 1427 def _get_key(cls, key):
1411 1428 """
1412 1429 Wrapper for generating a key, together with a prefix
1413 1430
1414 1431 :param key:
1415 1432 """
1416 1433 import rhodecode
1417 1434 prefix = ''
1418 1435 iid = rhodecode.CONFIG.get('instance_id')
1419 1436 if iid:
1420 1437 prefix = iid
1421 1438 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1422 1439
1423 1440 @classmethod
1424 1441 def get_by_key(cls, key):
1425 1442 return cls.query().filter(cls.cache_key == key).scalar()
1426 1443
1427 1444 @classmethod
1428 1445 def _get_or_create_key(cls, key, prefix, org_key, commit=True):
1429 1446 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1430 1447 if not inv_obj:
1431 1448 try:
1432 1449 inv_obj = CacheInvalidation(key, org_key)
1433 1450 Session().add(inv_obj)
1434 1451 if commit:
1435 1452 Session().commit()
1436 1453 except Exception:
1437 1454 log.error(traceback.format_exc())
1438 1455 Session().rollback()
1439 1456 return inv_obj
1440 1457
1441 1458 @classmethod
1442 1459 def invalidate(cls, key):
1443 1460 """
1444 1461 Returns Invalidation object if this given key should be invalidated
1445 1462 None otherwise. `cache_active = False` means that this cache
1446 1463 state is not valid and needs to be invalidated
1447 1464
1448 1465 :param key:
1449 1466 """
1450 1467
1451 1468 key, _prefix, _org_key = cls._get_key(key)
1452 1469 inv = cls._get_or_create_key(key, _prefix, _org_key)
1453 1470
1454 1471 if inv and inv.cache_active is False:
1455 1472 return inv
1456 1473
1457 1474 @classmethod
1458 1475 def set_invalidate(cls, key):
1459 1476 """
1460 1477 Mark this Cache key for invalidation
1461 1478
1462 1479 :param key:
1463 1480 """
1464 1481
1465 1482 key, _prefix, _org_key = cls._get_key(key)
1466 1483 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1467 1484 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1468 1485 _org_key))
1469 1486 try:
1470 1487 for inv_obj in inv_objs:
1471 1488 if inv_obj:
1472 1489 inv_obj.cache_active = False
1473 1490
1474 1491 Session().add(inv_obj)
1475 1492 Session().commit()
1476 1493 except Exception:
1477 1494 log.error(traceback.format_exc())
1478 1495 Session().rollback()
1479 1496
1480 1497 @classmethod
1481 1498 def set_valid(cls, key):
1482 1499 """
1483 1500 Mark this cache key as active and currently cached
1484 1501
1485 1502 :param key:
1486 1503 """
1487 1504 inv_obj = cls.get_by_key(key)
1488 1505 inv_obj.cache_active = True
1489 1506 Session().add(inv_obj)
1490 1507 Session().commit()
1491 1508
1492 1509 @classmethod
1493 1510 def get_cache_map(cls):
1494 1511
1495 1512 class cachemapdict(dict):
1496 1513
1497 1514 def __init__(self, *args, **kwargs):
1498 1515 fixkey = kwargs.get('fixkey')
1499 1516 if fixkey:
1500 1517 del kwargs['fixkey']
1501 1518 self.fixkey = fixkey
1502 1519 super(cachemapdict, self).__init__(*args, **kwargs)
1503 1520
1504 1521 def __getattr__(self, name):
1505 1522 key = name
1506 1523 if self.fixkey:
1507 1524 key, _prefix, _org_key = cls._get_key(key)
1508 1525 if key in self.__dict__:
1509 1526 return self.__dict__[key]
1510 1527 else:
1511 1528 return self[key]
1512 1529
1513 1530 def __getitem__(self, key):
1514 1531 if self.fixkey:
1515 1532 key, _prefix, _org_key = cls._get_key(key)
1516 1533 try:
1517 1534 return super(cachemapdict, self).__getitem__(key)
1518 1535 except KeyError:
1519 1536 return
1520 1537
1521 1538 cache_map = cachemapdict(fixkey=True)
1522 1539 for obj in cls.query().all():
1523 1540 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1524 1541 return cache_map
1525 1542
1526 1543
1527 1544 class ChangesetComment(Base, BaseModel):
1528 1545 __tablename__ = 'changeset_comments'
1529 1546 __table_args__ = (
1530 1547 Index('cc_revision_idx', 'revision'),
1531 1548 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1532 1549 'mysql_charset': 'utf8'},
1533 1550 )
1534 1551 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1535 1552 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1536 1553 revision = Column('revision', String(40), nullable=True)
1537 1554 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1538 1555 line_no = Column('line_no', Unicode(10), nullable=True)
1539 1556 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1540 1557 f_path = Column('f_path', Unicode(1000), nullable=True)
1541 1558 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1542 1559 text = Column('text', UnicodeText(25000), nullable=False)
1543 1560 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1544 1561 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1545 1562
1546 1563 author = relationship('User', lazy='joined')
1547 1564 repo = relationship('Repository')
1548 1565 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1549 1566 pull_request = relationship('PullRequest', lazy='joined')
1550 1567
1551 1568 @classmethod
1552 1569 def get_users(cls, revision=None, pull_request_id=None):
1553 1570 """
1554 1571 Returns user associated with this ChangesetComment. ie those
1555 1572 who actually commented
1556 1573
1557 1574 :param cls:
1558 1575 :param revision:
1559 1576 """
1560 1577 q = Session().query(User)\
1561 1578 .join(ChangesetComment.author)
1562 1579 if revision:
1563 1580 q = q.filter(cls.revision == revision)
1564 1581 elif pull_request_id:
1565 1582 q = q.filter(cls.pull_request_id == pull_request_id)
1566 1583 return q.all()
1567 1584
1568 1585
1569 1586 class ChangesetStatus(Base, BaseModel):
1570 1587 __tablename__ = 'changeset_statuses'
1571 1588 __table_args__ = (
1572 1589 Index('cs_revision_idx', 'revision'),
1573 1590 Index('cs_version_idx', 'version'),
1574 1591 UniqueConstraint('repo_id', 'revision', 'version'),
1575 1592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1576 1593 'mysql_charset': 'utf8'}
1577 1594 )
1578 1595 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1579 1596 STATUS_APPROVED = 'approved'
1580 1597 STATUS_REJECTED = 'rejected'
1581 1598 STATUS_UNDER_REVIEW = 'under_review'
1582 1599
1583 1600 STATUSES = [
1584 1601 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1585 1602 (STATUS_APPROVED, _("Approved")),
1586 1603 (STATUS_REJECTED, _("Rejected")),
1587 1604 (STATUS_UNDER_REVIEW, _("Under Review")),
1588 1605 ]
1589 1606
1590 1607 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1591 1608 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1592 1609 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1593 1610 revision = Column('revision', String(40), nullable=False)
1594 1611 status = Column('status', String(128), nullable=False, default=DEFAULT)
1595 1612 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1596 1613 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1597 1614 version = Column('version', Integer(), nullable=False, default=0)
1598 1615 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1599 1616
1600 1617 author = relationship('User', lazy='joined')
1601 1618 repo = relationship('Repository')
1602 1619 comment = relationship('ChangesetComment', lazy='joined')
1603 1620 pull_request = relationship('PullRequest', lazy='joined')
1604 1621
1605 1622 def __unicode__(self):
1606 1623 return u"<%s('%s:%s')>" % (
1607 1624 self.__class__.__name__,
1608 1625 self.status, self.author
1609 1626 )
1610 1627
1611 1628 @classmethod
1612 1629 def get_status_lbl(cls, value):
1613 1630 return dict(cls.STATUSES).get(value)
1614 1631
1615 1632 @property
1616 1633 def status_lbl(self):
1617 1634 return ChangesetStatus.get_status_lbl(self.status)
1618 1635
1619 1636
1620 1637 class PullRequest(Base, BaseModel):
1621 1638 __tablename__ = 'pull_requests'
1622 1639 __table_args__ = (
1623 1640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1624 1641 'mysql_charset': 'utf8'},
1625 1642 )
1626 1643
1627 1644 STATUS_NEW = u'new'
1628 1645 STATUS_OPEN = u'open'
1629 1646 STATUS_CLOSED = u'closed'
1630 1647
1631 1648 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1632 1649 title = Column('title', Unicode(256), nullable=True)
1633 1650 description = Column('description', UnicodeText(10240), nullable=True)
1634 1651 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1635 1652 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1636 1653 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1637 1654 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1638 1655 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1639 1656 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1640 1657 org_ref = Column('org_ref', Unicode(256), nullable=False)
1641 1658 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1642 1659 other_ref = Column('other_ref', Unicode(256), nullable=False)
1643 1660
1644 1661 @hybrid_property
1645 1662 def revisions(self):
1646 1663 return self._revisions.split(':')
1647 1664
1648 1665 @revisions.setter
1649 1666 def revisions(self, val):
1650 1667 self._revisions = ':'.join(val)
1651 1668
1652 1669 author = relationship('User', lazy='joined')
1653 1670 reviewers = relationship('PullRequestReviewers',
1654 1671 cascade="all, delete, delete-orphan")
1655 1672 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1656 1673 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1657 1674 statuses = relationship('ChangesetStatus')
1658 1675 comments = relationship('ChangesetComment',
1659 1676 cascade="all, delete, delete-orphan")
1660 1677
1661 1678 def is_closed(self):
1662 1679 return self.status == self.STATUS_CLOSED
1663 1680
1664 1681 def __json__(self):
1665 1682 return dict(
1666 1683 revisions=self.revisions
1667 1684 )
1668 1685
1669 1686
1670 1687 class PullRequestReviewers(Base, BaseModel):
1671 1688 __tablename__ = 'pull_request_reviewers'
1672 1689 __table_args__ = (
1673 1690 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1674 1691 'mysql_charset': 'utf8'},
1675 1692 )
1676 1693
1677 1694 def __init__(self, user=None, pull_request=None):
1678 1695 self.user = user
1679 1696 self.pull_request = pull_request
1680 1697
1681 1698 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1682 1699 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1683 1700 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1684 1701
1685 1702 user = relationship('User')
1686 1703 pull_request = relationship('PullRequest')
1687 1704
1688 1705
1689 1706 class Notification(Base, BaseModel):
1690 1707 __tablename__ = 'notifications'
1691 1708 __table_args__ = (
1692 1709 Index('notification_type_idx', 'type'),
1693 1710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1694 1711 'mysql_charset': 'utf8'},
1695 1712 )
1696 1713
1697 1714 TYPE_CHANGESET_COMMENT = u'cs_comment'
1698 1715 TYPE_MESSAGE = u'message'
1699 1716 TYPE_MENTION = u'mention'
1700 1717 TYPE_REGISTRATION = u'registration'
1701 1718 TYPE_PULL_REQUEST = u'pull_request'
1702 1719 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1703 1720
1704 1721 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1705 1722 subject = Column('subject', Unicode(512), nullable=True)
1706 1723 body = Column('body', UnicodeText(50000), nullable=True)
1707 1724 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1708 1725 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1709 1726 type_ = Column('type', Unicode(256))
1710 1727
1711 1728 created_by_user = relationship('User')
1712 1729 notifications_to_users = relationship('UserNotification', lazy='joined',
1713 1730 cascade="all, delete, delete-orphan")
1714 1731
1715 1732 @property
1716 1733 def recipients(self):
1717 1734 return [x.user for x in UserNotification.query()\
1718 1735 .filter(UserNotification.notification == self)\
1719 1736 .order_by(UserNotification.user_id.asc()).all()]
1720 1737
1721 1738 @classmethod
1722 1739 def create(cls, created_by, subject, body, recipients, type_=None):
1723 1740 if type_ is None:
1724 1741 type_ = Notification.TYPE_MESSAGE
1725 1742
1726 1743 notification = cls()
1727 1744 notification.created_by_user = created_by
1728 1745 notification.subject = subject
1729 1746 notification.body = body
1730 1747 notification.type_ = type_
1731 1748 notification.created_on = datetime.datetime.now()
1732 1749
1733 1750 for u in recipients:
1734 1751 assoc = UserNotification()
1735 1752 assoc.notification = notification
1736 1753 u.notifications.append(assoc)
1737 1754 Session().add(notification)
1738 1755 return notification
1739 1756
1740 1757 @property
1741 1758 def description(self):
1742 1759 from rhodecode.model.notification import NotificationModel
1743 1760 return NotificationModel().make_description(self)
1744 1761
1745 1762
1746 1763 class UserNotification(Base, BaseModel):
1747 1764 __tablename__ = 'user_to_notification'
1748 1765 __table_args__ = (
1749 1766 UniqueConstraint('user_id', 'notification_id'),
1750 1767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1751 1768 'mysql_charset': 'utf8'}
1752 1769 )
1753 1770 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1754 1771 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1755 1772 read = Column('read', Boolean, default=False)
1756 1773 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1757 1774
1758 1775 user = relationship('User', lazy="joined")
1759 1776 notification = relationship('Notification', lazy="joined",
1760 1777 order_by=lambda: Notification.created_on.desc(),)
1761 1778
1762 1779 def mark_as_read(self):
1763 1780 self.read = True
1764 1781 Session().add(self)
1765 1782
1766 1783
1767 1784 class DbMigrateVersion(Base, BaseModel):
1768 1785 __tablename__ = 'db_migrate_version'
1769 1786 __table_args__ = (
1770 1787 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1771 1788 'mysql_charset': 'utf8'},
1772 1789 )
1773 1790 repository_id = Column('repository_id', String(250), primary_key=True)
1774 1791 repository_path = Column('repository_path', Text)
1775 1792 version = Column('version', Integer)
@@ -1,268 +1,282 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="clone_uri">${_('Clone uri')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 45 </div>
46 46 </div>
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="repo_group">${_('Repository group')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 54 </div>
55 55 </div>
56 56 <div class="field">
57 57 <div class="label">
58 58 <label for="repo_type">${_('Type')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 62 </div>
63 63 </div>
64 64 <div class="field">
65 65 <div class="label">
66 66 <label for="landing_rev">${_('Landing revision')}:</label>
67 67 </div>
68 68 <div class="input">
69 69 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
70 70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 71 </div>
72 72 </div>
73 73 <div class="field">
74 74 <div class="label label-textarea">
75 75 <label for="description">${_('Description')}:</label>
76 76 </div>
77 77 <div class="textarea text-area editor">
78 78 ${h.textarea('description')}
79 79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 80 </div>
81 81 </div>
82 82
83 83 <div class="field">
84 84 <div class="label label-checkbox">
85 85 <label for="private">${_('Private repository')}:</label>
86 86 </div>
87 87 <div class="checkboxes">
88 88 ${h.checkbox('private',value="True")}
89 89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 90 </div>
91 91 </div>
92 92 <div class="field">
93 93 <div class="label label-checkbox">
94 94 <label for="enable_statistics">${_('Enable statistics')}:</label>
95 95 </div>
96 96 <div class="checkboxes">
97 97 ${h.checkbox('enable_statistics',value="True")}
98 98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 99 </div>
100 100 </div>
101 101 <div class="field">
102 102 <div class="label label-checkbox">
103 103 <label for="enable_downloads">${_('Enable downloads')}:</label>
104 104 </div>
105 105 <div class="checkboxes">
106 106 ${h.checkbox('enable_downloads',value="True")}
107 107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 108 </div>
109 109 </div>
110 110 <div class="field">
111 111 <div class="label label-checkbox">
112 112 <label for="enable_locking">${_('Enable locking')}:</label>
113 113 </div>
114 114 <div class="checkboxes">
115 115 ${h.checkbox('enable_locking',value="True")}
116 116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 117 </div>
118 118 </div>
119 119 <div class="field">
120 120 <div class="label">
121 121 <label for="user">${_('Owner')}:</label>
122 122 </div>
123 123 <div class="input input-medium ac">
124 124 <div class="perm_ac">
125 125 ${h.text('user',class_='yui-ac-input')}
126 126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 127 <div id="owner_container"></div>
128 128 </div>
129 129 </div>
130 130 </div>
131 131
132 132 <div class="field">
133 133 <div class="label">
134 134 <label for="input">${_('Permissions')}:</label>
135 135 </div>
136 136 <div class="input">
137 137 <%include file="repo_edit_perms.html"/>
138 138 </div>
139 139
140 140 <div class="buttons">
141 141 ${h.submit('save',_('Save'),class_="ui-btn large")}
142 142 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
143 143 </div>
144 144 </div>
145 145 </div>
146 146 </div>
147 147 ${h.end_form()}
148 148 </div>
149 149
150 150 <div class="box box-right">
151 151 <div class="title">
152 152 <h5>${_('Administration')}</h5>
153 153 </div>
154 154
155 155 <h3>${_('Statistics')}</h3>
156 156 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
157 157 <div class="form">
158 158 <div class="fields">
159 159 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
160 160 <div class="field" style="border:none;color:#888">
161 161 <ul>
162 162 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
163 163 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
164 164 </ul>
165 165 </div>
166 166 </div>
167 167 </div>
168 168 ${h.end_form()}
169 169
170 170 %if c.repo_info.clone_uri:
171 171 <h3>${_('Remote')}</h3>
172 172 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
173 173 <div class="form">
174 174 <div class="fields">
175 175 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
176 176 <div class="field" style="border:none">
177 177 <ul>
178 178 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
179 179 </ul>
180 180 </div>
181 181 </div>
182 182 </div>
183 183 ${h.end_form()}
184 184 %endif
185 185
186 186 <h3>${_('Cache')}</h3>
187 187 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
188 188 <div class="form">
189 189 <div class="fields">
190 190 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
191 <div class="field" style="border:none;color:#888">
192 <ul>
193 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
194 </li>
195 </ul>
196 </div>
197 <div class="field" style="border:none;">
198 ${_('List of cached values')}
199 <ul>
200 %for cache in c.repo_info.cache_keys:
201 <li>INSTANCE ID:${cache.prefix or '-'} ${cache.cache_args} CACHED: ${h.bool2icon(cache.cache_active)}</li>
202 %endfor
203 </ul>
204 </div>
191 205 </div>
192 206 </div>
193 207 ${h.end_form()}
194 208
195 209 <h3>${_('Public journal')}</h3>
196 210 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
197 211 <div class="form">
198 ${h.hidden('auth_token',str(h.get_token()))}
199 <div class="field">
200 %if c.in_public_journal:
201 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
202 %else:
203 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
204 %endif
205 </div>
206 <div class="field" style="border:none;color:#888">
207 <ul>
208 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
209 </li>
210 </ul>
211 </div>
212 ${h.hidden('auth_token',str(h.get_token()))}
213 <div class="field">
214 %if c.in_public_journal:
215 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
216 %else:
217 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
218 %endif
219 </div>
220 <div class="field" style="border:none;color:#888">
221 <ul>
222 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
223 </li>
224 </ul>
225 </div>
212 226 </div>
213 227 ${h.end_form()}
214 228
215 229 <h3>${_('Locking')}</h3>
216 230 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
217 231 <div class="form">
218 232 <div class="fields">
219 233 %if c.repo_info.locked[0]:
220 234 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
221 235 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
222 236 %else:
223 237 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
224 238 ${_('Repository is not locked')}
225 239 %endif
226 240 </div>
227 241 <div class="field" style="border:none;color:#888">
228 242 <ul>
229 243 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
230 244 </li>
231 245 </ul>
232 246 </div>
233 247 </div>
234 248 ${h.end_form()}
235 249
236 250 <h3>${_('Set as fork of')}</h3>
237 251 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
238 252 <div class="form">
239 253 <div class="fields">
240 254 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
241 255 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
242 256 </div>
243 257 <div class="field" style="border:none;color:#888">
244 258 <ul>
245 259 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
246 260 </ul>
247 261 </div>
248 262 </div>
249 263 ${h.end_form()}
250 264
251 265 <h3>${_('Delete')}</h3>
252 266 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
253 267 <div class="form">
254 268 <div class="fields">
255 269 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
256 270 </div>
257 271 <div class="field" style="border:none;color:#888">
258 272 <ul>
259 273 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
260 274 If you need fully delete it from filesystem please do it manually''')}
261 275 </li>
262 276 </ul>
263 277 </div>
264 278 </div>
265 279 ${h.end_form()}
266 280 </div>
267 281
268 282 </%def>
General Comments 0
You need to be logged in to leave comments. Login now