##// END OF EJS Templates
invalidate: clear CacheInvalidation when upgrading
Mads Kiilerich -
r3757:f08881dc beta
parent child Browse files
Show More
@@ -1,710 +1,714 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__, __py_version__
34 34
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.lib.utils import ask_ok
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 UserRepoGroupToPerm
40 UserRepoGroupToPerm, CacheInvalidation
41 41
42 42 from sqlalchemy.engine import create_engine
43 43 from rhodecode.model.repos_group import ReposGroupModel
44 44 #from rhodecode.model import meta
45 45 from rhodecode.model.meta import Session, Base
46 46 from rhodecode.model.repo import RepoModel
47 47 from rhodecode.model.permission import PermissionModel
48 48
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 def notify(msg):
54 54 """
55 55 Notification for migrations messages
56 56 """
57 57 ml = len(msg) + (4 * 2)
58 58 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
59 59
60 60
61 61 class DbManage(object):
62 62 def __init__(self, log_sql, dbconf, root, tests=False, cli_args={}):
63 63 self.dbname = dbconf.split('/')[-1]
64 64 self.tests = tests
65 65 self.root = root
66 66 self.dburi = dbconf
67 67 self.log_sql = log_sql
68 68 self.db_exists = False
69 69 self.cli_args = cli_args
70 70 self.init_db()
71 71
72 72 force_ask = self.cli_args.get('force_ask')
73 73 if force_ask is not None:
74 74 global ask_ok
75 75 ask_ok = lambda *args, **kwargs: force_ask
76 76
77 77 def init_db(self):
78 78 engine = create_engine(self.dburi, echo=self.log_sql)
79 79 init_model(engine)
80 80 self.sa = Session()
81 81
82 82 def create_tables(self, override=False):
83 83 """
84 84 Create a auth database
85 85 """
86 86
87 87 log.info("Any existing database is going to be destroyed")
88 88 if self.tests:
89 89 destroy = True
90 90 else:
91 91 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
92 92 if not destroy:
93 93 sys.exit('Nothing tables created')
94 94 if destroy:
95 95 Base.metadata.drop_all()
96 96
97 97 checkfirst = not override
98 98 Base.metadata.create_all(checkfirst=checkfirst)
99 99 log.info('Created tables for %s' % self.dbname)
100 100
101 101 def set_db_version(self):
102 102 ver = DbMigrateVersion()
103 103 ver.version = __dbversion__
104 104 ver.repository_id = 'rhodecode_db_migrations'
105 105 ver.repository_path = 'versions'
106 106 self.sa.add(ver)
107 107 log.info('db version set to: %s' % __dbversion__)
108 108
109 109 def upgrade(self):
110 110 """
111 111 Upgrades given database schema to given revision following
112 112 all needed steps, to perform the upgrade
113 113
114 114 """
115 115
116 116 from rhodecode.lib.dbmigrate.migrate.versioning import api
117 117 from rhodecode.lib.dbmigrate.migrate.exceptions import \
118 118 DatabaseNotControlledError
119 119
120 120 if 'sqlite' in self.dburi:
121 121 print (
122 122 '********************** WARNING **********************\n'
123 123 'Make sure your version of sqlite is at least 3.7.X. \n'
124 124 'Earlier versions are known to fail on some migrations\n'
125 125 '*****************************************************\n'
126 126 )
127 127 upgrade = ask_ok('You are about to perform database upgrade, make '
128 128 'sure You backed up your database before. '
129 129 'Continue ? [y/n]')
130 130 if not upgrade:
131 131 sys.exit('No upgrade performed')
132 132
133 133 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
134 134 'rhodecode/lib/dbmigrate')
135 135 db_uri = self.dburi
136 136
137 137 try:
138 138 curr_version = api.db_version(db_uri, repository_path)
139 139 msg = ('Found current database under version'
140 140 ' control with version %s' % curr_version)
141 141
142 142 except (RuntimeError, DatabaseNotControlledError):
143 143 curr_version = 1
144 144 msg = ('Current database is not under version control. Setting'
145 145 ' as version %s' % curr_version)
146 146 api.version_control(db_uri, repository_path, curr_version)
147 147
148 148 notify(msg)
149 149
150 150 if curr_version == __dbversion__:
151 151 sys.exit('This database is already at the newest version')
152 152
153 # clear cache keys
154 log.info("Clearing cache keys now...")
155 CacheInvalidation.clear_cache()
156
153 157 #======================================================================
154 158 # UPGRADE STEPS
155 159 #======================================================================
156 160
157 161 class UpgradeSteps(object):
158 162 """
159 163 Those steps follow schema versions so for example schema
160 164 for example schema with seq 002 == step_2 and so on.
161 165 """
162 166
163 167 def __init__(self, klass):
164 168 self.klass = klass
165 169
166 170 def step_0(self):
167 171 # step 0 is the schema upgrade, and than follow proper upgrades
168 172 notify('attempting to do database upgrade from '
169 173 'version %s to version %s' %(curr_version, __dbversion__))
170 174 api.upgrade(db_uri, repository_path, __dbversion__)
171 175 notify('Schema upgrade completed')
172 176
173 177 def step_1(self):
174 178 pass
175 179
176 180 def step_2(self):
177 181 notify('Patching repo paths for newer version of RhodeCode')
178 182 self.klass.fix_repo_paths()
179 183
180 184 notify('Patching default user of RhodeCode')
181 185 self.klass.fix_default_user()
182 186
183 187 log.info('Changing ui settings')
184 188 self.klass.create_ui_settings()
185 189
186 190 def step_3(self):
187 191 notify('Adding additional settings into RhodeCode db')
188 192 self.klass.fix_settings()
189 193 notify('Adding ldap defaults')
190 194 self.klass.create_ldap_options(skip_existing=True)
191 195
192 196 def step_4(self):
193 197 notify('create permissions and fix groups')
194 198 self.klass.create_permissions()
195 199 self.klass.fixup_groups()
196 200
197 201 def step_5(self):
198 202 pass
199 203
200 204 def step_6(self):
201 205
202 206 notify('re-checking permissions')
203 207 self.klass.create_permissions()
204 208
205 209 notify('installing new UI options')
206 210 sett4 = RhodeCodeSetting('show_public_icon', True)
207 211 Session().add(sett4)
208 212 sett5 = RhodeCodeSetting('show_private_icon', True)
209 213 Session().add(sett5)
210 214 sett6 = RhodeCodeSetting('stylify_metatags', False)
211 215 Session().add(sett6)
212 216
213 217 notify('fixing old PULL hook')
214 218 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
215 219 if _pull:
216 220 _pull.ui_key = RhodeCodeUi.HOOK_PULL
217 221 Session().add(_pull)
218 222
219 223 notify('fixing old PUSH hook')
220 224 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
221 225 if _push:
222 226 _push.ui_key = RhodeCodeUi.HOOK_PUSH
223 227 Session().add(_push)
224 228
225 229 notify('installing new pre-push hook')
226 230 hooks4 = RhodeCodeUi()
227 231 hooks4.ui_section = 'hooks'
228 232 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
229 233 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
230 234 Session().add(hooks4)
231 235
232 236 notify('installing new pre-pull hook')
233 237 hooks6 = RhodeCodeUi()
234 238 hooks6.ui_section = 'hooks'
235 239 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
236 240 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
237 241 Session().add(hooks6)
238 242
239 243 notify('installing hgsubversion option')
240 244 # enable hgsubversion disabled by default
241 245 hgsubversion = RhodeCodeUi()
242 246 hgsubversion.ui_section = 'extensions'
243 247 hgsubversion.ui_key = 'hgsubversion'
244 248 hgsubversion.ui_value = ''
245 249 hgsubversion.ui_active = False
246 250 Session().add(hgsubversion)
247 251
248 252 notify('installing hg git option')
249 253 # enable hggit disabled by default
250 254 hggit = RhodeCodeUi()
251 255 hggit.ui_section = 'extensions'
252 256 hggit.ui_key = 'hggit'
253 257 hggit.ui_value = ''
254 258 hggit.ui_active = False
255 259 Session().add(hggit)
256 260
257 261 notify('re-check default permissions')
258 262 default_user = User.get_by_username(User.DEFAULT_USER)
259 263 perm = Permission.get_by_key('hg.fork.repository')
260 264 reg_perm = UserToPerm()
261 265 reg_perm.user = default_user
262 266 reg_perm.permission = perm
263 267 Session().add(reg_perm)
264 268
265 269 def step_7(self):
266 270 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
267 271 Session().commit()
268 272 if perm_fixes:
269 273 notify('There was an inconsistent state of permissions '
270 274 'detected for default user. Permissions are now '
271 275 'reset to the default value for default user. '
272 276 'Please validate and check default permissions '
273 277 'in admin panel')
274 278
275 279 def step_8(self):
276 280 self.klass.populate_default_permissions()
277 281 self.klass.create_default_options(skip_existing=True)
278 282 Session().commit()
279 283
280 284 def step_9(self):
281 285 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
282 286 Session().commit()
283 287 if perm_fixes:
284 288 notify('There was an inconsistent state of permissions '
285 289 'detected for default user. Permissions are now '
286 290 'reset to the default value for default user. '
287 291 'Please validate and check default permissions '
288 292 'in admin panel')
289 293
290 294 def step_10(self):
291 295 pass
292 296
293 297 def step_11(self):
294 298 self.klass.update_repo_info()
295 299
296 300 def step_12(self):
297 301 self.klass.create_permissions()
298 302 self.klass.populate_default_permissions()
299 303
300 304 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
301 305
302 306 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
303 307 _step = None
304 308 for step in upgrade_steps:
305 309 notify('performing upgrade step %s' % step)
306 310 getattr(UpgradeSteps(self), 'step_%s' % step)()
307 311 self.sa.commit()
308 312 _step = step
309 313
310 314 notify('upgrade to version %s successful' % _step)
311 315
312 316 def fix_repo_paths(self):
313 317 """
314 318 Fixes a old rhodecode version path into new one without a '*'
315 319 """
316 320
317 321 paths = self.sa.query(RhodeCodeUi)\
318 322 .filter(RhodeCodeUi.ui_key == '/')\
319 323 .scalar()
320 324
321 325 paths.ui_value = paths.ui_value.replace('*', '')
322 326
323 327 try:
324 328 self.sa.add(paths)
325 329 self.sa.commit()
326 330 except Exception:
327 331 self.sa.rollback()
328 332 raise
329 333
330 334 def fix_default_user(self):
331 335 """
332 336 Fixes a old default user with some 'nicer' default values,
333 337 used mostly for anonymous access
334 338 """
335 339 def_user = self.sa.query(User)\
336 340 .filter(User.username == 'default')\
337 341 .one()
338 342
339 343 def_user.name = 'Anonymous'
340 344 def_user.lastname = 'User'
341 345 def_user.email = 'anonymous@rhodecode.org'
342 346
343 347 try:
344 348 self.sa.add(def_user)
345 349 self.sa.commit()
346 350 except Exception:
347 351 self.sa.rollback()
348 352 raise
349 353
350 354 def fix_settings(self):
351 355 """
352 356 Fixes rhodecode settings adds ga_code key for google analytics
353 357 """
354 358
355 359 hgsettings3 = RhodeCodeSetting('ga_code', '')
356 360
357 361 try:
358 362 self.sa.add(hgsettings3)
359 363 self.sa.commit()
360 364 except Exception:
361 365 self.sa.rollback()
362 366 raise
363 367
364 368 def admin_prompt(self, second=False):
365 369 if not self.tests:
366 370 import getpass
367 371
368 372 # defaults
369 373 defaults = self.cli_args
370 374 username = defaults.get('username')
371 375 password = defaults.get('password')
372 376 email = defaults.get('email')
373 377
374 378 def get_password():
375 379 password = getpass.getpass('Specify admin password '
376 380 '(min 6 chars):')
377 381 confirm = getpass.getpass('Confirm password:')
378 382
379 383 if password != confirm:
380 384 log.error('passwords mismatch')
381 385 return False
382 386 if len(password) < 6:
383 387 log.error('password is to short use at least 6 characters')
384 388 return False
385 389
386 390 return password
387 391 if username is None:
388 392 username = raw_input('Specify admin username:')
389 393 if password is None:
390 394 password = get_password()
391 395 if not password:
392 396 #second try
393 397 password = get_password()
394 398 if not password:
395 399 sys.exit()
396 400 if email is None:
397 401 email = raw_input('Specify admin email:')
398 402 self.create_user(username, password, email, True)
399 403 else:
400 404 log.info('creating admin and regular test users')
401 405 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
402 406 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
403 407 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
404 408 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
405 409 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
406 410
407 411 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
408 412 TEST_USER_ADMIN_EMAIL, True)
409 413
410 414 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
411 415 TEST_USER_REGULAR_EMAIL, False)
412 416
413 417 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
414 418 TEST_USER_REGULAR2_EMAIL, False)
415 419
416 420 def create_ui_settings(self):
417 421 """
418 422 Creates ui settings, fills out hooks
419 423 and disables dotencode
420 424 """
421 425
422 426 #HOOKS
423 427 hooks1_key = RhodeCodeUi.HOOK_UPDATE
424 428 hooks1_ = self.sa.query(RhodeCodeUi)\
425 429 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
426 430
427 431 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
428 432 hooks1.ui_section = 'hooks'
429 433 hooks1.ui_key = hooks1_key
430 434 hooks1.ui_value = 'hg update >&2'
431 435 hooks1.ui_active = False
432 436 self.sa.add(hooks1)
433 437
434 438 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
435 439 hooks2_ = self.sa.query(RhodeCodeUi)\
436 440 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
437 441 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
438 442 hooks2.ui_section = 'hooks'
439 443 hooks2.ui_key = hooks2_key
440 444 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
441 445 self.sa.add(hooks2)
442 446
443 447 hooks3 = RhodeCodeUi()
444 448 hooks3.ui_section = 'hooks'
445 449 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
446 450 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
447 451 self.sa.add(hooks3)
448 452
449 453 hooks4 = RhodeCodeUi()
450 454 hooks4.ui_section = 'hooks'
451 455 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
452 456 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
453 457 self.sa.add(hooks4)
454 458
455 459 hooks5 = RhodeCodeUi()
456 460 hooks5.ui_section = 'hooks'
457 461 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
458 462 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
459 463 self.sa.add(hooks5)
460 464
461 465 hooks6 = RhodeCodeUi()
462 466 hooks6.ui_section = 'hooks'
463 467 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
464 468 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
465 469 self.sa.add(hooks6)
466 470
467 471 # enable largefiles
468 472 largefiles = RhodeCodeUi()
469 473 largefiles.ui_section = 'extensions'
470 474 largefiles.ui_key = 'largefiles'
471 475 largefiles.ui_value = ''
472 476 self.sa.add(largefiles)
473 477
474 478 # enable hgsubversion disabled by default
475 479 hgsubversion = RhodeCodeUi()
476 480 hgsubversion.ui_section = 'extensions'
477 481 hgsubversion.ui_key = 'hgsubversion'
478 482 hgsubversion.ui_value = ''
479 483 hgsubversion.ui_active = False
480 484 self.sa.add(hgsubversion)
481 485
482 486 # enable hggit disabled by default
483 487 hggit = RhodeCodeUi()
484 488 hggit.ui_section = 'extensions'
485 489 hggit.ui_key = 'hggit'
486 490 hggit.ui_value = ''
487 491 hggit.ui_active = False
488 492 self.sa.add(hggit)
489 493
490 494 def create_ldap_options(self, skip_existing=False):
491 495 """Creates ldap settings"""
492 496
493 497 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
494 498 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
495 499 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
496 500 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
497 501 ('ldap_filter', ''), ('ldap_search_scope', ''),
498 502 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
499 503 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
500 504
501 505 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
502 506 log.debug('Skipping option %s' % k)
503 507 continue
504 508 setting = RhodeCodeSetting(k, v)
505 509 self.sa.add(setting)
506 510
507 511 def create_default_options(self, skip_existing=False):
508 512 """Creates default settings"""
509 513
510 514 for k, v in [
511 515 ('default_repo_enable_locking', False),
512 516 ('default_repo_enable_downloads', False),
513 517 ('default_repo_enable_statistics', False),
514 518 ('default_repo_private', False),
515 519 ('default_repo_type', 'hg')]:
516 520
517 521 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
518 522 log.debug('Skipping option %s' % k)
519 523 continue
520 524 setting = RhodeCodeSetting(k, v)
521 525 self.sa.add(setting)
522 526
523 527 def fixup_groups(self):
524 528 def_usr = User.get_default_user()
525 529 for g in RepoGroup.query().all():
526 530 g.group_name = g.get_new_name(g.name)
527 531 self.sa.add(g)
528 532 # get default perm
529 533 default = UserRepoGroupToPerm.query()\
530 534 .filter(UserRepoGroupToPerm.group == g)\
531 535 .filter(UserRepoGroupToPerm.user == def_usr)\
532 536 .scalar()
533 537
534 538 if default is None:
535 539 log.debug('missing default permission for group %s adding' % g)
536 540 perm_obj = ReposGroupModel()._create_default_perms(g)
537 541 self.sa.add(perm_obj)
538 542
539 543 def reset_permissions(self, username):
540 544 """
541 545 Resets permissions to default state, usefull when old systems had
542 546 bad permissions, we must clean them up
543 547
544 548 :param username:
545 549 :type username:
546 550 """
547 551 default_user = User.get_by_username(username)
548 552 if not default_user:
549 553 return
550 554
551 555 u2p = UserToPerm.query()\
552 556 .filter(UserToPerm.user == default_user).all()
553 557 fixed = False
554 558 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
555 559 for p in u2p:
556 560 Session().delete(p)
557 561 fixed = True
558 562 self.populate_default_permissions()
559 563 return fixed
560 564
561 565 def update_repo_info(self):
562 566 RepoModel.update_repoinfo()
563 567
564 568 def config_prompt(self, test_repo_path='', retries=3):
565 569 defaults = self.cli_args
566 570 _path = defaults.get('repos_location')
567 571 if retries == 3:
568 572 log.info('Setting up repositories config')
569 573
570 574 if _path is not None:
571 575 path = _path
572 576 elif not self.tests and not test_repo_path:
573 577 path = raw_input(
574 578 'Enter a valid absolute path to store repositories. '
575 579 'All repositories in that path will be added automatically:'
576 580 )
577 581 else:
578 582 path = test_repo_path
579 583 path_ok = True
580 584
581 585 # check proper dir
582 586 if not os.path.isdir(path):
583 587 path_ok = False
584 588 log.error('Given path %s is not a valid directory' % path)
585 589
586 590 elif not os.path.isabs(path):
587 591 path_ok = False
588 592 log.error('Given path %s is not an absolute path' % path)
589 593
590 594 # check write access
591 595 elif not os.access(path, os.W_OK) and path_ok:
592 596 path_ok = False
593 597 log.error('No write permission to given path %s' % path)
594 598
595 599 if retries == 0:
596 600 sys.exit('max retries reached')
597 601 if not path_ok:
598 602 retries -= 1
599 603 return self.config_prompt(test_repo_path, retries)
600 604
601 605 real_path = os.path.normpath(os.path.realpath(path))
602 606
603 607 if real_path != os.path.normpath(path):
604 608 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
605 609 'given path as %s ? [y/n]') % (real_path)):
606 610 log.error('Canceled by user')
607 611 sys.exit(-1)
608 612
609 613 return real_path
610 614
611 615 def create_settings(self, path):
612 616
613 617 self.create_ui_settings()
614 618
615 619 #HG UI OPTIONS
616 620 web1 = RhodeCodeUi()
617 621 web1.ui_section = 'web'
618 622 web1.ui_key = 'push_ssl'
619 623 web1.ui_value = 'false'
620 624
621 625 web2 = RhodeCodeUi()
622 626 web2.ui_section = 'web'
623 627 web2.ui_key = 'allow_archive'
624 628 web2.ui_value = 'gz zip bz2'
625 629
626 630 web3 = RhodeCodeUi()
627 631 web3.ui_section = 'web'
628 632 web3.ui_key = 'allow_push'
629 633 web3.ui_value = '*'
630 634
631 635 web4 = RhodeCodeUi()
632 636 web4.ui_section = 'web'
633 637 web4.ui_key = 'baseurl'
634 638 web4.ui_value = '/'
635 639
636 640 paths = RhodeCodeUi()
637 641 paths.ui_section = 'paths'
638 642 paths.ui_key = '/'
639 643 paths.ui_value = path
640 644
641 645 phases = RhodeCodeUi()
642 646 phases.ui_section = 'phases'
643 647 phases.ui_key = 'publish'
644 648 phases.ui_value = False
645 649
646 650 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
647 651 sett2 = RhodeCodeSetting('title', 'RhodeCode')
648 652 sett3 = RhodeCodeSetting('ga_code', '')
649 653
650 654 sett4 = RhodeCodeSetting('show_public_icon', True)
651 655 sett5 = RhodeCodeSetting('show_private_icon', True)
652 656 sett6 = RhodeCodeSetting('stylify_metatags', False)
653 657
654 658 self.sa.add(web1)
655 659 self.sa.add(web2)
656 660 self.sa.add(web3)
657 661 self.sa.add(web4)
658 662 self.sa.add(paths)
659 663 self.sa.add(sett1)
660 664 self.sa.add(sett2)
661 665 self.sa.add(sett3)
662 666 self.sa.add(sett4)
663 667 self.sa.add(sett5)
664 668 self.sa.add(sett6)
665 669
666 670 self.create_ldap_options()
667 671 self.create_default_options()
668 672
669 673 log.info('created ui config')
670 674
671 675 def create_user(self, username, password, email='', admin=False):
672 676 log.info('creating user %s' % username)
673 677 UserModel().create_or_update(username, password, email,
674 678 firstname='RhodeCode', lastname='Admin',
675 679 active=True, admin=admin)
676 680
677 681 def create_default_user(self):
678 682 log.info('creating default user')
679 683 # create default user for handling default permissions.
680 684 UserModel().create_or_update(username='default',
681 685 password=str(uuid.uuid1())[:8],
682 686 email='anonymous@rhodecode.org',
683 687 firstname='Anonymous', lastname='User')
684 688
685 689 def create_permissions(self):
686 690 """
687 691 Creates all permissions defined in the system
688 692 """
689 693 # module.(access|create|change|delete)_[name]
690 694 # module.(none|read|write|admin)
691 695 log.info('creating permissions')
692 696 PermissionModel(self.sa).create_permissions()
693 697
694 698 def populate_default_permissions(self):
695 699 """
696 700 Populate default permissions. It will create only the default
697 701 permissions that are missing, and not alter already defined ones
698 702 """
699 703 log.info('creating default user permissions')
700 704 PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER)
701 705
702 706 @staticmethod
703 707 def check_waitress():
704 708 """
705 709 Function executed at the end of setup
706 710 """
707 711 if not __py_version__ >= (2, 6):
708 712 notify('Python2.5 detected, please switch '
709 713 'egg:waitress#main -> egg:Paste#http '
710 714 'in your .ini file')
@@ -1,2170 +1,2179 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 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 # deprecated and left for backward compatibility
135 135 return cls.get_all()
136 136
137 137 @classmethod
138 138 def get_all(cls):
139 139 return cls.query().all()
140 140
141 141 @classmethod
142 142 def delete(cls, id_):
143 143 obj = cls.query().get(id_)
144 144 Session().delete(obj)
145 145
146 146 def __repr__(self):
147 147 if hasattr(self, '__unicode__'):
148 148 # python repr needs to return str
149 149 return safe_str(self.__unicode__())
150 150 return '<DB:%s>' % (self.__class__.__name__)
151 151
152 152
153 153 class RhodeCodeSetting(Base, BaseModel):
154 154 __tablename__ = 'rhodecode_settings'
155 155 __table_args__ = (
156 156 UniqueConstraint('app_settings_name'),
157 157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 158 'mysql_charset': 'utf8'}
159 159 )
160 160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163 163
164 164 def __init__(self, k='', v=''):
165 165 self.app_settings_name = k
166 166 self.app_settings_value = v
167 167
168 168 @validates('_app_settings_value')
169 169 def validate_settings_value(self, key, val):
170 170 assert type(val) == unicode
171 171 return val
172 172
173 173 @hybrid_property
174 174 def app_settings_value(self):
175 175 v = self._app_settings_value
176 176 if self.app_settings_name in ["ldap_active",
177 177 "default_repo_enable_statistics",
178 178 "default_repo_enable_locking",
179 179 "default_repo_private",
180 180 "default_repo_enable_downloads"]:
181 181 v = str2bool(v)
182 182 return v
183 183
184 184 @app_settings_value.setter
185 185 def app_settings_value(self, val):
186 186 """
187 187 Setter that will always make sure we use unicode in app_settings_value
188 188
189 189 :param val:
190 190 """
191 191 self._app_settings_value = safe_unicode(val)
192 192
193 193 def __unicode__(self):
194 194 return u"<%s('%s:%s')>" % (
195 195 self.__class__.__name__,
196 196 self.app_settings_name, self.app_settings_value
197 197 )
198 198
199 199 @classmethod
200 200 def get_by_name(cls, key):
201 201 return cls.query()\
202 202 .filter(cls.app_settings_name == key).scalar()
203 203
204 204 @classmethod
205 205 def get_by_name_or_create(cls, key):
206 206 res = cls.get_by_name(key)
207 207 if not res:
208 208 res = cls(key)
209 209 return res
210 210
211 211 @classmethod
212 212 def get_app_settings(cls, cache=False):
213 213
214 214 ret = cls.query()
215 215
216 216 if cache:
217 217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218 218
219 219 if not ret:
220 220 raise Exception('Could not get application settings !')
221 221 settings = {}
222 222 for each in ret:
223 223 settings['rhodecode_' + each.app_settings_name] = \
224 224 each.app_settings_value
225 225
226 226 return settings
227 227
228 228 @classmethod
229 229 def get_ldap_settings(cls, cache=False):
230 230 ret = cls.query()\
231 231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 232 fd = {}
233 233 for row in ret:
234 234 fd.update({row.app_settings_name: row.app_settings_value})
235 235
236 236 return fd
237 237
238 238 @classmethod
239 239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 240 ret = cls.query()\
241 241 .filter(cls.app_settings_name.startswith('default_')).all()
242 242 fd = {}
243 243 for row in ret:
244 244 key = row.app_settings_name
245 245 if strip_prefix:
246 246 key = remove_prefix(key, prefix='default_')
247 247 fd.update({key: row.app_settings_value})
248 248
249 249 return fd
250 250
251 251
252 252 class RhodeCodeUi(Base, BaseModel):
253 253 __tablename__ = 'rhodecode_ui'
254 254 __table_args__ = (
255 255 UniqueConstraint('ui_key'),
256 256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 257 'mysql_charset': 'utf8'}
258 258 )
259 259
260 260 HOOK_UPDATE = 'changegroup.update'
261 261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 262 HOOK_PUSH = 'changegroup.push_logger'
263 263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 264 HOOK_PULL = 'outgoing.pull_logger'
265 265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266 266
267 267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272 272
273 273 @classmethod
274 274 def get_by_key(cls, key):
275 275 return cls.query().filter(cls.ui_key == key).scalar()
276 276
277 277 @classmethod
278 278 def get_builtin_hooks(cls):
279 279 q = cls.query()
280 280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 283 return q.all()
284 284
285 285 @classmethod
286 286 def get_custom_hooks(cls):
287 287 q = cls.query()
288 288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 291 q = q.filter(cls.ui_section == 'hooks')
292 292 return q.all()
293 293
294 294 @classmethod
295 295 def get_repos_location(cls):
296 296 return cls.get_by_key('/').ui_value
297 297
298 298 @classmethod
299 299 def create_or_update_hook(cls, key, val):
300 300 new_ui = cls.get_by_key(key) or cls()
301 301 new_ui.ui_section = 'hooks'
302 302 new_ui.ui_active = True
303 303 new_ui.ui_key = key
304 304 new_ui.ui_value = val
305 305
306 306 Session().add(new_ui)
307 307
308 308 def __repr__(self):
309 309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 310 self.ui_value)
311 311
312 312
313 313 class User(Base, BaseModel):
314 314 __tablename__ = 'users'
315 315 __table_args__ = (
316 316 UniqueConstraint('username'), UniqueConstraint('email'),
317 317 Index('u_username_idx', 'username'),
318 318 Index('u_email_idx', 'email'),
319 319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 320 'mysql_charset': 'utf8'}
321 321 )
322 322 DEFAULT_USER = 'default'
323 323
324 324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
328 328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
329 329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
333 333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
336 336
337 337 user_log = relationship('UserLog')
338 338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
339 339
340 340 repositories = relationship('Repository')
341 341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
342 342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
343 343
344 344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
345 345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
346 346
347 347 group_member = relationship('UserGroupMember', cascade='all')
348 348
349 349 notifications = relationship('UserNotification', cascade='all')
350 350 # notifications assigned to this user
351 351 user_created_notifications = relationship('Notification', cascade='all')
352 352 # comments created by this user
353 353 user_comments = relationship('ChangesetComment', cascade='all')
354 354 #extra emails for this user
355 355 user_emails = relationship('UserEmailMap', cascade='all')
356 356
357 357 @hybrid_property
358 358 def email(self):
359 359 return self._email
360 360
361 361 @email.setter
362 362 def email(self, val):
363 363 self._email = val.lower() if val else None
364 364
365 365 @property
366 366 def firstname(self):
367 367 # alias for future
368 368 return self.name
369 369
370 370 @property
371 371 def emails(self):
372 372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
373 373 return [self.email] + [x.email for x in other]
374 374
375 375 @property
376 376 def ip_addresses(self):
377 377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
378 378 return [x.ip_addr for x in ret]
379 379
380 380 @property
381 381 def username_and_name(self):
382 382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
383 383
384 384 @property
385 385 def full_name(self):
386 386 return '%s %s' % (self.firstname, self.lastname)
387 387
388 388 @property
389 389 def full_name_or_username(self):
390 390 return ('%s %s' % (self.firstname, self.lastname)
391 391 if (self.firstname and self.lastname) else self.username)
392 392
393 393 @property
394 394 def full_contact(self):
395 395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
396 396
397 397 @property
398 398 def short_contact(self):
399 399 return '%s %s' % (self.firstname, self.lastname)
400 400
401 401 @property
402 402 def is_admin(self):
403 403 return self.admin
404 404
405 405 @property
406 406 def AuthUser(self):
407 407 """
408 408 Returns instance of AuthUser for this user
409 409 """
410 410 from rhodecode.lib.auth import AuthUser
411 411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
412 412 username=self.username)
413 413
414 414 def __unicode__(self):
415 415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
416 416 self.user_id, self.username)
417 417
418 418 @classmethod
419 419 def get_by_username(cls, username, case_insensitive=False, cache=False):
420 420 if case_insensitive:
421 421 q = cls.query().filter(cls.username.ilike(username))
422 422 else:
423 423 q = cls.query().filter(cls.username == username)
424 424
425 425 if cache:
426 426 q = q.options(FromCache(
427 427 "sql_cache_short",
428 428 "get_user_%s" % _hash_key(username)
429 429 )
430 430 )
431 431 return q.scalar()
432 432
433 433 @classmethod
434 434 def get_by_api_key(cls, api_key, cache=False):
435 435 q = cls.query().filter(cls.api_key == api_key)
436 436
437 437 if cache:
438 438 q = q.options(FromCache("sql_cache_short",
439 439 "get_api_key_%s" % api_key))
440 440 return q.scalar()
441 441
442 442 @classmethod
443 443 def get_by_email(cls, email, case_insensitive=False, cache=False):
444 444 if case_insensitive:
445 445 q = cls.query().filter(cls.email.ilike(email))
446 446 else:
447 447 q = cls.query().filter(cls.email == email)
448 448
449 449 if cache:
450 450 q = q.options(FromCache("sql_cache_short",
451 451 "get_email_key_%s" % email))
452 452
453 453 ret = q.scalar()
454 454 if ret is None:
455 455 q = UserEmailMap.query()
456 456 # try fetching in alternate email map
457 457 if case_insensitive:
458 458 q = q.filter(UserEmailMap.email.ilike(email))
459 459 else:
460 460 q = q.filter(UserEmailMap.email == email)
461 461 q = q.options(joinedload(UserEmailMap.user))
462 462 if cache:
463 463 q = q.options(FromCache("sql_cache_short",
464 464 "get_email_map_key_%s" % email))
465 465 ret = getattr(q.scalar(), 'user', None)
466 466
467 467 return ret
468 468
469 469 @classmethod
470 470 def get_from_cs_author(cls, author):
471 471 """
472 472 Tries to get User objects out of commit author string
473 473
474 474 :param author:
475 475 """
476 476 from rhodecode.lib.helpers import email, author_name
477 477 # Valid email in the attribute passed, see if they're in the system
478 478 _email = email(author)
479 479 if _email:
480 480 user = cls.get_by_email(_email, case_insensitive=True)
481 481 if user:
482 482 return user
483 483 # Maybe we can match by username?
484 484 _author = author_name(author)
485 485 user = cls.get_by_username(_author, case_insensitive=True)
486 486 if user:
487 487 return user
488 488
489 489 def update_lastlogin(self):
490 490 """Update user lastlogin"""
491 491 self.last_login = datetime.datetime.now()
492 492 Session().add(self)
493 493 log.debug('updated user %s lastlogin' % self.username)
494 494
495 495 @classmethod
496 496 def get_first_admin(cls):
497 497 user = User.query().filter(User.admin == True).first()
498 498 if user is None:
499 499 raise Exception('Missing administrative account!')
500 500 return user
501 501
502 502 @classmethod
503 503 def get_default_user(cls, cache=False):
504 504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
505 505 if user is None:
506 506 raise Exception('Missing default account!')
507 507 return user
508 508
509 509 def get_api_data(self):
510 510 """
511 511 Common function for generating user related data for API
512 512 """
513 513 user = self
514 514 data = dict(
515 515 user_id=user.user_id,
516 516 username=user.username,
517 517 firstname=user.name,
518 518 lastname=user.lastname,
519 519 email=user.email,
520 520 emails=user.emails,
521 521 api_key=user.api_key,
522 522 active=user.active,
523 523 admin=user.admin,
524 524 ldap_dn=user.ldap_dn,
525 525 last_login=user.last_login,
526 526 ip_addresses=user.ip_addresses
527 527 )
528 528 return data
529 529
530 530 def __json__(self):
531 531 data = dict(
532 532 full_name=self.full_name,
533 533 full_name_or_username=self.full_name_or_username,
534 534 short_contact=self.short_contact,
535 535 full_contact=self.full_contact
536 536 )
537 537 data.update(self.get_api_data())
538 538 return data
539 539
540 540
541 541 class UserEmailMap(Base, BaseModel):
542 542 __tablename__ = 'user_email_map'
543 543 __table_args__ = (
544 544 Index('uem_email_idx', 'email'),
545 545 UniqueConstraint('email'),
546 546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 547 'mysql_charset': 'utf8'}
548 548 )
549 549 __mapper_args__ = {}
550 550
551 551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
553 553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 554 user = relationship('User', lazy='joined')
555 555
556 556 @validates('_email')
557 557 def validate_email(self, key, email):
558 558 # check if this email is not main one
559 559 main_email = Session().query(User).filter(User.email == email).scalar()
560 560 if main_email is not None:
561 561 raise AttributeError('email %s is present is user table' % email)
562 562 return email
563 563
564 564 @hybrid_property
565 565 def email(self):
566 566 return self._email
567 567
568 568 @email.setter
569 569 def email(self, val):
570 570 self._email = val.lower() if val else None
571 571
572 572
573 573 class UserIpMap(Base, BaseModel):
574 574 __tablename__ = 'user_ip_map'
575 575 __table_args__ = (
576 576 UniqueConstraint('user_id', 'ip_addr'),
577 577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 578 'mysql_charset': 'utf8'}
579 579 )
580 580 __mapper_args__ = {}
581 581
582 582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
583 583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
584 584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
585 585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
586 586 user = relationship('User', lazy='joined')
587 587
588 588 @classmethod
589 589 def _get_ip_range(cls, ip_addr):
590 590 from rhodecode.lib import ipaddr
591 591 net = ipaddr.IPNetwork(address=ip_addr)
592 592 return [str(net.network), str(net.broadcast)]
593 593
594 594 def __json__(self):
595 595 return dict(
596 596 ip_addr=self.ip_addr,
597 597 ip_range=self._get_ip_range(self.ip_addr)
598 598 )
599 599
600 600
601 601 class UserLog(Base, BaseModel):
602 602 __tablename__ = 'user_logs'
603 603 __table_args__ = (
604 604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
605 605 'mysql_charset': 'utf8'},
606 606 )
607 607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
609 609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
611 611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
615 615
616 616 @property
617 617 def action_as_day(self):
618 618 return datetime.date(*self.action_date.timetuple()[:3])
619 619
620 620 user = relationship('User')
621 621 repository = relationship('Repository', cascade='')
622 622
623 623
624 624 class UserGroup(Base, BaseModel):
625 625 __tablename__ = 'users_groups'
626 626 __table_args__ = (
627 627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 628 'mysql_charset': 'utf8'},
629 629 )
630 630
631 631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
632 632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
633 633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
634 634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
635 635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636 636
637 637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
638 638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
639 639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
640 640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
642 642 user = relationship('User')
643 643
644 644 def __unicode__(self):
645 645 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
646 646 self.users_group_id,
647 647 self.users_group_name)
648 648
649 649 @classmethod
650 650 def get_by_group_name(cls, group_name, cache=False,
651 651 case_insensitive=False):
652 652 if case_insensitive:
653 653 q = cls.query().filter(cls.users_group_name.ilike(group_name))
654 654 else:
655 655 q = cls.query().filter(cls.users_group_name == group_name)
656 656 if cache:
657 657 q = q.options(FromCache(
658 658 "sql_cache_short",
659 659 "get_user_%s" % _hash_key(group_name)
660 660 )
661 661 )
662 662 return q.scalar()
663 663
664 664 @classmethod
665 665 def get(cls, users_group_id, cache=False):
666 666 users_group = cls.query()
667 667 if cache:
668 668 users_group = users_group.options(FromCache("sql_cache_short",
669 669 "get_users_group_%s" % users_group_id))
670 670 return users_group.get(users_group_id)
671 671
672 672 def get_api_data(self):
673 673 users_group = self
674 674
675 675 data = dict(
676 676 users_group_id=users_group.users_group_id,
677 677 group_name=users_group.users_group_name,
678 678 active=users_group.users_group_active,
679 679 )
680 680
681 681 return data
682 682
683 683
684 684 class UserGroupMember(Base, BaseModel):
685 685 __tablename__ = 'users_groups_members'
686 686 __table_args__ = (
687 687 {'extend_existing': True, 'mysql_engine': 'InnoDB',
688 688 'mysql_charset': 'utf8'},
689 689 )
690 690
691 691 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
692 692 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
693 693 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
694 694
695 695 user = relationship('User', lazy='joined')
696 696 users_group = relationship('UserGroup')
697 697
698 698 def __init__(self, gr_id='', u_id=''):
699 699 self.users_group_id = gr_id
700 700 self.user_id = u_id
701 701
702 702
703 703 class RepositoryField(Base, BaseModel):
704 704 __tablename__ = 'repositories_fields'
705 705 __table_args__ = (
706 706 UniqueConstraint('repository_id', 'field_key'), # no-multi field
707 707 {'extend_existing': True, 'mysql_engine': 'InnoDB',
708 708 'mysql_charset': 'utf8'},
709 709 )
710 710 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
711 711
712 712 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
713 713 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
714 714 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
715 715 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
716 716 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
717 717 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
718 718 field_type = Column("field_type", String(256), nullable=False, unique=None)
719 719 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
720 720
721 721 repository = relationship('Repository')
722 722
723 723 @property
724 724 def field_key_prefixed(self):
725 725 return 'ex_%s' % self.field_key
726 726
727 727 @classmethod
728 728 def un_prefix_key(cls, key):
729 729 if key.startswith(cls.PREFIX):
730 730 return key[len(cls.PREFIX):]
731 731 return key
732 732
733 733 @classmethod
734 734 def get_by_key_name(cls, key, repo):
735 735 row = cls.query()\
736 736 .filter(cls.repository == repo)\
737 737 .filter(cls.field_key == key).scalar()
738 738 return row
739 739
740 740
741 741 class Repository(Base, BaseModel):
742 742 __tablename__ = 'repositories'
743 743 __table_args__ = (
744 744 UniqueConstraint('repo_name'),
745 745 Index('r_repo_name_idx', 'repo_name'),
746 746 {'extend_existing': True, 'mysql_engine': 'InnoDB',
747 747 'mysql_charset': 'utf8'},
748 748 )
749 749
750 750 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
751 751 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
752 752 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
753 753 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
754 754 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
755 755 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
756 756 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
757 757 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
758 758 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
759 759 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
760 760 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
761 761 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
762 762 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
763 763 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
764 764 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
765 765
766 766 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
767 767 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
768 768
769 769 user = relationship('User')
770 770 fork = relationship('Repository', remote_side=repo_id)
771 771 group = relationship('RepoGroup')
772 772 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
773 773 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
774 774 stats = relationship('Statistics', cascade='all', uselist=False)
775 775
776 776 followers = relationship('UserFollowing',
777 777 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
778 778 cascade='all')
779 779 extra_fields = relationship('RepositoryField',
780 780 cascade="all, delete, delete-orphan")
781 781
782 782 logs = relationship('UserLog')
783 783 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
784 784
785 785 pull_requests_org = relationship('PullRequest',
786 786 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
787 787 cascade="all, delete, delete-orphan")
788 788
789 789 pull_requests_other = relationship('PullRequest',
790 790 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
791 791 cascade="all, delete, delete-orphan")
792 792
793 793 def __unicode__(self):
794 794 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
795 795 self.repo_name)
796 796
797 797 @hybrid_property
798 798 def locked(self):
799 799 # always should return [user_id, timelocked]
800 800 if self._locked:
801 801 _lock_info = self._locked.split(':')
802 802 return int(_lock_info[0]), _lock_info[1]
803 803 return [None, None]
804 804
805 805 @locked.setter
806 806 def locked(self, val):
807 807 if val and isinstance(val, (list, tuple)):
808 808 self._locked = ':'.join(map(str, val))
809 809 else:
810 810 self._locked = None
811 811
812 812 @hybrid_property
813 813 def changeset_cache(self):
814 814 from rhodecode.lib.vcs.backends.base import EmptyChangeset
815 815 dummy = EmptyChangeset().__json__()
816 816 if not self._changeset_cache:
817 817 return dummy
818 818 try:
819 819 return json.loads(self._changeset_cache)
820 820 except TypeError:
821 821 return dummy
822 822
823 823 @changeset_cache.setter
824 824 def changeset_cache(self, val):
825 825 try:
826 826 self._changeset_cache = json.dumps(val)
827 827 except Exception:
828 828 log.error(traceback.format_exc())
829 829
830 830 @classmethod
831 831 def url_sep(cls):
832 832 return URL_SEP
833 833
834 834 @classmethod
835 835 def normalize_repo_name(cls, repo_name):
836 836 """
837 837 Normalizes os specific repo_name to the format internally stored inside
838 838 dabatabase using URL_SEP
839 839
840 840 :param cls:
841 841 :param repo_name:
842 842 """
843 843 return cls.url_sep().join(repo_name.split(os.sep))
844 844
845 845 @classmethod
846 846 def get_by_repo_name(cls, repo_name):
847 847 q = Session().query(cls).filter(cls.repo_name == repo_name)
848 848 q = q.options(joinedload(Repository.fork))\
849 849 .options(joinedload(Repository.user))\
850 850 .options(joinedload(Repository.group))
851 851 return q.scalar()
852 852
853 853 @classmethod
854 854 def get_by_full_path(cls, repo_full_path):
855 855 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
856 856 repo_name = cls.normalize_repo_name(repo_name)
857 857 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
858 858
859 859 @classmethod
860 860 def get_repo_forks(cls, repo_id):
861 861 return cls.query().filter(Repository.fork_id == repo_id)
862 862
863 863 @classmethod
864 864 def base_path(cls):
865 865 """
866 866 Returns base path when all repos are stored
867 867
868 868 :param cls:
869 869 """
870 870 q = Session().query(RhodeCodeUi)\
871 871 .filter(RhodeCodeUi.ui_key == cls.url_sep())
872 872 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
873 873 return q.one().ui_value
874 874
875 875 @property
876 876 def forks(self):
877 877 """
878 878 Return forks of this repo
879 879 """
880 880 return Repository.get_repo_forks(self.repo_id)
881 881
882 882 @property
883 883 def parent(self):
884 884 """
885 885 Returns fork parent
886 886 """
887 887 return self.fork
888 888
889 889 @property
890 890 def just_name(self):
891 891 return self.repo_name.split(Repository.url_sep())[-1]
892 892
893 893 @property
894 894 def groups_with_parents(self):
895 895 groups = []
896 896 if self.group is None:
897 897 return groups
898 898
899 899 cur_gr = self.group
900 900 groups.insert(0, cur_gr)
901 901 while 1:
902 902 gr = getattr(cur_gr, 'parent_group', None)
903 903 cur_gr = cur_gr.parent_group
904 904 if gr is None:
905 905 break
906 906 groups.insert(0, gr)
907 907
908 908 return groups
909 909
910 910 @property
911 911 def groups_and_repo(self):
912 912 return self.groups_with_parents, self.just_name, self.repo_name
913 913
914 914 @LazyProperty
915 915 def repo_path(self):
916 916 """
917 917 Returns base full path for that repository means where it actually
918 918 exists on a filesystem
919 919 """
920 920 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
921 921 Repository.url_sep())
922 922 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
923 923 return q.one().ui_value
924 924
925 925 @property
926 926 def repo_full_path(self):
927 927 p = [self.repo_path]
928 928 # we need to split the name by / since this is how we store the
929 929 # names in the database, but that eventually needs to be converted
930 930 # into a valid system path
931 931 p += self.repo_name.split(Repository.url_sep())
932 932 return os.path.join(*map(safe_unicode, p))
933 933
934 934 @property
935 935 def cache_keys(self):
936 936 """
937 937 Returns associated cache keys for that repo
938 938 """
939 939 return CacheInvalidation.query()\
940 940 .filter(CacheInvalidation.cache_args == self.repo_name)\
941 941 .order_by(CacheInvalidation.cache_key)\
942 942 .all()
943 943
944 944 def get_new_name(self, repo_name):
945 945 """
946 946 returns new full repository name based on assigned group and new new
947 947
948 948 :param group_name:
949 949 """
950 950 path_prefix = self.group.full_path_splitted if self.group else []
951 951 return Repository.url_sep().join(path_prefix + [repo_name])
952 952
953 953 @property
954 954 def _ui(self):
955 955 """
956 956 Creates an db based ui object for this repository
957 957 """
958 958 from rhodecode.lib.utils import make_ui
959 959 return make_ui('db', clear_session=False)
960 960
961 961 @classmethod
962 962 def is_valid(cls, repo_name):
963 963 """
964 964 returns True if given repo name is a valid filesystem repository
965 965
966 966 :param cls:
967 967 :param repo_name:
968 968 """
969 969 from rhodecode.lib.utils import is_valid_repo
970 970
971 971 return is_valid_repo(repo_name, cls.base_path())
972 972
973 973 def get_api_data(self):
974 974 """
975 975 Common function for generating repo api data
976 976
977 977 """
978 978 repo = self
979 979 data = dict(
980 980 repo_id=repo.repo_id,
981 981 repo_name=repo.repo_name,
982 982 repo_type=repo.repo_type,
983 983 clone_uri=repo.clone_uri,
984 984 private=repo.private,
985 985 created_on=repo.created_on,
986 986 description=repo.description,
987 987 landing_rev=repo.landing_rev,
988 988 owner=repo.user.username,
989 989 fork_of=repo.fork.repo_name if repo.fork else None,
990 990 enable_statistics=repo.enable_statistics,
991 991 enable_locking=repo.enable_locking,
992 992 enable_downloads=repo.enable_downloads,
993 993 last_changeset=repo.changeset_cache,
994 994 locked_by=User.get(self.locked[0]).get_api_data() \
995 995 if self.locked[0] else None,
996 996 locked_date=time_to_datetime(self.locked[1]) \
997 997 if self.locked[1] else None
998 998 )
999 999 rc_config = RhodeCodeSetting.get_app_settings()
1000 1000 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1001 1001 if repository_fields:
1002 1002 for f in self.extra_fields:
1003 1003 data[f.field_key_prefixed] = f.field_value
1004 1004
1005 1005 return data
1006 1006
1007 1007 @classmethod
1008 1008 def lock(cls, repo, user_id):
1009 1009 repo.locked = [user_id, time.time()]
1010 1010 Session().add(repo)
1011 1011 Session().commit()
1012 1012
1013 1013 @classmethod
1014 1014 def unlock(cls, repo):
1015 1015 repo.locked = None
1016 1016 Session().add(repo)
1017 1017 Session().commit()
1018 1018
1019 1019 @classmethod
1020 1020 def getlock(cls, repo):
1021 1021 return repo.locked
1022 1022
1023 1023 @property
1024 1024 def last_db_change(self):
1025 1025 return self.updated_on
1026 1026
1027 1027 def clone_url(self, **override):
1028 1028 from pylons import url
1029 1029 from urlparse import urlparse
1030 1030 import urllib
1031 1031 parsed_url = urlparse(url('home', qualified=True))
1032 1032 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1033 1033 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1034 1034 args = {
1035 1035 'user': '',
1036 1036 'pass': '',
1037 1037 'scheme': parsed_url.scheme,
1038 1038 'netloc': parsed_url.netloc,
1039 1039 'prefix': decoded_path,
1040 1040 'path': self.repo_name
1041 1041 }
1042 1042
1043 1043 args.update(override)
1044 1044 return default_clone_uri % args
1045 1045
1046 1046 #==========================================================================
1047 1047 # SCM PROPERTIES
1048 1048 #==========================================================================
1049 1049
1050 1050 def get_changeset(self, rev=None):
1051 1051 return get_changeset_safe(self.scm_instance, rev)
1052 1052
1053 1053 def get_landing_changeset(self):
1054 1054 """
1055 1055 Returns landing changeset, or if that doesn't exist returns the tip
1056 1056 """
1057 1057 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1058 1058 return cs
1059 1059
1060 1060 def update_changeset_cache(self, cs_cache=None):
1061 1061 """
1062 1062 Update cache of last changeset for repository, keys should be::
1063 1063
1064 1064 short_id
1065 1065 raw_id
1066 1066 revision
1067 1067 message
1068 1068 date
1069 1069 author
1070 1070
1071 1071 :param cs_cache:
1072 1072 """
1073 1073 from rhodecode.lib.vcs.backends.base import BaseChangeset
1074 1074 if cs_cache is None:
1075 1075 cs_cache = EmptyChangeset()
1076 1076 # use no-cache version here
1077 1077 scm_repo = self.scm_instance_no_cache()
1078 1078 if scm_repo:
1079 1079 cs_cache = scm_repo.get_changeset()
1080 1080
1081 1081 if isinstance(cs_cache, BaseChangeset):
1082 1082 cs_cache = cs_cache.__json__()
1083 1083
1084 1084 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1085 1085 _default = datetime.datetime.fromtimestamp(0)
1086 1086 last_change = cs_cache.get('date') or _default
1087 1087 log.debug('updated repo %s with new cs cache %s'
1088 1088 % (self.repo_name, cs_cache))
1089 1089 self.updated_on = last_change
1090 1090 self.changeset_cache = cs_cache
1091 1091 Session().add(self)
1092 1092 Session().commit()
1093 1093 else:
1094 1094 log.debug('Skipping repo:%s already with latest changes'
1095 1095 % self.repo_name)
1096 1096
1097 1097 @property
1098 1098 def tip(self):
1099 1099 return self.get_changeset('tip')
1100 1100
1101 1101 @property
1102 1102 def author(self):
1103 1103 return self.tip.author
1104 1104
1105 1105 @property
1106 1106 def last_change(self):
1107 1107 return self.scm_instance.last_change
1108 1108
1109 1109 def get_comments(self, revisions=None):
1110 1110 """
1111 1111 Returns comments for this repository grouped by revisions
1112 1112
1113 1113 :param revisions: filter query by revisions only
1114 1114 """
1115 1115 cmts = ChangesetComment.query()\
1116 1116 .filter(ChangesetComment.repo == self)
1117 1117 if revisions:
1118 1118 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1119 1119 grouped = defaultdict(list)
1120 1120 for cmt in cmts.all():
1121 1121 grouped[cmt.revision].append(cmt)
1122 1122 return grouped
1123 1123
1124 1124 def statuses(self, revisions=None):
1125 1125 """
1126 1126 Returns statuses for this repository
1127 1127
1128 1128 :param revisions: list of revisions to get statuses for
1129 1129 :type revisions: list
1130 1130 """
1131 1131
1132 1132 statuses = ChangesetStatus.query()\
1133 1133 .filter(ChangesetStatus.repo == self)\
1134 1134 .filter(ChangesetStatus.version == 0)
1135 1135 if revisions:
1136 1136 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1137 1137 grouped = {}
1138 1138
1139 1139 #maybe we have open new pullrequest without a status ?
1140 1140 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1141 1141 status_lbl = ChangesetStatus.get_status_lbl(stat)
1142 1142 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1143 1143 for rev in pr.revisions:
1144 1144 pr_id = pr.pull_request_id
1145 1145 pr_repo = pr.other_repo.repo_name
1146 1146 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1147 1147
1148 1148 for stat in statuses.all():
1149 1149 pr_id = pr_repo = None
1150 1150 if stat.pull_request:
1151 1151 pr_id = stat.pull_request.pull_request_id
1152 1152 pr_repo = stat.pull_request.other_repo.repo_name
1153 1153 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1154 1154 pr_id, pr_repo]
1155 1155 return grouped
1156 1156
1157 1157 def _repo_size(self):
1158 1158 from rhodecode.lib import helpers as h
1159 1159 log.debug('calculating repository size...')
1160 1160 return h.format_byte_size(self.scm_instance.size)
1161 1161
1162 1162 #==========================================================================
1163 1163 # SCM CACHE INSTANCE
1164 1164 #==========================================================================
1165 1165
1166 1166 @property
1167 1167 def invalidate(self):
1168 1168 return CacheInvalidation.invalidate(self.repo_name)
1169 1169
1170 1170 def set_invalidate(self):
1171 1171 """
1172 1172 Mark caches of this repo as invalid.
1173 1173 """
1174 1174 CacheInvalidation.set_invalidate(self.repo_name)
1175 1175
1176 1176 def scm_instance_no_cache(self):
1177 1177 return self.__get_instance()
1178 1178
1179 1179 @property
1180 1180 def scm_instance(self):
1181 1181 import rhodecode
1182 1182 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1183 1183 if full_cache:
1184 1184 return self.scm_instance_cached()
1185 1185 return self.__get_instance()
1186 1186
1187 1187 def scm_instance_cached(self, cache_map=None):
1188 1188 @cache_region('long_term')
1189 1189 def _c(repo_name):
1190 1190 return self.__get_instance()
1191 1191 rn = self.repo_name
1192 1192
1193 1193 if cache_map:
1194 1194 # get using prefilled cache_map
1195 1195 invalidate_repo = cache_map[self.repo_name]
1196 1196 if invalidate_repo:
1197 1197 invalidate_repo = (None if invalidate_repo.cache_active
1198 1198 else invalidate_repo)
1199 1199 else:
1200 1200 # get from invalidate
1201 1201 invalidate_repo = self.invalidate
1202 1202
1203 1203 if invalidate_repo is not None:
1204 1204 region_invalidate(_c, None, rn)
1205 1205 log.debug('Cache for %s invalidated, getting new object' % (rn))
1206 1206 # update our cache
1207 1207 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1208 1208 else:
1209 1209 log.debug('Getting obj for %s from cache' % (rn))
1210 1210 return _c(rn)
1211 1211
1212 1212 def __get_instance(self):
1213 1213 repo_full_path = self.repo_full_path
1214 1214 try:
1215 1215 alias = get_scm(repo_full_path)[0]
1216 1216 log.debug('Creating instance of %s repository from %s'
1217 1217 % (alias, repo_full_path))
1218 1218 backend = get_backend(alias)
1219 1219 except VCSError:
1220 1220 log.error(traceback.format_exc())
1221 1221 log.error('Perhaps this repository is in db and not in '
1222 1222 'filesystem run rescan repositories with '
1223 1223 '"destroy old data " option from admin panel')
1224 1224 return
1225 1225
1226 1226 if alias == 'hg':
1227 1227
1228 1228 repo = backend(safe_str(repo_full_path), create=False,
1229 1229 baseui=self._ui)
1230 1230 # skip hidden web repository
1231 1231 if repo._get_hidden():
1232 1232 return
1233 1233 else:
1234 1234 repo = backend(repo_full_path, create=False)
1235 1235
1236 1236 return repo
1237 1237
1238 1238
1239 1239 class RepoGroup(Base, BaseModel):
1240 1240 __tablename__ = 'groups'
1241 1241 __table_args__ = (
1242 1242 UniqueConstraint('group_name', 'group_parent_id'),
1243 1243 CheckConstraint('group_id != group_parent_id'),
1244 1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 1245 'mysql_charset': 'utf8'},
1246 1246 )
1247 1247 __mapper_args__ = {'order_by': 'group_name'}
1248 1248
1249 1249 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1250 1250 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1251 1251 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1252 1252 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1253 1253 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1254 1254 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1255 1255
1256 1256 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1257 1257 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1258 1258 parent_group = relationship('RepoGroup', remote_side=group_id)
1259 1259 user = relationship('User')
1260 1260
1261 1261 def __init__(self, group_name='', parent_group=None):
1262 1262 self.group_name = group_name
1263 1263 self.parent_group = parent_group
1264 1264
1265 1265 def __unicode__(self):
1266 1266 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1267 1267 self.group_name)
1268 1268
1269 1269 @classmethod
1270 1270 def groups_choices(cls, groups=None, show_empty_group=True):
1271 1271 from webhelpers.html import literal as _literal
1272 1272 if not groups:
1273 1273 groups = cls.query().all()
1274 1274
1275 1275 repo_groups = []
1276 1276 if show_empty_group:
1277 1277 repo_groups = [('-1', '-- %s --' % _('top level'))]
1278 1278 sep = ' &raquo; '
1279 1279 _name = lambda k: _literal(sep.join(k))
1280 1280
1281 1281 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1282 1282 for x in groups])
1283 1283
1284 1284 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1285 1285 return repo_groups
1286 1286
1287 1287 @classmethod
1288 1288 def url_sep(cls):
1289 1289 return URL_SEP
1290 1290
1291 1291 @classmethod
1292 1292 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1293 1293 if case_insensitive:
1294 1294 gr = cls.query()\
1295 1295 .filter(cls.group_name.ilike(group_name))
1296 1296 else:
1297 1297 gr = cls.query()\
1298 1298 .filter(cls.group_name == group_name)
1299 1299 if cache:
1300 1300 gr = gr.options(FromCache(
1301 1301 "sql_cache_short",
1302 1302 "get_group_%s" % _hash_key(group_name)
1303 1303 )
1304 1304 )
1305 1305 return gr.scalar()
1306 1306
1307 1307 @property
1308 1308 def parents(self):
1309 1309 parents_recursion_limit = 5
1310 1310 groups = []
1311 1311 if self.parent_group is None:
1312 1312 return groups
1313 1313 cur_gr = self.parent_group
1314 1314 groups.insert(0, cur_gr)
1315 1315 cnt = 0
1316 1316 while 1:
1317 1317 cnt += 1
1318 1318 gr = getattr(cur_gr, 'parent_group', None)
1319 1319 cur_gr = cur_gr.parent_group
1320 1320 if gr is None:
1321 1321 break
1322 1322 if cnt == parents_recursion_limit:
1323 1323 # this will prevent accidental infinit loops
1324 1324 log.error('group nested more than %s' %
1325 1325 parents_recursion_limit)
1326 1326 break
1327 1327
1328 1328 groups.insert(0, gr)
1329 1329 return groups
1330 1330
1331 1331 @property
1332 1332 def children(self):
1333 1333 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1334 1334
1335 1335 @property
1336 1336 def name(self):
1337 1337 return self.group_name.split(RepoGroup.url_sep())[-1]
1338 1338
1339 1339 @property
1340 1340 def full_path(self):
1341 1341 return self.group_name
1342 1342
1343 1343 @property
1344 1344 def full_path_splitted(self):
1345 1345 return self.group_name.split(RepoGroup.url_sep())
1346 1346
1347 1347 @property
1348 1348 def repositories(self):
1349 1349 return Repository.query()\
1350 1350 .filter(Repository.group == self)\
1351 1351 .order_by(Repository.repo_name)
1352 1352
1353 1353 @property
1354 1354 def repositories_recursive_count(self):
1355 1355 cnt = self.repositories.count()
1356 1356
1357 1357 def children_count(group):
1358 1358 cnt = 0
1359 1359 for child in group.children:
1360 1360 cnt += child.repositories.count()
1361 1361 cnt += children_count(child)
1362 1362 return cnt
1363 1363
1364 1364 return cnt + children_count(self)
1365 1365
1366 1366 def _recursive_objects(self, include_repos=True):
1367 1367 all_ = []
1368 1368
1369 1369 def _get_members(root_gr):
1370 1370 if include_repos:
1371 1371 for r in root_gr.repositories:
1372 1372 all_.append(r)
1373 1373 childs = root_gr.children.all()
1374 1374 if childs:
1375 1375 for gr in childs:
1376 1376 all_.append(gr)
1377 1377 _get_members(gr)
1378 1378
1379 1379 _get_members(self)
1380 1380 return [self] + all_
1381 1381
1382 1382 def recursive_groups_and_repos(self):
1383 1383 """
1384 1384 Recursive return all groups, with repositories in those groups
1385 1385 """
1386 1386 return self._recursive_objects()
1387 1387
1388 1388 def recursive_groups(self):
1389 1389 """
1390 1390 Returns all children groups for this group including children of children
1391 1391 """
1392 1392 return self._recursive_objects(include_repos=False)
1393 1393
1394 1394 def get_new_name(self, group_name):
1395 1395 """
1396 1396 returns new full group name based on parent and new name
1397 1397
1398 1398 :param group_name:
1399 1399 """
1400 1400 path_prefix = (self.parent_group.full_path_splitted if
1401 1401 self.parent_group else [])
1402 1402 return RepoGroup.url_sep().join(path_prefix + [group_name])
1403 1403
1404 1404
1405 1405 class Permission(Base, BaseModel):
1406 1406 __tablename__ = 'permissions'
1407 1407 __table_args__ = (
1408 1408 Index('p_perm_name_idx', 'permission_name'),
1409 1409 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1410 1410 'mysql_charset': 'utf8'},
1411 1411 )
1412 1412 PERMS = [
1413 1413 ('hg.admin', _('RhodeCode Administrator')),
1414 1414
1415 1415 ('repository.none', _('Repository no access')),
1416 1416 ('repository.read', _('Repository read access')),
1417 1417 ('repository.write', _('Repository write access')),
1418 1418 ('repository.admin', _('Repository admin access')),
1419 1419
1420 1420 ('group.none', _('Repository group no access')),
1421 1421 ('group.read', _('Repository group read access')),
1422 1422 ('group.write', _('Repository group write access')),
1423 1423 ('group.admin', _('Repository group admin access')),
1424 1424
1425 1425 ('usergroup.none', _('User group no access')),
1426 1426 ('usergroup.read', _('User group read access')),
1427 1427 ('usergroup.write', _('User group write access')),
1428 1428 ('usergroup.admin', _('User group admin access')),
1429 1429
1430 1430 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1431 1431 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1432 1432
1433 1433 ('hg.usergroup.create.false', _('User Group creation disabled')),
1434 1434 ('hg.usergroup.create.true', _('User Group creation enabled')),
1435 1435
1436 1436 ('hg.create.none', _('Repository creation disabled')),
1437 1437 ('hg.create.repository', _('Repository creation enabled')),
1438 1438
1439 1439 ('hg.fork.none', _('Repository forking disabled')),
1440 1440 ('hg.fork.repository', _('Repository forking enabled')),
1441 1441
1442 1442 ('hg.register.none', _('Register disabled')),
1443 1443 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1444 1444 'with manual activation')),
1445 1445
1446 1446 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1447 1447 'with auto activation')),
1448 1448 ]
1449 1449
1450 1450 #definition of system default permissions for DEFAULT user
1451 1451 DEFAULT_USER_PERMISSIONS = [
1452 1452 'repository.read',
1453 1453 'group.read',
1454 1454 'usergroup.read',
1455 1455 'hg.create.repository',
1456 1456 'hg.fork.repository',
1457 1457 'hg.register.manual_activate',
1458 1458 ]
1459 1459
1460 1460 # defines which permissions are more important higher the more important
1461 1461 # Weight defines which permissions are more important.
1462 1462 # The higher number the more important.
1463 1463 PERM_WEIGHTS = {
1464 1464 'repository.none': 0,
1465 1465 'repository.read': 1,
1466 1466 'repository.write': 3,
1467 1467 'repository.admin': 4,
1468 1468
1469 1469 'group.none': 0,
1470 1470 'group.read': 1,
1471 1471 'group.write': 3,
1472 1472 'group.admin': 4,
1473 1473
1474 1474 'usergroup.none': 0,
1475 1475 'usergroup.read': 1,
1476 1476 'usergroup.write': 3,
1477 1477 'usergroup.admin': 4,
1478 1478 'hg.repogroup.create.false': 0,
1479 1479 'hg.repogroup.create.true': 1,
1480 1480
1481 1481 'hg.usergroup.create.false': 0,
1482 1482 'hg.usergroup.create.true': 1,
1483 1483
1484 1484 'hg.fork.none': 0,
1485 1485 'hg.fork.repository': 1,
1486 1486 'hg.create.none': 0,
1487 1487 'hg.create.repository': 1
1488 1488 }
1489 1489
1490 1490 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1491 1491 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1492 1492 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1493 1493
1494 1494 def __unicode__(self):
1495 1495 return u"<%s('%s:%s')>" % (
1496 1496 self.__class__.__name__, self.permission_id, self.permission_name
1497 1497 )
1498 1498
1499 1499 @classmethod
1500 1500 def get_by_key(cls, key):
1501 1501 return cls.query().filter(cls.permission_name == key).scalar()
1502 1502
1503 1503 @classmethod
1504 1504 def get_default_perms(cls, default_user_id):
1505 1505 q = Session().query(UserRepoToPerm, Repository, cls)\
1506 1506 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1507 1507 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1508 1508 .filter(UserRepoToPerm.user_id == default_user_id)
1509 1509
1510 1510 return q.all()
1511 1511
1512 1512 @classmethod
1513 1513 def get_default_group_perms(cls, default_user_id):
1514 1514 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1515 1515 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1516 1516 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1517 1517 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1518 1518
1519 1519 return q.all()
1520 1520
1521 1521 @classmethod
1522 1522 def get_default_user_group_perms(cls, default_user_id):
1523 1523 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1524 1524 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1525 1525 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1526 1526 .filter(UserUserGroupToPerm.user_id == default_user_id)
1527 1527
1528 1528 return q.all()
1529 1529
1530 1530
1531 1531 class UserRepoToPerm(Base, BaseModel):
1532 1532 __tablename__ = 'repo_to_perm'
1533 1533 __table_args__ = (
1534 1534 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1535 1535 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1536 1536 'mysql_charset': 'utf8'}
1537 1537 )
1538 1538 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1539 1539 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1540 1540 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1541 1541 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1542 1542
1543 1543 user = relationship('User')
1544 1544 repository = relationship('Repository')
1545 1545 permission = relationship('Permission')
1546 1546
1547 1547 @classmethod
1548 1548 def create(cls, user, repository, permission):
1549 1549 n = cls()
1550 1550 n.user = user
1551 1551 n.repository = repository
1552 1552 n.permission = permission
1553 1553 Session().add(n)
1554 1554 return n
1555 1555
1556 1556 def __unicode__(self):
1557 1557 return u'<%s => %s >' % (self.user, self.repository)
1558 1558
1559 1559
1560 1560 class UserUserGroupToPerm(Base, BaseModel):
1561 1561 __tablename__ = 'user_user_group_to_perm'
1562 1562 __table_args__ = (
1563 1563 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1564 1564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1565 1565 'mysql_charset': 'utf8'}
1566 1566 )
1567 1567 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1568 1568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1569 1569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1570 1570 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1571 1571
1572 1572 user = relationship('User')
1573 1573 user_group = relationship('UserGroup')
1574 1574 permission = relationship('Permission')
1575 1575
1576 1576 @classmethod
1577 1577 def create(cls, user, user_group, permission):
1578 1578 n = cls()
1579 1579 n.user = user
1580 1580 n.user_group = user_group
1581 1581 n.permission = permission
1582 1582 Session().add(n)
1583 1583 return n
1584 1584
1585 1585 def __unicode__(self):
1586 1586 return u'<%s => %s >' % (self.user, self.user_group)
1587 1587
1588 1588
1589 1589 class UserToPerm(Base, BaseModel):
1590 1590 __tablename__ = 'user_to_perm'
1591 1591 __table_args__ = (
1592 1592 UniqueConstraint('user_id', 'permission_id'),
1593 1593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1594 1594 'mysql_charset': 'utf8'}
1595 1595 )
1596 1596 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1597 1597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1598 1598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1599 1599
1600 1600 user = relationship('User')
1601 1601 permission = relationship('Permission', lazy='joined')
1602 1602
1603 1603 def __unicode__(self):
1604 1604 return u'<%s => %s >' % (self.user, self.permission)
1605 1605
1606 1606
1607 1607 class UserGroupRepoToPerm(Base, BaseModel):
1608 1608 __tablename__ = 'users_group_repo_to_perm'
1609 1609 __table_args__ = (
1610 1610 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1611 1611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1612 1612 'mysql_charset': 'utf8'}
1613 1613 )
1614 1614 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1615 1615 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1616 1616 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1617 1617 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1618 1618
1619 1619 users_group = relationship('UserGroup')
1620 1620 permission = relationship('Permission')
1621 1621 repository = relationship('Repository')
1622 1622
1623 1623 @classmethod
1624 1624 def create(cls, users_group, repository, permission):
1625 1625 n = cls()
1626 1626 n.users_group = users_group
1627 1627 n.repository = repository
1628 1628 n.permission = permission
1629 1629 Session().add(n)
1630 1630 return n
1631 1631
1632 1632 def __unicode__(self):
1633 1633 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1634 1634
1635 1635
1636 1636 #TODO; not sure if this will be ever used
1637 1637 class UserGroupUserGroupToPerm(Base, BaseModel):
1638 1638 __tablename__ = 'user_group_user_group_to_perm'
1639 1639 __table_args__ = (
1640 1640 UniqueConstraint('user_group_id', 'user_group_id', 'permission_id'),
1641 1641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1642 1642 'mysql_charset': 'utf8'}
1643 1643 )
1644 1644 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1645 1645 target_user_group_id = Column("target_users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1646 1646 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1647 1647 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1648 1648
1649 1649 target_user_group = relationship('UserGroup', remote_side=target_user_group_id, primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1650 1650 user_group = relationship('UserGroup', remote_side=user_group_id, primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1651 1651 permission = relationship('Permission')
1652 1652
1653 1653 @classmethod
1654 1654 def create(cls, target_user_group, user_group, permission):
1655 1655 n = cls()
1656 1656 n.target_user_group = target_user_group
1657 1657 n.user_group = user_group
1658 1658 n.permission = permission
1659 1659 Session().add(n)
1660 1660 return n
1661 1661
1662 1662 def __unicode__(self):
1663 1663 return u'<UserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1664 1664
1665 1665
1666 1666 class UserGroupToPerm(Base, BaseModel):
1667 1667 __tablename__ = 'users_group_to_perm'
1668 1668 __table_args__ = (
1669 1669 UniqueConstraint('users_group_id', 'permission_id',),
1670 1670 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1671 1671 'mysql_charset': 'utf8'}
1672 1672 )
1673 1673 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1674 1674 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1675 1675 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1676 1676
1677 1677 users_group = relationship('UserGroup')
1678 1678 permission = relationship('Permission')
1679 1679
1680 1680
1681 1681 class UserRepoGroupToPerm(Base, BaseModel):
1682 1682 __tablename__ = 'user_repo_group_to_perm'
1683 1683 __table_args__ = (
1684 1684 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1685 1685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1686 1686 'mysql_charset': 'utf8'}
1687 1687 )
1688 1688
1689 1689 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1690 1690 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1691 1691 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1692 1692 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1693 1693
1694 1694 user = relationship('User')
1695 1695 group = relationship('RepoGroup')
1696 1696 permission = relationship('Permission')
1697 1697
1698 1698
1699 1699 class UserGroupRepoGroupToPerm(Base, BaseModel):
1700 1700 __tablename__ = 'users_group_repo_group_to_perm'
1701 1701 __table_args__ = (
1702 1702 UniqueConstraint('users_group_id', 'group_id'),
1703 1703 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1704 1704 'mysql_charset': 'utf8'}
1705 1705 )
1706 1706
1707 1707 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)
1708 1708 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1709 1709 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1710 1710 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1711 1711
1712 1712 users_group = relationship('UserGroup')
1713 1713 permission = relationship('Permission')
1714 1714 group = relationship('RepoGroup')
1715 1715
1716 1716
1717 1717 class Statistics(Base, BaseModel):
1718 1718 __tablename__ = 'statistics'
1719 1719 __table_args__ = (
1720 1720 UniqueConstraint('repository_id'),
1721 1721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1722 1722 'mysql_charset': 'utf8'}
1723 1723 )
1724 1724 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1725 1725 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1726 1726 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1727 1727 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1728 1728 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1729 1729 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1730 1730
1731 1731 repository = relationship('Repository', single_parent=True)
1732 1732
1733 1733
1734 1734 class UserFollowing(Base, BaseModel):
1735 1735 __tablename__ = 'user_followings'
1736 1736 __table_args__ = (
1737 1737 UniqueConstraint('user_id', 'follows_repository_id'),
1738 1738 UniqueConstraint('user_id', 'follows_user_id'),
1739 1739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1740 1740 'mysql_charset': 'utf8'}
1741 1741 )
1742 1742
1743 1743 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1744 1744 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1745 1745 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1746 1746 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1747 1747 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1748 1748
1749 1749 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1750 1750
1751 1751 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1752 1752 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1753 1753
1754 1754 @classmethod
1755 1755 def get_repo_followers(cls, repo_id):
1756 1756 return cls.query().filter(cls.follows_repo_id == repo_id)
1757 1757
1758 1758
1759 1759 class CacheInvalidation(Base, BaseModel):
1760 1760 __tablename__ = 'cache_invalidation'
1761 1761 __table_args__ = (
1762 1762 UniqueConstraint('cache_key'),
1763 1763 Index('key_idx', 'cache_key'),
1764 1764 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1765 1765 'mysql_charset': 'utf8'},
1766 1766 )
1767 1767 # cache_id, not used
1768 1768 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1769 1769 # cache_key as created by _get_cache_key
1770 1770 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1771 1771 # cache_args is a repo_name
1772 1772 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1773 1773 # instance sets cache_active True when it is caching,
1774 1774 # other instances set cache_active to False to indicate that this cache is invalid
1775 1775 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1776 1776
1777 1777 def __init__(self, cache_key, repo_name=''):
1778 1778 self.cache_key = cache_key
1779 1779 self.cache_args = repo_name
1780 1780 self.cache_active = False
1781 1781
1782 1782 def __unicode__(self):
1783 1783 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1784 1784 self.cache_id, self.cache_key, self.cache_active)
1785 1785
1786 1786 def _cache_key_partition(self):
1787 1787 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1788 1788 return prefix, repo_name, suffix
1789 1789
1790 1790 def get_prefix(self):
1791 1791 """
1792 1792 get prefix that might have been used in _get_cache_key to
1793 1793 generate self.cache_key. Only used for informational purposes
1794 1794 in repo_edit.html.
1795 1795 """
1796 1796 # prefix, repo_name, suffix
1797 1797 return self._cache_key_partition()[0]
1798 1798
1799 1799 def get_suffix(self):
1800 1800 """
1801 1801 get suffix that might have been used in _get_cache_key to
1802 1802 generate self.cache_key. Only used for informational purposes
1803 1803 in repo_edit.html.
1804 1804 """
1805 1805 # prefix, repo_name, suffix
1806 1806 return self._cache_key_partition()[2]
1807 1807
1808 1808 @classmethod
1809 def clear_cache(cls):
1810 """
1811 Delete all cache keys from database.
1812 Should only be run when all instances are down and all entries thus stale.
1813 """
1814 cls.query().delete()
1815 Session().commit()
1816
1817 @classmethod
1809 1818 def _get_cache_key(cls, key):
1810 1819 """
1811 1820 Wrapper for generating a unique cache key for this instance and "key".
1812 1821 key must / will start with a repo_name which will be stored in .cache_args .
1813 1822 """
1814 1823 import rhodecode
1815 1824 prefix = rhodecode.CONFIG.get('instance_id', '')
1816 1825 return "%s%s" % (prefix, key)
1817 1826
1818 1827 @classmethod
1819 1828 def invalidate(cls, key):
1820 1829 """
1821 1830 Returns Invalidation object if the local cache with the given key is invalid,
1822 1831 None otherwise.
1823 1832 """
1824 1833 repo_name = key
1825 1834 repo_name = remove_suffix(repo_name, '_README')
1826 1835 repo_name = remove_suffix(repo_name, '_RSS')
1827 1836 repo_name = remove_suffix(repo_name, '_ATOM')
1828 1837
1829 1838 cache_key = cls._get_cache_key(key)
1830 1839 inv_obj = Session().query(cls).filter(cls.cache_key == cache_key).scalar()
1831 1840 if not inv_obj:
1832 1841 try:
1833 1842 inv_obj = CacheInvalidation(cache_key, repo_name)
1834 1843 Session().add(inv_obj)
1835 1844 Session().commit()
1836 1845 except Exception:
1837 1846 log.error(traceback.format_exc())
1838 1847 Session().rollback()
1839 1848 return
1840 1849
1841 1850 if not inv_obj.cache_active:
1842 1851 # `cache_active = False` means that this cache
1843 1852 # no longer is valid
1844 1853 return inv_obj
1845 1854
1846 1855 @classmethod
1847 1856 def set_invalidate(cls, repo_name):
1848 1857 """
1849 1858 Mark all caches of a repo as invalid in the database.
1850 1859 """
1851 1860 invalidated_keys = []
1852 1861 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1853 1862
1854 1863 try:
1855 1864 for inv_obj in inv_objs:
1856 1865 log.debug('marking %s key for invalidation based on repo_name=%s'
1857 1866 % (inv_obj, safe_str(repo_name)))
1858 1867 inv_obj.cache_active = False
1859 1868 invalidated_keys.append(inv_obj.cache_key)
1860 1869 Session().add(inv_obj)
1861 1870 Session().commit()
1862 1871 except Exception:
1863 1872 log.error(traceback.format_exc())
1864 1873 Session().rollback()
1865 1874 return invalidated_keys
1866 1875
1867 1876 @classmethod
1868 1877 def set_valid(cls, cache_key):
1869 1878 """
1870 1879 Mark this cache key as active and currently cached
1871 1880 """
1872 1881 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1873 1882 inv_obj.cache_active = True
1874 1883 Session().add(inv_obj)
1875 1884 Session().commit()
1876 1885
1877 1886 @classmethod
1878 1887 def get_cache_map(cls):
1879 1888
1880 1889 class cachemapdict(dict):
1881 1890
1882 1891 def __init__(self, *args, **kwargs):
1883 1892 self.fixkey = kwargs.pop('fixkey', False)
1884 1893 super(cachemapdict, self).__init__(*args, **kwargs)
1885 1894
1886 1895 def __getattr__(self, name):
1887 1896 cache_key = name
1888 1897 if self.fixkey:
1889 1898 cache_key = cls._get_cache_key(name)
1890 1899 if cache_key in self.__dict__:
1891 1900 return self.__dict__[cache_key]
1892 1901 else:
1893 1902 return self[cache_key]
1894 1903
1895 1904 def __getitem__(self, name):
1896 1905 cache_key = name
1897 1906 if self.fixkey:
1898 1907 cache_key = cls._get_cache_key(name)
1899 1908 try:
1900 1909 return super(cachemapdict, self).__getitem__(cache_key)
1901 1910 except KeyError:
1902 1911 return None
1903 1912
1904 1913 cache_map = cachemapdict(fixkey=True)
1905 1914 for obj in cls.query().all():
1906 1915 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1907 1916 return cache_map
1908 1917
1909 1918
1910 1919 class ChangesetComment(Base, BaseModel):
1911 1920 __tablename__ = 'changeset_comments'
1912 1921 __table_args__ = (
1913 1922 Index('cc_revision_idx', 'revision'),
1914 1923 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1915 1924 'mysql_charset': 'utf8'},
1916 1925 )
1917 1926 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1918 1927 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1919 1928 revision = Column('revision', String(40), nullable=True)
1920 1929 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1921 1930 line_no = Column('line_no', Unicode(10), nullable=True)
1922 1931 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1923 1932 f_path = Column('f_path', Unicode(1000), nullable=True)
1924 1933 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1925 1934 text = Column('text', UnicodeText(25000), nullable=False)
1926 1935 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1927 1936 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1928 1937
1929 1938 author = relationship('User', lazy='joined')
1930 1939 repo = relationship('Repository')
1931 1940 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1932 1941 pull_request = relationship('PullRequest', lazy='joined')
1933 1942
1934 1943 @classmethod
1935 1944 def get_users(cls, revision=None, pull_request_id=None):
1936 1945 """
1937 1946 Returns user associated with this ChangesetComment. ie those
1938 1947 who actually commented
1939 1948
1940 1949 :param cls:
1941 1950 :param revision:
1942 1951 """
1943 1952 q = Session().query(User)\
1944 1953 .join(ChangesetComment.author)
1945 1954 if revision:
1946 1955 q = q.filter(cls.revision == revision)
1947 1956 elif pull_request_id:
1948 1957 q = q.filter(cls.pull_request_id == pull_request_id)
1949 1958 return q.all()
1950 1959
1951 1960
1952 1961 class ChangesetStatus(Base, BaseModel):
1953 1962 __tablename__ = 'changeset_statuses'
1954 1963 __table_args__ = (
1955 1964 Index('cs_revision_idx', 'revision'),
1956 1965 Index('cs_version_idx', 'version'),
1957 1966 UniqueConstraint('repo_id', 'revision', 'version'),
1958 1967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1959 1968 'mysql_charset': 'utf8'}
1960 1969 )
1961 1970 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1962 1971 STATUS_APPROVED = 'approved'
1963 1972 STATUS_REJECTED = 'rejected'
1964 1973 STATUS_UNDER_REVIEW = 'under_review'
1965 1974
1966 1975 STATUSES = [
1967 1976 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1968 1977 (STATUS_APPROVED, _("Approved")),
1969 1978 (STATUS_REJECTED, _("Rejected")),
1970 1979 (STATUS_UNDER_REVIEW, _("Under Review")),
1971 1980 ]
1972 1981
1973 1982 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1974 1983 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1975 1984 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1976 1985 revision = Column('revision', String(40), nullable=False)
1977 1986 status = Column('status', String(128), nullable=False, default=DEFAULT)
1978 1987 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1979 1988 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1980 1989 version = Column('version', Integer(), nullable=False, default=0)
1981 1990 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1982 1991
1983 1992 author = relationship('User', lazy='joined')
1984 1993 repo = relationship('Repository')
1985 1994 comment = relationship('ChangesetComment', lazy='joined')
1986 1995 pull_request = relationship('PullRequest', lazy='joined')
1987 1996
1988 1997 def __unicode__(self):
1989 1998 return u"<%s('%s:%s')>" % (
1990 1999 self.__class__.__name__,
1991 2000 self.status, self.author
1992 2001 )
1993 2002
1994 2003 @classmethod
1995 2004 def get_status_lbl(cls, value):
1996 2005 return dict(cls.STATUSES).get(value)
1997 2006
1998 2007 @property
1999 2008 def status_lbl(self):
2000 2009 return ChangesetStatus.get_status_lbl(self.status)
2001 2010
2002 2011
2003 2012 class PullRequest(Base, BaseModel):
2004 2013 __tablename__ = 'pull_requests'
2005 2014 __table_args__ = (
2006 2015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2007 2016 'mysql_charset': 'utf8'},
2008 2017 )
2009 2018
2010 2019 STATUS_NEW = u'new'
2011 2020 STATUS_OPEN = u'open'
2012 2021 STATUS_CLOSED = u'closed'
2013 2022
2014 2023 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
2015 2024 title = Column('title', Unicode(256), nullable=True)
2016 2025 description = Column('description', UnicodeText(10240), nullable=True)
2017 2026 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
2018 2027 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2019 2028 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2020 2029 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2021 2030 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
2022 2031 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2023 2032 org_ref = Column('org_ref', Unicode(256), nullable=False)
2024 2033 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2025 2034 other_ref = Column('other_ref', Unicode(256), nullable=False)
2026 2035
2027 2036 @hybrid_property
2028 2037 def revisions(self):
2029 2038 return self._revisions.split(':')
2030 2039
2031 2040 @revisions.setter
2032 2041 def revisions(self, val):
2033 2042 self._revisions = ':'.join(val)
2034 2043
2035 2044 @property
2036 2045 def org_ref_parts(self):
2037 2046 return self.org_ref.split(':')
2038 2047
2039 2048 @property
2040 2049 def other_ref_parts(self):
2041 2050 return self.other_ref.split(':')
2042 2051
2043 2052 author = relationship('User', lazy='joined')
2044 2053 reviewers = relationship('PullRequestReviewers',
2045 2054 cascade="all, delete, delete-orphan")
2046 2055 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2047 2056 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2048 2057 statuses = relationship('ChangesetStatus')
2049 2058 comments = relationship('ChangesetComment',
2050 2059 cascade="all, delete, delete-orphan")
2051 2060
2052 2061 def is_closed(self):
2053 2062 return self.status == self.STATUS_CLOSED
2054 2063
2055 2064 @property
2056 2065 def last_review_status(self):
2057 2066 return self.statuses[-1].status if self.statuses else ''
2058 2067
2059 2068 def __json__(self):
2060 2069 return dict(
2061 2070 revisions=self.revisions
2062 2071 )
2063 2072
2064 2073
2065 2074 class PullRequestReviewers(Base, BaseModel):
2066 2075 __tablename__ = 'pull_request_reviewers'
2067 2076 __table_args__ = (
2068 2077 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2069 2078 'mysql_charset': 'utf8'},
2070 2079 )
2071 2080
2072 2081 def __init__(self, user=None, pull_request=None):
2073 2082 self.user = user
2074 2083 self.pull_request = pull_request
2075 2084
2076 2085 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2077 2086 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2078 2087 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2079 2088
2080 2089 user = relationship('User')
2081 2090 pull_request = relationship('PullRequest')
2082 2091
2083 2092
2084 2093 class Notification(Base, BaseModel):
2085 2094 __tablename__ = 'notifications'
2086 2095 __table_args__ = (
2087 2096 Index('notification_type_idx', 'type'),
2088 2097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2089 2098 'mysql_charset': 'utf8'},
2090 2099 )
2091 2100
2092 2101 TYPE_CHANGESET_COMMENT = u'cs_comment'
2093 2102 TYPE_MESSAGE = u'message'
2094 2103 TYPE_MENTION = u'mention'
2095 2104 TYPE_REGISTRATION = u'registration'
2096 2105 TYPE_PULL_REQUEST = u'pull_request'
2097 2106 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2098 2107
2099 2108 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2100 2109 subject = Column('subject', Unicode(512), nullable=True)
2101 2110 body = Column('body', UnicodeText(50000), nullable=True)
2102 2111 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2103 2112 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2104 2113 type_ = Column('type', Unicode(256))
2105 2114
2106 2115 created_by_user = relationship('User')
2107 2116 notifications_to_users = relationship('UserNotification', lazy='joined',
2108 2117 cascade="all, delete, delete-orphan")
2109 2118
2110 2119 @property
2111 2120 def recipients(self):
2112 2121 return [x.user for x in UserNotification.query()\
2113 2122 .filter(UserNotification.notification == self)\
2114 2123 .order_by(UserNotification.user_id.asc()).all()]
2115 2124
2116 2125 @classmethod
2117 2126 def create(cls, created_by, subject, body, recipients, type_=None):
2118 2127 if type_ is None:
2119 2128 type_ = Notification.TYPE_MESSAGE
2120 2129
2121 2130 notification = cls()
2122 2131 notification.created_by_user = created_by
2123 2132 notification.subject = subject
2124 2133 notification.body = body
2125 2134 notification.type_ = type_
2126 2135 notification.created_on = datetime.datetime.now()
2127 2136
2128 2137 for u in recipients:
2129 2138 assoc = UserNotification()
2130 2139 assoc.notification = notification
2131 2140 u.notifications.append(assoc)
2132 2141 Session().add(notification)
2133 2142 return notification
2134 2143
2135 2144 @property
2136 2145 def description(self):
2137 2146 from rhodecode.model.notification import NotificationModel
2138 2147 return NotificationModel().make_description(self)
2139 2148
2140 2149
2141 2150 class UserNotification(Base, BaseModel):
2142 2151 __tablename__ = 'user_to_notification'
2143 2152 __table_args__ = (
2144 2153 UniqueConstraint('user_id', 'notification_id'),
2145 2154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2146 2155 'mysql_charset': 'utf8'}
2147 2156 )
2148 2157 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2149 2158 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2150 2159 read = Column('read', Boolean, default=False)
2151 2160 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2152 2161
2153 2162 user = relationship('User', lazy="joined")
2154 2163 notification = relationship('Notification', lazy="joined",
2155 2164 order_by=lambda: Notification.created_on.desc(),)
2156 2165
2157 2166 def mark_as_read(self):
2158 2167 self.read = True
2159 2168 Session().add(self)
2160 2169
2161 2170
2162 2171 class DbMigrateVersion(Base, BaseModel):
2163 2172 __tablename__ = 'db_migrate_version'
2164 2173 __table_args__ = (
2165 2174 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2166 2175 'mysql_charset': 'utf8'},
2167 2176 )
2168 2177 repository_id = Column('repository_id', String(250), primary_key=True)
2169 2178 repository_path = Column('repository_path', Text)
2170 2179 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now