##// END OF EJS Templates
Starting RhodeCode 1.4 Branch
marcink -
r2214:2fd474e6 codereview
parent child Browse files
Show More
@@ -0,0 +1,28
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.db_1_4_0
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Database Models for RhodeCode <=1.4.X
7
8 :created_on: Apr 08, 2010
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 #TODO: replace that will db.py content after 1.5 Release
27
28 from rhodecode.model.db import * No newline at end of file
@@ -1,94 +1,94
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 VERSION = (1, 3, 5, 'b')
29 VERSION = (1, 4, 0, '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__ = 5 # defines current db version for migrations
41 __dbversion__ = 6 # defines current db version for migrations
42 42 __platform__ = platform.system()
43 43 __license__ = 'GPLv3'
44 44 __py_version__ = sys.version_info
45 45
46 46 PLATFORM_WIN = ('Windows')
47 47 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
48 48
49 49 requirements = [
50 50 "Pylons==1.0.0",
51 51 "Beaker==1.6.3",
52 52 "WebHelpers==1.3",
53 53 "formencode==1.2.4",
54 54 "SQLAlchemy==0.7.6",
55 55 "Mako==0.7.0",
56 56 "pygments>=1.4",
57 57 "whoosh>=2.3.0,<2.4",
58 58 "celery>=2.2.5,<2.3",
59 59 "babel",
60 60 "python-dateutil>=1.5.0,<2.0.0",
61 61 "dulwich>=0.8.5,<0.9.0",
62 62 "webob==1.0.8",
63 63 "markdown==2.1.1",
64 64 "docutils==0.8.1",
65 65 ]
66 66
67 67 if __py_version__ < (2, 6):
68 68 requirements.append("simplejson")
69 69 requirements.append("pysqlite")
70 70
71 71 if __platform__ in PLATFORM_WIN:
72 72 requirements.append("mercurial>=2.2,<2.3")
73 73 else:
74 74 requirements.append("py-bcrypt")
75 75 requirements.append("mercurial>=2.2,<2.3")
76 76
77 77
78 78 def get_version():
79 79 """Returns shorter version (digit parts only) as string."""
80 80
81 81 return '.'.join((str(each) for each in VERSION[:3]))
82 82
83 83 BACKENDS = {
84 84 'hg': 'Mercurial repository',
85 85 'git': 'Git repository',
86 86 }
87 87
88 88 CELERY_ON = False
89 89
90 90 # link to config for pylons
91 91 CONFIG = {}
92 92
93 93 # Linked module for extensions
94 94 EXTENSIONS = {}
@@ -1,527 +1,529
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 from rhodecode.model import meta
35 35
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.lib.utils import ask_ok
38 38 from rhodecode.model import init_model
39 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
41 41 UserRepoGroupToPerm
42 42
43 43 from sqlalchemy.engine import create_engine
44 44 from rhodecode.model.repos_group import ReposGroupModel
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class DbManage(object):
50 50 def __init__(self, log_sql, dbconf, root, tests=False):
51 51 self.dbname = dbconf.split('/')[-1]
52 52 self.tests = tests
53 53 self.root = root
54 54 self.dburi = dbconf
55 55 self.log_sql = log_sql
56 56 self.db_exists = False
57 57 self.init_db()
58 58
59 59 def init_db(self):
60 60 engine = create_engine(self.dburi, echo=self.log_sql)
61 61 init_model(engine)
62 62 self.sa = meta.Session
63 63
64 64 def create_tables(self, override=False):
65 65 """
66 66 Create a auth database
67 67 """
68 68
69 69 log.info("Any existing database is going to be destroyed")
70 70 if self.tests:
71 71 destroy = True
72 72 else:
73 73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
74 74 if not destroy:
75 75 sys.exit()
76 76 if destroy:
77 77 meta.Base.metadata.drop_all()
78 78
79 79 checkfirst = not override
80 80 meta.Base.metadata.create_all(checkfirst=checkfirst)
81 81 log.info('Created tables for %s' % self.dbname)
82 82
83 83 def set_db_version(self):
84 84 ver = DbMigrateVersion()
85 85 ver.version = __dbversion__
86 86 ver.repository_id = 'rhodecode_db_migrations'
87 87 ver.repository_path = 'versions'
88 88 self.sa.add(ver)
89 89 log.info('db version set to: %s' % __dbversion__)
90 90
91 91 def upgrade(self):
92 92 """
93 93 Upgrades given database schema to given revision following
94 94 all needed steps, to perform the upgrade
95 95
96 96 """
97 97
98 98 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 99 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 100 DatabaseNotControlledError
101 101
102 102 if 'sqlite' in self.dburi:
103 103 print (
104 104 '********************** WARNING **********************\n'
105 105 'Make sure your version of sqlite is at least 3.7.X. \n'
106 106 'Earlier versions are known to fail on some migrations\n'
107 107 '*****************************************************\n'
108 108 )
109 109 upgrade = ask_ok('You are about to perform database upgrade, make '
110 110 'sure You backed up your database before. '
111 111 'Continue ? [y/n]')
112 112 if not upgrade:
113 113 sys.exit('Nothing done')
114 114
115 115 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
116 116 'rhodecode/lib/dbmigrate')
117 117 db_uri = self.dburi
118 118
119 119 try:
120 120 curr_version = api.db_version(db_uri, repository_path)
121 121 msg = ('Found current database under version'
122 122 ' control with version %s' % curr_version)
123 123
124 124 except (RuntimeError, DatabaseNotControlledError):
125 125 curr_version = 1
126 126 msg = ('Current database is not under version control. Setting'
127 127 ' as version %s' % curr_version)
128 128 api.version_control(db_uri, repository_path, curr_version)
129 129
130 130 print (msg)
131 131
132 132 if curr_version == __dbversion__:
133 133 sys.exit('This database is already at the newest version')
134 134
135 135 #======================================================================
136 136 # UPGRADE STEPS
137 137 #======================================================================
138 138 class UpgradeSteps(object):
139 139 """
140 140 Those steps follow schema versions so for example schema
141 141 for example schema with seq 002 == step_2 and so on.
142 142 """
143 143
144 144 def __init__(self, klass):
145 145 self.klass = klass
146 146
147 147 def step_0(self):
148 148 # step 0 is the schema upgrade, and than follow proper upgrades
149 149 print ('attempting to do database upgrade to version %s' \
150 150 % __dbversion__)
151 151 api.upgrade(db_uri, repository_path, __dbversion__)
152 152 print ('Schema upgrade completed')
153 153
154 154 def step_1(self):
155 155 pass
156 156
157 157 def step_2(self):
158 158 print ('Patching repo paths for newer version of RhodeCode')
159 159 self.klass.fix_repo_paths()
160 160
161 161 print ('Patching default user of RhodeCode')
162 162 self.klass.fix_default_user()
163 163
164 164 log.info('Changing ui settings')
165 165 self.klass.create_ui_settings()
166 166
167 167 def step_3(self):
168 168 print ('Adding additional settings into RhodeCode db')
169 169 self.klass.fix_settings()
170 170 print ('Adding ldap defaults')
171 171 self.klass.create_ldap_options(skip_existing=True)
172 172
173 173 def step_4(self):
174 174 print ('create permissions and fix groups')
175 175 self.klass.create_permissions()
176 176 self.klass.fixup_groups()
177 177
178 178 def step_5(self):
179 179 pass
180 180
181 def step_6(self):
182 pass
181 183 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
182 184
183 185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
184 186 for step in upgrade_steps:
185 187 print ('performing upgrade step %s' % step)
186 188 getattr(UpgradeSteps(self), 'step_%s' % step)()
187 189 self.sa.commit()
188 190
189 191 def fix_repo_paths(self):
190 192 """
191 193 Fixes a old rhodecode version path into new one without a '*'
192 194 """
193 195
194 196 paths = self.sa.query(RhodeCodeUi)\
195 197 .filter(RhodeCodeUi.ui_key == '/')\
196 198 .scalar()
197 199
198 200 paths.ui_value = paths.ui_value.replace('*', '')
199 201
200 202 try:
201 203 self.sa.add(paths)
202 204 self.sa.commit()
203 205 except:
204 206 self.sa.rollback()
205 207 raise
206 208
207 209 def fix_default_user(self):
208 210 """
209 211 Fixes a old default user with some 'nicer' default values,
210 212 used mostly for anonymous access
211 213 """
212 214 def_user = self.sa.query(User)\
213 215 .filter(User.username == 'default')\
214 216 .one()
215 217
216 218 def_user.name = 'Anonymous'
217 219 def_user.lastname = 'User'
218 220 def_user.email = 'anonymous@rhodecode.org'
219 221
220 222 try:
221 223 self.sa.add(def_user)
222 224 self.sa.commit()
223 225 except:
224 226 self.sa.rollback()
225 227 raise
226 228
227 229 def fix_settings(self):
228 230 """
229 231 Fixes rhodecode settings adds ga_code key for google analytics
230 232 """
231 233
232 234 hgsettings3 = RhodeCodeSetting('ga_code', '')
233 235
234 236 try:
235 237 self.sa.add(hgsettings3)
236 238 self.sa.commit()
237 239 except:
238 240 self.sa.rollback()
239 241 raise
240 242
241 243 def admin_prompt(self, second=False):
242 244 if not self.tests:
243 245 import getpass
244 246
245 247 def get_password():
246 248 password = getpass.getpass('Specify admin password '
247 249 '(min 6 chars):')
248 250 confirm = getpass.getpass('Confirm password:')
249 251
250 252 if password != confirm:
251 253 log.error('passwords mismatch')
252 254 return False
253 255 if len(password) < 6:
254 256 log.error('password is to short use at least 6 characters')
255 257 return False
256 258
257 259 return password
258 260
259 261 username = raw_input('Specify admin username:')
260 262
261 263 password = get_password()
262 264 if not password:
263 265 #second try
264 266 password = get_password()
265 267 if not password:
266 268 sys.exit()
267 269
268 270 email = raw_input('Specify admin email:')
269 271 self.create_user(username, password, email, True)
270 272 else:
271 273 log.info('creating admin and regular test users')
272 274 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
273 275 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
274 276 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
275 277 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
276 278 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
277 279
278 280 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
279 281 TEST_USER_ADMIN_EMAIL, True)
280 282
281 283 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
282 284 TEST_USER_REGULAR_EMAIL, False)
283 285
284 286 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
285 287 TEST_USER_REGULAR2_EMAIL, False)
286 288
287 289 def create_ui_settings(self):
288 290 """
289 291 Creates ui settings, fills out hooks
290 292 and disables dotencode
291 293 """
292 294
293 295 #HOOKS
294 296 hooks1_key = RhodeCodeUi.HOOK_UPDATE
295 297 hooks1_ = self.sa.query(RhodeCodeUi)\
296 298 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
297 299
298 300 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
299 301 hooks1.ui_section = 'hooks'
300 302 hooks1.ui_key = hooks1_key
301 303 hooks1.ui_value = 'hg update >&2'
302 304 hooks1.ui_active = False
303 305
304 306 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
305 307 hooks2_ = self.sa.query(RhodeCodeUi)\
306 308 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
307 309
308 310 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
309 311 hooks2.ui_section = 'hooks'
310 312 hooks2.ui_key = hooks2_key
311 313 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
312 314
313 315 hooks3 = RhodeCodeUi()
314 316 hooks3.ui_section = 'hooks'
315 317 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
316 318 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
317 319
318 320 hooks4 = RhodeCodeUi()
319 321 hooks4.ui_section = 'hooks'
320 322 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
321 323 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
322 324
323 325 # For mercurial 1.7 set backward comapatibility with format
324 326 dotencode_disable = RhodeCodeUi()
325 327 dotencode_disable.ui_section = 'format'
326 328 dotencode_disable.ui_key = 'dotencode'
327 329 dotencode_disable.ui_value = 'false'
328 330
329 331 # enable largefiles
330 332 largefiles = RhodeCodeUi()
331 333 largefiles.ui_section = 'extensions'
332 334 largefiles.ui_key = 'largefiles'
333 335 largefiles.ui_value = ''
334 336
335 337 self.sa.add(hooks1)
336 338 self.sa.add(hooks2)
337 339 self.sa.add(hooks3)
338 340 self.sa.add(hooks4)
339 341 self.sa.add(largefiles)
340 342
341 343 def create_ldap_options(self, skip_existing=False):
342 344 """Creates ldap settings"""
343 345
344 346 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
345 347 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
346 348 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
347 349 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
348 350 ('ldap_filter', ''), ('ldap_search_scope', ''),
349 351 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
350 352 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
351 353
352 354 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
353 355 log.debug('Skipping option %s' % k)
354 356 continue
355 357 setting = RhodeCodeSetting(k, v)
356 358 self.sa.add(setting)
357 359
358 360 def fixup_groups(self):
359 361 def_usr = User.get_by_username('default')
360 362 for g in RepoGroup.query().all():
361 363 g.group_name = g.get_new_name(g.name)
362 364 self.sa.add(g)
363 365 # get default perm
364 366 default = UserRepoGroupToPerm.query()\
365 367 .filter(UserRepoGroupToPerm.group == g)\
366 368 .filter(UserRepoGroupToPerm.user == def_usr)\
367 369 .scalar()
368 370
369 371 if default is None:
370 372 log.debug('missing default permission for group %s adding' % g)
371 373 ReposGroupModel()._create_default_perms(g)
372 374
373 375 def config_prompt(self, test_repo_path='', retries=3):
374 376 if retries == 3:
375 377 log.info('Setting up repositories config')
376 378
377 379 if not self.tests and not test_repo_path:
378 380 path = raw_input(
379 381 'Enter a valid absolute path to store repositories. '
380 382 'All repositories in that path will be added automatically:'
381 383 )
382 384 else:
383 385 path = test_repo_path
384 386 path_ok = True
385 387
386 388 # check proper dir
387 389 if not os.path.isdir(path):
388 390 path_ok = False
389 391 log.error('Given path %s is not a valid directory' % path)
390 392
391 393 elif not os.path.isabs(path):
392 394 path_ok = False
393 395 log.error('Given path %s is not an absolute path' % path)
394 396
395 397 # check write access
396 398 elif not os.access(path, os.W_OK) and path_ok:
397 399 path_ok = False
398 400 log.error('No write permission to given path %s' % path)
399 401
400 402 if retries == 0:
401 403 sys.exit('max retries reached')
402 404 if path_ok is False:
403 405 retries -= 1
404 406 return self.config_prompt(test_repo_path, retries)
405 407
406 408 return path
407 409
408 410 def create_settings(self, path):
409 411
410 412 self.create_ui_settings()
411 413
412 414 #HG UI OPTIONS
413 415 web1 = RhodeCodeUi()
414 416 web1.ui_section = 'web'
415 417 web1.ui_key = 'push_ssl'
416 418 web1.ui_value = 'false'
417 419
418 420 web2 = RhodeCodeUi()
419 421 web2.ui_section = 'web'
420 422 web2.ui_key = 'allow_archive'
421 423 web2.ui_value = 'gz zip bz2'
422 424
423 425 web3 = RhodeCodeUi()
424 426 web3.ui_section = 'web'
425 427 web3.ui_key = 'allow_push'
426 428 web3.ui_value = '*'
427 429
428 430 web4 = RhodeCodeUi()
429 431 web4.ui_section = 'web'
430 432 web4.ui_key = 'baseurl'
431 433 web4.ui_value = '/'
432 434
433 435 paths = RhodeCodeUi()
434 436 paths.ui_section = 'paths'
435 437 paths.ui_key = '/'
436 438 paths.ui_value = path
437 439
438 440 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
439 441 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
440 442 hgsettings3 = RhodeCodeSetting('ga_code', '')
441 443
442 444 self.sa.add(web1)
443 445 self.sa.add(web2)
444 446 self.sa.add(web3)
445 447 self.sa.add(web4)
446 448 self.sa.add(paths)
447 449 self.sa.add(hgsettings1)
448 450 self.sa.add(hgsettings2)
449 451 self.sa.add(hgsettings3)
450 452
451 453 self.create_ldap_options()
452 454
453 455 log.info('created ui config')
454 456
455 457 def create_user(self, username, password, email='', admin=False):
456 458 log.info('creating user %s' % username)
457 459 UserModel().create_or_update(username, password, email,
458 460 name='RhodeCode', lastname='Admin',
459 461 active=True, admin=admin)
460 462
461 463 def create_default_user(self):
462 464 log.info('creating default user')
463 465 # create default user for handling default permissions.
464 466 UserModel().create_or_update(username='default',
465 467 password=str(uuid.uuid1())[:8],
466 468 email='anonymous@rhodecode.org',
467 469 name='Anonymous', lastname='User')
468 470
469 471 def create_permissions(self):
470 472 # module.(access|create|change|delete)_[name]
471 473 # module.(none|read|write|admin)
472 474 perms = [
473 475 ('repository.none', 'Repository no access'),
474 476 ('repository.read', 'Repository read access'),
475 477 ('repository.write', 'Repository write access'),
476 478 ('repository.admin', 'Repository admin access'),
477 479
478 480 ('group.none', 'Repositories Group no access'),
479 481 ('group.read', 'Repositories Group read access'),
480 482 ('group.write', 'Repositories Group write access'),
481 483 ('group.admin', 'Repositories Group admin access'),
482 484
483 485 ('hg.admin', 'Hg Administrator'),
484 486 ('hg.create.repository', 'Repository create'),
485 487 ('hg.create.none', 'Repository creation disabled'),
486 488 ('hg.register.none', 'Register disabled'),
487 489 ('hg.register.manual_activate', 'Register new user with RhodeCode '
488 490 'without manual activation'),
489 491
490 492 ('hg.register.auto_activate', 'Register new user with RhodeCode '
491 493 'without auto activation'),
492 494 ]
493 495
494 496 for p in perms:
495 497 if not Permission.get_by_key(p[0]):
496 498 new_perm = Permission()
497 499 new_perm.permission_name = p[0]
498 500 new_perm.permission_longname = p[1]
499 501 self.sa.add(new_perm)
500 502
501 503 def populate_default_permissions(self):
502 504 log.info('creating default user permissions')
503 505
504 506 default_user = self.sa.query(User)\
505 507 .filter(User.username == 'default').scalar()
506 508
507 509 reg_perm = UserToPerm()
508 510 reg_perm.user = default_user
509 511 reg_perm.permission = self.sa.query(Permission)\
510 512 .filter(Permission.permission_name == 'hg.register.manual_activate')\
511 513 .scalar()
512 514
513 515 create_repo_perm = UserToPerm()
514 516 create_repo_perm.user = default_user
515 517 create_repo_perm.permission = self.sa.query(Permission)\
516 518 .filter(Permission.permission_name == 'hg.create.repository')\
517 519 .scalar()
518 520
519 521 default_repo_perm = UserToPerm()
520 522 default_repo_perm.user = default_user
521 523 default_repo_perm.permission = self.sa.query(Permission)\
522 524 .filter(Permission.permission_name == 'repository.read')\
523 525 .scalar()
524 526
525 527 self.sa.add(reg_perm)
526 528 self.sa.add(create_repo_perm)
527 529 self.sa.add(default_repo_perm)
@@ -1,1097 +1,1097
1 1 # -*- coding: utf-8 -*-
2 2 """
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
3 rhodecode.model.db_1_2_0
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 Database Models for RhodeCode
6 Database Models for RhodeCode <=1.2.X
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 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from rhodecode.lib.vcs import get_backend
38 38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 39 from rhodecode.lib.vcs.exceptions import VCSError
40 40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 43 generate_api_key, safe_unicode
44 44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 45 from rhodecode.lib.compat import json
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48 from rhodecode.lib.caching_query import FromCache
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 class ModelSerializer(json.JSONEncoder):
58 58 """
59 59 Simple Serializer for JSON,
60 60
61 61 usage::
62 62
63 63 to make object customized for serialization implement a __json__
64 64 method that will return a dict for serialization into json
65 65
66 66 example::
67 67
68 68 class Task(object):
69 69
70 70 def __init__(self, name, value):
71 71 self.name = name
72 72 self.value = value
73 73
74 74 def __json__(self):
75 75 return dict(name=self.name,
76 76 value=self.value)
77 77
78 78 """
79 79
80 80 def default(self, obj):
81 81
82 82 if hasattr(obj, '__json__'):
83 83 return obj.__json__()
84 84 else:
85 85 return json.JSONEncoder.default(self, obj)
86 86
87 87 class BaseModel(object):
88 88 """Base Model for all classess
89 89
90 90 """
91 91
92 92 @classmethod
93 93 def _get_keys(cls):
94 94 """return column names for this model """
95 95 return class_mapper(cls).c.keys()
96 96
97 97 def get_dict(self):
98 98 """return dict with keys and values corresponding
99 99 to this model data """
100 100
101 101 d = {}
102 102 for k in self._get_keys():
103 103 d[k] = getattr(self, k)
104 104 return d
105 105
106 106 def get_appstruct(self):
107 107 """return list with keys and values tupples corresponding
108 108 to this model data """
109 109
110 110 l = []
111 111 for k in self._get_keys():
112 112 l.append((k, getattr(self, k),))
113 113 return l
114 114
115 115 def populate_obj(self, populate_dict):
116 116 """populate model with data from given populate_dict"""
117 117
118 118 for k in self._get_keys():
119 119 if k in populate_dict:
120 120 setattr(self, k, populate_dict[k])
121 121
122 122 @classmethod
123 123 def query(cls):
124 124 return Session.query(cls)
125 125
126 126 @classmethod
127 127 def get(cls, id_):
128 128 if id_:
129 129 return cls.query().get(id_)
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session.delete(obj)
139 139 Session.commit()
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
145 145 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 146 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147 147 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
148 148
149 149 def __init__(self, k='', v=''):
150 150 self.app_settings_name = k
151 151 self.app_settings_value = v
152 152
153 153
154 154 @validates('_app_settings_value')
155 155 def validate_settings_value(self, key, val):
156 156 assert type(val) == unicode
157 157 return val
158 158
159 159 @hybrid_property
160 160 def app_settings_value(self):
161 161 v = self._app_settings_value
162 162 if v == 'ldap_active':
163 163 v = str2bool(v)
164 164 return v
165 165
166 166 @app_settings_value.setter
167 167 def app_settings_value(self, val):
168 168 """
169 169 Setter that will always make sure we use unicode in app_settings_value
170 170
171 171 :param val:
172 172 """
173 173 self._app_settings_value = safe_unicode(val)
174 174
175 175 def __repr__(self):
176 176 return "<%s('%s:%s')>" % (self.__class__.__name__,
177 177 self.app_settings_name, self.app_settings_value)
178 178
179 179
180 180 @classmethod
181 181 def get_by_name(cls, ldap_key):
182 182 return cls.query()\
183 183 .filter(cls.app_settings_name == ldap_key).scalar()
184 184
185 185 @classmethod
186 186 def get_app_settings(cls, cache=False):
187 187
188 188 ret = cls.query()
189 189
190 190 if cache:
191 191 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
192 192
193 193 if not ret:
194 194 raise Exception('Could not get application settings !')
195 195 settings = {}
196 196 for each in ret:
197 197 settings['rhodecode_' + each.app_settings_name] = \
198 198 each.app_settings_value
199 199
200 200 return settings
201 201
202 202 @classmethod
203 203 def get_ldap_settings(cls, cache=False):
204 204 ret = cls.query()\
205 205 .filter(cls.app_settings_name.startswith('ldap_')).all()
206 206 fd = {}
207 207 for row in ret:
208 208 fd.update({row.app_settings_name:row.app_settings_value})
209 209
210 210 return fd
211 211
212 212
213 213 class RhodeCodeUi(Base, BaseModel):
214 214 __tablename__ = 'rhodecode_ui'
215 215 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
216 216
217 217 HOOK_UPDATE = 'changegroup.update'
218 218 HOOK_REPO_SIZE = 'changegroup.repo_size'
219 219 HOOK_PUSH = 'pretxnchangegroup.push_logger'
220 220 HOOK_PULL = 'preoutgoing.pull_logger'
221 221
222 222 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
223 223 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 224 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 225 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
226 226 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
227 227
228 228
229 229 @classmethod
230 230 def get_by_key(cls, key):
231 231 return cls.query().filter(cls.ui_key == key)
232 232
233 233
234 234 @classmethod
235 235 def get_builtin_hooks(cls):
236 236 q = cls.query()
237 237 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
238 238 cls.HOOK_REPO_SIZE,
239 239 cls.HOOK_PUSH, cls.HOOK_PULL]))
240 240 return q.all()
241 241
242 242 @classmethod
243 243 def get_custom_hooks(cls):
244 244 q = cls.query()
245 245 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
246 246 cls.HOOK_REPO_SIZE,
247 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 248 q = q.filter(cls.ui_section == 'hooks')
249 249 return q.all()
250 250
251 251 @classmethod
252 252 def create_or_update_hook(cls, key, val):
253 253 new_ui = cls.get_by_key(key).scalar() or cls()
254 254 new_ui.ui_section = 'hooks'
255 255 new_ui.ui_active = True
256 256 new_ui.ui_key = key
257 257 new_ui.ui_value = val
258 258
259 259 Session.add(new_ui)
260 260 Session.commit()
261 261
262 262
263 263 class User(Base, BaseModel):
264 264 __tablename__ = 'users'
265 265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
266 266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
270 270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
271 271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 273 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
275 275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277 277
278 278 user_log = relationship('UserLog', cascade='all')
279 279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
280 280
281 281 repositories = relationship('Repository')
282 282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
283 283 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
284 284
285 285 group_member = relationship('UsersGroupMember', cascade='all')
286 286
287 287 @property
288 288 def full_contact(self):
289 289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
290 290
291 291 @property
292 292 def short_contact(self):
293 293 return '%s %s' % (self.name, self.lastname)
294 294
295 295 @property
296 296 def is_admin(self):
297 297 return self.admin
298 298
299 299 def __repr__(self):
300 300 try:
301 301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 302 self.user_id, self.username)
303 303 except:
304 304 return self.__class__.__name__
305 305
306 306 @classmethod
307 307 def get_by_username(cls, username, case_insensitive=False):
308 308 if case_insensitive:
309 309 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
310 310 else:
311 311 return Session.query(cls).filter(cls.username == username).scalar()
312 312
313 313 @classmethod
314 314 def get_by_api_key(cls, api_key):
315 315 return cls.query().filter(cls.api_key == api_key).one()
316 316
317 317 def update_lastlogin(self):
318 318 """Update user lastlogin"""
319 319
320 320 self.last_login = datetime.datetime.now()
321 321 Session.add(self)
322 322 Session.commit()
323 323 log.debug('updated user %s lastlogin' % self.username)
324 324
325 325 @classmethod
326 326 def create(cls, form_data):
327 327 from rhodecode.lib.auth import get_crypt_password
328 328
329 329 try:
330 330 new_user = cls()
331 331 for k, v in form_data.items():
332 332 if k == 'password':
333 333 v = get_crypt_password(v)
334 334 setattr(new_user, k, v)
335 335
336 336 new_user.api_key = generate_api_key(form_data['username'])
337 337 Session.add(new_user)
338 338 Session.commit()
339 339 return new_user
340 340 except:
341 341 log.error(traceback.format_exc())
342 342 Session.rollback()
343 343 raise
344 344
345 345 class UserLog(Base, BaseModel):
346 346 __tablename__ = 'user_logs'
347 347 __table_args__ = {'extend_existing':True}
348 348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
350 350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
351 351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
354 354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
355 355
356 356 @property
357 357 def action_as_day(self):
358 358 return date(*self.action_date.timetuple()[:3])
359 359
360 360 user = relationship('User')
361 361 repository = relationship('Repository')
362 362
363 363
364 364 class UsersGroup(Base, BaseModel):
365 365 __tablename__ = 'users_groups'
366 366 __table_args__ = {'extend_existing':True}
367 367
368 368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
369 369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
370 370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
371 371
372 372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
373 373
374 374 def __repr__(self):
375 375 return '<userGroup(%s)>' % (self.users_group_name)
376 376
377 377 @classmethod
378 378 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
379 379 if case_insensitive:
380 380 gr = cls.query()\
381 381 .filter(cls.users_group_name.ilike(group_name))
382 382 else:
383 383 gr = cls.query()\
384 384 .filter(cls.users_group_name == group_name)
385 385 if cache:
386 386 gr = gr.options(FromCache("sql_cache_short",
387 387 "get_user_%s" % group_name))
388 388 return gr.scalar()
389 389
390 390
391 391 @classmethod
392 392 def get(cls, users_group_id, cache=False):
393 393 users_group = cls.query()
394 394 if cache:
395 395 users_group = users_group.options(FromCache("sql_cache_short",
396 396 "get_users_group_%s" % users_group_id))
397 397 return users_group.get(users_group_id)
398 398
399 399 @classmethod
400 400 def create(cls, form_data):
401 401 try:
402 402 new_users_group = cls()
403 403 for k, v in form_data.items():
404 404 setattr(new_users_group, k, v)
405 405
406 406 Session.add(new_users_group)
407 407 Session.commit()
408 408 return new_users_group
409 409 except:
410 410 log.error(traceback.format_exc())
411 411 Session.rollback()
412 412 raise
413 413
414 414 @classmethod
415 415 def update(cls, users_group_id, form_data):
416 416
417 417 try:
418 418 users_group = cls.get(users_group_id, cache=False)
419 419
420 420 for k, v in form_data.items():
421 421 if k == 'users_group_members':
422 422 users_group.members = []
423 423 Session.flush()
424 424 members_list = []
425 425 if v:
426 426 v = [v] if isinstance(v, basestring) else v
427 427 for u_id in set(v):
428 428 member = UsersGroupMember(users_group_id, u_id)
429 429 members_list.append(member)
430 430 setattr(users_group, 'members', members_list)
431 431 setattr(users_group, k, v)
432 432
433 433 Session.add(users_group)
434 434 Session.commit()
435 435 except:
436 436 log.error(traceback.format_exc())
437 437 Session.rollback()
438 438 raise
439 439
440 440 @classmethod
441 441 def delete(cls, users_group_id):
442 442 try:
443 443
444 444 # check if this group is not assigned to repo
445 445 assigned_groups = UsersGroupRepoToPerm.query()\
446 446 .filter(UsersGroupRepoToPerm.users_group_id ==
447 447 users_group_id).all()
448 448
449 449 if assigned_groups:
450 450 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
451 451 assigned_groups)
452 452
453 453 users_group = cls.get(users_group_id, cache=False)
454 454 Session.delete(users_group)
455 455 Session.commit()
456 456 except:
457 457 log.error(traceback.format_exc())
458 458 Session.rollback()
459 459 raise
460 460
461 461 class UsersGroupMember(Base, BaseModel):
462 462 __tablename__ = 'users_groups_members'
463 463 __table_args__ = {'extend_existing':True}
464 464
465 465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
467 467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 468
469 469 user = relationship('User', lazy='joined')
470 470 users_group = relationship('UsersGroup')
471 471
472 472 def __init__(self, gr_id='', u_id=''):
473 473 self.users_group_id = gr_id
474 474 self.user_id = u_id
475 475
476 476 @staticmethod
477 477 def add_user_to_group(group, user):
478 478 ugm = UsersGroupMember()
479 479 ugm.users_group = group
480 480 ugm.user = user
481 481 Session.add(ugm)
482 482 Session.commit()
483 483 return ugm
484 484
485 485 class Repository(Base, BaseModel):
486 486 __tablename__ = 'repositories'
487 487 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
488 488
489 489 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 490 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 491 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
492 492 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
493 493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
494 494 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
495 495 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
496 496 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
497 497 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
498 498 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
499 499
500 500 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
501 501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
502 502
503 503
504 504 user = relationship('User')
505 505 fork = relationship('Repository', remote_side=repo_id)
506 506 group = relationship('RepoGroup')
507 507 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
508 508 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
509 509 stats = relationship('Statistics', cascade='all', uselist=False)
510 510
511 511 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
512 512
513 513 logs = relationship('UserLog', cascade='all')
514 514
515 515 def __repr__(self):
516 516 return "<%s('%s:%s')>" % (self.__class__.__name__,
517 517 self.repo_id, self.repo_name)
518 518
519 519 @classmethod
520 520 def url_sep(cls):
521 521 return '/'
522 522
523 523 @classmethod
524 524 def get_by_repo_name(cls, repo_name):
525 525 q = Session.query(cls).filter(cls.repo_name == repo_name)
526 526 q = q.options(joinedload(Repository.fork))\
527 527 .options(joinedload(Repository.user))\
528 528 .options(joinedload(Repository.group))
529 529 return q.one()
530 530
531 531 @classmethod
532 532 def get_repo_forks(cls, repo_id):
533 533 return cls.query().filter(Repository.fork_id == repo_id)
534 534
535 535 @classmethod
536 536 def base_path(cls):
537 537 """
538 538 Returns base path when all repos are stored
539 539
540 540 :param cls:
541 541 """
542 542 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
543 543 cls.url_sep())
544 544 q.options(FromCache("sql_cache_short", "repository_repo_path"))
545 545 return q.one().ui_value
546 546
547 547 @property
548 548 def just_name(self):
549 549 return self.repo_name.split(Repository.url_sep())[-1]
550 550
551 551 @property
552 552 def groups_with_parents(self):
553 553 groups = []
554 554 if self.group is None:
555 555 return groups
556 556
557 557 cur_gr = self.group
558 558 groups.insert(0, cur_gr)
559 559 while 1:
560 560 gr = getattr(cur_gr, 'parent_group', None)
561 561 cur_gr = cur_gr.parent_group
562 562 if gr is None:
563 563 break
564 564 groups.insert(0, gr)
565 565
566 566 return groups
567 567
568 568 @property
569 569 def groups_and_repo(self):
570 570 return self.groups_with_parents, self.just_name
571 571
572 572 @LazyProperty
573 573 def repo_path(self):
574 574 """
575 575 Returns base full path for that repository means where it actually
576 576 exists on a filesystem
577 577 """
578 578 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
579 579 Repository.url_sep())
580 580 q.options(FromCache("sql_cache_short", "repository_repo_path"))
581 581 return q.one().ui_value
582 582
583 583 @property
584 584 def repo_full_path(self):
585 585 p = [self.repo_path]
586 586 # we need to split the name by / since this is how we store the
587 587 # names in the database, but that eventually needs to be converted
588 588 # into a valid system path
589 589 p += self.repo_name.split(Repository.url_sep())
590 590 return os.path.join(*p)
591 591
592 592 def get_new_name(self, repo_name):
593 593 """
594 594 returns new full repository name based on assigned group and new new
595 595
596 596 :param group_name:
597 597 """
598 598 path_prefix = self.group.full_path_splitted if self.group else []
599 599 return Repository.url_sep().join(path_prefix + [repo_name])
600 600
601 601 @property
602 602 def _ui(self):
603 603 """
604 604 Creates an db based ui object for this repository
605 605 """
606 606 from mercurial import ui
607 607 from mercurial import config
608 608 baseui = ui.ui()
609 609
610 610 #clean the baseui object
611 611 baseui._ocfg = config.config()
612 612 baseui._ucfg = config.config()
613 613 baseui._tcfg = config.config()
614 614
615 615
616 616 ret = RhodeCodeUi.query()\
617 617 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
618 618
619 619 hg_ui = ret
620 620 for ui_ in hg_ui:
621 621 if ui_.ui_active:
622 622 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
623 623 ui_.ui_key, ui_.ui_value)
624 624 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
625 625
626 626 return baseui
627 627
628 628 @classmethod
629 629 def is_valid(cls, repo_name):
630 630 """
631 631 returns True if given repo name is a valid filesystem repository
632 632
633 633 :param cls:
634 634 :param repo_name:
635 635 """
636 636 from rhodecode.lib.utils import is_valid_repo
637 637
638 638 return is_valid_repo(repo_name, cls.base_path())
639 639
640 640
641 641 #==========================================================================
642 642 # SCM PROPERTIES
643 643 #==========================================================================
644 644
645 645 def get_changeset(self, rev):
646 646 return get_changeset_safe(self.scm_instance, rev)
647 647
648 648 @property
649 649 def tip(self):
650 650 return self.get_changeset('tip')
651 651
652 652 @property
653 653 def author(self):
654 654 return self.tip.author
655 655
656 656 @property
657 657 def last_change(self):
658 658 return self.scm_instance.last_change
659 659
660 660 #==========================================================================
661 661 # SCM CACHE INSTANCE
662 662 #==========================================================================
663 663
664 664 @property
665 665 def invalidate(self):
666 666 return CacheInvalidation.invalidate(self.repo_name)
667 667
668 668 def set_invalidate(self):
669 669 """
670 670 set a cache for invalidation for this instance
671 671 """
672 672 CacheInvalidation.set_invalidate(self.repo_name)
673 673
674 674 @LazyProperty
675 675 def scm_instance(self):
676 676 return self.__get_instance()
677 677
678 678 @property
679 679 def scm_instance_cached(self):
680 680 @cache_region('long_term')
681 681 def _c(repo_name):
682 682 return self.__get_instance()
683 683 rn = self.repo_name
684 684
685 685 inv = self.invalidate
686 686 if inv is not None:
687 687 region_invalidate(_c, None, rn)
688 688 # update our cache
689 689 CacheInvalidation.set_valid(inv.cache_key)
690 690 return _c(rn)
691 691
692 692 def __get_instance(self):
693 693
694 694 repo_full_path = self.repo_full_path
695 695
696 696 try:
697 697 alias = get_scm(repo_full_path)[0]
698 698 log.debug('Creating instance of %s repository' % alias)
699 699 backend = get_backend(alias)
700 700 except VCSError:
701 701 log.error(traceback.format_exc())
702 702 log.error('Perhaps this repository is in db and not in '
703 703 'filesystem run rescan repositories with '
704 704 '"destroy old data " option from admin panel')
705 705 return
706 706
707 707 if alias == 'hg':
708 708
709 709 repo = backend(safe_str(repo_full_path), create=False,
710 710 baseui=self._ui)
711 711 # skip hidden web repository
712 712 if repo._get_hidden():
713 713 return
714 714 else:
715 715 repo = backend(repo_full_path, create=False)
716 716
717 717 return repo
718 718
719 719
720 720 class Group(Base, BaseModel):
721 721 __tablename__ = 'groups'
722 722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
723 723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
724 724 __mapper_args__ = {'order_by':'group_name'}
725 725
726 726 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
727 727 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
728 728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
729 729 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
730 730
731 731 parent_group = relationship('Group', remote_side=group_id)
732 732
733 733 def __init__(self, group_name='', parent_group=None):
734 734 self.group_name = group_name
735 735 self.parent_group = parent_group
736 736
737 737 def __repr__(self):
738 738 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
739 739 self.group_name)
740 740
741 741 @classmethod
742 742 def groups_choices(cls):
743 743 from webhelpers.html import literal as _literal
744 744 repo_groups = [('', '')]
745 745 sep = ' &raquo; '
746 746 _name = lambda k: _literal(sep.join(k))
747 747
748 748 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
749 749 for x in cls.query().all()])
750 750
751 751 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
752 752 return repo_groups
753 753
754 754 @classmethod
755 755 def url_sep(cls):
756 756 return '/'
757 757
758 758 @classmethod
759 759 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
760 760 if case_insensitive:
761 761 gr = cls.query()\
762 762 .filter(cls.group_name.ilike(group_name))
763 763 else:
764 764 gr = cls.query()\
765 765 .filter(cls.group_name == group_name)
766 766 if cache:
767 767 gr = gr.options(FromCache("sql_cache_short",
768 768 "get_group_%s" % group_name))
769 769 return gr.scalar()
770 770
771 771 @property
772 772 def parents(self):
773 773 parents_recursion_limit = 5
774 774 groups = []
775 775 if self.parent_group is None:
776 776 return groups
777 777 cur_gr = self.parent_group
778 778 groups.insert(0, cur_gr)
779 779 cnt = 0
780 780 while 1:
781 781 cnt += 1
782 782 gr = getattr(cur_gr, 'parent_group', None)
783 783 cur_gr = cur_gr.parent_group
784 784 if gr is None:
785 785 break
786 786 if cnt == parents_recursion_limit:
787 787 # this will prevent accidental infinit loops
788 788 log.error('group nested more than %s' %
789 789 parents_recursion_limit)
790 790 break
791 791
792 792 groups.insert(0, gr)
793 793 return groups
794 794
795 795 @property
796 796 def children(self):
797 797 return Group.query().filter(Group.parent_group == self)
798 798
799 799 @property
800 800 def name(self):
801 801 return self.group_name.split(Group.url_sep())[-1]
802 802
803 803 @property
804 804 def full_path(self):
805 805 return self.group_name
806 806
807 807 @property
808 808 def full_path_splitted(self):
809 809 return self.group_name.split(Group.url_sep())
810 810
811 811 @property
812 812 def repositories(self):
813 813 return Repository.query().filter(Repository.group == self)
814 814
815 815 @property
816 816 def repositories_recursive_count(self):
817 817 cnt = self.repositories.count()
818 818
819 819 def children_count(group):
820 820 cnt = 0
821 821 for child in group.children:
822 822 cnt += child.repositories.count()
823 823 cnt += children_count(child)
824 824 return cnt
825 825
826 826 return cnt + children_count(self)
827 827
828 828
829 829 def get_new_name(self, group_name):
830 830 """
831 831 returns new full group name based on parent and new name
832 832
833 833 :param group_name:
834 834 """
835 835 path_prefix = (self.parent_group.full_path_splitted if
836 836 self.parent_group else [])
837 837 return Group.url_sep().join(path_prefix + [group_name])
838 838
839 839
840 840 class Permission(Base, BaseModel):
841 841 __tablename__ = 'permissions'
842 842 __table_args__ = {'extend_existing':True}
843 843 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
844 844 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
845 845 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
846 846
847 847 def __repr__(self):
848 848 return "<%s('%s:%s')>" % (self.__class__.__name__,
849 849 self.permission_id, self.permission_name)
850 850
851 851 @classmethod
852 852 def get_by_key(cls, key):
853 853 return cls.query().filter(cls.permission_name == key).scalar()
854 854
855 855 class UserRepoToPerm(Base, BaseModel):
856 856 __tablename__ = 'repo_to_perm'
857 857 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
858 858 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
860 860 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
861 861 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
862 862
863 863 user = relationship('User')
864 864 permission = relationship('Permission')
865 865 repository = relationship('Repository')
866 866
867 867 class UserToPerm(Base, BaseModel):
868 868 __tablename__ = 'user_to_perm'
869 869 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
870 870 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
871 871 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
872 872 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
873 873
874 874 user = relationship('User')
875 875 permission = relationship('Permission')
876 876
877 877 @classmethod
878 878 def has_perm(cls, user_id, perm):
879 879 if not isinstance(perm, Permission):
880 880 raise Exception('perm needs to be an instance of Permission class')
881 881
882 882 return cls.query().filter(cls.user_id == user_id)\
883 883 .filter(cls.permission == perm).scalar() is not None
884 884
885 885 @classmethod
886 886 def grant_perm(cls, user_id, perm):
887 887 if not isinstance(perm, Permission):
888 888 raise Exception('perm needs to be an instance of Permission class')
889 889
890 890 new = cls()
891 891 new.user_id = user_id
892 892 new.permission = perm
893 893 try:
894 894 Session.add(new)
895 895 Session.commit()
896 896 except:
897 897 Session.rollback()
898 898
899 899
900 900 @classmethod
901 901 def revoke_perm(cls, user_id, perm):
902 902 if not isinstance(perm, Permission):
903 903 raise Exception('perm needs to be an instance of Permission class')
904 904
905 905 try:
906 906 cls.query().filter(cls.user_id == user_id)\
907 907 .filter(cls.permission == perm).delete()
908 908 Session.commit()
909 909 except:
910 910 Session.rollback()
911 911
912 912 class UsersGroupRepoToPerm(Base, BaseModel):
913 913 __tablename__ = 'users_group_repo_to_perm'
914 914 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
915 915 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
916 916 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
917 917 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
918 918 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
919 919
920 920 users_group = relationship('UsersGroup')
921 921 permission = relationship('Permission')
922 922 repository = relationship('Repository')
923 923
924 924 def __repr__(self):
925 925 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
926 926
927 927 class UsersGroupToPerm(Base, BaseModel):
928 928 __tablename__ = 'users_group_to_perm'
929 929 __table_args__ = {'extend_existing':True}
930 930 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 931 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
932 932 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
933 933
934 934 users_group = relationship('UsersGroup')
935 935 permission = relationship('Permission')
936 936
937 937
938 938 @classmethod
939 939 def has_perm(cls, users_group_id, perm):
940 940 if not isinstance(perm, Permission):
941 941 raise Exception('perm needs to be an instance of Permission class')
942 942
943 943 return cls.query().filter(cls.users_group_id ==
944 944 users_group_id)\
945 945 .filter(cls.permission == perm)\
946 946 .scalar() is not None
947 947
948 948 @classmethod
949 949 def grant_perm(cls, users_group_id, perm):
950 950 if not isinstance(perm, Permission):
951 951 raise Exception('perm needs to be an instance of Permission class')
952 952
953 953 new = cls()
954 954 new.users_group_id = users_group_id
955 955 new.permission = perm
956 956 try:
957 957 Session.add(new)
958 958 Session.commit()
959 959 except:
960 960 Session.rollback()
961 961
962 962
963 963 @classmethod
964 964 def revoke_perm(cls, users_group_id, perm):
965 965 if not isinstance(perm, Permission):
966 966 raise Exception('perm needs to be an instance of Permission class')
967 967
968 968 try:
969 969 cls.query().filter(cls.users_group_id == users_group_id)\
970 970 .filter(cls.permission == perm).delete()
971 971 Session.commit()
972 972 except:
973 973 Session.rollback()
974 974
975 975
976 976 class UserRepoGroupToPerm(Base, BaseModel):
977 977 __tablename__ = 'group_to_perm'
978 978 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
979 979
980 980 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
981 981 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
982 982 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
983 983 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
984 984
985 985 user = relationship('User')
986 986 permission = relationship('Permission')
987 987 group = relationship('RepoGroup')
988 988
989 989 class Statistics(Base, BaseModel):
990 990 __tablename__ = 'statistics'
991 991 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
992 992 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
993 993 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
994 994 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
995 995 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
996 996 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
997 997 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
998 998
999 999 repository = relationship('Repository', single_parent=True)
1000 1000
1001 1001 class UserFollowing(Base, BaseModel):
1002 1002 __tablename__ = 'user_followings'
1003 1003 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1004 1004 UniqueConstraint('user_id', 'follows_user_id')
1005 1005 , {'extend_existing':True})
1006 1006
1007 1007 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1008 1008 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1009 1009 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1010 1010 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1011 1011 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1012 1012
1013 1013 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1014 1014
1015 1015 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1016 1016 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1017 1017
1018 1018
1019 1019 @classmethod
1020 1020 def get_repo_followers(cls, repo_id):
1021 1021 return cls.query().filter(cls.follows_repo_id == repo_id)
1022 1022
1023 1023 class CacheInvalidation(Base, BaseModel):
1024 1024 __tablename__ = 'cache_invalidation'
1025 1025 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1026 1026 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1027 1027 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1028 1028 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1029 1029 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1030 1030
1031 1031
1032 1032 def __init__(self, cache_key, cache_args=''):
1033 1033 self.cache_key = cache_key
1034 1034 self.cache_args = cache_args
1035 1035 self.cache_active = False
1036 1036
1037 1037 def __repr__(self):
1038 1038 return "<%s('%s:%s')>" % (self.__class__.__name__,
1039 1039 self.cache_id, self.cache_key)
1040 1040
1041 1041 @classmethod
1042 1042 def invalidate(cls, key):
1043 1043 """
1044 1044 Returns Invalidation object if this given key should be invalidated
1045 1045 None otherwise. `cache_active = False` means that this cache
1046 1046 state is not valid and needs to be invalidated
1047 1047
1048 1048 :param key:
1049 1049 """
1050 1050 return cls.query()\
1051 1051 .filter(CacheInvalidation.cache_key == key)\
1052 1052 .filter(CacheInvalidation.cache_active == False)\
1053 1053 .scalar()
1054 1054
1055 1055 @classmethod
1056 1056 def set_invalidate(cls, key):
1057 1057 """
1058 1058 Mark this Cache key for invalidation
1059 1059
1060 1060 :param key:
1061 1061 """
1062 1062
1063 1063 log.debug('marking %s for invalidation' % key)
1064 1064 inv_obj = Session.query(cls)\
1065 1065 .filter(cls.cache_key == key).scalar()
1066 1066 if inv_obj:
1067 1067 inv_obj.cache_active = False
1068 1068 else:
1069 1069 log.debug('cache key not found in invalidation db -> creating one')
1070 1070 inv_obj = CacheInvalidation(key)
1071 1071
1072 1072 try:
1073 1073 Session.add(inv_obj)
1074 1074 Session.commit()
1075 1075 except Exception:
1076 1076 log.error(traceback.format_exc())
1077 1077 Session.rollback()
1078 1078
1079 1079 @classmethod
1080 1080 def set_valid(cls, key):
1081 1081 """
1082 1082 Mark this cache key as active and currently cached
1083 1083
1084 1084 :param key:
1085 1085 """
1086 1086 inv_obj = Session.query(CacheInvalidation)\
1087 1087 .filter(CacheInvalidation.cache_key == key).scalar()
1088 1088 inv_obj.cache_active = True
1089 1089 Session.add(inv_obj)
1090 1090 Session.commit()
1091 1091
1092 1092 class DbMigrateVersion(Base, BaseModel):
1093 1093 __tablename__ = 'db_migrate_version'
1094 1094 __table_args__ = {'extend_existing':True}
1095 1095 repository_id = Column('repository_id', String(250), primary_key=True)
1096 1096 repository_path = Column('repository_path', Text)
1097 1097 version = Column('version', Integer)
This diff has been collapsed as it changes many lines, (1274 lines changed) Show them Hide them
@@ -1,28 +1,1292
1 1 # -*- coding: utf-8 -*-
2 2 """
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
3 rhodecode.model.db_1_3_0
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 Database Models for RhodeCode
6 Database Models for RhodeCode <=1.3.X
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 #TODO: when branch 1.3 is finished replacem with db.py content
26 import os
27 import logging
28 import datetime
29 import traceback
30 from collections import defaultdict
31
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
36
37 from rhodecode.lib.vcs import get_backend
38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 safe_unicode
44 from rhodecode.lib.compat import json
45 from rhodecode.lib.caching_query import FromCache
46
47 from rhodecode.model.meta import Base, Session
48 import hashlib
49
50
51 log = logging.getLogger(__name__)
52
53 #==============================================================================
54 # BASE CLASSES
55 #==============================================================================
56
57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58
59
60 class ModelSerializer(json.JSONEncoder):
61 """
62 Simple Serializer for JSON,
63
64 usage::
65
66 to make object customized for serialization implement a __json__
67 method that will return a dict for serialization into json
68
69 example::
70
71 class Task(object):
72
73 def __init__(self, name, value):
74 self.name = name
75 self.value = value
76
77 def __json__(self):
78 return dict(name=self.name,
79 value=self.value)
80
81 """
82
83 def default(self, obj):
84
85 if hasattr(obj, '__json__'):
86 return obj.__json__()
87 else:
88 return json.JSONEncoder.default(self, obj)
89
90
91 class BaseModel(object):
92 """
93 Base Model for all classess
94 """
95
96 @classmethod
97 def _get_keys(cls):
98 """return column names for this model """
99 return class_mapper(cls).c.keys()
100
101 def get_dict(self):
102 """
103 return dict with keys and values corresponding
104 to this model data """
105
106 d = {}
107 for k in self._get_keys():
108 d[k] = getattr(self, k)
109
110 # also use __json__() if present to get additional fields
111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 d[k] = val
113 return d
114
115 def get_appstruct(self):
116 """return list with keys and values tupples corresponding
117 to this model data """
118
119 l = []
120 for k in self._get_keys():
121 l.append((k, getattr(self, k),))
122 return l
123
124 def populate_obj(self, populate_dict):
125 """populate model with data from given populate_dict"""
126
127 for k in self._get_keys():
128 if k in populate_dict:
129 setattr(self, k, populate_dict[k])
130
131 @classmethod
132 def query(cls):
133 return Session.query(cls)
134
135 @classmethod
136 def get(cls, id_):
137 if id_:
138 return cls.query().get(id_)
139
140 @classmethod
141 def getAll(cls):
142 return cls.query().all()
143
144 @classmethod
145 def delete(cls, id_):
146 obj = cls.query().get(id_)
147 Session.delete(obj)
148
149 def __repr__(self):
150 if hasattr(self, '__unicode__'):
151 # python repr needs to return str
152 return safe_str(self.__unicode__())
153 return '<DB:%s>' % (self.__class__.__name__)
154
155 class RhodeCodeSetting(Base, BaseModel):
156 __tablename__ = 'rhodecode_settings'
157 __table_args__ = (
158 UniqueConstraint('app_settings_name'),
159 {'extend_existing': True, 'mysql_engine':'InnoDB',
160 'mysql_charset': 'utf8'}
161 )
162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
163 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
164 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165
166 def __init__(self, k='', v=''):
167 self.app_settings_name = k
168 self.app_settings_value = v
169
170 @validates('_app_settings_value')
171 def validate_settings_value(self, key, val):
172 assert type(val) == unicode
173 return val
174
175 @hybrid_property
176 def app_settings_value(self):
177 v = self._app_settings_value
178 if self.app_settings_name == 'ldap_active':
179 v = str2bool(v)
180 return v
181
182 @app_settings_value.setter
183 def app_settings_value(self, val):
184 """
185 Setter that will always make sure we use unicode in app_settings_value
186
187 :param val:
188 """
189 self._app_settings_value = safe_unicode(val)
190
191 def __unicode__(self):
192 return u"<%s('%s:%s')>" % (
193 self.__class__.__name__,
194 self.app_settings_name, self.app_settings_value
195 )
196
197 @classmethod
198 def get_by_name(cls, ldap_key):
199 return cls.query()\
200 .filter(cls.app_settings_name == ldap_key).scalar()
201
202 @classmethod
203 def get_app_settings(cls, cache=False):
204
205 ret = cls.query()
206
207 if cache:
208 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
209
210 if not ret:
211 raise Exception('Could not get application settings !')
212 settings = {}
213 for each in ret:
214 settings['rhodecode_' + each.app_settings_name] = \
215 each.app_settings_value
216
217 return settings
218
219 @classmethod
220 def get_ldap_settings(cls, cache=False):
221 ret = cls.query()\
222 .filter(cls.app_settings_name.startswith('ldap_')).all()
223 fd = {}
224 for row in ret:
225 fd.update({row.app_settings_name:row.app_settings_value})
226
227 return fd
228
229
230 class RhodeCodeUi(Base, BaseModel):
231 __tablename__ = 'rhodecode_ui'
232 __table_args__ = (
233 UniqueConstraint('ui_key'),
234 {'extend_existing': True, 'mysql_engine':'InnoDB',
235 'mysql_charset': 'utf8'}
236 )
237
238 HOOK_UPDATE = 'changegroup.update'
239 HOOK_REPO_SIZE = 'changegroup.repo_size'
240 HOOK_PUSH = 'pretxnchangegroup.push_logger'
241 HOOK_PULL = 'preoutgoing.pull_logger'
242
243 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
248
249 @classmethod
250 def get_by_key(cls, key):
251 return cls.query().filter(cls.ui_key == key)
252
253 @classmethod
254 def get_builtin_hooks(cls):
255 q = cls.query()
256 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
257 cls.HOOK_REPO_SIZE,
258 cls.HOOK_PUSH, cls.HOOK_PULL]))
259 return q.all()
260
261 @classmethod
262 def get_custom_hooks(cls):
263 q = cls.query()
264 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
265 cls.HOOK_REPO_SIZE,
266 cls.HOOK_PUSH, cls.HOOK_PULL]))
267 q = q.filter(cls.ui_section == 'hooks')
268 return q.all()
269
270 @classmethod
271 def create_or_update_hook(cls, key, val):
272 new_ui = cls.get_by_key(key).scalar() or cls()
273 new_ui.ui_section = 'hooks'
274 new_ui.ui_active = True
275 new_ui.ui_key = key
276 new_ui.ui_value = val
277
278 Session.add(new_ui)
279
280
281 class User(Base, BaseModel):
282 __tablename__ = 'users'
283 __table_args__ = (
284 UniqueConstraint('username'), UniqueConstraint('email'),
285 {'extend_existing': True, 'mysql_engine':'InnoDB',
286 'mysql_charset': 'utf8'}
287 )
288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
289 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
292 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
293 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
297 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299
300 user_log = relationship('UserLog', cascade='all')
301 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
302
303 repositories = relationship('Repository')
304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
305 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
306 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
307
308 group_member = relationship('UsersGroupMember', cascade='all')
309
310 notifications = relationship('UserNotification', cascade='all')
311 # notifications assigned to this user
312 user_created_notifications = relationship('Notification', cascade='all')
313 # comments created by this user
314 user_comments = relationship('ChangesetComment', cascade='all')
315
316 @hybrid_property
317 def email(self):
318 return self._email
319
320 @email.setter
321 def email(self, val):
322 self._email = val.lower() if val else None
323
324 @property
325 def full_name(self):
326 return '%s %s' % (self.name, self.lastname)
327
328 @property
329 def full_name_or_username(self):
330 return ('%s %s' % (self.name, self.lastname)
331 if (self.name and self.lastname) else self.username)
332
333 @property
334 def full_contact(self):
335 return '%s %s <%s>' % (self.name, self.lastname, self.email)
336
337 @property
338 def short_contact(self):
339 return '%s %s' % (self.name, self.lastname)
340
341 @property
342 def is_admin(self):
343 return self.admin
344
345 def __unicode__(self):
346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
347 self.user_id, self.username)
348
349 @classmethod
350 def get_by_username(cls, username, case_insensitive=False, cache=False):
351 if case_insensitive:
352 q = cls.query().filter(cls.username.ilike(username))
353 else:
354 q = cls.query().filter(cls.username == username)
355
356 if cache:
357 q = q.options(FromCache(
358 "sql_cache_short",
359 "get_user_%s" % _hash_key(username)
360 )
361 )
362 return q.scalar()
363
364 @classmethod
365 def get_by_api_key(cls, api_key, cache=False):
366 q = cls.query().filter(cls.api_key == api_key)
367
368 if cache:
369 q = q.options(FromCache("sql_cache_short",
370 "get_api_key_%s" % api_key))
371 return q.scalar()
372
373 @classmethod
374 def get_by_email(cls, email, case_insensitive=False, cache=False):
375 if case_insensitive:
376 q = cls.query().filter(cls.email.ilike(email))
377 else:
378 q = cls.query().filter(cls.email == email)
379
380 if cache:
381 q = q.options(FromCache("sql_cache_short",
382 "get_api_key_%s" % email))
383 return q.scalar()
384
385 def update_lastlogin(self):
386 """Update user lastlogin"""
387 self.last_login = datetime.datetime.now()
388 Session.add(self)
389 log.debug('updated user %s lastlogin' % self.username)
390
391 def __json__(self):
392 return dict(
393 user_id=self.user_id,
394 first_name=self.name,
395 last_name=self.lastname,
396 email=self.email,
397 full_name=self.full_name,
398 full_name_or_username=self.full_name_or_username,
399 short_contact=self.short_contact,
400 full_contact=self.full_contact
401 )
402
403
404 class UserLog(Base, BaseModel):
405 __tablename__ = 'user_logs'
406 __table_args__ = (
407 {'extend_existing': True, 'mysql_engine':'InnoDB',
408 'mysql_charset': 'utf8'},
409 )
410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
412 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
413 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
414 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
417
418 @property
419 def action_as_day(self):
420 return datetime.date(*self.action_date.timetuple()[:3])
421
422 user = relationship('User')
423 repository = relationship('Repository', cascade='')
424
425
426 class UsersGroup(Base, BaseModel):
427 __tablename__ = 'users_groups'
428 __table_args__ = (
429 {'extend_existing': True, 'mysql_engine':'InnoDB',
430 'mysql_charset': 'utf8'},
431 )
432
433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
434 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
436
437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
438 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
439 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
440
441 def __unicode__(self):
442 return u'<userGroup(%s)>' % (self.users_group_name)
443
444 @classmethod
445 def get_by_group_name(cls, group_name, cache=False,
446 case_insensitive=False):
447 if case_insensitive:
448 q = cls.query().filter(cls.users_group_name.ilike(group_name))
449 else:
450 q = cls.query().filter(cls.users_group_name == group_name)
451 if cache:
452 q = q.options(FromCache(
453 "sql_cache_short",
454 "get_user_%s" % _hash_key(group_name)
455 )
456 )
457 return q.scalar()
458
459 @classmethod
460 def get(cls, users_group_id, cache=False):
461 users_group = cls.query()
462 if cache:
463 users_group = users_group.options(FromCache("sql_cache_short",
464 "get_users_group_%s" % users_group_id))
465 return users_group.get(users_group_id)
466
467
468 class UsersGroupMember(Base, BaseModel):
469 __tablename__ = 'users_groups_members'
470 __table_args__ = (
471 {'extend_existing': True, 'mysql_engine':'InnoDB',
472 'mysql_charset': 'utf8'},
473 )
474
475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
477 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
478
479 user = relationship('User', lazy='joined')
480 users_group = relationship('UsersGroup')
481
482 def __init__(self, gr_id='', u_id=''):
483 self.users_group_id = gr_id
484 self.user_id = u_id
485
486
487 class Repository(Base, BaseModel):
488 __tablename__ = 'repositories'
489 __table_args__ = (
490 UniqueConstraint('repo_name'),
491 {'extend_existing': True, 'mysql_engine':'InnoDB',
492 'mysql_charset': 'utf8'},
493 )
494
495 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
496 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
497 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
498 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
499 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
500 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
501 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
502 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
503 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
505
506 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
508
509 user = relationship('User')
510 fork = relationship('Repository', remote_side=repo_id)
511 group = relationship('RepoGroup')
512 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
513 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
514 stats = relationship('Statistics', cascade='all', uselist=False)
515
516 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
517
518 logs = relationship('UserLog')
519
520 def __unicode__(self):
521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
522 self.repo_name)
523
524 @classmethod
525 def url_sep(cls):
526 return '/'
527
528 @classmethod
529 def get_by_repo_name(cls, repo_name):
530 q = Session.query(cls).filter(cls.repo_name == repo_name)
531 q = q.options(joinedload(Repository.fork))\
532 .options(joinedload(Repository.user))\
533 .options(joinedload(Repository.group))
534 return q.scalar()
535
536 @classmethod
537 def get_repo_forks(cls, repo_id):
538 return cls.query().filter(Repository.fork_id == repo_id)
539
540 @classmethod
541 def base_path(cls):
542 """
543 Returns base path when all repos are stored
544
545 :param cls:
546 """
547 q = Session.query(RhodeCodeUi)\
548 .filter(RhodeCodeUi.ui_key == cls.url_sep())
549 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
550 return q.one().ui_value
551
552 @property
553 def just_name(self):
554 return self.repo_name.split(Repository.url_sep())[-1]
555
556 @property
557 def groups_with_parents(self):
558 groups = []
559 if self.group is None:
560 return groups
561
562 cur_gr = self.group
563 groups.insert(0, cur_gr)
564 while 1:
565 gr = getattr(cur_gr, 'parent_group', None)
566 cur_gr = cur_gr.parent_group
567 if gr is None:
568 break
569 groups.insert(0, gr)
570
571 return groups
572
573 @property
574 def groups_and_repo(self):
575 return self.groups_with_parents, self.just_name
576
577 @LazyProperty
578 def repo_path(self):
579 """
580 Returns base full path for that repository means where it actually
581 exists on a filesystem
582 """
583 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
584 Repository.url_sep())
585 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
586 return q.one().ui_value
587
588 @property
589 def repo_full_path(self):
590 p = [self.repo_path]
591 # we need to split the name by / since this is how we store the
592 # names in the database, but that eventually needs to be converted
593 # into a valid system path
594 p += self.repo_name.split(Repository.url_sep())
595 return os.path.join(*p)
596
597 def get_new_name(self, repo_name):
598 """
599 returns new full repository name based on assigned group and new new
600
601 :param group_name:
602 """
603 path_prefix = self.group.full_path_splitted if self.group else []
604 return Repository.url_sep().join(path_prefix + [repo_name])
605
606 @property
607 def _ui(self):
608 """
609 Creates an db based ui object for this repository
610 """
611 from mercurial import ui
612 from mercurial import config
613 baseui = ui.ui()
614
615 #clean the baseui object
616 baseui._ocfg = config.config()
617 baseui._ucfg = config.config()
618 baseui._tcfg = config.config()
619
620 ret = RhodeCodeUi.query()\
621 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
622
623 hg_ui = ret
624 for ui_ in hg_ui:
625 if ui_.ui_active:
626 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
627 ui_.ui_key, ui_.ui_value)
628 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
629
630 return baseui
631
632 @classmethod
633 def is_valid(cls, repo_name):
634 """
635 returns True if given repo name is a valid filesystem repository
636
637 :param cls:
638 :param repo_name:
639 """
640 from rhodecode.lib.utils import is_valid_repo
641
642 return is_valid_repo(repo_name, cls.base_path())
643
644 #==========================================================================
645 # SCM PROPERTIES
646 #==========================================================================
647
648 def get_changeset(self, rev):
649 return get_changeset_safe(self.scm_instance, rev)
650
651 @property
652 def tip(self):
653 return self.get_changeset('tip')
654
655 @property
656 def author(self):
657 return self.tip.author
27 658
28 from rhodecode.model.db import *
659 @property
660 def last_change(self):
661 return self.scm_instance.last_change
662
663 def comments(self, revisions=None):
664 """
665 Returns comments for this repository grouped by revisions
666
667 :param revisions: filter query by revisions only
668 """
669 cmts = ChangesetComment.query()\
670 .filter(ChangesetComment.repo == self)
671 if revisions:
672 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
673 grouped = defaultdict(list)
674 for cmt in cmts.all():
675 grouped[cmt.revision].append(cmt)
676 return grouped
677
678 #==========================================================================
679 # SCM CACHE INSTANCE
680 #==========================================================================
681
682 @property
683 def invalidate(self):
684 return CacheInvalidation.invalidate(self.repo_name)
685
686 def set_invalidate(self):
687 """
688 set a cache for invalidation for this instance
689 """
690 CacheInvalidation.set_invalidate(self.repo_name)
691
692 @LazyProperty
693 def scm_instance(self):
694 return self.__get_instance()
695
696 @property
697 def scm_instance_cached(self):
698 @cache_region('long_term')
699 def _c(repo_name):
700 return self.__get_instance()
701 rn = self.repo_name
702 log.debug('Getting cached instance of repo')
703 inv = self.invalidate
704 if inv is not None:
705 region_invalidate(_c, None, rn)
706 # update our cache
707 CacheInvalidation.set_valid(inv.cache_key)
708 return _c(rn)
709
710 def __get_instance(self):
711 repo_full_path = self.repo_full_path
712 try:
713 alias = get_scm(repo_full_path)[0]
714 log.debug('Creating instance of %s repository' % alias)
715 backend = get_backend(alias)
716 except VCSError:
717 log.error(traceback.format_exc())
718 log.error('Perhaps this repository is in db and not in '
719 'filesystem run rescan repositories with '
720 '"destroy old data " option from admin panel')
721 return
722
723 if alias == 'hg':
724
725 repo = backend(safe_str(repo_full_path), create=False,
726 baseui=self._ui)
727 # skip hidden web repository
728 if repo._get_hidden():
729 return
730 else:
731 repo = backend(repo_full_path, create=False)
732
733 return repo
734
735
736 class RepoGroup(Base, BaseModel):
737 __tablename__ = 'groups'
738 __table_args__ = (
739 UniqueConstraint('group_name', 'group_parent_id'),
740 CheckConstraint('group_id != group_parent_id'),
741 {'extend_existing': True, 'mysql_engine':'InnoDB',
742 'mysql_charset': 'utf8'},
743 )
744 __mapper_args__ = {'order_by': 'group_name'}
745
746 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
747 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
748 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
749 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
750
751 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
752 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
753
754 parent_group = relationship('RepoGroup', remote_side=group_id)
755
756 def __init__(self, group_name='', parent_group=None):
757 self.group_name = group_name
758 self.parent_group = parent_group
759
760 def __unicode__(self):
761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
762 self.group_name)
763
764 @classmethod
765 def groups_choices(cls):
766 from webhelpers.html import literal as _literal
767 repo_groups = [('', '')]
768 sep = ' &raquo; '
769 _name = lambda k: _literal(sep.join(k))
770
771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
772 for x in cls.query().all()])
773
774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
775 return repo_groups
776
777 @classmethod
778 def url_sep(cls):
779 return '/'
780
781 @classmethod
782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
783 if case_insensitive:
784 gr = cls.query()\
785 .filter(cls.group_name.ilike(group_name))
786 else:
787 gr = cls.query()\
788 .filter(cls.group_name == group_name)
789 if cache:
790 gr = gr.options(FromCache(
791 "sql_cache_short",
792 "get_group_%s" % _hash_key(group_name)
793 )
794 )
795 return gr.scalar()
796
797 @property
798 def parents(self):
799 parents_recursion_limit = 5
800 groups = []
801 if self.parent_group is None:
802 return groups
803 cur_gr = self.parent_group
804 groups.insert(0, cur_gr)
805 cnt = 0
806 while 1:
807 cnt += 1
808 gr = getattr(cur_gr, 'parent_group', None)
809 cur_gr = cur_gr.parent_group
810 if gr is None:
811 break
812 if cnt == parents_recursion_limit:
813 # this will prevent accidental infinit loops
814 log.error('group nested more than %s' %
815 parents_recursion_limit)
816 break
817
818 groups.insert(0, gr)
819 return groups
820
821 @property
822 def children(self):
823 return RepoGroup.query().filter(RepoGroup.parent_group == self)
824
825 @property
826 def name(self):
827 return self.group_name.split(RepoGroup.url_sep())[-1]
828
829 @property
830 def full_path(self):
831 return self.group_name
832
833 @property
834 def full_path_splitted(self):
835 return self.group_name.split(RepoGroup.url_sep())
836
837 @property
838 def repositories(self):
839 return Repository.query()\
840 .filter(Repository.group == self)\
841 .order_by(Repository.repo_name)
842
843 @property
844 def repositories_recursive_count(self):
845 cnt = self.repositories.count()
846
847 def children_count(group):
848 cnt = 0
849 for child in group.children:
850 cnt += child.repositories.count()
851 cnt += children_count(child)
852 return cnt
853
854 return cnt + children_count(self)
855
856 def get_new_name(self, group_name):
857 """
858 returns new full group name based on parent and new name
859
860 :param group_name:
861 """
862 path_prefix = (self.parent_group.full_path_splitted if
863 self.parent_group else [])
864 return RepoGroup.url_sep().join(path_prefix + [group_name])
865
866
867 class Permission(Base, BaseModel):
868 __tablename__ = 'permissions'
869 __table_args__ = (
870 {'extend_existing': True, 'mysql_engine':'InnoDB',
871 'mysql_charset': 'utf8'},
872 )
873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
875 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
876
877 def __unicode__(self):
878 return u"<%s('%s:%s')>" % (
879 self.__class__.__name__, self.permission_id, self.permission_name
880 )
881
882 @classmethod
883 def get_by_key(cls, key):
884 return cls.query().filter(cls.permission_name == key).scalar()
885
886 @classmethod
887 def get_default_perms(cls, default_user_id):
888 q = Session.query(UserRepoToPerm, Repository, cls)\
889 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
890 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
891 .filter(UserRepoToPerm.user_id == default_user_id)
892
893 return q.all()
894
895 @classmethod
896 def get_default_group_perms(cls, default_user_id):
897 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
898 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
899 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
900 .filter(UserRepoGroupToPerm.user_id == default_user_id)
901
902 return q.all()
903
904
905 class UserRepoToPerm(Base, BaseModel):
906 __tablename__ = 'repo_to_perm'
907 __table_args__ = (
908 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
909 {'extend_existing': True, 'mysql_engine':'InnoDB',
910 'mysql_charset': 'utf8'}
911 )
912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
916
917 user = relationship('User')
918 repository = relationship('Repository')
919 permission = relationship('Permission')
920
921 @classmethod
922 def create(cls, user, repository, permission):
923 n = cls()
924 n.user = user
925 n.repository = repository
926 n.permission = permission
927 Session.add(n)
928 return n
929
930 def __unicode__(self):
931 return u'<user:%s => %s >' % (self.user, self.repository)
932
933
934 class UserToPerm(Base, BaseModel):
935 __tablename__ = 'user_to_perm'
936 __table_args__ = (
937 UniqueConstraint('user_id', 'permission_id'),
938 {'extend_existing': True, 'mysql_engine':'InnoDB',
939 'mysql_charset': 'utf8'}
940 )
941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
943 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
944
945 user = relationship('User')
946 permission = relationship('Permission', lazy='joined')
947
948
949 class UsersGroupRepoToPerm(Base, BaseModel):
950 __tablename__ = 'users_group_repo_to_perm'
951 __table_args__ = (
952 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
953 {'extend_existing': True, 'mysql_engine':'InnoDB',
954 'mysql_charset': 'utf8'}
955 )
956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
959 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
960
961 users_group = relationship('UsersGroup')
962 permission = relationship('Permission')
963 repository = relationship('Repository')
964
965 @classmethod
966 def create(cls, users_group, repository, permission):
967 n = cls()
968 n.users_group = users_group
969 n.repository = repository
970 n.permission = permission
971 Session.add(n)
972 return n
973
974 def __unicode__(self):
975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
976
977
978 class UsersGroupToPerm(Base, BaseModel):
979 __tablename__ = 'users_group_to_perm'
980 __table_args__ = (
981 UniqueConstraint('users_group_id', 'permission_id',),
982 {'extend_existing': True, 'mysql_engine':'InnoDB',
983 'mysql_charset': 'utf8'}
984 )
985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988
989 users_group = relationship('UsersGroup')
990 permission = relationship('Permission')
991
992
993 class UserRepoGroupToPerm(Base, BaseModel):
994 __tablename__ = 'user_repo_group_to_perm'
995 __table_args__ = (
996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
997 {'extend_existing': True, 'mysql_engine':'InnoDB',
998 'mysql_charset': 'utf8'}
999 )
1000
1001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1003 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1005
1006 user = relationship('User')
1007 group = relationship('RepoGroup')
1008 permission = relationship('Permission')
1009
1010
1011 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1012 __tablename__ = 'users_group_repo_group_to_perm'
1013 __table_args__ = (
1014 UniqueConstraint('users_group_id', 'group_id'),
1015 {'extend_existing': True, 'mysql_engine':'InnoDB',
1016 'mysql_charset': 'utf8'}
1017 )
1018
1019 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)
1020 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1021 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1022 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1023
1024 users_group = relationship('UsersGroup')
1025 permission = relationship('Permission')
1026 group = relationship('RepoGroup')
1027
1028
1029 class Statistics(Base, BaseModel):
1030 __tablename__ = 'statistics'
1031 __table_args__ = (
1032 UniqueConstraint('repository_id'),
1033 {'extend_existing': True, 'mysql_engine':'InnoDB',
1034 'mysql_charset': 'utf8'}
1035 )
1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1039 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1040 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1041 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1042
1043 repository = relationship('Repository', single_parent=True)
1044
1045
1046 class UserFollowing(Base, BaseModel):
1047 __tablename__ = 'user_followings'
1048 __table_args__ = (
1049 UniqueConstraint('user_id', 'follows_repository_id'),
1050 UniqueConstraint('user_id', 'follows_user_id'),
1051 {'extend_existing': True, 'mysql_engine':'InnoDB',
1052 'mysql_charset': 'utf8'}
1053 )
1054
1055 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1057 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1058 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1059 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1060
1061 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1062
1063 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1064 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1065
1066 @classmethod
1067 def get_repo_followers(cls, repo_id):
1068 return cls.query().filter(cls.follows_repo_id == repo_id)
1069
1070
1071 class CacheInvalidation(Base, BaseModel):
1072 __tablename__ = 'cache_invalidation'
1073 __table_args__ = (
1074 UniqueConstraint('cache_key'),
1075 {'extend_existing': True, 'mysql_engine':'InnoDB',
1076 'mysql_charset': 'utf8'},
1077 )
1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1080 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1081 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1082
1083 def __init__(self, cache_key, cache_args=''):
1084 self.cache_key = cache_key
1085 self.cache_args = cache_args
1086 self.cache_active = False
1087
1088 def __unicode__(self):
1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1090 self.cache_id, self.cache_key)
1091 @classmethod
1092 def clear_cache(cls):
1093 cls.query().delete()
1094
1095 @classmethod
1096 def _get_key(cls, key):
1097 """
1098 Wrapper for generating a key, together with a prefix
1099
1100 :param key:
1101 """
1102 import rhodecode
1103 prefix = ''
1104 iid = rhodecode.CONFIG.get('instance_id')
1105 if iid:
1106 prefix = iid
1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1108
1109 @classmethod
1110 def get_by_key(cls, key):
1111 return cls.query().filter(cls.cache_key == key).scalar()
1112
1113 @classmethod
1114 def _get_or_create_key(cls, key, prefix, org_key):
1115 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1116 if not inv_obj:
1117 try:
1118 inv_obj = CacheInvalidation(key, org_key)
1119 Session.add(inv_obj)
1120 Session.commit()
1121 except Exception:
1122 log.error(traceback.format_exc())
1123 Session.rollback()
1124 return inv_obj
1125
1126 @classmethod
1127 def invalidate(cls, key):
1128 """
1129 Returns Invalidation object if this given key should be invalidated
1130 None otherwise. `cache_active = False` means that this cache
1131 state is not valid and needs to be invalidated
1132
1133 :param key:
1134 """
1135
1136 key, _prefix, _org_key = cls._get_key(key)
1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1138
1139 if inv and inv.cache_active is False:
1140 return inv
1141
1142 @classmethod
1143 def set_invalidate(cls, key):
1144 """
1145 Mark this Cache key for invalidation
1146
1147 :param key:
1148 """
1149
1150 key, _prefix, _org_key = cls._get_key(key)
1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1153 _org_key))
1154 try:
1155 for inv_obj in inv_objs:
1156 if inv_obj:
1157 inv_obj.cache_active = False
1158
1159 Session.add(inv_obj)
1160 Session.commit()
1161 except Exception:
1162 log.error(traceback.format_exc())
1163 Session.rollback()
1164
1165 @classmethod
1166 def set_valid(cls, key):
1167 """
1168 Mark this cache key as active and currently cached
1169
1170 :param key:
1171 """
1172 inv_obj = cls.get_by_key(key)
1173 inv_obj.cache_active = True
1174 Session.add(inv_obj)
1175 Session.commit()
1176
1177
1178 class ChangesetComment(Base, BaseModel):
1179 __tablename__ = 'changeset_comments'
1180 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine':'InnoDB',
1182 'mysql_charset': 'utf8'},
1183 )
1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1186 revision = Column('revision', String(40), nullable=False)
1187 line_no = Column('line_no', Unicode(10), nullable=True)
1188 f_path = Column('f_path', Unicode(1000), nullable=True)
1189 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1190 text = Column('text', Unicode(25000), nullable=False)
1191 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1192
1193 author = relationship('User', lazy='joined')
1194 repo = relationship('Repository')
1195
1196 @classmethod
1197 def get_users(cls, revision):
1198 """
1199 Returns user associated with this changesetComment. ie those
1200 who actually commented
1201
1202 :param cls:
1203 :param revision:
1204 """
1205 return Session.query(User)\
1206 .filter(cls.revision == revision)\
1207 .join(ChangesetComment.author).all()
1208
1209
1210 class Notification(Base, BaseModel):
1211 __tablename__ = 'notifications'
1212 __table_args__ = (
1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1214 'mysql_charset': 'utf8'},
1215 )
1216
1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1218 TYPE_MESSAGE = u'message'
1219 TYPE_MENTION = u'mention'
1220 TYPE_REGISTRATION = u'registration'
1221
1222 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1223 subject = Column('subject', Unicode(512), nullable=True)
1224 body = Column('body', Unicode(50000), nullable=True)
1225 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1226 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1227 type_ = Column('type', Unicode(256))
1228
1229 created_by_user = relationship('User')
1230 notifications_to_users = relationship('UserNotification', lazy='joined',
1231 cascade="all, delete, delete-orphan")
1232
1233 @property
1234 def recipients(self):
1235 return [x.user for x in UserNotification.query()\
1236 .filter(UserNotification.notification == self).all()]
1237
1238 @classmethod
1239 def create(cls, created_by, subject, body, recipients, type_=None):
1240 if type_ is None:
1241 type_ = Notification.TYPE_MESSAGE
1242
1243 notification = cls()
1244 notification.created_by_user = created_by
1245 notification.subject = subject
1246 notification.body = body
1247 notification.type_ = type_
1248 notification.created_on = datetime.datetime.now()
1249
1250 for u in recipients:
1251 assoc = UserNotification()
1252 assoc.notification = notification
1253 u.notifications.append(assoc)
1254 Session.add(notification)
1255 return notification
1256
1257 @property
1258 def description(self):
1259 from rhodecode.model.notification import NotificationModel
1260 return NotificationModel().make_description(self)
1261
1262
1263 class UserNotification(Base, BaseModel):
1264 __tablename__ = 'user_to_notification'
1265 __table_args__ = (
1266 UniqueConstraint('user_id', 'notification_id'),
1267 {'extend_existing': True, 'mysql_engine':'InnoDB',
1268 'mysql_charset': 'utf8'}
1269 )
1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1271 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1272 read = Column('read', Boolean, default=False)
1273 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1274
1275 user = relationship('User', lazy="joined")
1276 notification = relationship('Notification', lazy="joined",
1277 order_by=lambda: Notification.created_on.desc(),)
1278
1279 def mark_as_read(self):
1280 self.read = True
1281 Session.add(self)
1282
1283
1284 class DbMigrateVersion(Base, BaseModel):
1285 __tablename__ = 'db_migrate_version'
1286 __table_args__ = (
1287 {'extend_existing': True, 'mysql_engine':'InnoDB',
1288 'mysql_charset': 'utf8'},
1289 )
1290 repository_id = Column('repository_id', String(250), primary_key=True)
1291 repository_path = Column('repository_path', Text)
1292 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now