##// END OF EJS Templates
Remove null from revision column as for comments inside pull requests to work properly...
marcink -
r2797:c9baaacb beta
parent child Browse files
Show More
@@ -0,0 +1,51 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
9
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12
13 from rhodecode.model.meta import Base
14 from rhodecode.model import meta
15
16 log = logging.getLogger(__name__)
17
18
19 def upgrade(migrate_engine):
20 """
21 Upgrade operations go here.
22 Don't create your own engine; bind migrate_engine to your metadata
23 """
24
25 #==========================================================================
26 # CHANGESET_COMMENTS
27 #==========================================================================
28 from rhodecode.lib.dbmigrate.schema.db_1_4_0 import ChangesetComment
29 tbl_name = ChangesetComment.__tablename__
30 tbl = Table(tbl_name,
31 MetaData(bind=migrate_engine), autoload=True,
32 autoload_with=migrate_engine)
33 col = tbl.columns.revision
34
35 # remove nullability from revision field
36 col.alter(nullable=True)
37
38 #==========================================================================
39 # REPOSITORY
40 #==========================================================================
41 from rhodecode.lib.dbmigrate.schema.db_1_4_0 import Repository
42 tbl = Repository.__table__
43 updated_on = Column('updated_on', DateTime(timezone=False),
44 nullable=True, unique=None)
45 # create created on column for future lightweight main page
46 updated_on.create(table=tbl)
47
48
49 def downgrade(migrate_engine):
50 meta = MetaData()
51 meta.bind = migrate_engine
@@ -1,67 +1,67 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.__init__
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode, a web based repository management based on pylons
7 7 versioning implementation: http://www.python.org/dev/peps/pep-0386/
8 8
9 9 :created_on: Apr 9, 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 import sys
27 27 import platform
28 28
29 29 VERSION = (1, 4, 1, 'b')
30 30
31 31 try:
32 32 from rhodecode.lib import get_current_revision
33 33 _rev = get_current_revision()
34 34 if _rev and len(VERSION) > 3:
35 35 VERSION += ('dev%s' % _rev[0],)
36 36 except ImportError:
37 37 pass
38 38
39 39 __version__ = ('.'.join((str(each) for each in VERSION[:3])) +
40 40 '.'.join(VERSION[3:]))
41 __dbversion__ = 6 # defines current db version for migrations
41 __dbversion__ = 7 # defines current db version for migrations
42 42 __platform__ = platform.system()
43 43 __license__ = 'GPLv3'
44 44 __py_version__ = sys.version_info
45 45 __author__ = 'Marcin Kuzminski'
46 46 __url__ = 'http://rhodecode.org'
47 47
48 48 PLATFORM_WIN = ('Windows')
49 49 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS') #depracated
50 50
51 51 is_windows = __platform__ in PLATFORM_WIN
52 52 is_unix = not is_windows
53 53
54 54
55 55 BACKENDS = {
56 56 'hg': 'Mercurial repository',
57 57 'git': 'Git repository',
58 58 }
59 59
60 60 CELERY_ON = False
61 61 CELERY_EAGER = False
62 62
63 63 # link to config for pylons
64 64 CONFIG = {}
65 65
66 66 # Linked module for extensions
67 67 EXTENSIONS = {}
@@ -1,622 +1,625 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__
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 40 UserRepoGroupToPerm
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
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 def notify(msg):
52 52 """
53 53 Notification for migrations messages
54 54 """
55 55 ml = len(msg) + (4 * 2)
56 56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
57 57
58 58
59 59 class DbManage(object):
60 60 def __init__(self, log_sql, dbconf, root, tests=False):
61 61 self.dbname = dbconf.split('/')[-1]
62 62 self.tests = tests
63 63 self.root = root
64 64 self.dburi = dbconf
65 65 self.log_sql = log_sql
66 66 self.db_exists = False
67 67 self.init_db()
68 68
69 69 def init_db(self):
70 70 engine = create_engine(self.dburi, echo=self.log_sql)
71 71 init_model(engine)
72 72 self.sa = Session()
73 73
74 74 def create_tables(self, override=False, defaults={}):
75 75 """
76 76 Create a auth database
77 77 """
78 78 quiet = defaults.get('quiet')
79 79 log.info("Any existing database is going to be destroyed")
80 80 if self.tests or quiet:
81 81 destroy = True
82 82 else:
83 83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
84 84 if not destroy:
85 85 sys.exit()
86 86 if destroy:
87 87 Base.metadata.drop_all()
88 88
89 89 checkfirst = not override
90 90 Base.metadata.create_all(checkfirst=checkfirst)
91 91 log.info('Created tables for %s' % self.dbname)
92 92
93 93 def set_db_version(self):
94 94 ver = DbMigrateVersion()
95 95 ver.version = __dbversion__
96 96 ver.repository_id = 'rhodecode_db_migrations'
97 97 ver.repository_path = 'versions'
98 98 self.sa.add(ver)
99 99 log.info('db version set to: %s' % __dbversion__)
100 100
101 101 def upgrade(self):
102 102 """
103 103 Upgrades given database schema to given revision following
104 104 all needed steps, to perform the upgrade
105 105
106 106 """
107 107
108 108 from rhodecode.lib.dbmigrate.migrate.versioning import api
109 109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
110 110 DatabaseNotControlledError
111 111
112 112 if 'sqlite' in self.dburi:
113 113 print (
114 114 '********************** WARNING **********************\n'
115 115 'Make sure your version of sqlite is at least 3.7.X. \n'
116 116 'Earlier versions are known to fail on some migrations\n'
117 117 '*****************************************************\n'
118 118 )
119 119 upgrade = ask_ok('You are about to perform database upgrade, make '
120 120 'sure You backed up your database before. '
121 121 'Continue ? [y/n]')
122 122 if not upgrade:
123 123 sys.exit('Nothing done')
124 124
125 125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
126 126 'rhodecode/lib/dbmigrate')
127 127 db_uri = self.dburi
128 128
129 129 try:
130 130 curr_version = api.db_version(db_uri, repository_path)
131 131 msg = ('Found current database under version'
132 132 ' control with version %s' % curr_version)
133 133
134 134 except (RuntimeError, DatabaseNotControlledError):
135 135 curr_version = 1
136 136 msg = ('Current database is not under version control. Setting'
137 137 ' as version %s' % curr_version)
138 138 api.version_control(db_uri, repository_path, curr_version)
139 139
140 140 notify(msg)
141 141
142 142 if curr_version == __dbversion__:
143 143 sys.exit('This database is already at the newest version')
144 144
145 145 #======================================================================
146 146 # UPGRADE STEPS
147 147 #======================================================================
148 148
149 149 class UpgradeSteps(object):
150 150 """
151 151 Those steps follow schema versions so for example schema
152 152 for example schema with seq 002 == step_2 and so on.
153 153 """
154 154
155 155 def __init__(self, klass):
156 156 self.klass = klass
157 157
158 158 def step_0(self):
159 159 # step 0 is the schema upgrade, and than follow proper upgrades
160 160 notify('attempting to do database upgrade to version %s' \
161 161 % __dbversion__)
162 162 api.upgrade(db_uri, repository_path, __dbversion__)
163 163 notify('Schema upgrade completed')
164 164
165 165 def step_1(self):
166 166 pass
167 167
168 168 def step_2(self):
169 169 notify('Patching repo paths for newer version of RhodeCode')
170 170 self.klass.fix_repo_paths()
171 171
172 172 notify('Patching default user of RhodeCode')
173 173 self.klass.fix_default_user()
174 174
175 175 log.info('Changing ui settings')
176 176 self.klass.create_ui_settings()
177 177
178 178 def step_3(self):
179 179 notify('Adding additional settings into RhodeCode db')
180 180 self.klass.fix_settings()
181 181 notify('Adding ldap defaults')
182 182 self.klass.create_ldap_options(skip_existing=True)
183 183
184 184 def step_4(self):
185 185 notify('create permissions and fix groups')
186 186 self.klass.create_permissions()
187 187 self.klass.fixup_groups()
188 188
189 189 def step_5(self):
190 190 pass
191 191
192 192 def step_6(self):
193 193
194 194 notify('re-checking permissions')
195 195 self.klass.create_permissions()
196 196
197 197 notify('installing new UI options')
198 198 sett4 = RhodeCodeSetting('show_public_icon', True)
199 199 Session().add(sett4)
200 200 sett5 = RhodeCodeSetting('show_private_icon', True)
201 201 Session().add(sett5)
202 202 sett6 = RhodeCodeSetting('stylify_metatags', False)
203 203 Session().add(sett6)
204 204
205 205 notify('fixing old PULL hook')
206 206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
207 207 if _pull:
208 208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
209 209 Session().add(_pull)
210 210
211 211 notify('fixing old PUSH hook')
212 212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
213 213 if _push:
214 214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
215 215 Session().add(_push)
216 216
217 217 notify('installing new pre-push hook')
218 218 hooks4 = RhodeCodeUi()
219 219 hooks4.ui_section = 'hooks'
220 220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
221 221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
222 222 Session().add(hooks4)
223 223
224 224 notify('installing new pre-pull hook')
225 225 hooks6 = RhodeCodeUi()
226 226 hooks6.ui_section = 'hooks'
227 227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
228 228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
229 229 Session().add(hooks6)
230 230
231 231 notify('installing hgsubversion option')
232 232 # enable hgsubversion disabled by default
233 233 hgsubversion = RhodeCodeUi()
234 234 hgsubversion.ui_section = 'extensions'
235 235 hgsubversion.ui_key = 'hgsubversion'
236 236 hgsubversion.ui_value = ''
237 237 hgsubversion.ui_active = False
238 238 Session().add(hgsubversion)
239 239
240 240 notify('installing hg git option')
241 241 # enable hggit disabled by default
242 242 hggit = RhodeCodeUi()
243 243 hggit.ui_section = 'extensions'
244 244 hggit.ui_key = 'hggit'
245 245 hggit.ui_value = ''
246 246 hggit.ui_active = False
247 247 Session().add(hggit)
248 248
249 249 notify('re-check default permissions')
250 250 default_user = User.get_by_username(User.DEFAULT_USER)
251 251 perm = Permission.get_by_key('hg.fork.repository')
252 252 reg_perm = UserToPerm()
253 253 reg_perm.user = default_user
254 254 reg_perm.permission = perm
255 255 Session().add(reg_perm)
256 256
257 def step_7(self):
258 pass
259
257 260 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
258 261
259 262 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
260 263 _step = None
261 264 for step in upgrade_steps:
262 265 notify('performing upgrade step %s' % step)
263 266 getattr(UpgradeSteps(self), 'step_%s' % step)()
264 267 self.sa.commit()
265 268 _step = step
266 269
267 270 notify('upgrade to version %s successful' % _step)
268 271
269 272 def fix_repo_paths(self):
270 273 """
271 274 Fixes a old rhodecode version path into new one without a '*'
272 275 """
273 276
274 277 paths = self.sa.query(RhodeCodeUi)\
275 278 .filter(RhodeCodeUi.ui_key == '/')\
276 279 .scalar()
277 280
278 281 paths.ui_value = paths.ui_value.replace('*', '')
279 282
280 283 try:
281 284 self.sa.add(paths)
282 285 self.sa.commit()
283 286 except:
284 287 self.sa.rollback()
285 288 raise
286 289
287 290 def fix_default_user(self):
288 291 """
289 292 Fixes a old default user with some 'nicer' default values,
290 293 used mostly for anonymous access
291 294 """
292 295 def_user = self.sa.query(User)\
293 296 .filter(User.username == 'default')\
294 297 .one()
295 298
296 299 def_user.name = 'Anonymous'
297 300 def_user.lastname = 'User'
298 301 def_user.email = 'anonymous@rhodecode.org'
299 302
300 303 try:
301 304 self.sa.add(def_user)
302 305 self.sa.commit()
303 306 except:
304 307 self.sa.rollback()
305 308 raise
306 309
307 310 def fix_settings(self):
308 311 """
309 312 Fixes rhodecode settings adds ga_code key for google analytics
310 313 """
311 314
312 315 hgsettings3 = RhodeCodeSetting('ga_code', '')
313 316
314 317 try:
315 318 self.sa.add(hgsettings3)
316 319 self.sa.commit()
317 320 except:
318 321 self.sa.rollback()
319 322 raise
320 323
321 324 def admin_prompt(self, second=False, defaults={}):
322 325 if not self.tests:
323 326 import getpass
324 327
325 328 # defaults
326 329 username = defaults.get('username')
327 330 password = defaults.get('password')
328 331 email = defaults.get('email')
329 332
330 333 def get_password():
331 334 password = getpass.getpass('Specify admin password '
332 335 '(min 6 chars):')
333 336 confirm = getpass.getpass('Confirm password:')
334 337
335 338 if password != confirm:
336 339 log.error('passwords mismatch')
337 340 return False
338 341 if len(password) < 6:
339 342 log.error('password is to short use at least 6 characters')
340 343 return False
341 344
342 345 return password
343 346 if username is None:
344 347 username = raw_input('Specify admin username:')
345 348 if password is None:
346 349 password = get_password()
347 350 if not password:
348 351 #second try
349 352 password = get_password()
350 353 if not password:
351 354 sys.exit()
352 355 if email is None:
353 356 email = raw_input('Specify admin email:')
354 357 self.create_user(username, password, email, True)
355 358 else:
356 359 log.info('creating admin and regular test users')
357 360 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
358 361 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
359 362 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
360 363 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
361 364 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
362 365
363 366 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
364 367 TEST_USER_ADMIN_EMAIL, True)
365 368
366 369 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
367 370 TEST_USER_REGULAR_EMAIL, False)
368 371
369 372 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
370 373 TEST_USER_REGULAR2_EMAIL, False)
371 374
372 375 def create_ui_settings(self):
373 376 """
374 377 Creates ui settings, fills out hooks
375 378 and disables dotencode
376 379 """
377 380
378 381 #HOOKS
379 382 hooks1_key = RhodeCodeUi.HOOK_UPDATE
380 383 hooks1_ = self.sa.query(RhodeCodeUi)\
381 384 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
382 385
383 386 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
384 387 hooks1.ui_section = 'hooks'
385 388 hooks1.ui_key = hooks1_key
386 389 hooks1.ui_value = 'hg update >&2'
387 390 hooks1.ui_active = False
388 391 self.sa.add(hooks1)
389 392
390 393 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
391 394 hooks2_ = self.sa.query(RhodeCodeUi)\
392 395 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
393 396 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
394 397 hooks2.ui_section = 'hooks'
395 398 hooks2.ui_key = hooks2_key
396 399 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
397 400 self.sa.add(hooks2)
398 401
399 402 hooks3 = RhodeCodeUi()
400 403 hooks3.ui_section = 'hooks'
401 404 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
402 405 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
403 406 self.sa.add(hooks3)
404 407
405 408 hooks4 = RhodeCodeUi()
406 409 hooks4.ui_section = 'hooks'
407 410 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
408 411 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
409 412 self.sa.add(hooks4)
410 413
411 414 hooks5 = RhodeCodeUi()
412 415 hooks5.ui_section = 'hooks'
413 416 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
414 417 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
415 418 self.sa.add(hooks5)
416 419
417 420 hooks6 = RhodeCodeUi()
418 421 hooks6.ui_section = 'hooks'
419 422 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
420 423 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
421 424 self.sa.add(hooks6)
422 425
423 426 # enable largefiles
424 427 largefiles = RhodeCodeUi()
425 428 largefiles.ui_section = 'extensions'
426 429 largefiles.ui_key = 'largefiles'
427 430 largefiles.ui_value = ''
428 431 self.sa.add(largefiles)
429 432
430 433 # enable hgsubversion disabled by default
431 434 hgsubversion = RhodeCodeUi()
432 435 hgsubversion.ui_section = 'extensions'
433 436 hgsubversion.ui_key = 'hgsubversion'
434 437 hgsubversion.ui_value = ''
435 438 hgsubversion.ui_active = False
436 439 self.sa.add(hgsubversion)
437 440
438 441 # enable hggit disabled by default
439 442 hggit = RhodeCodeUi()
440 443 hggit.ui_section = 'extensions'
441 444 hggit.ui_key = 'hggit'
442 445 hggit.ui_value = ''
443 446 hggit.ui_active = False
444 447 self.sa.add(hggit)
445 448
446 449 def create_ldap_options(self, skip_existing=False):
447 450 """Creates ldap settings"""
448 451
449 452 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
450 453 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
451 454 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
452 455 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
453 456 ('ldap_filter', ''), ('ldap_search_scope', ''),
454 457 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
455 458 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
456 459
457 460 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
458 461 log.debug('Skipping option %s' % k)
459 462 continue
460 463 setting = RhodeCodeSetting(k, v)
461 464 self.sa.add(setting)
462 465
463 466 def fixup_groups(self):
464 467 def_usr = User.get_by_username('default')
465 468 for g in RepoGroup.query().all():
466 469 g.group_name = g.get_new_name(g.name)
467 470 self.sa.add(g)
468 471 # get default perm
469 472 default = UserRepoGroupToPerm.query()\
470 473 .filter(UserRepoGroupToPerm.group == g)\
471 474 .filter(UserRepoGroupToPerm.user == def_usr)\
472 475 .scalar()
473 476
474 477 if default is None:
475 478 log.debug('missing default permission for group %s adding' % g)
476 479 ReposGroupModel()._create_default_perms(g)
477 480
478 481 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
479 482 _path = defaults.get('repos_location')
480 483 if retries == 3:
481 484 log.info('Setting up repositories config')
482 485
483 486 if _path is not None:
484 487 path = _path
485 488 elif not self.tests and not test_repo_path:
486 489 path = raw_input(
487 490 'Enter a valid absolute path to store repositories. '
488 491 'All repositories in that path will be added automatically:'
489 492 )
490 493 else:
491 494 path = test_repo_path
492 495 path_ok = True
493 496
494 497 # check proper dir
495 498 if not os.path.isdir(path):
496 499 path_ok = False
497 500 log.error('Given path %s is not a valid directory' % path)
498 501
499 502 elif not os.path.isabs(path):
500 503 path_ok = False
501 504 log.error('Given path %s is not an absolute path' % path)
502 505
503 506 # check write access
504 507 elif not os.access(path, os.W_OK) and path_ok:
505 508 path_ok = False
506 509 log.error('No write permission to given path %s' % path)
507 510
508 511 if retries == 0:
509 512 sys.exit('max retries reached')
510 513 if path_ok is False:
511 514 retries -= 1
512 515 return self.config_prompt(test_repo_path, retries)
513 516
514 517 return path
515 518
516 519 def create_settings(self, path):
517 520
518 521 self.create_ui_settings()
519 522
520 523 #HG UI OPTIONS
521 524 web1 = RhodeCodeUi()
522 525 web1.ui_section = 'web'
523 526 web1.ui_key = 'push_ssl'
524 527 web1.ui_value = 'false'
525 528
526 529 web2 = RhodeCodeUi()
527 530 web2.ui_section = 'web'
528 531 web2.ui_key = 'allow_archive'
529 532 web2.ui_value = 'gz zip bz2'
530 533
531 534 web3 = RhodeCodeUi()
532 535 web3.ui_section = 'web'
533 536 web3.ui_key = 'allow_push'
534 537 web3.ui_value = '*'
535 538
536 539 web4 = RhodeCodeUi()
537 540 web4.ui_section = 'web'
538 541 web4.ui_key = 'baseurl'
539 542 web4.ui_value = '/'
540 543
541 544 paths = RhodeCodeUi()
542 545 paths.ui_section = 'paths'
543 546 paths.ui_key = '/'
544 547 paths.ui_value = path
545 548
546 549 phases = RhodeCodeUi()
547 550 phases.ui_section = 'phases'
548 551 phases.ui_key = 'publish'
549 552 phases.ui_value = False
550 553
551 554 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
552 555 sett2 = RhodeCodeSetting('title', 'RhodeCode')
553 556 sett3 = RhodeCodeSetting('ga_code', '')
554 557
555 558 sett4 = RhodeCodeSetting('show_public_icon', True)
556 559 sett5 = RhodeCodeSetting('show_private_icon', True)
557 560 sett6 = RhodeCodeSetting('stylify_metatags', False)
558 561
559 562 self.sa.add(web1)
560 563 self.sa.add(web2)
561 564 self.sa.add(web3)
562 565 self.sa.add(web4)
563 566 self.sa.add(paths)
564 567 self.sa.add(sett1)
565 568 self.sa.add(sett2)
566 569 self.sa.add(sett3)
567 570 self.sa.add(sett4)
568 571 self.sa.add(sett5)
569 572 self.sa.add(sett6)
570 573
571 574 self.create_ldap_options()
572 575
573 576 log.info('created ui config')
574 577
575 578 def create_user(self, username, password, email='', admin=False):
576 579 log.info('creating user %s' % username)
577 580 UserModel().create_or_update(username, password, email,
578 581 firstname='RhodeCode', lastname='Admin',
579 582 active=True, admin=admin)
580 583
581 584 def create_default_user(self):
582 585 log.info('creating default user')
583 586 # create default user for handling default permissions.
584 587 UserModel().create_or_update(username='default',
585 588 password=str(uuid.uuid1())[:8],
586 589 email='anonymous@rhodecode.org',
587 590 firstname='Anonymous', lastname='User')
588 591
589 592 def create_permissions(self):
590 593 # module.(access|create|change|delete)_[name]
591 594 # module.(none|read|write|admin)
592 595
593 596 for p in Permission.PERMS:
594 597 if not Permission.get_by_key(p[0]):
595 598 new_perm = Permission()
596 599 new_perm.permission_name = p[0]
597 600 new_perm.permission_longname = p[0]
598 601 self.sa.add(new_perm)
599 602
600 603 def populate_default_permissions(self):
601 604 log.info('creating default user permissions')
602 605
603 606 default_user = User.get_by_username('default')
604 607
605 608 for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
606 609 'hg.fork.repository', 'repository.read']:
607 610
608 611 perm = self.sa.query(Permission)\
609 612 .filter(Permission.permission_name == def_perm)\
610 613 .scalar()
611 614 if not perm:
612 615 raise Exception(
613 616 'CRITICAL: permission %s not found inside database !!'
614 617 % def_perm
615 618 )
616 619 if not UserToPerm.query()\
617 620 .filter(UserToPerm.permission == perm)\
618 621 .filter(UserToPerm.user == default_user).scalar():
619 622 reg_perm = UserToPerm()
620 623 reg_perm.user = default_user
621 624 reg_perm.permission = perm
622 625 self.sa.add(reg_perm)
@@ -1,1770 +1,1771 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 if id_:
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 return cls.query().all()
130 130
131 131 @classmethod
132 132 def delete(cls, id_):
133 133 obj = cls.query().get(id_)
134 134 Session().delete(obj)
135 135
136 136 def __repr__(self):
137 137 if hasattr(self, '__unicode__'):
138 138 # python repr needs to return str
139 139 return safe_str(self.__unicode__())
140 140 return '<DB:%s>' % (self.__class__.__name__)
141 141
142 142
143 143 class RhodeCodeSetting(Base, BaseModel):
144 144 __tablename__ = 'rhodecode_settings'
145 145 __table_args__ = (
146 146 UniqueConstraint('app_settings_name'),
147 147 {'extend_existing': True, 'mysql_engine': 'InnoDB',
148 148 'mysql_charset': 'utf8'}
149 149 )
150 150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
151 151 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153 153
154 154 def __init__(self, k='', v=''):
155 155 self.app_settings_name = k
156 156 self.app_settings_value = v
157 157
158 158 @validates('_app_settings_value')
159 159 def validate_settings_value(self, key, val):
160 160 assert type(val) == unicode
161 161 return val
162 162
163 163 @hybrid_property
164 164 def app_settings_value(self):
165 165 v = self._app_settings_value
166 166 if self.app_settings_name == 'ldap_active':
167 167 v = str2bool(v)
168 168 return v
169 169
170 170 @app_settings_value.setter
171 171 def app_settings_value(self, val):
172 172 """
173 173 Setter that will always make sure we use unicode in app_settings_value
174 174
175 175 :param val:
176 176 """
177 177 self._app_settings_value = safe_unicode(val)
178 178
179 179 def __unicode__(self):
180 180 return u"<%s('%s:%s')>" % (
181 181 self.__class__.__name__,
182 182 self.app_settings_name, self.app_settings_value
183 183 )
184 184
185 185 @classmethod
186 186 def get_by_name(cls, key):
187 187 return cls.query()\
188 188 .filter(cls.app_settings_name == key).scalar()
189 189
190 190 @classmethod
191 191 def get_by_name_or_create(cls, key):
192 192 res = cls.get_by_name(key)
193 193 if not res:
194 194 res = cls(key)
195 195 return res
196 196
197 197 @classmethod
198 198 def get_app_settings(cls, cache=False):
199 199
200 200 ret = cls.query()
201 201
202 202 if cache:
203 203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
204 204
205 205 if not ret:
206 206 raise Exception('Could not get application settings !')
207 207 settings = {}
208 208 for each in ret:
209 209 settings['rhodecode_' + each.app_settings_name] = \
210 210 each.app_settings_value
211 211
212 212 return settings
213 213
214 214 @classmethod
215 215 def get_ldap_settings(cls, cache=False):
216 216 ret = cls.query()\
217 217 .filter(cls.app_settings_name.startswith('ldap_')).all()
218 218 fd = {}
219 219 for row in ret:
220 220 fd.update({row.app_settings_name: row.app_settings_value})
221 221
222 222 return fd
223 223
224 224
225 225 class RhodeCodeUi(Base, BaseModel):
226 226 __tablename__ = 'rhodecode_ui'
227 227 __table_args__ = (
228 228 UniqueConstraint('ui_key'),
229 229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
230 230 'mysql_charset': 'utf8'}
231 231 )
232 232
233 233 HOOK_UPDATE = 'changegroup.update'
234 234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 235 HOOK_PUSH = 'changegroup.push_logger'
236 236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
237 237 HOOK_PULL = 'outgoing.pull_logger'
238 238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
239 239
240 240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
241 241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 242 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 243 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 244 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
245 245
246 246 @classmethod
247 247 def get_by_key(cls, key):
248 248 return cls.query().filter(cls.ui_key == key).scalar()
249 249
250 250 @classmethod
251 251 def get_builtin_hooks(cls):
252 252 q = cls.query()
253 253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
254 254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
255 255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
256 256 return q.all()
257 257
258 258 @classmethod
259 259 def get_custom_hooks(cls):
260 260 q = cls.query()
261 261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
262 262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
263 263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
264 264 q = q.filter(cls.ui_section == 'hooks')
265 265 return q.all()
266 266
267 267 @classmethod
268 268 def get_repos_location(cls):
269 269 return cls.get_by_key('/').ui_value
270 270
271 271 @classmethod
272 272 def create_or_update_hook(cls, key, val):
273 273 new_ui = cls.get_by_key(key) or cls()
274 274 new_ui.ui_section = 'hooks'
275 275 new_ui.ui_active = True
276 276 new_ui.ui_key = key
277 277 new_ui.ui_value = val
278 278
279 279 Session().add(new_ui)
280 280
281 281
282 282 class User(Base, BaseModel):
283 283 __tablename__ = 'users'
284 284 __table_args__ = (
285 285 UniqueConstraint('username'), UniqueConstraint('email'),
286 286 Index('u_username_idx', 'username'),
287 287 Index('u_email_idx', 'email'),
288 288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
289 289 'mysql_charset': 'utf8'}
290 290 )
291 291 DEFAULT_USER = 'default'
292 292
293 293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 294 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 295 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 296 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
297 297 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
298 298 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 299 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 300 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 301 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
302 302 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 303 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 304 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
305 305
306 306 user_log = relationship('UserLog', cascade='all')
307 307 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
308 308
309 309 repositories = relationship('Repository')
310 310 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
311 311 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
312 312 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
313 313
314 314 group_member = relationship('UsersGroupMember', cascade='all')
315 315
316 316 notifications = relationship('UserNotification', cascade='all')
317 317 # notifications assigned to this user
318 318 user_created_notifications = relationship('Notification', cascade='all')
319 319 # comments created by this user
320 320 user_comments = relationship('ChangesetComment', cascade='all')
321 321 #extra emails for this user
322 322 user_emails = relationship('UserEmailMap', cascade='all')
323 323
324 324 @hybrid_property
325 325 def email(self):
326 326 return self._email
327 327
328 328 @email.setter
329 329 def email(self, val):
330 330 self._email = val.lower() if val else None
331 331
332 332 @property
333 333 def firstname(self):
334 334 # alias for future
335 335 return self.name
336 336
337 337 @property
338 338 def emails(self):
339 339 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
340 340 return [self.email] + [x.email for x in other]
341 341
342 342 @property
343 343 def username_and_name(self):
344 344 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
345 345
346 346 @property
347 347 def full_name(self):
348 348 return '%s %s' % (self.firstname, self.lastname)
349 349
350 350 @property
351 351 def full_name_or_username(self):
352 352 return ('%s %s' % (self.firstname, self.lastname)
353 353 if (self.firstname and self.lastname) else self.username)
354 354
355 355 @property
356 356 def full_contact(self):
357 357 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
358 358
359 359 @property
360 360 def short_contact(self):
361 361 return '%s %s' % (self.firstname, self.lastname)
362 362
363 363 @property
364 364 def is_admin(self):
365 365 return self.admin
366 366
367 367 def __unicode__(self):
368 368 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
369 369 self.user_id, self.username)
370 370
371 371 @classmethod
372 372 def get_by_username(cls, username, case_insensitive=False, cache=False):
373 373 if case_insensitive:
374 374 q = cls.query().filter(cls.username.ilike(username))
375 375 else:
376 376 q = cls.query().filter(cls.username == username)
377 377
378 378 if cache:
379 379 q = q.options(FromCache(
380 380 "sql_cache_short",
381 381 "get_user_%s" % _hash_key(username)
382 382 )
383 383 )
384 384 return q.scalar()
385 385
386 386 @classmethod
387 387 def get_by_api_key(cls, api_key, cache=False):
388 388 q = cls.query().filter(cls.api_key == api_key)
389 389
390 390 if cache:
391 391 q = q.options(FromCache("sql_cache_short",
392 392 "get_api_key_%s" % api_key))
393 393 return q.scalar()
394 394
395 395 @classmethod
396 396 def get_by_email(cls, email, case_insensitive=False, cache=False):
397 397 if case_insensitive:
398 398 q = cls.query().filter(cls.email.ilike(email))
399 399 else:
400 400 q = cls.query().filter(cls.email == email)
401 401
402 402 if cache:
403 403 q = q.options(FromCache("sql_cache_short",
404 404 "get_email_key_%s" % email))
405 405
406 406 ret = q.scalar()
407 407 if ret is None:
408 408 q = UserEmailMap.query()
409 409 # try fetching in alternate email map
410 410 if case_insensitive:
411 411 q = q.filter(UserEmailMap.email.ilike(email))
412 412 else:
413 413 q = q.filter(UserEmailMap.email == email)
414 414 q = q.options(joinedload(UserEmailMap.user))
415 415 if cache:
416 416 q = q.options(FromCache("sql_cache_short",
417 417 "get_email_map_key_%s" % email))
418 418 ret = getattr(q.scalar(), 'user', None)
419 419
420 420 return ret
421 421
422 422 def update_lastlogin(self):
423 423 """Update user lastlogin"""
424 424 self.last_login = datetime.datetime.now()
425 425 Session().add(self)
426 426 log.debug('updated user %s lastlogin' % self.username)
427 427
428 428 def get_api_data(self):
429 429 """
430 430 Common function for generating user related data for API
431 431 """
432 432 user = self
433 433 data = dict(
434 434 user_id=user.user_id,
435 435 username=user.username,
436 436 firstname=user.name,
437 437 lastname=user.lastname,
438 438 email=user.email,
439 439 emails=user.emails,
440 440 api_key=user.api_key,
441 441 active=user.active,
442 442 admin=user.admin,
443 443 ldap_dn=user.ldap_dn,
444 444 last_login=user.last_login,
445 445 )
446 446 return data
447 447
448 448 def __json__(self):
449 449 data = dict(
450 450 full_name=self.full_name,
451 451 full_name_or_username=self.full_name_or_username,
452 452 short_contact=self.short_contact,
453 453 full_contact=self.full_contact
454 454 )
455 455 data.update(self.get_api_data())
456 456 return data
457 457
458 458
459 459 class UserEmailMap(Base, BaseModel):
460 460 __tablename__ = 'user_email_map'
461 461 __table_args__ = (
462 462 Index('uem_email_idx', 'email'),
463 463 UniqueConstraint('email'),
464 464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 465 'mysql_charset': 'utf8'}
466 466 )
467 467 __mapper_args__ = {}
468 468
469 469 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
470 470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
471 471 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
472 472 user = relationship('User', lazy='joined')
473 473
474 474 @validates('_email')
475 475 def validate_email(self, key, email):
476 476 # check if this email is not main one
477 477 main_email = Session().query(User).filter(User.email == email).scalar()
478 478 if main_email is not None:
479 479 raise AttributeError('email %s is present is user table' % email)
480 480 return email
481 481
482 482 @hybrid_property
483 483 def email(self):
484 484 return self._email
485 485
486 486 @email.setter
487 487 def email(self, val):
488 488 self._email = val.lower() if val else None
489 489
490 490
491 491 class UserLog(Base, BaseModel):
492 492 __tablename__ = 'user_logs'
493 493 __table_args__ = (
494 494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 495 'mysql_charset': 'utf8'},
496 496 )
497 497 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 498 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
499 499 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
500 500 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
501 501 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
502 502 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
503 503 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
504 504
505 505 @property
506 506 def action_as_day(self):
507 507 return datetime.date(*self.action_date.timetuple()[:3])
508 508
509 509 user = relationship('User')
510 510 repository = relationship('Repository', cascade='')
511 511
512 512
513 513 class UsersGroup(Base, BaseModel):
514 514 __tablename__ = 'users_groups'
515 515 __table_args__ = (
516 516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 517 'mysql_charset': 'utf8'},
518 518 )
519 519
520 520 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
521 521 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
522 522 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
523 523 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 524
525 525 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
526 526 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
527 527 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
528 528
529 529 def __unicode__(self):
530 530 return u'<userGroup(%s)>' % (self.users_group_name)
531 531
532 532 @classmethod
533 533 def get_by_group_name(cls, group_name, cache=False,
534 534 case_insensitive=False):
535 535 if case_insensitive:
536 536 q = cls.query().filter(cls.users_group_name.ilike(group_name))
537 537 else:
538 538 q = cls.query().filter(cls.users_group_name == group_name)
539 539 if cache:
540 540 q = q.options(FromCache(
541 541 "sql_cache_short",
542 542 "get_user_%s" % _hash_key(group_name)
543 543 )
544 544 )
545 545 return q.scalar()
546 546
547 547 @classmethod
548 548 def get(cls, users_group_id, cache=False):
549 549 users_group = cls.query()
550 550 if cache:
551 551 users_group = users_group.options(FromCache("sql_cache_short",
552 552 "get_users_group_%s" % users_group_id))
553 553 return users_group.get(users_group_id)
554 554
555 555 def get_api_data(self):
556 556 users_group = self
557 557
558 558 data = dict(
559 559 users_group_id=users_group.users_group_id,
560 560 group_name=users_group.users_group_name,
561 561 active=users_group.users_group_active,
562 562 )
563 563
564 564 return data
565 565
566 566
567 567 class UsersGroupMember(Base, BaseModel):
568 568 __tablename__ = 'users_groups_members'
569 569 __table_args__ = (
570 570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
571 571 'mysql_charset': 'utf8'},
572 572 )
573 573
574 574 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
575 575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
576 576 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
577 577
578 578 user = relationship('User', lazy='joined')
579 579 users_group = relationship('UsersGroup')
580 580
581 581 def __init__(self, gr_id='', u_id=''):
582 582 self.users_group_id = gr_id
583 583 self.user_id = u_id
584 584
585 585
586 586 class Repository(Base, BaseModel):
587 587 __tablename__ = 'repositories'
588 588 __table_args__ = (
589 589 UniqueConstraint('repo_name'),
590 590 Index('r_repo_name_idx', 'repo_name'),
591 591 {'extend_existing': True, 'mysql_engine': 'InnoDB',
592 592 'mysql_charset': 'utf8'},
593 593 )
594 594
595 595 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
596 596 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
597 597 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
598 598 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
599 599 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
600 600 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
601 601 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
602 602 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
603 603 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
604 604 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
605 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
605 606 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
606 607 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
607 608 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
608 609
609 610 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
610 611 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
611 612
612 613 user = relationship('User')
613 614 fork = relationship('Repository', remote_side=repo_id)
614 615 group = relationship('RepoGroup')
615 616 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
616 617 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
617 618 stats = relationship('Statistics', cascade='all', uselist=False)
618 619
619 620 followers = relationship('UserFollowing',
620 621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
621 622 cascade='all')
622 623
623 624 logs = relationship('UserLog')
624 625 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
625 626
626 627 pull_requests_org = relationship('PullRequest',
627 628 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
628 629 cascade="all, delete, delete-orphan")
629 630
630 631 pull_requests_other = relationship('PullRequest',
631 632 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
632 633 cascade="all, delete, delete-orphan")
633 634
634 635 def __unicode__(self):
635 636 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
636 637 self.repo_name)
637 638
638 639 @hybrid_property
639 640 def locked(self):
640 641 # always should return [user_id, timelocked]
641 642 if self._locked:
642 643 _lock_info = self._locked.split(':')
643 644 return int(_lock_info[0]), _lock_info[1]
644 645 return [None, None]
645 646
646 647 @locked.setter
647 648 def locked(self, val):
648 649 if val and isinstance(val, (list, tuple)):
649 650 self._locked = ':'.join(map(str, val))
650 651 else:
651 652 self._locked = None
652 653
653 654 @classmethod
654 655 def url_sep(cls):
655 656 return URL_SEP
656 657
657 658 @classmethod
658 659 def get_by_repo_name(cls, repo_name):
659 660 q = Session().query(cls).filter(cls.repo_name == repo_name)
660 661 q = q.options(joinedload(Repository.fork))\
661 662 .options(joinedload(Repository.user))\
662 663 .options(joinedload(Repository.group))
663 664 return q.scalar()
664 665
665 666 @classmethod
666 667 def get_by_full_path(cls, repo_full_path):
667 668 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
668 669 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
669 670
670 671 @classmethod
671 672 def get_repo_forks(cls, repo_id):
672 673 return cls.query().filter(Repository.fork_id == repo_id)
673 674
674 675 @classmethod
675 676 def base_path(cls):
676 677 """
677 678 Returns base path when all repos are stored
678 679
679 680 :param cls:
680 681 """
681 682 q = Session().query(RhodeCodeUi)\
682 683 .filter(RhodeCodeUi.ui_key == cls.url_sep())
683 684 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
684 685 return q.one().ui_value
685 686
686 687 @property
687 688 def forks(self):
688 689 """
689 690 Return forks of this repo
690 691 """
691 692 return Repository.get_repo_forks(self.repo_id)
692 693
693 694 @property
694 695 def parent(self):
695 696 """
696 697 Returns fork parent
697 698 """
698 699 return self.fork
699 700
700 701 @property
701 702 def just_name(self):
702 703 return self.repo_name.split(Repository.url_sep())[-1]
703 704
704 705 @property
705 706 def groups_with_parents(self):
706 707 groups = []
707 708 if self.group is None:
708 709 return groups
709 710
710 711 cur_gr = self.group
711 712 groups.insert(0, cur_gr)
712 713 while 1:
713 714 gr = getattr(cur_gr, 'parent_group', None)
714 715 cur_gr = cur_gr.parent_group
715 716 if gr is None:
716 717 break
717 718 groups.insert(0, gr)
718 719
719 720 return groups
720 721
721 722 @property
722 723 def groups_and_repo(self):
723 724 return self.groups_with_parents, self.just_name
724 725
725 726 @LazyProperty
726 727 def repo_path(self):
727 728 """
728 729 Returns base full path for that repository means where it actually
729 730 exists on a filesystem
730 731 """
731 732 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
732 733 Repository.url_sep())
733 734 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
734 735 return q.one().ui_value
735 736
736 737 @property
737 738 def repo_full_path(self):
738 739 p = [self.repo_path]
739 740 # we need to split the name by / since this is how we store the
740 741 # names in the database, but that eventually needs to be converted
741 742 # into a valid system path
742 743 p += self.repo_name.split(Repository.url_sep())
743 744 return os.path.join(*p)
744 745
745 746 def get_new_name(self, repo_name):
746 747 """
747 748 returns new full repository name based on assigned group and new new
748 749
749 750 :param group_name:
750 751 """
751 752 path_prefix = self.group.full_path_splitted if self.group else []
752 753 return Repository.url_sep().join(path_prefix + [repo_name])
753 754
754 755 @property
755 756 def _ui(self):
756 757 """
757 758 Creates an db based ui object for this repository
758 759 """
759 760 from mercurial import ui
760 761 from mercurial import config
761 762 baseui = ui.ui()
762 763
763 764 #clean the baseui object
764 765 baseui._ocfg = config.config()
765 766 baseui._ucfg = config.config()
766 767 baseui._tcfg = config.config()
767 768
768 769 ret = RhodeCodeUi.query()\
769 770 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
770 771
771 772 hg_ui = ret
772 773 for ui_ in hg_ui:
773 774 if ui_.ui_active:
774 775 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
775 776 ui_.ui_key, ui_.ui_value)
776 777 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
777 778 if ui_.ui_key == 'push_ssl':
778 779 # force set push_ssl requirement to False, rhodecode
779 780 # handles that
780 781 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
781 782
782 783 return baseui
783 784
784 785 @classmethod
785 786 def inject_ui(cls, repo, extras={}):
786 787 from rhodecode.lib.vcs.backends.hg import MercurialRepository
787 788 from rhodecode.lib.vcs.backends.git import GitRepository
788 789 required = (MercurialRepository, GitRepository)
789 790 if not isinstance(repo, required):
790 791 raise Exception('repo must be instance of %s' % required)
791 792
792 793 # inject ui extra param to log this action via push logger
793 794 for k, v in extras.items():
794 795 repo._repo.ui.setconfig('rhodecode_extras', k, v)
795 796
796 797 @classmethod
797 798 def is_valid(cls, repo_name):
798 799 """
799 800 returns True if given repo name is a valid filesystem repository
800 801
801 802 :param cls:
802 803 :param repo_name:
803 804 """
804 805 from rhodecode.lib.utils import is_valid_repo
805 806
806 807 return is_valid_repo(repo_name, cls.base_path())
807 808
808 809 def get_api_data(self):
809 810 """
810 811 Common function for generating repo api data
811 812
812 813 """
813 814 repo = self
814 815 data = dict(
815 816 repo_id=repo.repo_id,
816 817 repo_name=repo.repo_name,
817 818 repo_type=repo.repo_type,
818 819 clone_uri=repo.clone_uri,
819 820 private=repo.private,
820 821 created_on=repo.created_on,
821 822 description=repo.description,
822 823 landing_rev=repo.landing_rev,
823 824 owner=repo.user.username,
824 825 fork_of=repo.fork.repo_name if repo.fork else None
825 826 )
826 827
827 828 return data
828 829
829 830 @classmethod
830 831 def lock(cls, repo, user_id):
831 832 repo.locked = [user_id, time.time()]
832 833 Session().add(repo)
833 834 Session().commit()
834 835
835 836 @classmethod
836 837 def unlock(cls, repo):
837 838 repo.locked = None
838 839 Session().add(repo)
839 840 Session().commit()
840 841
841 842 #==========================================================================
842 843 # SCM PROPERTIES
843 844 #==========================================================================
844 845
845 846 def get_changeset(self, rev=None):
846 847 return get_changeset_safe(self.scm_instance, rev)
847 848
848 849 def get_landing_changeset(self):
849 850 """
850 851 Returns landing changeset, or if that doesn't exist returns the tip
851 852 """
852 853 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
853 854 return cs
854 855
855 856 @property
856 857 def tip(self):
857 858 return self.get_changeset('tip')
858 859
859 860 @property
860 861 def author(self):
861 862 return self.tip.author
862 863
863 864 @property
864 865 def last_change(self):
865 866 return self.scm_instance.last_change
866 867
867 868 def get_comments(self, revisions=None):
868 869 """
869 870 Returns comments for this repository grouped by revisions
870 871
871 872 :param revisions: filter query by revisions only
872 873 """
873 874 cmts = ChangesetComment.query()\
874 875 .filter(ChangesetComment.repo == self)
875 876 if revisions:
876 877 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
877 878 grouped = defaultdict(list)
878 879 for cmt in cmts.all():
879 880 grouped[cmt.revision].append(cmt)
880 881 return grouped
881 882
882 883 def statuses(self, revisions=None):
883 884 """
884 885 Returns statuses for this repository
885 886
886 887 :param revisions: list of revisions to get statuses for
887 888 :type revisions: list
888 889 """
889 890
890 891 statuses = ChangesetStatus.query()\
891 892 .filter(ChangesetStatus.repo == self)\
892 893 .filter(ChangesetStatus.version == 0)
893 894 if revisions:
894 895 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
895 896 grouped = {}
896 897
897 898 #maybe we have open new pullrequest without a status ?
898 899 stat = ChangesetStatus.STATUS_UNDER_REVIEW
899 900 status_lbl = ChangesetStatus.get_status_lbl(stat)
900 901 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
901 902 for rev in pr.revisions:
902 903 pr_id = pr.pull_request_id
903 904 pr_repo = pr.other_repo.repo_name
904 905 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
905 906
906 907 for stat in statuses.all():
907 908 pr_id = pr_repo = None
908 909 if stat.pull_request:
909 910 pr_id = stat.pull_request.pull_request_id
910 911 pr_repo = stat.pull_request.other_repo.repo_name
911 912 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
912 913 pr_id, pr_repo]
913 914 return grouped
914 915
915 916 #==========================================================================
916 917 # SCM CACHE INSTANCE
917 918 #==========================================================================
918 919
919 920 @property
920 921 def invalidate(self):
921 922 return CacheInvalidation.invalidate(self.repo_name)
922 923
923 924 def set_invalidate(self):
924 925 """
925 926 set a cache for invalidation for this instance
926 927 """
927 928 CacheInvalidation.set_invalidate(self.repo_name)
928 929
929 930 @LazyProperty
930 931 def scm_instance(self):
931 932 return self.__get_instance()
932 933
933 934 def scm_instance_cached(self, cache_map=None):
934 935 @cache_region('long_term')
935 936 def _c(repo_name):
936 937 return self.__get_instance()
937 938 rn = self.repo_name
938 939 log.debug('Getting cached instance of repo')
939 940
940 941 if cache_map:
941 942 # get using prefilled cache_map
942 943 invalidate_repo = cache_map[self.repo_name]
943 944 if invalidate_repo:
944 945 invalidate_repo = (None if invalidate_repo.cache_active
945 946 else invalidate_repo)
946 947 else:
947 948 # get from invalidate
948 949 invalidate_repo = self.invalidate
949 950
950 951 if invalidate_repo is not None:
951 952 region_invalidate(_c, None, rn)
952 953 # update our cache
953 954 CacheInvalidation.set_valid(invalidate_repo.cache_key)
954 955 return _c(rn)
955 956
956 957 def __get_instance(self):
957 958 repo_full_path = self.repo_full_path
958 959 try:
959 960 alias = get_scm(repo_full_path)[0]
960 961 log.debug('Creating instance of %s repository' % alias)
961 962 backend = get_backend(alias)
962 963 except VCSError:
963 964 log.error(traceback.format_exc())
964 965 log.error('Perhaps this repository is in db and not in '
965 966 'filesystem run rescan repositories with '
966 967 '"destroy old data " option from admin panel')
967 968 return
968 969
969 970 if alias == 'hg':
970 971
971 972 repo = backend(safe_str(repo_full_path), create=False,
972 973 baseui=self._ui)
973 974 # skip hidden web repository
974 975 if repo._get_hidden():
975 976 return
976 977 else:
977 978 repo = backend(repo_full_path, create=False)
978 979
979 980 return repo
980 981
981 982
982 983 class RepoGroup(Base, BaseModel):
983 984 __tablename__ = 'groups'
984 985 __table_args__ = (
985 986 UniqueConstraint('group_name', 'group_parent_id'),
986 987 CheckConstraint('group_id != group_parent_id'),
987 988 {'extend_existing': True, 'mysql_engine': 'InnoDB',
988 989 'mysql_charset': 'utf8'},
989 990 )
990 991 __mapper_args__ = {'order_by': 'group_name'}
991 992
992 993 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
993 994 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
994 995 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
995 996 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
996 997 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
997 998
998 999 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
999 1000 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1000 1001
1001 1002 parent_group = relationship('RepoGroup', remote_side=group_id)
1002 1003
1003 1004 def __init__(self, group_name='', parent_group=None):
1004 1005 self.group_name = group_name
1005 1006 self.parent_group = parent_group
1006 1007
1007 1008 def __unicode__(self):
1008 1009 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1009 1010 self.group_name)
1010 1011
1011 1012 @classmethod
1012 1013 def groups_choices(cls):
1013 1014 from webhelpers.html import literal as _literal
1014 1015 repo_groups = [('', '')]
1015 1016 sep = ' &raquo; '
1016 1017 _name = lambda k: _literal(sep.join(k))
1017 1018
1018 1019 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1019 1020 for x in cls.query().all()])
1020 1021
1021 1022 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1022 1023 return repo_groups
1023 1024
1024 1025 @classmethod
1025 1026 def url_sep(cls):
1026 1027 return URL_SEP
1027 1028
1028 1029 @classmethod
1029 1030 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1030 1031 if case_insensitive:
1031 1032 gr = cls.query()\
1032 1033 .filter(cls.group_name.ilike(group_name))
1033 1034 else:
1034 1035 gr = cls.query()\
1035 1036 .filter(cls.group_name == group_name)
1036 1037 if cache:
1037 1038 gr = gr.options(FromCache(
1038 1039 "sql_cache_short",
1039 1040 "get_group_%s" % _hash_key(group_name)
1040 1041 )
1041 1042 )
1042 1043 return gr.scalar()
1043 1044
1044 1045 @property
1045 1046 def parents(self):
1046 1047 parents_recursion_limit = 5
1047 1048 groups = []
1048 1049 if self.parent_group is None:
1049 1050 return groups
1050 1051 cur_gr = self.parent_group
1051 1052 groups.insert(0, cur_gr)
1052 1053 cnt = 0
1053 1054 while 1:
1054 1055 cnt += 1
1055 1056 gr = getattr(cur_gr, 'parent_group', None)
1056 1057 cur_gr = cur_gr.parent_group
1057 1058 if gr is None:
1058 1059 break
1059 1060 if cnt == parents_recursion_limit:
1060 1061 # this will prevent accidental infinit loops
1061 1062 log.error('group nested more than %s' %
1062 1063 parents_recursion_limit)
1063 1064 break
1064 1065
1065 1066 groups.insert(0, gr)
1066 1067 return groups
1067 1068
1068 1069 @property
1069 1070 def children(self):
1070 1071 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1071 1072
1072 1073 @property
1073 1074 def name(self):
1074 1075 return self.group_name.split(RepoGroup.url_sep())[-1]
1075 1076
1076 1077 @property
1077 1078 def full_path(self):
1078 1079 return self.group_name
1079 1080
1080 1081 @property
1081 1082 def full_path_splitted(self):
1082 1083 return self.group_name.split(RepoGroup.url_sep())
1083 1084
1084 1085 @property
1085 1086 def repositories(self):
1086 1087 return Repository.query()\
1087 1088 .filter(Repository.group == self)\
1088 1089 .order_by(Repository.repo_name)
1089 1090
1090 1091 @property
1091 1092 def repositories_recursive_count(self):
1092 1093 cnt = self.repositories.count()
1093 1094
1094 1095 def children_count(group):
1095 1096 cnt = 0
1096 1097 for child in group.children:
1097 1098 cnt += child.repositories.count()
1098 1099 cnt += children_count(child)
1099 1100 return cnt
1100 1101
1101 1102 return cnt + children_count(self)
1102 1103
1103 1104 def recursive_groups_and_repos(self):
1104 1105 """
1105 1106 Recursive return all groups, with repositories in those groups
1106 1107 """
1107 1108 all_ = []
1108 1109
1109 1110 def _get_members(root_gr):
1110 1111 for r in root_gr.repositories:
1111 1112 all_.append(r)
1112 1113 childs = root_gr.children.all()
1113 1114 if childs:
1114 1115 for gr in childs:
1115 1116 all_.append(gr)
1116 1117 _get_members(gr)
1117 1118
1118 1119 _get_members(self)
1119 1120 return [self] + all_
1120 1121
1121 1122 def get_new_name(self, group_name):
1122 1123 """
1123 1124 returns new full group name based on parent and new name
1124 1125
1125 1126 :param group_name:
1126 1127 """
1127 1128 path_prefix = (self.parent_group.full_path_splitted if
1128 1129 self.parent_group else [])
1129 1130 return RepoGroup.url_sep().join(path_prefix + [group_name])
1130 1131
1131 1132
1132 1133 class Permission(Base, BaseModel):
1133 1134 __tablename__ = 'permissions'
1134 1135 __table_args__ = (
1135 1136 Index('p_perm_name_idx', 'permission_name'),
1136 1137 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1137 1138 'mysql_charset': 'utf8'},
1138 1139 )
1139 1140 PERMS = [
1140 1141 ('repository.none', _('Repository no access')),
1141 1142 ('repository.read', _('Repository read access')),
1142 1143 ('repository.write', _('Repository write access')),
1143 1144 ('repository.admin', _('Repository admin access')),
1144 1145
1145 1146 ('group.none', _('Repositories Group no access')),
1146 1147 ('group.read', _('Repositories Group read access')),
1147 1148 ('group.write', _('Repositories Group write access')),
1148 1149 ('group.admin', _('Repositories Group admin access')),
1149 1150
1150 1151 ('hg.admin', _('RhodeCode Administrator')),
1151 1152 ('hg.create.none', _('Repository creation disabled')),
1152 1153 ('hg.create.repository', _('Repository creation enabled')),
1153 1154 ('hg.fork.none', _('Repository forking disabled')),
1154 1155 ('hg.fork.repository', _('Repository forking enabled')),
1155 1156 ('hg.register.none', _('Register disabled')),
1156 1157 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1157 1158 'with manual activation')),
1158 1159
1159 1160 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1160 1161 'with auto activation')),
1161 1162 ]
1162 1163
1163 1164 # defines which permissions are more important higher the more important
1164 1165 PERM_WEIGHTS = {
1165 1166 'repository.none': 0,
1166 1167 'repository.read': 1,
1167 1168 'repository.write': 3,
1168 1169 'repository.admin': 4,
1169 1170
1170 1171 'group.none': 0,
1171 1172 'group.read': 1,
1172 1173 'group.write': 3,
1173 1174 'group.admin': 4,
1174 1175
1175 1176 'hg.fork.none': 0,
1176 1177 'hg.fork.repository': 1,
1177 1178 'hg.create.none': 0,
1178 1179 'hg.create.repository':1
1179 1180 }
1180 1181
1181 1182 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1182 1183 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1183 1184 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1184 1185
1185 1186 def __unicode__(self):
1186 1187 return u"<%s('%s:%s')>" % (
1187 1188 self.__class__.__name__, self.permission_id, self.permission_name
1188 1189 )
1189 1190
1190 1191 @classmethod
1191 1192 def get_by_key(cls, key):
1192 1193 return cls.query().filter(cls.permission_name == key).scalar()
1193 1194
1194 1195 @classmethod
1195 1196 def get_default_perms(cls, default_user_id):
1196 1197 q = Session().query(UserRepoToPerm, Repository, cls)\
1197 1198 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1198 1199 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1199 1200 .filter(UserRepoToPerm.user_id == default_user_id)
1200 1201
1201 1202 return q.all()
1202 1203
1203 1204 @classmethod
1204 1205 def get_default_group_perms(cls, default_user_id):
1205 1206 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1206 1207 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1207 1208 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1208 1209 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1209 1210
1210 1211 return q.all()
1211 1212
1212 1213
1213 1214 class UserRepoToPerm(Base, BaseModel):
1214 1215 __tablename__ = 'repo_to_perm'
1215 1216 __table_args__ = (
1216 1217 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1217 1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1218 1219 'mysql_charset': 'utf8'}
1219 1220 )
1220 1221 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1221 1222 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1222 1223 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1223 1224 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1224 1225
1225 1226 user = relationship('User')
1226 1227 repository = relationship('Repository')
1227 1228 permission = relationship('Permission')
1228 1229
1229 1230 @classmethod
1230 1231 def create(cls, user, repository, permission):
1231 1232 n = cls()
1232 1233 n.user = user
1233 1234 n.repository = repository
1234 1235 n.permission = permission
1235 1236 Session().add(n)
1236 1237 return n
1237 1238
1238 1239 def __unicode__(self):
1239 1240 return u'<user:%s => %s >' % (self.user, self.repository)
1240 1241
1241 1242
1242 1243 class UserToPerm(Base, BaseModel):
1243 1244 __tablename__ = 'user_to_perm'
1244 1245 __table_args__ = (
1245 1246 UniqueConstraint('user_id', 'permission_id'),
1246 1247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1247 1248 'mysql_charset': 'utf8'}
1248 1249 )
1249 1250 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1250 1251 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1251 1252 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1252 1253
1253 1254 user = relationship('User')
1254 1255 permission = relationship('Permission', lazy='joined')
1255 1256
1256 1257
1257 1258 class UsersGroupRepoToPerm(Base, BaseModel):
1258 1259 __tablename__ = 'users_group_repo_to_perm'
1259 1260 __table_args__ = (
1260 1261 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1261 1262 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1262 1263 'mysql_charset': 'utf8'}
1263 1264 )
1264 1265 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1265 1266 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1266 1267 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1267 1268 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1268 1269
1269 1270 users_group = relationship('UsersGroup')
1270 1271 permission = relationship('Permission')
1271 1272 repository = relationship('Repository')
1272 1273
1273 1274 @classmethod
1274 1275 def create(cls, users_group, repository, permission):
1275 1276 n = cls()
1276 1277 n.users_group = users_group
1277 1278 n.repository = repository
1278 1279 n.permission = permission
1279 1280 Session().add(n)
1280 1281 return n
1281 1282
1282 1283 def __unicode__(self):
1283 1284 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1284 1285
1285 1286
1286 1287 class UsersGroupToPerm(Base, BaseModel):
1287 1288 __tablename__ = 'users_group_to_perm'
1288 1289 __table_args__ = (
1289 1290 UniqueConstraint('users_group_id', 'permission_id',),
1290 1291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1291 1292 'mysql_charset': 'utf8'}
1292 1293 )
1293 1294 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1294 1295 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1295 1296 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1296 1297
1297 1298 users_group = relationship('UsersGroup')
1298 1299 permission = relationship('Permission')
1299 1300
1300 1301
1301 1302 class UserRepoGroupToPerm(Base, BaseModel):
1302 1303 __tablename__ = 'user_repo_group_to_perm'
1303 1304 __table_args__ = (
1304 1305 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1305 1306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1306 1307 'mysql_charset': 'utf8'}
1307 1308 )
1308 1309
1309 1310 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1310 1311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1311 1312 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1312 1313 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1313 1314
1314 1315 user = relationship('User')
1315 1316 group = relationship('RepoGroup')
1316 1317 permission = relationship('Permission')
1317 1318
1318 1319
1319 1320 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1320 1321 __tablename__ = 'users_group_repo_group_to_perm'
1321 1322 __table_args__ = (
1322 1323 UniqueConstraint('users_group_id', 'group_id'),
1323 1324 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1324 1325 'mysql_charset': 'utf8'}
1325 1326 )
1326 1327
1327 1328 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)
1328 1329 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1329 1330 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1330 1331 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1331 1332
1332 1333 users_group = relationship('UsersGroup')
1333 1334 permission = relationship('Permission')
1334 1335 group = relationship('RepoGroup')
1335 1336
1336 1337
1337 1338 class Statistics(Base, BaseModel):
1338 1339 __tablename__ = 'statistics'
1339 1340 __table_args__ = (
1340 1341 UniqueConstraint('repository_id'),
1341 1342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1342 1343 'mysql_charset': 'utf8'}
1343 1344 )
1344 1345 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1345 1346 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1346 1347 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1347 1348 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1348 1349 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1349 1350 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1350 1351
1351 1352 repository = relationship('Repository', single_parent=True)
1352 1353
1353 1354
1354 1355 class UserFollowing(Base, BaseModel):
1355 1356 __tablename__ = 'user_followings'
1356 1357 __table_args__ = (
1357 1358 UniqueConstraint('user_id', 'follows_repository_id'),
1358 1359 UniqueConstraint('user_id', 'follows_user_id'),
1359 1360 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1360 1361 'mysql_charset': 'utf8'}
1361 1362 )
1362 1363
1363 1364 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1364 1365 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1365 1366 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1366 1367 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1367 1368 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1368 1369
1369 1370 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1370 1371
1371 1372 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1372 1373 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1373 1374
1374 1375 @classmethod
1375 1376 def get_repo_followers(cls, repo_id):
1376 1377 return cls.query().filter(cls.follows_repo_id == repo_id)
1377 1378
1378 1379
1379 1380 class CacheInvalidation(Base, BaseModel):
1380 1381 __tablename__ = 'cache_invalidation'
1381 1382 __table_args__ = (
1382 1383 UniqueConstraint('cache_key'),
1383 1384 Index('key_idx', 'cache_key'),
1384 1385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1385 1386 'mysql_charset': 'utf8'},
1386 1387 )
1387 1388 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1388 1389 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1389 1390 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1390 1391 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1391 1392
1392 1393 def __init__(self, cache_key, cache_args=''):
1393 1394 self.cache_key = cache_key
1394 1395 self.cache_args = cache_args
1395 1396 self.cache_active = False
1396 1397
1397 1398 def __unicode__(self):
1398 1399 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1399 1400 self.cache_id, self.cache_key)
1400 1401
1401 1402 @classmethod
1402 1403 def clear_cache(cls):
1403 1404 cls.query().delete()
1404 1405
1405 1406 @classmethod
1406 1407 def _get_key(cls, key):
1407 1408 """
1408 1409 Wrapper for generating a key, together with a prefix
1409 1410
1410 1411 :param key:
1411 1412 """
1412 1413 import rhodecode
1413 1414 prefix = ''
1414 1415 iid = rhodecode.CONFIG.get('instance_id')
1415 1416 if iid:
1416 1417 prefix = iid
1417 1418 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1418 1419
1419 1420 @classmethod
1420 1421 def get_by_key(cls, key):
1421 1422 return cls.query().filter(cls.cache_key == key).scalar()
1422 1423
1423 1424 @classmethod
1424 1425 def _get_or_create_key(cls, key, prefix, org_key):
1425 1426 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1426 1427 if not inv_obj:
1427 1428 try:
1428 1429 inv_obj = CacheInvalidation(key, org_key)
1429 1430 Session().add(inv_obj)
1430 1431 Session().commit()
1431 1432 except Exception:
1432 1433 log.error(traceback.format_exc())
1433 1434 Session().rollback()
1434 1435 return inv_obj
1435 1436
1436 1437 @classmethod
1437 1438 def invalidate(cls, key):
1438 1439 """
1439 1440 Returns Invalidation object if this given key should be invalidated
1440 1441 None otherwise. `cache_active = False` means that this cache
1441 1442 state is not valid and needs to be invalidated
1442 1443
1443 1444 :param key:
1444 1445 """
1445 1446
1446 1447 key, _prefix, _org_key = cls._get_key(key)
1447 1448 inv = cls._get_or_create_key(key, _prefix, _org_key)
1448 1449
1449 1450 if inv and inv.cache_active is False:
1450 1451 return inv
1451 1452
1452 1453 @classmethod
1453 1454 def set_invalidate(cls, key):
1454 1455 """
1455 1456 Mark this Cache key for invalidation
1456 1457
1457 1458 :param key:
1458 1459 """
1459 1460
1460 1461 key, _prefix, _org_key = cls._get_key(key)
1461 1462 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1462 1463 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1463 1464 _org_key))
1464 1465 try:
1465 1466 for inv_obj in inv_objs:
1466 1467 if inv_obj:
1467 1468 inv_obj.cache_active = False
1468 1469
1469 1470 Session().add(inv_obj)
1470 1471 Session().commit()
1471 1472 except Exception:
1472 1473 log.error(traceback.format_exc())
1473 1474 Session().rollback()
1474 1475
1475 1476 @classmethod
1476 1477 def set_valid(cls, key):
1477 1478 """
1478 1479 Mark this cache key as active and currently cached
1479 1480
1480 1481 :param key:
1481 1482 """
1482 1483 inv_obj = cls.get_by_key(key)
1483 1484 inv_obj.cache_active = True
1484 1485 Session().add(inv_obj)
1485 1486 Session().commit()
1486 1487
1487 1488 @classmethod
1488 1489 def get_cache_map(cls):
1489 1490
1490 1491 class cachemapdict(dict):
1491 1492
1492 1493 def __init__(self, *args, **kwargs):
1493 1494 fixkey = kwargs.get('fixkey')
1494 1495 if fixkey:
1495 1496 del kwargs['fixkey']
1496 1497 self.fixkey = fixkey
1497 1498 super(cachemapdict, self).__init__(*args, **kwargs)
1498 1499
1499 1500 def __getattr__(self, name):
1500 1501 key = name
1501 1502 if self.fixkey:
1502 1503 key, _prefix, _org_key = cls._get_key(key)
1503 1504 if key in self.__dict__:
1504 1505 return self.__dict__[key]
1505 1506 else:
1506 1507 return self[key]
1507 1508
1508 1509 def __getitem__(self, key):
1509 1510 if self.fixkey:
1510 1511 key, _prefix, _org_key = cls._get_key(key)
1511 1512 try:
1512 1513 return super(cachemapdict, self).__getitem__(key)
1513 1514 except KeyError:
1514 1515 return
1515 1516
1516 1517 cache_map = cachemapdict(fixkey=True)
1517 1518 for obj in cls.query().all():
1518 1519 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1519 1520 return cache_map
1520 1521
1521 1522
1522 1523 class ChangesetComment(Base, BaseModel):
1523 1524 __tablename__ = 'changeset_comments'
1524 1525 __table_args__ = (
1525 1526 Index('cc_revision_idx', 'revision'),
1526 1527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1527 1528 'mysql_charset': 'utf8'},
1528 1529 )
1529 1530 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1530 1531 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1531 1532 revision = Column('revision', String(40), nullable=True)
1532 1533 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1533 1534 line_no = Column('line_no', Unicode(10), nullable=True)
1534 1535 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1535 1536 f_path = Column('f_path', Unicode(1000), nullable=True)
1536 1537 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1537 1538 text = Column('text', UnicodeText(25000), nullable=False)
1538 1539 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1539 1540 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1540 1541
1541 1542 author = relationship('User', lazy='joined')
1542 1543 repo = relationship('Repository')
1543 1544 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1544 1545 pull_request = relationship('PullRequest', lazy='joined')
1545 1546
1546 1547 @classmethod
1547 1548 def get_users(cls, revision=None, pull_request_id=None):
1548 1549 """
1549 1550 Returns user associated with this ChangesetComment. ie those
1550 1551 who actually commented
1551 1552
1552 1553 :param cls:
1553 1554 :param revision:
1554 1555 """
1555 1556 q = Session().query(User)\
1556 1557 .join(ChangesetComment.author)
1557 1558 if revision:
1558 1559 q = q.filter(cls.revision == revision)
1559 1560 elif pull_request_id:
1560 1561 q = q.filter(cls.pull_request_id == pull_request_id)
1561 1562 return q.all()
1562 1563
1563 1564
1564 1565 class ChangesetStatus(Base, BaseModel):
1565 1566 __tablename__ = 'changeset_statuses'
1566 1567 __table_args__ = (
1567 1568 Index('cs_revision_idx', 'revision'),
1568 1569 Index('cs_version_idx', 'version'),
1569 1570 UniqueConstraint('repo_id', 'revision', 'version'),
1570 1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 1572 'mysql_charset': 'utf8'}
1572 1573 )
1573 1574 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1574 1575 STATUS_APPROVED = 'approved'
1575 1576 STATUS_REJECTED = 'rejected'
1576 1577 STATUS_UNDER_REVIEW = 'under_review'
1577 1578
1578 1579 STATUSES = [
1579 1580 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1580 1581 (STATUS_APPROVED, _("Approved")),
1581 1582 (STATUS_REJECTED, _("Rejected")),
1582 1583 (STATUS_UNDER_REVIEW, _("Under Review")),
1583 1584 ]
1584 1585
1585 1586 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1586 1587 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1587 1588 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1588 1589 revision = Column('revision', String(40), nullable=False)
1589 1590 status = Column('status', String(128), nullable=False, default=DEFAULT)
1590 1591 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1591 1592 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1592 1593 version = Column('version', Integer(), nullable=False, default=0)
1593 1594 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1594 1595
1595 1596 author = relationship('User', lazy='joined')
1596 1597 repo = relationship('Repository')
1597 1598 comment = relationship('ChangesetComment', lazy='joined')
1598 1599 pull_request = relationship('PullRequest', lazy='joined')
1599 1600
1600 1601 def __unicode__(self):
1601 1602 return u"<%s('%s:%s')>" % (
1602 1603 self.__class__.__name__,
1603 1604 self.status, self.author
1604 1605 )
1605 1606
1606 1607 @classmethod
1607 1608 def get_status_lbl(cls, value):
1608 1609 return dict(cls.STATUSES).get(value)
1609 1610
1610 1611 @property
1611 1612 def status_lbl(self):
1612 1613 return ChangesetStatus.get_status_lbl(self.status)
1613 1614
1614 1615
1615 1616 class PullRequest(Base, BaseModel):
1616 1617 __tablename__ = 'pull_requests'
1617 1618 __table_args__ = (
1618 1619 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1619 1620 'mysql_charset': 'utf8'},
1620 1621 )
1621 1622
1622 1623 STATUS_NEW = u'new'
1623 1624 STATUS_OPEN = u'open'
1624 1625 STATUS_CLOSED = u'closed'
1625 1626
1626 1627 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1627 1628 title = Column('title', Unicode(256), nullable=True)
1628 1629 description = Column('description', UnicodeText(10240), nullable=True)
1629 1630 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1630 1631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1631 1632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1632 1633 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1633 1634 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1634 1635 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1635 1636 org_ref = Column('org_ref', Unicode(256), nullable=False)
1636 1637 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1637 1638 other_ref = Column('other_ref', Unicode(256), nullable=False)
1638 1639
1639 1640 @hybrid_property
1640 1641 def revisions(self):
1641 1642 return self._revisions.split(':')
1642 1643
1643 1644 @revisions.setter
1644 1645 def revisions(self, val):
1645 1646 self._revisions = ':'.join(val)
1646 1647
1647 1648 author = relationship('User', lazy='joined')
1648 1649 reviewers = relationship('PullRequestReviewers',
1649 1650 cascade="all, delete, delete-orphan")
1650 1651 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1651 1652 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1652 1653 statuses = relationship('ChangesetStatus')
1653 1654 comments = relationship('ChangesetComment',
1654 1655 cascade="all, delete, delete-orphan")
1655 1656
1656 1657 def is_closed(self):
1657 1658 return self.status == self.STATUS_CLOSED
1658 1659
1659 1660 def __json__(self):
1660 1661 return dict(
1661 1662 revisions=self.revisions
1662 1663 )
1663 1664
1664 1665
1665 1666 class PullRequestReviewers(Base, BaseModel):
1666 1667 __tablename__ = 'pull_request_reviewers'
1667 1668 __table_args__ = (
1668 1669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1669 1670 'mysql_charset': 'utf8'},
1670 1671 )
1671 1672
1672 1673 def __init__(self, user=None, pull_request=None):
1673 1674 self.user = user
1674 1675 self.pull_request = pull_request
1675 1676
1676 1677 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1677 1678 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1678 1679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1679 1680
1680 1681 user = relationship('User')
1681 1682 pull_request = relationship('PullRequest')
1682 1683
1683 1684
1684 1685 class Notification(Base, BaseModel):
1685 1686 __tablename__ = 'notifications'
1686 1687 __table_args__ = (
1687 1688 Index('notification_type_idx', 'type'),
1688 1689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1689 1690 'mysql_charset': 'utf8'},
1690 1691 )
1691 1692
1692 1693 TYPE_CHANGESET_COMMENT = u'cs_comment'
1693 1694 TYPE_MESSAGE = u'message'
1694 1695 TYPE_MENTION = u'mention'
1695 1696 TYPE_REGISTRATION = u'registration'
1696 1697 TYPE_PULL_REQUEST = u'pull_request'
1697 1698 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1698 1699
1699 1700 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1700 1701 subject = Column('subject', Unicode(512), nullable=True)
1701 1702 body = Column('body', UnicodeText(50000), nullable=True)
1702 1703 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1703 1704 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1704 1705 type_ = Column('type', Unicode(256))
1705 1706
1706 1707 created_by_user = relationship('User')
1707 1708 notifications_to_users = relationship('UserNotification', lazy='joined',
1708 1709 cascade="all, delete, delete-orphan")
1709 1710
1710 1711 @property
1711 1712 def recipients(self):
1712 1713 return [x.user for x in UserNotification.query()\
1713 1714 .filter(UserNotification.notification == self)\
1714 1715 .order_by(UserNotification.user_id.asc()).all()]
1715 1716
1716 1717 @classmethod
1717 1718 def create(cls, created_by, subject, body, recipients, type_=None):
1718 1719 if type_ is None:
1719 1720 type_ = Notification.TYPE_MESSAGE
1720 1721
1721 1722 notification = cls()
1722 1723 notification.created_by_user = created_by
1723 1724 notification.subject = subject
1724 1725 notification.body = body
1725 1726 notification.type_ = type_
1726 1727 notification.created_on = datetime.datetime.now()
1727 1728
1728 1729 for u in recipients:
1729 1730 assoc = UserNotification()
1730 1731 assoc.notification = notification
1731 1732 u.notifications.append(assoc)
1732 1733 Session().add(notification)
1733 1734 return notification
1734 1735
1735 1736 @property
1736 1737 def description(self):
1737 1738 from rhodecode.model.notification import NotificationModel
1738 1739 return NotificationModel().make_description(self)
1739 1740
1740 1741
1741 1742 class UserNotification(Base, BaseModel):
1742 1743 __tablename__ = 'user_to_notification'
1743 1744 __table_args__ = (
1744 1745 UniqueConstraint('user_id', 'notification_id'),
1745 1746 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1746 1747 'mysql_charset': 'utf8'}
1747 1748 )
1748 1749 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1749 1750 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1750 1751 read = Column('read', Boolean, default=False)
1751 1752 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1752 1753
1753 1754 user = relationship('User', lazy="joined")
1754 1755 notification = relationship('Notification', lazy="joined",
1755 1756 order_by=lambda: Notification.created_on.desc(),)
1756 1757
1757 1758 def mark_as_read(self):
1758 1759 self.read = True
1759 1760 Session().add(self)
1760 1761
1761 1762
1762 1763 class DbMigrateVersion(Base, BaseModel):
1763 1764 __tablename__ = 'db_migrate_version'
1764 1765 __table_args__ = (
1765 1766 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1766 1767 'mysql_charset': 'utf8'},
1767 1768 )
1768 1769 repository_id = Column('repository_id', String(250), primary_key=True)
1769 1770 repository_path = Column('repository_path', Text)
1770 1771 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now