##// END OF EJS Templates
migration: removed deprecated mapper_args attributes that cause migration to break
super-admin -
r5160:d8fcae98 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,1261 +1,1261 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import logging
22 22 import datetime
23 23 import traceback
24 24 from collections import defaultdict
25 25
26 26 from sqlalchemy import *
27 27 from sqlalchemy.ext.hybrid import hybrid_property
28 28 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
29 29 from beaker.cache import cache_region, region_invalidate
30 30
31 31 from rhodecode.lib.vcs import get_backend
32 32 from rhodecode.lib.vcs.utils.helpers import get_scm
33 33 from rhodecode.lib.vcs.exceptions import VCSError
34 34 from zope.cachedescriptors.property import Lazy as LazyProperty
35 35
36 36 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.caching_query import FromCache
39 39
40 40 from rhodecode.model.meta import Base, Session
41 41 import hashlib
42 42
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46 #==============================================================================
47 47 # BASE CLASSES
48 48 #==============================================================================
49 49
50 50 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
51 51
52 52
53 53 class ModelSerializer(json.JSONEncoder):
54 54 """
55 55 Simple Serializer for JSON,
56 56
57 57 usage::
58 58
59 59 to make object customized for serialization implement a __json__
60 60 method that will return a dict for serialization into json
61 61
62 62 example::
63 63
64 64 class Task(object):
65 65
66 66 def __init__(self, name, value):
67 67 self.name = name
68 68 self.value = value
69 69
70 70 def __json__(self):
71 71 return dict(name=self.name,
72 72 value=self.value)
73 73
74 74 """
75 75
76 76 def default(self, obj):
77 77
78 78 if hasattr(obj, '__json__'):
79 79 return obj.__json__()
80 80 else:
81 81 return json.JSONEncoder.default(self, obj)
82 82
83 83
84 84 class BaseModel(object):
85 85 """
86 86 Base Model for all classess
87 87 """
88 88
89 89 @classmethod
90 90 def _get_keys(cls):
91 91 """return column names for this model """
92 92 return class_mapper(cls).c.keys()
93 93
94 94 def get_dict(self):
95 95 """
96 96 return dict with keys and values corresponding
97 97 to this model data """
98 98
99 99 d = {}
100 100 for k in self._get_keys():
101 101 d[k] = getattr(self, k)
102 102
103 103 # also use __json__() if present to get additional fields
104 104 for k, val in getattr(self, '__json__', lambda: {})().items():
105 105 d[k] = val
106 106 return d
107 107
108 108 def get_appstruct(self):
109 109 """return list with keys and values tupples corresponding
110 110 to this model data """
111 111
112 112 l = []
113 113 for k in self._get_keys():
114 114 l.append((k, getattr(self, k),))
115 115 return l
116 116
117 117 def populate_obj(self, populate_dict):
118 118 """populate model with data from given populate_dict"""
119 119
120 120 for k in self._get_keys():
121 121 if k in populate_dict:
122 122 setattr(self, k, populate_dict[k])
123 123
124 124 @classmethod
125 125 def query(cls):
126 126 return Session.query(cls)
127 127
128 128 @classmethod
129 129 def get(cls, id_):
130 130 if id_:
131 131 return cls.query().get(id_)
132 132
133 133 @classmethod
134 134 def getAll(cls):
135 135 return cls.query().all()
136 136
137 137 @classmethod
138 138 def delete(cls, id_):
139 139 obj = cls.query().get(id_)
140 140 Session.delete(obj)
141 141
142 142 def __repr__(self):
143 143 if hasattr(self, '__unicode__'):
144 144 # python repr needs to return str
145 145 return safe_str(self.__unicode__())
146 146 return '<DB:%s>' % (self.__class__.__name__)
147 147
148 148
149 149 class RhodeCodeSetting(Base, BaseModel):
150 150 __tablename__ = 'rhodecode_settings'
151 151 __table_args__ = (
152 152 UniqueConstraint('app_settings_name'),
153 153 {'extend_existing': True, 'mysql_engine':'InnoDB',
154 154 'mysql_charset': 'utf8'}
155 155 )
156 156 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
157 157 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
158 158 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
159 159
160 160 def __init__(self, k='', v=''):
161 161 self.app_settings_name = k
162 162 self.app_settings_value = v
163 163
164 164 @validates('_app_settings_value')
165 165 def validate_settings_value(self, key, val):
166 166 assert type(val) == str
167 167 return val
168 168
169 169 @hybrid_property
170 170 def app_settings_value(self):
171 171 v = self._app_settings_value
172 172 if self.app_settings_name == 'ldap_active':
173 173 v = str2bool(v)
174 174 return v
175 175
176 176 @app_settings_value.setter
177 177 def app_settings_value(self, val):
178 178 """
179 179 Setter that will always make sure we use unicode in app_settings_value
180 180
181 181 :param val:
182 182 """
183 183 self._app_settings_value = safe_str(val)
184 184
185 185 def __unicode__(self):
186 186 return u"<%s('%s:%s')>" % (
187 187 self.__class__.__name__,
188 188 self.app_settings_name, self.app_settings_value
189 189 )
190 190
191 191 @classmethod
192 192 def get_by_name(cls, ldap_key):
193 193 return cls.query()\
194 194 .filter(cls.app_settings_name == ldap_key).scalar()
195 195
196 196 @classmethod
197 197 def get_app_settings(cls, cache=False):
198 198
199 199 ret = cls.query()
200 200
201 201 if cache:
202 202 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
203 203
204 204 if not ret:
205 205 raise Exception('Could not get application settings !')
206 206 settings = {}
207 207 for each in ret:
208 208 settings['rhodecode_' + each.app_settings_name] = \
209 209 each.app_settings_value
210 210
211 211 return settings
212 212
213 213 @classmethod
214 214 def get_ldap_settings(cls, cache=False):
215 215 ret = cls.query()\
216 216 .filter(cls.app_settings_name.startswith('ldap_')).all()
217 217 fd = {}
218 218 for row in ret:
219 219 fd.update({row.app_settings_name:row.app_settings_value})
220 220
221 221 return fd
222 222
223 223
224 224 class RhodeCodeUi(Base, BaseModel):
225 225 __tablename__ = 'rhodecode_ui'
226 226 __table_args__ = (
227 227 UniqueConstraint('ui_key'),
228 228 {'extend_existing': True, 'mysql_engine':'InnoDB',
229 229 'mysql_charset': 'utf8'}
230 230 )
231 231
232 232 HOOK_REPO_SIZE = 'changegroup.repo_size'
233 233 HOOK_PUSH = 'pretxnchangegroup.push_logger'
234 234 HOOK_PULL = 'preoutgoing.pull_logger'
235 235
236 236 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
237 237 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
238 238 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
239 239 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
240 240 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
241 241
242 242 @classmethod
243 243 def get_by_key(cls, key):
244 244 return cls.query().filter(cls.ui_key == key)
245 245
246 246 @classmethod
247 247 def get_builtin_hooks(cls):
248 248 q = cls.query()
249 249 q = q.filter(cls.ui_key.in_([cls.HOOK_REPO_SIZE,
250 250 cls.HOOK_PUSH, cls.HOOK_PULL]))
251 251 return q.all()
252 252
253 253 @classmethod
254 254 def get_custom_hooks(cls):
255 255 q = cls.query()
256 256 q = q.filter(~cls.ui_key.in_([cls.HOOK_REPO_SIZE,
257 257 cls.HOOK_PUSH, cls.HOOK_PULL]))
258 258 q = q.filter(cls.ui_section == 'hooks')
259 259 return q.all()
260 260
261 261 @classmethod
262 262 def create_or_update_hook(cls, key, val):
263 263 new_ui = cls.get_by_key(key).scalar() or cls()
264 264 new_ui.ui_section = 'hooks'
265 265 new_ui.ui_active = True
266 266 new_ui.ui_key = key
267 267 new_ui.ui_value = val
268 268
269 269 Session.add(new_ui)
270 270
271 271
272 272 class User(Base, BaseModel):
273 273 __tablename__ = 'users'
274 274 __table_args__ = (
275 275 UniqueConstraint('username'), UniqueConstraint('email'),
276 276 {'extend_existing': True, 'mysql_engine':'InnoDB',
277 277 'mysql_charset': 'utf8'}
278 278 )
279 279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 280 username = Column("username", String(255), nullable=True, unique=None, default=None)
281 281 password = Column("password", String(255), nullable=True, unique=None, default=None)
282 282 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
283 283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
284 284 name = Column("name", String(255), nullable=True, unique=None, default=None)
285 285 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
286 286 _email = Column("email", String(255), nullable=True, unique=None, default=None)
287 287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
288 288 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
289 289 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
290 290
291 291 user_log = relationship('UserLog', cascade='all')
292 292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
293 293
294 294 repositories = relationship('Repository')
295 295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
297 297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
298 298
299 299 group_member = relationship('UserGroupMember', cascade='all')
300 300
301 301 notifications = relationship('UserNotification', cascade='all')
302 302 # notifications assigned to this user
303 303 user_created_notifications = relationship('Notification', cascade='all')
304 304 # comments created by this user
305 305 user_comments = relationship('ChangesetComment', cascade='all')
306 306
307 307 @hybrid_property
308 308 def email(self):
309 309 return self._email
310 310
311 311 @email.setter
312 312 def email(self, val):
313 313 self._email = val.lower() if val else None
314 314
315 315 @property
316 316 def full_name(self):
317 317 return '%s %s' % (self.name, self.lastname)
318 318
319 319 @property
320 320 def full_name_or_username(self):
321 321 return ('%s %s' % (self.name, self.lastname)
322 322 if (self.name and self.lastname) else self.username)
323 323
324 324 @property
325 325 def full_contact(self):
326 326 return '%s %s <%s>' % (self.name, self.lastname, self.email)
327 327
328 328 @property
329 329 def short_contact(self):
330 330 return '%s %s' % (self.name, self.lastname)
331 331
332 332 @property
333 333 def is_admin(self):
334 334 return self.admin
335 335
336 336 def __unicode__(self):
337 337 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
338 338 self.user_id, self.username)
339 339
340 340 @classmethod
341 341 def get_by_username(cls, username, case_insensitive=False, cache=False):
342 342 if case_insensitive:
343 343 q = cls.query().filter(cls.username.ilike(username))
344 344 else:
345 345 q = cls.query().filter(cls.username == username)
346 346
347 347 if cache:
348 348 q = q.options(FromCache(
349 349 "sql_cache_short",
350 350 "get_user_%s" % _hash_key(username)
351 351 )
352 352 )
353 353 return q.scalar()
354 354
355 355 @classmethod
356 356 def get_by_auth_token(cls, auth_token, cache=False):
357 357 q = cls.query().filter(cls.api_key == auth_token)
358 358
359 359 if cache:
360 360 q = q.options(FromCache("sql_cache_short",
361 361 "get_auth_token_%s" % auth_token))
362 362 return q.scalar()
363 363
364 364 @classmethod
365 365 def get_by_email(cls, email, case_insensitive=False, cache=False):
366 366 if case_insensitive:
367 367 q = cls.query().filter(cls.email.ilike(email))
368 368 else:
369 369 q = cls.query().filter(cls.email == email)
370 370
371 371 if cache:
372 372 q = q.options(FromCache("sql_cache_short",
373 373 "get_auth_token_%s" % email))
374 374 return q.scalar()
375 375
376 376 def update_lastlogin(self):
377 377 """Update user lastlogin"""
378 378 self.last_login = datetime.datetime.now()
379 379 Session.add(self)
380 380 log.debug('updated user %s lastlogin', self.username)
381 381
382 382 def __json__(self):
383 383 return dict(
384 384 user_id=self.user_id,
385 385 first_name=self.name,
386 386 last_name=self.lastname,
387 387 email=self.email,
388 388 full_name=self.full_name,
389 389 full_name_or_username=self.full_name_or_username,
390 390 short_contact=self.short_contact,
391 391 full_contact=self.full_contact
392 392 )
393 393
394 394
395 395 class UserLog(Base, BaseModel):
396 396 __tablename__ = 'user_logs'
397 397 __table_args__ = (
398 398 {'extend_existing': True, 'mysql_engine':'InnoDB',
399 399 'mysql_charset': 'utf8'},
400 400 )
401 401 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
402 402 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
403 403 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
404 404 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
405 405 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
406 406 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
407 407 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
408 408
409 409 @property
410 410 def action_as_day(self):
411 411 return datetime.date(*self.action_date.timetuple()[:3])
412 412
413 413 user = relationship('User')
414 414 repository = relationship('Repository', cascade='')
415 415
416 416
417 417 class UserGroup(Base, BaseModel):
418 418 __tablename__ = 'users_groups'
419 419 __table_args__ = (
420 420 {'extend_existing': True, 'mysql_engine':'InnoDB',
421 421 'mysql_charset': 'utf8'},
422 422 )
423 423
424 424 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
425 425 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
426 426 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
427 427
428 428 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
429 429 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
430 430 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
431 431
432 432 def __unicode__(self):
433 433 return u'<userGroup(%s)>' % (self.users_group_name)
434 434
435 435 @classmethod
436 436 def get_by_group_name(cls, group_name, cache=False,
437 437 case_insensitive=False):
438 438 if case_insensitive:
439 439 q = cls.query().filter(cls.users_group_name.ilike(group_name))
440 440 else:
441 441 q = cls.query().filter(cls.users_group_name == group_name)
442 442 if cache:
443 443 q = q.options(FromCache(
444 444 "sql_cache_short",
445 445 "get_user_%s" % _hash_key(group_name)
446 446 )
447 447 )
448 448 return q.scalar()
449 449
450 450 @classmethod
451 451 def get(cls, users_group_id, cache=False):
452 452 users_group = cls.query()
453 453 if cache:
454 454 users_group = users_group.options(FromCache("sql_cache_short",
455 455 "get_users_group_%s" % users_group_id))
456 456 return users_group.get(users_group_id)
457 457
458 458
459 459 class UserGroupMember(Base, BaseModel):
460 460 __tablename__ = 'users_groups_members'
461 461 __table_args__ = (
462 462 {'extend_existing': True, 'mysql_engine':'InnoDB',
463 463 'mysql_charset': 'utf8'},
464 464 )
465 465
466 466 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 467 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
468 468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
469 469
470 470 user = relationship('User', lazy='joined')
471 471 users_group = relationship('UserGroup')
472 472
473 473 def __init__(self, gr_id='', u_id=''):
474 474 self.users_group_id = gr_id
475 475 self.user_id = u_id
476 476
477 477
478 478 class Repository(Base, BaseModel):
479 479 __tablename__ = 'repositories'
480 480 __table_args__ = (
481 481 UniqueConstraint('repo_name'),
482 482 {'extend_existing': True, 'mysql_engine':'InnoDB',
483 483 'mysql_charset': 'utf8'},
484 484 )
485 485
486 486 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
487 487 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
488 488 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
489 489 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default='hg')
490 490 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
491 491 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
492 492 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
493 493 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
494 494 description = Column("description", String(10000), nullable=True, unique=None, default=None)
495 495 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
496 496
497 497 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
498 498 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
499 499
500 500 user = relationship('User')
501 501 fork = relationship('Repository', remote_side=repo_id)
502 502 group = relationship('RepoGroup')
503 503 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
504 504 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
505 505 stats = relationship('Statistics', cascade='all', uselist=False)
506 506
507 507 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
508 508
509 509 logs = relationship('UserLog')
510 510
511 511 def __unicode__(self):
512 512 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
513 513 self.repo_name)
514 514
515 515 @classmethod
516 516 def url_sep(cls):
517 517 return '/'
518 518
519 519 @classmethod
520 520 def get_by_repo_name(cls, repo_name):
521 521 q = Session.query(cls).filter(cls.repo_name == repo_name)
522 522 q = q.options(joinedload(Repository.fork))\
523 523 .options(joinedload(Repository.user))\
524 524 .options(joinedload(Repository.group))
525 525 return q.scalar()
526 526
527 527 @classmethod
528 528 def get_repo_forks(cls, repo_id):
529 529 return cls.query().filter(Repository.fork_id == repo_id)
530 530
531 531 @classmethod
532 532 def base_path(cls):
533 533 """
534 534 Returns base path when all repos are stored
535 535
536 536 :param cls:
537 537 """
538 538 q = Session.query(RhodeCodeUi)\
539 539 .filter(RhodeCodeUi.ui_key == cls.url_sep())
540 540 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
541 541 return q.one().ui_value
542 542
543 543 @property
544 544 def just_name(self):
545 545 return self.repo_name.split(Repository.url_sep())[-1]
546 546
547 547 @property
548 548 def groups_with_parents(self):
549 549 groups = []
550 550 if self.group is None:
551 551 return groups
552 552
553 553 cur_gr = self.group
554 554 groups.insert(0, cur_gr)
555 555 while 1:
556 556 gr = getattr(cur_gr, 'parent_group', None)
557 557 cur_gr = cur_gr.parent_group
558 558 if gr is None:
559 559 break
560 560 groups.insert(0, gr)
561 561
562 562 return groups
563 563
564 564 @property
565 565 def groups_and_repo(self):
566 566 return self.groups_with_parents, self.just_name
567 567
568 568 @LazyProperty
569 569 def repo_path(self):
570 570 """
571 571 Returns base full path for that repository means where it actually
572 572 exists on a filesystem
573 573 """
574 574 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
575 575 Repository.url_sep())
576 576 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
577 577 return q.one().ui_value
578 578
579 579 @property
580 580 def repo_full_path(self):
581 581 p = [self.repo_path]
582 582 # we need to split the name by / since this is how we store the
583 583 # names in the database, but that eventually needs to be converted
584 584 # into a valid system path
585 585 p += self.repo_name.split(Repository.url_sep())
586 586 return os.path.join(*p)
587 587
588 588 def get_new_name(self, repo_name):
589 589 """
590 590 returns new full repository name based on assigned group and new new
591 591
592 592 :param group_name:
593 593 """
594 594 path_prefix = self.group.full_path_splitted if self.group else []
595 595 return Repository.url_sep().join(path_prefix + [repo_name])
596 596
597 597 @property
598 598 def _config(self):
599 599 """
600 600 Returns db based config object.
601 601 """
602 602 from rhodecode.lib.utils import make_db_config
603 603 return make_db_config(clear_session=False)
604 604
605 605 @classmethod
606 606 def is_valid(cls, repo_name):
607 607 """
608 608 returns True if given repo name is a valid filesystem repository
609 609
610 610 :param cls:
611 611 :param repo_name:
612 612 """
613 613 from rhodecode.lib.utils import is_valid_repo
614 614
615 615 return is_valid_repo(repo_name, cls.base_path())
616 616
617 617 #==========================================================================
618 618 # SCM PROPERTIES
619 619 #==========================================================================
620 620
621 621 def get_commit(self, rev):
622 622 return get_commit_safe(self.scm_instance, rev)
623 623
624 624 @property
625 625 def tip(self):
626 626 return self.get_commit('tip')
627 627
628 628 @property
629 629 def author(self):
630 630 return self.tip.author
631 631
632 632 @property
633 633 def last_change(self):
634 634 return self.scm_instance.last_change
635 635
636 636 def comments(self, revisions=None):
637 637 """
638 638 Returns comments for this repository grouped by revisions
639 639
640 640 :param revisions: filter query by revisions only
641 641 """
642 642 cmts = ChangesetComment.query()\
643 643 .filter(ChangesetComment.repo == self)
644 644 if revisions:
645 645 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
646 646 grouped = defaultdict(list)
647 647 for cmt in cmts.all():
648 648 grouped[cmt.revision].append(cmt)
649 649 return grouped
650 650
651 651 #==========================================================================
652 652 # SCM CACHE INSTANCE
653 653 #==========================================================================
654 654
655 655 @property
656 656 def invalidate(self):
657 657 return CacheInvalidation.invalidate(self.repo_name)
658 658
659 659 def set_invalidate(self):
660 660 """
661 661 set a cache for invalidation for this instance
662 662 """
663 663 CacheInvalidation.set_invalidate(self.repo_name)
664 664
665 665 @LazyProperty
666 666 def scm_instance(self):
667 667 return self.__get_instance()
668 668
669 669 @property
670 670 def scm_instance_cached(self):
671 671 return self.__get_instance()
672 672
673 673 def __get_instance(self):
674 674 repo_full_path = self.repo_full_path
675 675 try:
676 676 alias = get_scm(repo_full_path)[0]
677 677 log.debug('Creating instance of %s repository', alias)
678 678 backend = get_backend(alias)
679 679 except VCSError:
680 680 log.error(traceback.format_exc())
681 681 log.error('Perhaps this repository is in db and not in '
682 682 'filesystem run rescan repositories with '
683 683 '"destroy old data " option from admin panel')
684 684 return
685 685
686 686 if alias == 'hg':
687 687
688 688 repo = backend(safe_str(repo_full_path), create=False,
689 689 config=self._config)
690 690 else:
691 691 repo = backend(repo_full_path, create=False)
692 692
693 693 return repo
694 694
695 695
696 696 class RepoGroup(Base, BaseModel):
697 697 __tablename__ = 'groups'
698 698 __table_args__ = (
699 699 UniqueConstraint('group_name', 'group_parent_id'),
700 700 {'extend_existing': True, 'mysql_engine':'InnoDB',
701 701 'mysql_charset': 'utf8'},
702 702 )
703 __mapper_args__ = {'order_by': 'group_name'}
703
704 704
705 705 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
706 706 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
707 707 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
708 708 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
709 709
710 710 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
711 711 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
712 712
713 713 parent_group = relationship('RepoGroup', remote_side=group_id)
714 714
715 715 def __init__(self, group_name='', parent_group=None):
716 716 self.group_name = group_name
717 717 self.parent_group = parent_group
718 718
719 719 def __unicode__(self):
720 720 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
721 721 self.group_name)
722 722
723 723 @classmethod
724 724 def url_sep(cls):
725 725 return '/'
726 726
727 727 @classmethod
728 728 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
729 729 if case_insensitive:
730 730 gr = cls.query()\
731 731 .filter(cls.group_name.ilike(group_name))
732 732 else:
733 733 gr = cls.query()\
734 734 .filter(cls.group_name == group_name)
735 735 if cache:
736 736 gr = gr.options(FromCache(
737 737 "sql_cache_short",
738 738 "get_group_%s" % _hash_key(group_name)
739 739 )
740 740 )
741 741 return gr.scalar()
742 742
743 743 @property
744 744 def parents(self):
745 745 parents_recursion_limit = 5
746 746 groups = []
747 747 if self.parent_group is None:
748 748 return groups
749 749 cur_gr = self.parent_group
750 750 groups.insert(0, cur_gr)
751 751 cnt = 0
752 752 while 1:
753 753 cnt += 1
754 754 gr = getattr(cur_gr, 'parent_group', None)
755 755 cur_gr = cur_gr.parent_group
756 756 if gr is None:
757 757 break
758 758 if cnt == parents_recursion_limit:
759 759 # this will prevent accidental infinit loops
760 760 log.error('group nested more than %s', parents_recursion_limit)
761 761 break
762 762
763 763 groups.insert(0, gr)
764 764 return groups
765 765
766 766 @property
767 767 def children(self):
768 768 return RepoGroup.query().filter(RepoGroup.parent_group == self)
769 769
770 770 @property
771 771 def name(self):
772 772 return self.group_name.split(RepoGroup.url_sep())[-1]
773 773
774 774 @property
775 775 def full_path(self):
776 776 return self.group_name
777 777
778 778 @property
779 779 def full_path_splitted(self):
780 780 return self.group_name.split(RepoGroup.url_sep())
781 781
782 782 @property
783 783 def repositories(self):
784 784 return Repository.query()\
785 785 .filter(Repository.group == self)\
786 786 .order_by(Repository.repo_name)
787 787
788 788 @property
789 789 def repositories_recursive_count(self):
790 790 cnt = self.repositories.count()
791 791
792 792 def children_count(group):
793 793 cnt = 0
794 794 for child in group.children:
795 795 cnt += child.repositories.count()
796 796 cnt += children_count(child)
797 797 return cnt
798 798
799 799 return cnt + children_count(self)
800 800
801 801 def get_new_name(self, group_name):
802 802 """
803 803 returns new full group name based on parent and new name
804 804
805 805 :param group_name:
806 806 """
807 807 path_prefix = (self.parent_group.full_path_splitted if
808 808 self.parent_group else [])
809 809 return RepoGroup.url_sep().join(path_prefix + [group_name])
810 810
811 811
812 812 class Permission(Base, BaseModel):
813 813 __tablename__ = 'permissions'
814 814 __table_args__ = (
815 815 {'extend_existing': True, 'mysql_engine':'InnoDB',
816 816 'mysql_charset': 'utf8'},
817 817 )
818 818 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
819 819 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
820 820 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
821 821
822 822 def __unicode__(self):
823 823 return u"<%s('%s:%s')>" % (
824 824 self.__class__.__name__, self.permission_id, self.permission_name
825 825 )
826 826
827 827 @classmethod
828 828 def get_by_key(cls, key):
829 829 return cls.query().filter(cls.permission_name == key).scalar()
830 830
831 831 @classmethod
832 832 def get_default_repo_perms(cls, default_user_id):
833 833 q = Session.query(UserRepoToPerm, Repository, cls)\
834 834 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
835 835 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
836 836 .filter(UserRepoToPerm.user_id == default_user_id)
837 837
838 838 return q.all()
839 839
840 840 @classmethod
841 841 def get_default_group_perms(cls, default_user_id):
842 842 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
843 843 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
844 844 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
845 845 .filter(UserRepoGroupToPerm.user_id == default_user_id)
846 846
847 847 return q.all()
848 848
849 849
850 850 class UserRepoToPerm(Base, BaseModel):
851 851 __tablename__ = 'repo_to_perm'
852 852 __table_args__ = (
853 853 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
854 854 {'extend_existing': True, 'mysql_engine':'InnoDB',
855 855 'mysql_charset': 'utf8'}
856 856 )
857 857 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
858 858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
859 859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
860 860 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
861 861
862 862 user = relationship('User')
863 863 repository = relationship('Repository')
864 864 permission = relationship('Permission')
865 865
866 866 @classmethod
867 867 def create(cls, user, repository, permission):
868 868 n = cls()
869 869 n.user = user
870 870 n.repository = repository
871 871 n.permission = permission
872 872 Session.add(n)
873 873 return n
874 874
875 875 def __unicode__(self):
876 876 return u'<user:%s => %s >' % (self.user, self.repository)
877 877
878 878
879 879 class UserToPerm(Base, BaseModel):
880 880 __tablename__ = 'user_to_perm'
881 881 __table_args__ = (
882 882 UniqueConstraint('user_id', 'permission_id'),
883 883 {'extend_existing': True, 'mysql_engine':'InnoDB',
884 884 'mysql_charset': 'utf8'}
885 885 )
886 886 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
887 887 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
888 888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
889 889
890 890 user = relationship('User')
891 891 permission = relationship('Permission', lazy='joined')
892 892
893 893
894 894 class UserGroupRepoToPerm(Base, BaseModel):
895 895 __tablename__ = 'users_group_repo_to_perm'
896 896 __table_args__ = (
897 897 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
898 898 {'extend_existing': True, 'mysql_engine':'InnoDB',
899 899 'mysql_charset': 'utf8'}
900 900 )
901 901 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
902 902 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
903 903 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
904 904 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
905 905
906 906 users_group = relationship('UserGroup')
907 907 permission = relationship('Permission')
908 908 repository = relationship('Repository')
909 909
910 910 @classmethod
911 911 def create(cls, users_group, repository, permission):
912 912 n = cls()
913 913 n.users_group = users_group
914 914 n.repository = repository
915 915 n.permission = permission
916 916 Session.add(n)
917 917 return n
918 918
919 919 def __unicode__(self):
920 920 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
921 921
922 922
923 923 class UserGroupToPerm(Base, BaseModel):
924 924 __tablename__ = 'users_group_to_perm'
925 925 __table_args__ = (
926 926 UniqueConstraint('users_group_id', 'permission_id',),
927 927 {'extend_existing': True, 'mysql_engine':'InnoDB',
928 928 'mysql_charset': 'utf8'}
929 929 )
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('UserGroup')
935 935 permission = relationship('Permission')
936 936
937 937
938 938 class UserRepoGroupToPerm(Base, BaseModel):
939 939 __tablename__ = 'user_repo_group_to_perm'
940 940 __table_args__ = (
941 941 UniqueConstraint('user_id', 'group_id', 'permission_id'),
942 942 {'extend_existing': True, 'mysql_engine':'InnoDB',
943 943 'mysql_charset': 'utf8'}
944 944 )
945 945
946 946 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
947 947 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
948 948 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
949 949 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
950 950
951 951 user = relationship('User')
952 952 group = relationship('RepoGroup')
953 953 permission = relationship('Permission')
954 954
955 955
956 956 class UserGroupRepoGroupToPerm(Base, BaseModel):
957 957 __tablename__ = 'users_group_repo_group_to_perm'
958 958 __table_args__ = (
959 959 UniqueConstraint('users_group_id', 'group_id'),
960 960 {'extend_existing': True, 'mysql_engine':'InnoDB',
961 961 'mysql_charset': 'utf8'}
962 962 )
963 963
964 964 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)
965 965 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
966 966 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
967 967 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
968 968
969 969 users_group = relationship('UserGroup')
970 970 permission = relationship('Permission')
971 971 group = relationship('RepoGroup')
972 972
973 973
974 974 class Statistics(Base, BaseModel):
975 975 __tablename__ = 'statistics'
976 976 __table_args__ = (
977 977 UniqueConstraint('repository_id'),
978 978 {'extend_existing': True, 'mysql_engine':'InnoDB',
979 979 'mysql_charset': 'utf8'}
980 980 )
981 981 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
982 982 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
983 983 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
984 984 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
985 985 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
986 986 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
987 987
988 988 repository = relationship('Repository', single_parent=True)
989 989
990 990
991 991 class UserFollowing(Base, BaseModel):
992 992 __tablename__ = 'user_followings'
993 993 __table_args__ = (
994 994 UniqueConstraint('user_id', 'follows_repository_id'),
995 995 UniqueConstraint('user_id', 'follows_user_id'),
996 996 {'extend_existing': True, 'mysql_engine':'InnoDB',
997 997 'mysql_charset': 'utf8'}
998 998 )
999 999
1000 1000 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1001 1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1002 1002 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1003 1003 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1004 1004 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1005 1005
1006 1006 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1007 1007
1008 1008 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1009 1009 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1010 1010
1011 1011 @classmethod
1012 1012 def get_repo_followers(cls, repo_id):
1013 1013 return cls.query().filter(cls.follows_repo_id == repo_id)
1014 1014
1015 1015
1016 1016 class CacheInvalidation(Base, BaseModel):
1017 1017 __tablename__ = 'cache_invalidation'
1018 1018 __table_args__ = (
1019 1019 UniqueConstraint('cache_key'),
1020 1020 {'extend_existing': True, 'mysql_engine':'InnoDB',
1021 1021 'mysql_charset': 'utf8'},
1022 1022 )
1023 1023 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1024 1024 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
1025 1025 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
1026 1026 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1027 1027
1028 1028 def __init__(self, cache_key, cache_args=''):
1029 1029 self.cache_key = cache_key
1030 1030 self.cache_args = cache_args
1031 1031 self.cache_active = False
1032 1032
1033 1033 def __unicode__(self):
1034 1034 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1035 1035 self.cache_id, self.cache_key)
1036 1036
1037 1037 @classmethod
1038 1038 def _get_key(cls, key):
1039 1039 """
1040 1040 Wrapper for generating a key, together with a prefix
1041 1041
1042 1042 :param key:
1043 1043 """
1044 1044 import rhodecode
1045 1045 prefix = ''
1046 1046 iid = rhodecode.CONFIG.get('instance_id')
1047 1047 if iid:
1048 1048 prefix = iid
1049 1049 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1050 1050
1051 1051 @classmethod
1052 1052 def get_by_key(cls, key):
1053 1053 return cls.query().filter(cls.cache_key == key).scalar()
1054 1054
1055 1055 @classmethod
1056 1056 def _get_or_create_key(cls, key, prefix, org_key):
1057 1057 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1058 1058 if not inv_obj:
1059 1059 try:
1060 1060 inv_obj = CacheInvalidation(key, org_key)
1061 1061 Session.add(inv_obj)
1062 1062 Session.commit()
1063 1063 except Exception:
1064 1064 log.error(traceback.format_exc())
1065 1065 Session.rollback()
1066 1066 return inv_obj
1067 1067
1068 1068 @classmethod
1069 1069 def invalidate(cls, key):
1070 1070 """
1071 1071 Returns Invalidation object if this given key should be invalidated
1072 1072 None otherwise. `cache_active = False` means that this cache
1073 1073 state is not valid and needs to be invalidated
1074 1074
1075 1075 :param key:
1076 1076 """
1077 1077
1078 1078 key, _prefix, _org_key = cls._get_key(key)
1079 1079 inv = cls._get_or_create_key(key, _prefix, _org_key)
1080 1080
1081 1081 if inv and inv.cache_active is False:
1082 1082 return inv
1083 1083
1084 1084 @classmethod
1085 1085 def set_invalidate(cls, key):
1086 1086 """
1087 1087 Mark this Cache key for invalidation
1088 1088
1089 1089 :param key:
1090 1090 """
1091 1091
1092 1092 key, _prefix, _org_key = cls._get_key(key)
1093 1093 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1094 1094 log.debug('marking %s key[s] %s for invalidation', len(inv_objs), _org_key)
1095 1095 try:
1096 1096 for inv_obj in inv_objs:
1097 1097 if inv_obj:
1098 1098 inv_obj.cache_active = False
1099 1099
1100 1100 Session.add(inv_obj)
1101 1101 Session.commit()
1102 1102 except Exception:
1103 1103 log.error(traceback.format_exc())
1104 1104 Session.rollback()
1105 1105
1106 1106 @classmethod
1107 1107 def set_valid(cls, key):
1108 1108 """
1109 1109 Mark this cache key as active and currently cached
1110 1110
1111 1111 :param key:
1112 1112 """
1113 1113 inv_obj = cls.get_by_key(key)
1114 1114 inv_obj.cache_active = True
1115 1115 Session.add(inv_obj)
1116 1116 Session.commit()
1117 1117
1118 1118
1119 1119 class ChangesetComment(Base, BaseModel):
1120 1120 __tablename__ = 'changeset_comments'
1121 1121 __table_args__ = (
1122 1122 {'extend_existing': True, 'mysql_engine':'InnoDB',
1123 1123 'mysql_charset': 'utf8'},
1124 1124 )
1125 1125 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1126 1126 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1127 1127 revision = Column('revision', String(40), nullable=False)
1128 1128 line_no = Column('line_no', Unicode(10), nullable=True)
1129 1129 f_path = Column('f_path', Unicode(1000), nullable=True)
1130 1130 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1131 1131 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1132 1132 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1133 1133
1134 1134 author = relationship('User', lazy='joined')
1135 1135 repo = relationship('Repository')
1136 1136
1137 1137 @classmethod
1138 1138 def get_users(cls, revision):
1139 1139 """
1140 1140 Returns user associated with this changesetComment. ie those
1141 1141 who actually commented
1142 1142
1143 1143 :param cls:
1144 1144 :param revision:
1145 1145 """
1146 1146 return Session.query(User)\
1147 1147 .filter(cls.revision == revision)\
1148 1148 .join(ChangesetComment.author).all()
1149 1149
1150 1150
1151 1151 class Notification(Base, BaseModel):
1152 1152 __tablename__ = 'notifications'
1153 1153 __table_args__ = (
1154 1154 {'extend_existing': True, 'mysql_engine':'InnoDB',
1155 1155 'mysql_charset': 'utf8'},
1156 1156 )
1157 1157
1158 1158 TYPE_CHANGESET_COMMENT = u'cs_comment'
1159 1159 TYPE_MESSAGE = u'message'
1160 1160 TYPE_MENTION = u'mention'
1161 1161 TYPE_REGISTRATION = u'registration'
1162 1162
1163 1163 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1164 1164 subject = Column('subject', Unicode(512), nullable=True)
1165 1165 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1166 1166 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1167 1167 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1168 1168 type_ = Column('type', Unicode(256))
1169 1169
1170 1170 created_by_user = relationship('User')
1171 1171 notifications_to_users = relationship('UserNotification', lazy='joined',
1172 1172 cascade="all, delete, delete-orphan")
1173 1173
1174 1174 @property
1175 1175 def recipients(self):
1176 1176 return [x.user for x in UserNotification.query()\
1177 1177 .filter(UserNotification.notification == self).all()]
1178 1178
1179 1179 @classmethod
1180 1180 def create(cls, created_by, subject, body, recipients, type_=None):
1181 1181 if type_ is None:
1182 1182 type_ = Notification.TYPE_MESSAGE
1183 1183
1184 1184 notification = cls()
1185 1185 notification.created_by_user = created_by
1186 1186 notification.subject = subject
1187 1187 notification.body = body
1188 1188 notification.type_ = type_
1189 1189 notification.created_on = datetime.datetime.now()
1190 1190
1191 1191 for u in recipients:
1192 1192 assoc = UserNotification()
1193 1193 assoc.notification = notification
1194 1194 u.notifications.append(assoc)
1195 1195 Session.add(notification)
1196 1196 return notification
1197 1197
1198 1198 @property
1199 1199 def description(self):
1200 1200 from rhodecode.model.notification import NotificationModel
1201 1201 return NotificationModel().make_description(self)
1202 1202
1203 1203
1204 1204 class UserNotification(Base, BaseModel):
1205 1205 __tablename__ = 'user_to_notification'
1206 1206 __table_args__ = (
1207 1207 UniqueConstraint('user_id', 'notification_id'),
1208 1208 {'extend_existing': True, 'mysql_engine':'InnoDB',
1209 1209 'mysql_charset': 'utf8'}
1210 1210 )
1211 1211 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1212 1212 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1213 1213 read = Column('read', Boolean, default=False)
1214 1214 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1215 1215
1216 1216 user = relationship('User', lazy="joined")
1217 1217 notification = relationship('Notification', lazy="joined",
1218 1218 order_by=lambda: Notification.created_on.desc(),)
1219 1219
1220 1220 def mark_as_read(self):
1221 1221 self.read = True
1222 1222 Session.add(self)
1223 1223
1224 1224
1225 1225 class DbMigrateVersion(Base, BaseModel):
1226 1226 __tablename__ = 'db_migrate_version'
1227 1227 __table_args__ = (
1228 1228 {'extend_existing': True, 'mysql_engine':'InnoDB',
1229 1229 'mysql_charset': 'utf8'},
1230 1230 )
1231 1231 repository_id = Column('repository_id', String(250), primary_key=True)
1232 1232 repository_path = Column('repository_path', Text)
1233 1233 version = Column('version', Integer)
1234 1234
1235 1235 ## this is migration from 1_4_0, but now it's here to overcome a problem of
1236 1236 ## attaching a FK to this from 1_3_0 !
1237 1237
1238 1238
1239 1239 class PullRequest(Base, BaseModel):
1240 1240 __tablename__ = 'pull_requests'
1241 1241 __table_args__ = (
1242 1242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1243 1243 'mysql_charset': 'utf8'},
1244 1244 )
1245 1245
1246 1246 STATUS_NEW = u'new'
1247 1247 STATUS_OPEN = u'open'
1248 1248 STATUS_CLOSED = u'closed'
1249 1249
1250 1250 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1251 1251 title = Column('title', Unicode(256), nullable=True)
1252 1252 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1253 1253 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1254 1254 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1255 1255 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1256 1256 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1257 1257 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql')) # 500 revisions max
1258 1258 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1259 1259 org_ref = Column('org_ref', Unicode(256), nullable=False)
1260 1260 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1261 1261 other_ref = Column('other_ref', Unicode(256), nullable=False)
@@ -1,969 +1,969 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import os
22 22 import time
23 23 import logging
24 24 import datetime
25 25 import traceback
26 26 import hashlib
27 27 import collections
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
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 zope.cachedescriptors.property import Lazy as LazyProperty
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, remove_suffix
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.caching_query import FromCache
45 45
46 46 from rhodecode.model.meta import Base, Session
47 47
48 48 URL_SEP = '/'
49 49 log = logging.getLogger(__name__)
50 50
51 51 #==============================================================================
52 52 # BASE CLASSES
53 53 #==============================================================================
54 54
55 55 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
56 56
57 57
58 58 class BaseModel(object):
59 59 """
60 60 Base Model for all classes
61 61 """
62 62
63 63 @classmethod
64 64 def _get_keys(cls):
65 65 """return column names for this model """
66 66 return class_mapper(cls).c.keys()
67 67
68 68 def get_dict(self):
69 69 """
70 70 return dict with keys and values corresponding
71 71 to this model data """
72 72
73 73 d = {}
74 74 for k in self._get_keys():
75 75 d[k] = getattr(self, k)
76 76
77 77 # also use __json__() if present to get additional fields
78 78 _json_attr = getattr(self, '__json__', None)
79 79 if _json_attr:
80 80 # update with attributes from __json__
81 81 if callable(_json_attr):
82 82 _json_attr = _json_attr()
83 83 for k, val in _json_attr.items():
84 84 d[k] = val
85 85 return d
86 86
87 87 def get_appstruct(self):
88 88 """return list with keys and values tupples corresponding
89 89 to this model data """
90 90
91 91 l = []
92 92 for k in self._get_keys():
93 93 l.append((k, getattr(self, k),))
94 94 return l
95 95
96 96 def populate_obj(self, populate_dict):
97 97 """populate model with data from given populate_dict"""
98 98
99 99 for k in self._get_keys():
100 100 if k in populate_dict:
101 101 setattr(self, k, populate_dict[k])
102 102
103 103 @classmethod
104 104 def query(cls):
105 105 return Session().query(cls)
106 106
107 107 @classmethod
108 108 def get(cls, id_):
109 109 if id_:
110 110 return cls.query().get(id_)
111 111
112 112 @classmethod
113 113 def get_or_404(cls, id_):
114 114 try:
115 115 id_ = int(id_)
116 116 except (TypeError, ValueError):
117 117 raise HTTPNotFound
118 118
119 119 res = cls.query().get(id_)
120 120 if not res:
121 121 raise HTTPNotFound
122 122 return res
123 123
124 124 @classmethod
125 125 def getAll(cls):
126 126 return cls.query().all()
127 127
128 128 @classmethod
129 129 def delete(cls, id_):
130 130 obj = cls.query().get(id_)
131 131 Session().delete(obj)
132 132
133 133 def __repr__(self):
134 134 if hasattr(self, '__unicode__'):
135 135 # python repr needs to return str
136 136 return safe_str(self.__unicode__())
137 137 return '<DB:%s>' % (self.__class__.__name__)
138 138
139 139
140 140 class RhodeCodeSetting(Base, BaseModel):
141 141 __tablename__ = 'rhodecode_settings'
142 142 __table_args__ = (
143 143 UniqueConstraint('app_settings_name'),
144 144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
145 145 'mysql_charset': 'utf8'}
146 146 )
147 147 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
148 148 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
149 149 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
150 150
151 151 def __init__(self, k='', v=''):
152 152 self.app_settings_name = k
153 153 self.app_settings_value = v
154 154
155 155 @validates('_app_settings_value')
156 156 def validate_settings_value(self, key, val):
157 157 assert type(val) == str
158 158 return val
159 159
160 160 @hybrid_property
161 161 def app_settings_value(self):
162 162 v = self._app_settings_value
163 163 if self.app_settings_name == 'ldap_active':
164 164 v = str2bool(v)
165 165 return v
166 166
167 167 @app_settings_value.setter
168 168 def app_settings_value(self, val):
169 169 """
170 170 Setter that will always make sure we use unicode in app_settings_value
171 171
172 172 :param val:
173 173 """
174 174 self._app_settings_value = safe_str(val)
175 175
176 176 def __unicode__(self):
177 177 return u"<%s('%s:%s')>" % (
178 178 self.__class__.__name__,
179 179 self.app_settings_name, self.app_settings_value
180 180 )
181 181
182 182
183 183 class RhodeCodeUi(Base, BaseModel):
184 184 __tablename__ = 'rhodecode_ui'
185 185 __table_args__ = (
186 186 UniqueConstraint('ui_key'),
187 187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
188 188 'mysql_charset': 'utf8'}
189 189 )
190 190
191 191 HOOK_REPO_SIZE = 'changegroup.repo_size'
192 192 HOOK_PUSH = 'changegroup.push_logger'
193 193 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
194 194 HOOK_PULL = 'outgoing.pull_logger'
195 195 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
196 196
197 197 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
198 198 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
199 199 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
200 200 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
201 201 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
202 202
203 203
204 204
205 205 class User(Base, BaseModel):
206 206 __tablename__ = 'users'
207 207 __table_args__ = (
208 208 UniqueConstraint('username'), UniqueConstraint('email'),
209 209 Index('u_username_idx', 'username'),
210 210 Index('u_email_idx', 'email'),
211 211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
212 212 'mysql_charset': 'utf8'}
213 213 )
214 214 DEFAULT_USER = 'default'
215 215 DEFAULT_PERMISSIONS = [
216 216 'hg.register.manual_activate', 'hg.create.repository',
217 217 'hg.fork.repository', 'repository.read', 'group.read'
218 218 ]
219 219 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
220 220 username = Column("username", String(255), nullable=True, unique=None, default=None)
221 221 password = Column("password", String(255), nullable=True, unique=None, default=None)
222 222 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
223 223 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
224 224 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
225 225 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
226 226 _email = Column("email", String(255), nullable=True, unique=None, default=None)
227 227 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
228 228 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
229 229 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
230 230 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
231 231
232 232 user_log = relationship('UserLog', cascade='all')
233 233 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
234 234
235 235 repositories = relationship('Repository')
236 236 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
237 237 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
238 238 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
239 239
240 240 group_member = relationship('UserGroupMember', cascade='all')
241 241
242 242 notifications = relationship('UserNotification', cascade='all')
243 243 # notifications assigned to this user
244 244 user_created_notifications = relationship('Notification', cascade='all')
245 245 # comments created by this user
246 246 user_comments = relationship('ChangesetComment', cascade='all')
247 247 user_emails = relationship('UserEmailMap', cascade='all')
248 248
249 249 @hybrid_property
250 250 def email(self):
251 251 return self._email
252 252
253 253 @email.setter
254 254 def email(self, val):
255 255 self._email = val.lower() if val else None
256 256
257 257 @property
258 258 def firstname(self):
259 259 # alias for future
260 260 return self.name
261 261
262 262 @property
263 263 def username_and_name(self):
264 264 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
265 265
266 266 @property
267 267 def full_name(self):
268 268 return '%s %s' % (self.firstname, self.lastname)
269 269
270 270 @property
271 271 def full_contact(self):
272 272 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
273 273
274 274 @property
275 275 def short_contact(self):
276 276 return '%s %s' % (self.firstname, self.lastname)
277 277
278 278 @property
279 279 def is_admin(self):
280 280 return self.admin
281 281
282 282 @classmethod
283 283 def get_by_username(cls, username, case_insensitive=False, cache=False):
284 284 if case_insensitive:
285 285 q = cls.query().filter(cls.username.ilike(username))
286 286 else:
287 287 q = cls.query().filter(cls.username == username)
288 288
289 289 if cache:
290 290 q = q.options(FromCache(
291 291 "sql_cache_short",
292 292 "get_user_%s" % _hash_key(username)
293 293 )
294 294 )
295 295 return q.scalar()
296 296
297 297 @classmethod
298 298 def get_by_auth_token(cls, auth_token, cache=False):
299 299 q = cls.query().filter(cls.api_key == auth_token)
300 300
301 301 if cache:
302 302 q = q.options(FromCache("sql_cache_short",
303 303 "get_auth_token_%s" % auth_token))
304 304 return q.scalar()
305 305
306 306 @classmethod
307 307 def get_by_email(cls, email, case_insensitive=False, cache=False):
308 308 if case_insensitive:
309 309 q = cls.query().filter(cls.email.ilike(email))
310 310 else:
311 311 q = cls.query().filter(cls.email == email)
312 312
313 313 if cache:
314 314 q = q.options(FromCache("sql_cache_short",
315 315 "get_email_key_%s" % email))
316 316
317 317 ret = q.scalar()
318 318 if ret is None:
319 319 q = UserEmailMap.query()
320 320 # try fetching in alternate email map
321 321 if case_insensitive:
322 322 q = q.filter(UserEmailMap.email.ilike(email))
323 323 else:
324 324 q = q.filter(UserEmailMap.email == email)
325 325 q = q.options(joinedload(UserEmailMap.user))
326 326 if cache:
327 327 q = q.options(FromCache("sql_cache_short",
328 328 "get_email_map_key_%s" % email))
329 329 ret = getattr(q.scalar(), 'user', None)
330 330
331 331 return ret
332 332
333 333
334 334 class UserEmailMap(Base, BaseModel):
335 335 __tablename__ = 'user_email_map'
336 336 __table_args__ = (
337 337 Index('uem_email_idx', 'email'),
338 338 UniqueConstraint('email'),
339 339 {'extend_existing': True, 'mysql_engine': 'InnoDB',
340 340 'mysql_charset': 'utf8'}
341 341 )
342 __mapper_args__ = {}
342
343 343
344 344 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
345 345 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
346 346 _email = Column("email", String(255), nullable=True, unique=False, default=None)
347 347 user = relationship('User', lazy='joined')
348 348
349 349 @validates('_email')
350 350 def validate_email(self, key, email):
351 351 # check if this email is not main one
352 352 main_email = Session().query(User).filter(User.email == email).scalar()
353 353 if main_email is not None:
354 354 raise AttributeError('email %s is present is user table' % email)
355 355 return email
356 356
357 357 @hybrid_property
358 358 def email(self):
359 359 return self._email
360 360
361 361 @email.setter
362 362 def email(self, val):
363 363 self._email = val.lower() if val else None
364 364
365 365
366 366 class UserLog(Base, BaseModel):
367 367 __tablename__ = 'user_logs'
368 368 __table_args__ = (
369 369 {'extend_existing': True, 'mysql_engine': 'InnoDB',
370 370 'mysql_charset': 'utf8'},
371 371 )
372 372 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
373 373 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
374 374 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
375 375 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
376 376 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
377 377 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
378 378 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
379 379
380 380
381 381 user = relationship('User')
382 382 repository = relationship('Repository', cascade='')
383 383
384 384
385 385 class UserGroup(Base, BaseModel):
386 386 __tablename__ = 'users_groups'
387 387 __table_args__ = (
388 388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
389 389 'mysql_charset': 'utf8'},
390 390 )
391 391
392 392 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
393 393 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
394 394 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
395 395 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
396 396
397 397 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
398 398 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
399 399 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
400 400
401 401 def __unicode__(self):
402 402 return u'<userGroup(%s)>' % (self.users_group_name)
403 403
404 404 @classmethod
405 405 def get_by_group_name(cls, group_name, cache=False,
406 406 case_insensitive=False):
407 407 if case_insensitive:
408 408 q = cls.query().filter(cls.users_group_name.ilike(group_name))
409 409 else:
410 410 q = cls.query().filter(cls.users_group_name == group_name)
411 411 if cache:
412 412 q = q.options(FromCache(
413 413 "sql_cache_short",
414 414 "get_user_%s" % _hash_key(group_name)
415 415 )
416 416 )
417 417 return q.scalar()
418 418
419 419 @classmethod
420 420 def get(cls, users_group_id, cache=False):
421 421 user_group = cls.query()
422 422 if cache:
423 423 user_group = user_group.options(FromCache("sql_cache_short",
424 424 "get_users_group_%s" % users_group_id))
425 425 return user_group.get(users_group_id)
426 426
427 427
428 428 class UserGroupMember(Base, BaseModel):
429 429 __tablename__ = 'users_groups_members'
430 430 __table_args__ = (
431 431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
432 432 'mysql_charset': 'utf8'},
433 433 )
434 434
435 435 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
436 436 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
437 437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
438 438
439 439 user = relationship('User', lazy='joined')
440 440 users_group = relationship('UserGroup')
441 441
442 442 def __init__(self, gr_id='', u_id=''):
443 443 self.users_group_id = gr_id
444 444 self.user_id = u_id
445 445
446 446
447 447 class Repository(Base, BaseModel):
448 448 __tablename__ = 'repositories'
449 449 __table_args__ = (
450 450 UniqueConstraint('repo_name'),
451 451 Index('r_repo_name_idx', 'repo_name'),
452 452 {'extend_existing': True, 'mysql_engine': 'InnoDB',
453 453 'mysql_charset': 'utf8'},
454 454 )
455 455
456 456 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
457 457 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
458 458 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
459 459 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
460 460 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
461 461 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
462 462 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
463 463 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
464 464 description = Column("description", String(10000), nullable=True, unique=None, default=None)
465 465 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
466 466 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
467 467 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
468 468 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
469 469 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
470 470
471 471 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
472 472 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
473 473
474 474 user = relationship('User')
475 475 fork = relationship('Repository', remote_side=repo_id)
476 476 group = relationship('RepoGroup')
477 477 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
478 478 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
479 479 stats = relationship('Statistics', cascade='all', uselist=False)
480 480
481 481 followers = relationship('UserFollowing',
482 482 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
483 483 cascade='all')
484 484
485 485 logs = relationship('UserLog')
486 486 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
487 487
488 488 pull_requests_org = relationship('PullRequest',
489 489 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
490 490 cascade="all, delete, delete-orphan")
491 491
492 492 pull_requests_other = relationship('PullRequest',
493 493 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
494 494 cascade="all, delete, delete-orphan")
495 495
496 496 def __unicode__(self):
497 497 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
498 498 self.repo_name)
499 499
500 500
501 501 @classmethod
502 502 def get_by_repo_name(cls, repo_name):
503 503 q = Session().query(cls).filter(cls.repo_name == repo_name)
504 504 q = q.options(joinedload(Repository.fork))\
505 505 .options(joinedload(Repository.user))\
506 506 .options(joinedload(Repository.group))
507 507 return q.scalar()
508 508
509 509
510 510 class RepoGroup(Base, BaseModel):
511 511 __tablename__ = 'groups'
512 512 __table_args__ = (
513 513 UniqueConstraint('group_name', 'group_parent_id'),
514 514 {'extend_existing': True, 'mysql_engine': 'InnoDB',
515 515 'mysql_charset': 'utf8'},
516 516 )
517 __mapper_args__ = {'order_by': 'group_name'}
517
518 518
519 519 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
520 520 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
521 521 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
522 522 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
523 523 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
524 524
525 525 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
526 526 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
527 527 parent_group = relationship('RepoGroup', remote_side=group_id)
528 528
529 529 def __init__(self, group_name='', parent_group=None):
530 530 self.group_name = group_name
531 531 self.parent_group = parent_group
532 532
533 533 def __unicode__(self):
534 534 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
535 535 self.group_name)
536 536
537 537 @classmethod
538 538 def url_sep(cls):
539 539 return URL_SEP
540 540
541 541 @classmethod
542 542 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
543 543 if case_insensitive:
544 544 gr = cls.query()\
545 545 .filter(cls.group_name.ilike(group_name))
546 546 else:
547 547 gr = cls.query()\
548 548 .filter(cls.group_name == group_name)
549 549 if cache:
550 550 gr = gr.options(FromCache(
551 551 "sql_cache_short",
552 552 "get_group_%s" % _hash_key(group_name)
553 553 )
554 554 )
555 555 return gr.scalar()
556 556
557 557
558 558 class Permission(Base, BaseModel):
559 559 __tablename__ = 'permissions'
560 560 __table_args__ = (
561 561 Index('p_perm_name_idx', 'permission_name'),
562 562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
563 563 'mysql_charset': 'utf8'},
564 564 )
565 565 PERMS = [
566 566 ('repository.none', _('Repository no access')),
567 567 ('repository.read', _('Repository read access')),
568 568 ('repository.write', _('Repository write access')),
569 569 ('repository.admin', _('Repository admin access')),
570 570
571 571 ('group.none', _('Repositories Group no access')),
572 572 ('group.read', _('Repositories Group read access')),
573 573 ('group.write', _('Repositories Group write access')),
574 574 ('group.admin', _('Repositories Group admin access')),
575 575
576 576 ('hg.admin', _('RhodeCode Administrator')),
577 577 ('hg.create.none', _('Repository creation disabled')),
578 578 ('hg.create.repository', _('Repository creation enabled')),
579 579 ('hg.fork.none', _('Repository forking disabled')),
580 580 ('hg.fork.repository', _('Repository forking enabled')),
581 581 ('hg.register.none', _('Register disabled')),
582 582 ('hg.register.manual_activate', _('Register new user with RhodeCode '
583 583 'with manual activation')),
584 584
585 585 ('hg.register.auto_activate', _('Register new user with RhodeCode '
586 586 'with auto activation')),
587 587 ]
588 588
589 589 # defines which permissions are more important higher the more important
590 590 PERM_WEIGHTS = {
591 591 'repository.none': 0,
592 592 'repository.read': 1,
593 593 'repository.write': 3,
594 594 'repository.admin': 4,
595 595
596 596 'group.none': 0,
597 597 'group.read': 1,
598 598 'group.write': 3,
599 599 'group.admin': 4,
600 600
601 601 'hg.fork.none': 0,
602 602 'hg.fork.repository': 1,
603 603 'hg.create.none': 0,
604 604 'hg.create.repository':1
605 605 }
606 606
607 607 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 608 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
609 609 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
610 610
611 611 def __unicode__(self):
612 612 return u"<%s('%s:%s')>" % (
613 613 self.__class__.__name__, self.permission_id, self.permission_name
614 614 )
615 615
616 616 @classmethod
617 617 def get_by_key(cls, key):
618 618 return cls.query().filter(cls.permission_name == key).scalar()
619 619
620 620
621 621 class UserRepoToPerm(Base, BaseModel):
622 622 __tablename__ = 'repo_to_perm'
623 623 __table_args__ = (
624 624 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
625 625 {'extend_existing': True, 'mysql_engine': 'InnoDB',
626 626 'mysql_charset': 'utf8'}
627 627 )
628 628 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
629 629 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
630 630 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
631 631 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
632 632
633 633 user = relationship('User')
634 634 repository = relationship('Repository')
635 635 permission = relationship('Permission')
636 636
637 637 def __unicode__(self):
638 638 return u'<user:%s => %s >' % (self.user, self.repository)
639 639
640 640
641 641 class UserToPerm(Base, BaseModel):
642 642 __tablename__ = 'user_to_perm'
643 643 __table_args__ = (
644 644 UniqueConstraint('user_id', 'permission_id'),
645 645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
646 646 'mysql_charset': 'utf8'}
647 647 )
648 648 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
649 649 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
650 650 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
651 651
652 652 user = relationship('User')
653 653 permission = relationship('Permission', lazy='joined')
654 654
655 655
656 656 class UserGroupRepoToPerm(Base, BaseModel):
657 657 __tablename__ = 'users_group_repo_to_perm'
658 658 __table_args__ = (
659 659 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
660 660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
661 661 'mysql_charset': 'utf8'}
662 662 )
663 663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
664 664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
665 665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
666 666 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
667 667
668 668 users_group = relationship('UserGroup')
669 669 permission = relationship('Permission')
670 670 repository = relationship('Repository')
671 671
672 672 def __unicode__(self):
673 673 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
674 674
675 675
676 676 class UserGroupToPerm(Base, BaseModel):
677 677 __tablename__ = 'users_group_to_perm'
678 678 __table_args__ = (
679 679 UniqueConstraint('users_group_id', 'permission_id',),
680 680 {'extend_existing': True, 'mysql_engine': 'InnoDB',
681 681 'mysql_charset': 'utf8'}
682 682 )
683 683 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
684 684 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
685 685 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
686 686
687 687 users_group = relationship('UserGroup')
688 688 permission = relationship('Permission')
689 689
690 690
691 691 class UserRepoGroupToPerm(Base, BaseModel):
692 692 __tablename__ = 'user_repo_group_to_perm'
693 693 __table_args__ = (
694 694 UniqueConstraint('user_id', 'group_id', 'permission_id'),
695 695 {'extend_existing': True, 'mysql_engine': 'InnoDB',
696 696 'mysql_charset': 'utf8'}
697 697 )
698 698
699 699 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
700 700 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
701 701 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
702 702 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
703 703
704 704 user = relationship('User')
705 705 group = relationship('RepoGroup')
706 706 permission = relationship('Permission')
707 707
708 708
709 709 class UserGroupRepoGroupToPerm(Base, BaseModel):
710 710 __tablename__ = 'users_group_repo_group_to_perm'
711 711 __table_args__ = (
712 712 UniqueConstraint('users_group_id', 'group_id'),
713 713 {'extend_existing': True, 'mysql_engine': 'InnoDB',
714 714 'mysql_charset': 'utf8'}
715 715 )
716 716
717 717 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)
718 718 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
719 719 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
720 720 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
721 721
722 722 users_group = relationship('UserGroup')
723 723 permission = relationship('Permission')
724 724 group = relationship('RepoGroup')
725 725
726 726
727 727 class Statistics(Base, BaseModel):
728 728 __tablename__ = 'statistics'
729 729 __table_args__ = (
730 730 UniqueConstraint('repository_id'),
731 731 {'extend_existing': True, 'mysql_engine': 'InnoDB',
732 732 'mysql_charset': 'utf8'}
733 733 )
734 734 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
735 735 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
736 736 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
737 737 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
738 738 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
739 739 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
740 740
741 741 repository = relationship('Repository', single_parent=True)
742 742
743 743
744 744 class UserFollowing(Base, BaseModel):
745 745 __tablename__ = 'user_followings'
746 746 __table_args__ = (
747 747 UniqueConstraint('user_id', 'follows_repository_id'),
748 748 UniqueConstraint('user_id', 'follows_user_id'),
749 749 {'extend_existing': True, 'mysql_engine': 'InnoDB',
750 750 'mysql_charset': 'utf8'}
751 751 )
752 752
753 753 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
754 754 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
755 755 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
756 756 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
757 757 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
758 758
759 759 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
760 760
761 761 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
762 762 follows_repository = relationship('Repository', order_by='Repository.repo_name')
763 763
764 764
765 765 class CacheInvalidation(Base, BaseModel):
766 766 __tablename__ = 'cache_invalidation'
767 767 __table_args__ = (
768 768 UniqueConstraint('cache_key'),
769 769 Index('key_idx', 'cache_key'),
770 770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
771 771 'mysql_charset': 'utf8'},
772 772 )
773 773 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
774 774 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
775 775 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
776 776 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
777 777
778 778 def __init__(self, cache_key, cache_args=''):
779 779 self.cache_key = cache_key
780 780 self.cache_args = cache_args
781 781 self.cache_active = False
782 782
783 783
784 784 class ChangesetComment(Base, BaseModel):
785 785 __tablename__ = 'changeset_comments'
786 786 __table_args__ = (
787 787 Index('cc_revision_idx', 'revision'),
788 788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
789 789 'mysql_charset': 'utf8'},
790 790 )
791 791 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
792 792 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
793 793 revision = Column('revision', String(40), nullable=True)
794 794 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
795 795 line_no = Column('line_no', Unicode(10), nullable=True)
796 796 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
797 797 f_path = Column('f_path', Unicode(1000), nullable=True)
798 798 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
799 799 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
800 800 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
801 801 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
802 802
803 803 author = relationship('User', lazy='joined')
804 804 repo = relationship('Repository')
805 805 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
806 806 pull_request = relationship('PullRequest', lazy='joined')
807 807
808 808 @classmethod
809 809 def get_users(cls, revision=None, pull_request_id=None):
810 810 """
811 811 Returns user associated with this ChangesetComment. ie those
812 812 who actually commented
813 813
814 814 :param cls:
815 815 :param revision:
816 816 """
817 817 q = Session().query(User)\
818 818 .join(ChangesetComment.author)
819 819 if revision:
820 820 q = q.filter(cls.revision == revision)
821 821 elif pull_request_id:
822 822 q = q.filter(cls.pull_request_id == pull_request_id)
823 823 return q.all()
824 824
825 825
826 826 class ChangesetStatus(Base, BaseModel):
827 827 __tablename__ = 'changeset_statuses'
828 828 __table_args__ = (
829 829 Index('cs_revision_idx', 'revision'),
830 830 Index('cs_version_idx', 'version'),
831 831 UniqueConstraint('repo_id', 'revision', 'version'),
832 832 {'extend_existing': True, 'mysql_engine': 'InnoDB',
833 833 'mysql_charset': 'utf8'}
834 834 )
835 835 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
836 836 STATUS_APPROVED = 'approved'
837 837 STATUS_REJECTED = 'rejected'
838 838 STATUS_UNDER_REVIEW = 'under_review'
839 839
840 840 STATUSES = [
841 841 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
842 842 (STATUS_APPROVED, _("Approved")),
843 843 (STATUS_REJECTED, _("Rejected")),
844 844 (STATUS_UNDER_REVIEW, _("Under Review")),
845 845 ]
846 846
847 847 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
848 848 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
849 849 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
850 850 revision = Column('revision', String(40), nullable=False)
851 851 status = Column('status', String(128), nullable=False, default=DEFAULT)
852 852 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
853 853 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
854 854 version = Column('version', Integer(), nullable=False, default=0)
855 855 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
856 856
857 857 author = relationship('User', lazy='joined')
858 858 repo = relationship('Repository')
859 859 comment = relationship('ChangesetComment', lazy='joined')
860 860 pull_request = relationship('PullRequest', lazy='joined')
861 861
862 862
863 863
864 864 class PullRequest(Base, BaseModel):
865 865 __tablename__ = 'pull_requests'
866 866 __table_args__ = (
867 867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
868 868 'mysql_charset': 'utf8'},
869 869 )
870 870
871 871 STATUS_NEW = u'new'
872 872 STATUS_OPEN = u'open'
873 873 STATUS_CLOSED = u'closed'
874 874
875 875 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
876 876 title = Column('title', Unicode(256), nullable=True)
877 877 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
878 878 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
879 879 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
880 880 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
881 881 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
882 882 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
883 883 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
884 884 org_ref = Column('org_ref', Unicode(256), nullable=False)
885 885 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
886 886 other_ref = Column('other_ref', Unicode(256), nullable=False)
887 887
888 888 author = relationship('User', lazy='joined')
889 889 reviewers = relationship('PullRequestReviewers',
890 890 cascade="all, delete, delete-orphan")
891 891 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
892 892 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
893 893 statuses = relationship('ChangesetStatus')
894 894 comments = relationship('ChangesetComment',
895 895 cascade="all, delete, delete-orphan")
896 896
897 897
898 898 class PullRequestReviewers(Base, BaseModel):
899 899 __tablename__ = 'pull_request_reviewers'
900 900 __table_args__ = (
901 901 {'extend_existing': True, 'mysql_engine': 'InnoDB',
902 902 'mysql_charset': 'utf8'},
903 903 )
904 904
905 905 def __init__(self, user=None, pull_request=None):
906 906 self.user = user
907 907 self.pull_request = pull_request
908 908
909 909 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
910 910 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
911 911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
912 912
913 913 user = relationship('User')
914 914 pull_request = relationship('PullRequest')
915 915
916 916
917 917 class Notification(Base, BaseModel):
918 918 __tablename__ = 'notifications'
919 919 __table_args__ = (
920 920 Index('notification_type_idx', 'type'),
921 921 {'extend_existing': True, 'mysql_engine': 'InnoDB',
922 922 'mysql_charset': 'utf8'},
923 923 )
924 924
925 925 TYPE_CHANGESET_COMMENT = u'cs_comment'
926 926 TYPE_MESSAGE = u'message'
927 927 TYPE_MENTION = u'mention'
928 928 TYPE_REGISTRATION = u'registration'
929 929 TYPE_PULL_REQUEST = u'pull_request'
930 930 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
931 931
932 932 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
933 933 subject = Column('subject', Unicode(512), nullable=True)
934 934 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
935 935 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
936 936 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
937 937 type_ = Column('type', Unicode(256))
938 938
939 939 created_by_user = relationship('User')
940 940 notifications_to_users = relationship('UserNotification', lazy='joined',
941 941 cascade="all, delete, delete-orphan")
942 942
943 943
944 944 class UserNotification(Base, BaseModel):
945 945 __tablename__ = 'user_to_notification'
946 946 __table_args__ = (
947 947 UniqueConstraint('user_id', 'notification_id'),
948 948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
949 949 'mysql_charset': 'utf8'}
950 950 )
951 951 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
952 952 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
953 953 read = Column('read', Boolean, default=False)
954 954 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
955 955
956 956 user = relationship('User', lazy="joined")
957 957 notification = relationship('Notification', lazy="joined",
958 958 order_by=lambda: Notification.created_on.desc(),)
959 959
960 960
961 961 class DbMigrateVersion(Base, BaseModel):
962 962 __tablename__ = 'db_migrate_version'
963 963 __table_args__ = (
964 964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
965 965 'mysql_charset': 'utf8'},
966 966 )
967 967 repository_id = Column('repository_id', String(250), primary_key=True)
968 968 repository_path = Column('repository_path', Text)
969 969 version = Column('version', Integer)
@@ -1,990 +1,990 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import os
22 22 import time
23 23 import logging
24 24 import datetime
25 25 import traceback
26 26 import hashlib
27 27 import collections
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, remove_suffix, remove_prefix
44 44 from rhodecode.lib.ext_json import json
45 45 from rhodecode.lib.caching_query import FromCache
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48
49 49 URL_SEP = '/'
50 50 log = logging.getLogger(__name__)
51 51
52 52 #==============================================================================
53 53 # BASE CLASSES
54 54 #==============================================================================
55 55
56 56 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
57 57
58 58
59 59 class BaseModel(object):
60 60 """
61 61 Base Model for all classes
62 62 """
63 63
64 64 @classmethod
65 65 def _get_keys(cls):
66 66 """return column names for this model """
67 67 return class_mapper(cls).c.keys()
68 68
69 69 def get_dict(self):
70 70 """
71 71 return dict with keys and values corresponding
72 72 to this model data """
73 73
74 74 d = {}
75 75 for k in self._get_keys():
76 76 d[k] = getattr(self, k)
77 77
78 78 # also use __json__() if present to get additional fields
79 79 _json_attr = getattr(self, '__json__', None)
80 80 if _json_attr:
81 81 # update with attributes from __json__
82 82 if callable(_json_attr):
83 83 _json_attr = _json_attr()
84 84 for k, val in _json_attr.items():
85 85 d[k] = val
86 86 return d
87 87
88 88 def get_appstruct(self):
89 89 """return list with keys and values tupples corresponding
90 90 to this model data """
91 91
92 92 l = []
93 93 for k in self._get_keys():
94 94 l.append((k, getattr(self, k),))
95 95 return l
96 96
97 97 def populate_obj(self, populate_dict):
98 98 """populate model with data from given populate_dict"""
99 99
100 100 for k in self._get_keys():
101 101 if k in populate_dict:
102 102 setattr(self, k, populate_dict[k])
103 103
104 104 @classmethod
105 105 def query(cls):
106 106 return Session().query(cls)
107 107
108 108 @classmethod
109 109 def get(cls, id_):
110 110 if id_:
111 111 return cls.query().get(id_)
112 112
113 113 @classmethod
114 114 def get_or_404(cls, id_):
115 115 try:
116 116 id_ = int(id_)
117 117 except (TypeError, ValueError):
118 118 raise HTTPNotFound
119 119
120 120 res = cls.query().get(id_)
121 121 if not res:
122 122 raise HTTPNotFound
123 123 return res
124 124
125 125 @classmethod
126 126 def getAll(cls):
127 127 # deprecated and left for backward compatibility
128 128 return cls.get_all()
129 129
130 130 @classmethod
131 131 def get_all(cls):
132 132 return cls.query().all()
133 133
134 134 @classmethod
135 135 def delete(cls, id_):
136 136 obj = cls.query().get(id_)
137 137 Session().delete(obj)
138 138
139 139 def __repr__(self):
140 140 if hasattr(self, '__unicode__'):
141 141 # python repr needs to return str
142 142 return safe_str(self.__unicode__())
143 143 return '<DB:%s>' % (self.__class__.__name__)
144 144
145 145
146 146 class RhodeCodeSetting(Base, BaseModel):
147 147 __tablename__ = 'rhodecode_settings'
148 148 __table_args__ = (
149 149 UniqueConstraint('app_settings_name'),
150 150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 151 'mysql_charset': 'utf8'}
152 152 )
153 153 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 154 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
155 155 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
156 156
157 157 def __init__(self, k='', v=''):
158 158 self.app_settings_name = k
159 159 self.app_settings_value = v
160 160
161 161 @validates('_app_settings_value')
162 162 def validate_settings_value(self, key, val):
163 163 assert type(val) == str
164 164 return val
165 165
166 166 @hybrid_property
167 167 def app_settings_value(self):
168 168 v = self._app_settings_value
169 169 if self.app_settings_name in ["ldap_active",
170 170 "default_repo_enable_statistics",
171 171 "default_repo_enable_locking",
172 172 "default_repo_private",
173 173 "default_repo_enable_downloads"]:
174 174 v = str2bool(v)
175 175 return v
176 176
177 177 @app_settings_value.setter
178 178 def app_settings_value(self, val):
179 179 """
180 180 Setter that will always make sure we use unicode in app_settings_value
181 181
182 182 :param val:
183 183 """
184 184 self._app_settings_value = safe_str(val)
185 185
186 186 def __unicode__(self):
187 187 return u"<%s('%s:%s')>" % (
188 188 self.__class__.__name__,
189 189 self.app_settings_name, self.app_settings_value
190 190 )
191 191
192 192
193 193 class RhodeCodeUi(Base, BaseModel):
194 194 __tablename__ = 'rhodecode_ui'
195 195 __table_args__ = (
196 196 UniqueConstraint('ui_key'),
197 197 {'extend_existing': True, 'mysql_engine': 'InnoDB',
198 198 'mysql_charset': 'utf8'}
199 199 )
200 200
201 201 HOOK_REPO_SIZE = 'changegroup.repo_size'
202 202 HOOK_PUSH = 'changegroup.push_logger'
203 203 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
204 204 HOOK_PULL = 'outgoing.pull_logger'
205 205 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
206 206
207 207 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
208 208 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
209 209 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
210 210 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
211 211 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
212 212
213 213
214 214
215 215 class User(Base, BaseModel):
216 216 __tablename__ = 'users'
217 217 __table_args__ = (
218 218 UniqueConstraint('username'), UniqueConstraint('email'),
219 219 Index('u_username_idx', 'username'),
220 220 Index('u_email_idx', 'email'),
221 221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 222 'mysql_charset': 'utf8'}
223 223 )
224 224 DEFAULT_USER = 'default'
225 225 DEFAULT_PERMISSIONS = [
226 226 'hg.register.manual_activate', 'hg.create.repository',
227 227 'hg.fork.repository', 'repository.read', 'group.read'
228 228 ]
229 229 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
230 230 username = Column("username", String(255), nullable=True, unique=None, default=None)
231 231 password = Column("password", String(255), nullable=True, unique=None, default=None)
232 232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
233 233 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
234 234 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
235 235 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
236 236 _email = Column("email", String(255), nullable=True, unique=None, default=None)
237 237 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
238 238 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
239 239 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
240 240 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
241 241
242 242 user_log = relationship('UserLog')
243 243 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
244 244
245 245 repositories = relationship('Repository')
246 246 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
247 247 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
248 248
249 249 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
250 250 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
251 251
252 252 group_member = relationship('UserGroupMember', cascade='all')
253 253
254 254 notifications = relationship('UserNotification', cascade='all')
255 255 # notifications assigned to this user
256 256 user_created_notifications = relationship('Notification', cascade='all')
257 257 # comments created by this user
258 258 user_comments = relationship('ChangesetComment', cascade='all')
259 259 user_emails = relationship('UserEmailMap', cascade='all')
260 260
261 261 @hybrid_property
262 262 def email(self):
263 263 return self._email
264 264
265 265 @email.setter
266 266 def email(self, val):
267 267 self._email = val.lower() if val else None
268 268
269 269 @property
270 270 def firstname(self):
271 271 # alias for future
272 272 return self.name
273 273
274 274 @property
275 275 def username_and_name(self):
276 276 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
277 277
278 278 @property
279 279 def full_name(self):
280 280 return '%s %s' % (self.firstname, self.lastname)
281 281
282 282 @property
283 283 def full_contact(self):
284 284 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
285 285
286 286 @property
287 287 def short_contact(self):
288 288 return '%s %s' % (self.firstname, self.lastname)
289 289
290 290 @property
291 291 def is_admin(self):
292 292 return self.admin
293 293
294 294 @classmethod
295 295 def get_by_username(cls, username, case_insensitive=False, cache=False):
296 296 if case_insensitive:
297 297 q = cls.query().filter(cls.username.ilike(username))
298 298 else:
299 299 q = cls.query().filter(cls.username == username)
300 300
301 301 if cache:
302 302 q = q.options(FromCache(
303 303 "sql_cache_short",
304 304 "get_user_%s" % _hash_key(username)
305 305 )
306 306 )
307 307 return q.scalar()
308 308
309 309 @classmethod
310 310 def get_by_auth_token(cls, auth_token, cache=False):
311 311 q = cls.query().filter(cls.api_key == auth_token)
312 312
313 313 if cache:
314 314 q = q.options(FromCache("sql_cache_short",
315 315 "get_auth_token_%s" % auth_token))
316 316 return q.scalar()
317 317
318 318 @classmethod
319 319 def get_by_email(cls, email, case_insensitive=False, cache=False):
320 320 if case_insensitive:
321 321 q = cls.query().filter(cls.email.ilike(email))
322 322 else:
323 323 q = cls.query().filter(cls.email == email)
324 324
325 325 if cache:
326 326 q = q.options(FromCache("sql_cache_short",
327 327 "get_email_key_%s" % email))
328 328
329 329 ret = q.scalar()
330 330 if ret is None:
331 331 q = UserEmailMap.query()
332 332 # try fetching in alternate email map
333 333 if case_insensitive:
334 334 q = q.filter(UserEmailMap.email.ilike(email))
335 335 else:
336 336 q = q.filter(UserEmailMap.email == email)
337 337 q = q.options(joinedload(UserEmailMap.user))
338 338 if cache:
339 339 q = q.options(FromCache("sql_cache_short",
340 340 "get_email_map_key_%s" % email))
341 341 ret = getattr(q.scalar(), 'user', None)
342 342
343 343 return ret
344 344
345 345
346 346 class UserEmailMap(Base, BaseModel):
347 347 __tablename__ = 'user_email_map'
348 348 __table_args__ = (
349 349 Index('uem_email_idx', 'email'),
350 350 UniqueConstraint('email'),
351 351 {'extend_existing': True, 'mysql_engine': 'InnoDB',
352 352 'mysql_charset': 'utf8'}
353 353 )
354 __mapper_args__ = {}
354
355 355
356 356 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
357 357 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
358 358 _email = Column("email", String(255), nullable=True, unique=False, default=None)
359 359 user = relationship('User', lazy='joined')
360 360
361 361 @validates('_email')
362 362 def validate_email(self, key, email):
363 363 # check if this email is not main one
364 364 main_email = Session().query(User).filter(User.email == email).scalar()
365 365 if main_email is not None:
366 366 raise AttributeError('email %s is present is user table' % email)
367 367 return email
368 368
369 369 @hybrid_property
370 370 def email(self):
371 371 return self._email
372 372
373 373 @email.setter
374 374 def email(self, val):
375 375 self._email = val.lower() if val else None
376 376
377 377
378 378 class UserLog(Base, BaseModel):
379 379 __tablename__ = 'user_logs'
380 380 __table_args__ = (
381 381 {'extend_existing': True, 'mysql_engine': 'InnoDB',
382 382 'mysql_charset': 'utf8'},
383 383 )
384 384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
385 385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
386 386 username = Column("username", String(255), nullable=True, unique=None, default=None)
387 387 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
388 388 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
389 389 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
390 390 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
391 391 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
392 392
393 393
394 394 user = relationship('User')
395 395 repository = relationship('Repository', cascade='')
396 396
397 397
398 398 class UserGroup(Base, BaseModel):
399 399 __tablename__ = 'users_groups'
400 400 __table_args__ = (
401 401 {'extend_existing': True, 'mysql_engine': 'InnoDB',
402 402 'mysql_charset': 'utf8'},
403 403 )
404 404
405 405 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
406 406 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
407 407 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
408 408 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
409 409
410 410 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
411 411 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
412 412 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
413 413
414 414 def __unicode__(self):
415 415 return u'<userGroup(%s)>' % (self.users_group_name)
416 416
417 417 @classmethod
418 418 def get_by_group_name(cls, group_name, cache=False,
419 419 case_insensitive=False):
420 420 if case_insensitive:
421 421 q = cls.query().filter(cls.users_group_name.ilike(group_name))
422 422 else:
423 423 q = cls.query().filter(cls.users_group_name == group_name)
424 424 if cache:
425 425 q = q.options(FromCache(
426 426 "sql_cache_short",
427 427 "get_user_%s" % _hash_key(group_name)
428 428 )
429 429 )
430 430 return q.scalar()
431 431
432 432 @classmethod
433 433 def get(cls, users_group_id, cache=False):
434 434 user_group = cls.query()
435 435 if cache:
436 436 user_group = user_group.options(FromCache("sql_cache_short",
437 437 "get_users_group_%s" % users_group_id))
438 438 return user_group.get(users_group_id)
439 439
440 440
441 441 class UserGroupMember(Base, BaseModel):
442 442 __tablename__ = 'users_groups_members'
443 443 __table_args__ = (
444 444 {'extend_existing': True, 'mysql_engine': 'InnoDB',
445 445 'mysql_charset': 'utf8'},
446 446 )
447 447
448 448 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
449 449 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
450 450 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
451 451
452 452 user = relationship('User', lazy='joined')
453 453 users_group = relationship('UserGroup')
454 454
455 455 def __init__(self, gr_id='', u_id=''):
456 456 self.users_group_id = gr_id
457 457 self.user_id = u_id
458 458
459 459
460 460 class Repository(Base, BaseModel):
461 461 __tablename__ = 'repositories'
462 462 __table_args__ = (
463 463 UniqueConstraint('repo_name'),
464 464 Index('r_repo_name_idx', 'repo_name'),
465 465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
466 466 'mysql_charset': 'utf8'},
467 467 )
468 468
469 469 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
470 470 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
471 471 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
472 472 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
473 473 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
474 474 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
475 475 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
476 476 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
477 477 description = Column("description", String(10000), nullable=True, unique=None, default=None)
478 478 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
479 479 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
480 480 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
481 481 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
482 482 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
483 483
484 484 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
485 485 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
486 486
487 487 user = relationship('User')
488 488 fork = relationship('Repository', remote_side=repo_id)
489 489 group = relationship('RepoGroup')
490 490 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
491 491 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
492 492 stats = relationship('Statistics', cascade='all', uselist=False)
493 493
494 494 followers = relationship('UserFollowing',
495 495 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
496 496 cascade='all')
497 497
498 498 logs = relationship('UserLog')
499 499 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
500 500
501 501 pull_requests_org = relationship('PullRequest',
502 502 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
503 503 cascade="all, delete, delete-orphan")
504 504
505 505 pull_requests_other = relationship('PullRequest',
506 506 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
507 507 cascade="all, delete, delete-orphan")
508 508
509 509 def __unicode__(self):
510 510 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
511 511 self.repo_name)
512 512
513 513
514 514 @classmethod
515 515 def get_by_repo_name(cls, repo_name):
516 516 q = Session().query(cls).filter(cls.repo_name == repo_name)
517 517 q = q.options(joinedload(Repository.fork))\
518 518 .options(joinedload(Repository.user))\
519 519 .options(joinedload(Repository.group))
520 520 return q.scalar()
521 521
522 522
523 523 class RepoGroup(Base, BaseModel):
524 524 __tablename__ = 'groups'
525 525 __table_args__ = (
526 526 UniqueConstraint('group_name', 'group_parent_id'),
527 527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 528 'mysql_charset': 'utf8'},
529 529 )
530 __mapper_args__ = {'order_by': 'group_name'}
530
531 531
532 532 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
533 533 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
534 534 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
535 535 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
536 536 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
537 537
538 538 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
539 539 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
540 540 parent_group = relationship('RepoGroup', remote_side=group_id)
541 541
542 542 def __init__(self, group_name='', parent_group=None):
543 543 self.group_name = group_name
544 544 self.parent_group = parent_group
545 545
546 546 def __unicode__(self):
547 547 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
548 548 self.group_name)
549 549
550 550 @classmethod
551 551 def url_sep(cls):
552 552 return URL_SEP
553 553
554 554 @classmethod
555 555 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
556 556 if case_insensitive:
557 557 gr = cls.query()\
558 558 .filter(cls.group_name.ilike(group_name))
559 559 else:
560 560 gr = cls.query()\
561 561 .filter(cls.group_name == group_name)
562 562 if cache:
563 563 gr = gr.options(FromCache(
564 564 "sql_cache_short",
565 565 "get_group_%s" % _hash_key(group_name)
566 566 )
567 567 )
568 568 return gr.scalar()
569 569
570 570
571 571 class Permission(Base, BaseModel):
572 572 __tablename__ = 'permissions'
573 573 __table_args__ = (
574 574 Index('p_perm_name_idx', 'permission_name'),
575 575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
576 576 'mysql_charset': 'utf8'},
577 577 )
578 578 PERMS = [
579 579 ('repository.none', _('Repository no access')),
580 580 ('repository.read', _('Repository read access')),
581 581 ('repository.write', _('Repository write access')),
582 582 ('repository.admin', _('Repository admin access')),
583 583
584 584 ('group.none', _('Repositories Group no access')),
585 585 ('group.read', _('Repositories Group read access')),
586 586 ('group.write', _('Repositories Group write access')),
587 587 ('group.admin', _('Repositories Group admin access')),
588 588
589 589 ('hg.admin', _('RhodeCode Administrator')),
590 590 ('hg.create.none', _('Repository creation disabled')),
591 591 ('hg.create.repository', _('Repository creation enabled')),
592 592 ('hg.fork.none', _('Repository forking disabled')),
593 593 ('hg.fork.repository', _('Repository forking enabled')),
594 594 ('hg.register.none', _('Register disabled')),
595 595 ('hg.register.manual_activate', _('Register new user with RhodeCode '
596 596 'with manual activation')),
597 597
598 598 ('hg.register.auto_activate', _('Register new user with RhodeCode '
599 599 'with auto activation')),
600 600 ]
601 601
602 602 # defines which permissions are more important higher the more important
603 603 PERM_WEIGHTS = {
604 604 'repository.none': 0,
605 605 'repository.read': 1,
606 606 'repository.write': 3,
607 607 'repository.admin': 4,
608 608
609 609 'group.none': 0,
610 610 'group.read': 1,
611 611 'group.write': 3,
612 612 'group.admin': 4,
613 613
614 614 'hg.fork.none': 0,
615 615 'hg.fork.repository': 1,
616 616 'hg.create.none': 0,
617 617 'hg.create.repository':1
618 618 }
619 619
620 620 DEFAULT_USER_PERMISSIONS = [
621 621 'repository.read',
622 622 'group.read',
623 623 'hg.create.repository',
624 624 'hg.fork.repository',
625 625 'hg.register.manual_activate',
626 626 ]
627 627
628 628 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
629 629 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
630 630 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
631 631
632 632 def __unicode__(self):
633 633 return u"<%s('%s:%s')>" % (
634 634 self.__class__.__name__, self.permission_id, self.permission_name
635 635 )
636 636
637 637 @classmethod
638 638 def get_by_key(cls, key):
639 639 return cls.query().filter(cls.permission_name == key).scalar()
640 640
641 641
642 642 class UserRepoToPerm(Base, BaseModel):
643 643 __tablename__ = 'repo_to_perm'
644 644 __table_args__ = (
645 645 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
646 646 {'extend_existing': True, 'mysql_engine': 'InnoDB',
647 647 'mysql_charset': 'utf8'}
648 648 )
649 649 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
650 650 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
651 651 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
652 652 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
653 653
654 654 user = relationship('User')
655 655 repository = relationship('Repository')
656 656 permission = relationship('Permission')
657 657
658 658 def __unicode__(self):
659 659 return u'<user:%s => %s >' % (self.user, self.repository)
660 660
661 661
662 662 class UserToPerm(Base, BaseModel):
663 663 __tablename__ = 'user_to_perm'
664 664 __table_args__ = (
665 665 UniqueConstraint('user_id', 'permission_id'),
666 666 {'extend_existing': True, 'mysql_engine': 'InnoDB',
667 667 'mysql_charset': 'utf8'}
668 668 )
669 669 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 670 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
671 671 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
672 672
673 673 user = relationship('User')
674 674 permission = relationship('Permission', lazy='joined')
675 675
676 676
677 677 class UserGroupRepoToPerm(Base, BaseModel):
678 678 __tablename__ = 'users_group_repo_to_perm'
679 679 __table_args__ = (
680 680 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
681 681 {'extend_existing': True, 'mysql_engine': 'InnoDB',
682 682 'mysql_charset': 'utf8'}
683 683 )
684 684 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
685 685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
686 686 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
687 687 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
688 688
689 689 users_group = relationship('UserGroup')
690 690 permission = relationship('Permission')
691 691 repository = relationship('Repository')
692 692
693 693 def __unicode__(self):
694 694 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
695 695
696 696
697 697 class UserGroupToPerm(Base, BaseModel):
698 698 __tablename__ = 'users_group_to_perm'
699 699 __table_args__ = (
700 700 UniqueConstraint('users_group_id', 'permission_id',),
701 701 {'extend_existing': True, 'mysql_engine': 'InnoDB',
702 702 'mysql_charset': 'utf8'}
703 703 )
704 704 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
705 705 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
706 706 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
707 707
708 708 users_group = relationship('UserGroup')
709 709 permission = relationship('Permission')
710 710
711 711
712 712 class UserRepoGroupToPerm(Base, BaseModel):
713 713 __tablename__ = 'user_repo_group_to_perm'
714 714 __table_args__ = (
715 715 UniqueConstraint('user_id', 'group_id', 'permission_id'),
716 716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
717 717 'mysql_charset': 'utf8'}
718 718 )
719 719
720 720 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
721 721 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
722 722 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
723 723 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
724 724
725 725 user = relationship('User')
726 726 group = relationship('RepoGroup')
727 727 permission = relationship('Permission')
728 728
729 729
730 730 class UserGroupRepoGroupToPerm(Base, BaseModel):
731 731 __tablename__ = 'users_group_repo_group_to_perm'
732 732 __table_args__ = (
733 733 UniqueConstraint('users_group_id', 'group_id'),
734 734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
735 735 'mysql_charset': 'utf8'}
736 736 )
737 737
738 738 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)
739 739 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
740 740 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
741 741 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
742 742
743 743 users_group = relationship('UserGroup')
744 744 permission = relationship('Permission')
745 745 group = relationship('RepoGroup')
746 746
747 747
748 748 class Statistics(Base, BaseModel):
749 749 __tablename__ = 'statistics'
750 750 __table_args__ = (
751 751 UniqueConstraint('repository_id'),
752 752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
753 753 'mysql_charset': 'utf8'}
754 754 )
755 755 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
756 756 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
757 757 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
758 758 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
759 759 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
760 760 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
761 761
762 762 repository = relationship('Repository', single_parent=True)
763 763
764 764
765 765 class UserFollowing(Base, BaseModel):
766 766 __tablename__ = 'user_followings'
767 767 __table_args__ = (
768 768 UniqueConstraint('user_id', 'follows_repository_id'),
769 769 UniqueConstraint('user_id', 'follows_user_id'),
770 770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
771 771 'mysql_charset': 'utf8'}
772 772 )
773 773
774 774 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
775 775 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
776 776 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
777 777 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
778 778 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
779 779
780 780 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
781 781
782 782 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
783 783 follows_repository = relationship('Repository', order_by='Repository.repo_name')
784 784
785 785
786 786 class CacheInvalidation(Base, BaseModel):
787 787 __tablename__ = 'cache_invalidation'
788 788 __table_args__ = (
789 789 UniqueConstraint('cache_key'),
790 790 Index('key_idx', 'cache_key'),
791 791 {'extend_existing': True, 'mysql_engine': 'InnoDB',
792 792 'mysql_charset': 'utf8'},
793 793 )
794 794 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
795 795 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
796 796 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
797 797 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
798 798
799 799 def __init__(self, cache_key, cache_args=''):
800 800 self.cache_key = cache_key
801 801 self.cache_args = cache_args
802 802 self.cache_active = False
803 803
804 804
805 805 class ChangesetComment(Base, BaseModel):
806 806 __tablename__ = 'changeset_comments'
807 807 __table_args__ = (
808 808 Index('cc_revision_idx', 'revision'),
809 809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
810 810 'mysql_charset': 'utf8'},
811 811 )
812 812 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
813 813 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
814 814 revision = Column('revision', String(40), nullable=True)
815 815 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
816 816 line_no = Column('line_no', Unicode(10), nullable=True)
817 817 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
818 818 f_path = Column('f_path', Unicode(1000), nullable=True)
819 819 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
820 820 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
821 821 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
822 822 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
823 823
824 824 author = relationship('User', lazy='joined')
825 825 repo = relationship('Repository')
826 826 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
827 827 pull_request = relationship('PullRequest', lazy='joined')
828 828
829 829 @classmethod
830 830 def get_users(cls, revision=None, pull_request_id=None):
831 831 """
832 832 Returns user associated with this ChangesetComment. ie those
833 833 who actually commented
834 834
835 835 :param cls:
836 836 :param revision:
837 837 """
838 838 q = Session().query(User)\
839 839 .join(ChangesetComment.author)
840 840 if revision:
841 841 q = q.filter(cls.revision == revision)
842 842 elif pull_request_id:
843 843 q = q.filter(cls.pull_request_id == pull_request_id)
844 844 return q.all()
845 845
846 846
847 847 class ChangesetStatus(Base, BaseModel):
848 848 __tablename__ = 'changeset_statuses'
849 849 __table_args__ = (
850 850 Index('cs_revision_idx', 'revision'),
851 851 Index('cs_version_idx', 'version'),
852 852 UniqueConstraint('repo_id', 'revision', 'version'),
853 853 {'extend_existing': True, 'mysql_engine': 'InnoDB',
854 854 'mysql_charset': 'utf8'}
855 855 )
856 856 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
857 857 STATUS_APPROVED = 'approved'
858 858 STATUS_REJECTED = 'rejected'
859 859 STATUS_UNDER_REVIEW = 'under_review'
860 860
861 861 STATUSES = [
862 862 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
863 863 (STATUS_APPROVED, _("Approved")),
864 864 (STATUS_REJECTED, _("Rejected")),
865 865 (STATUS_UNDER_REVIEW, _("Under Review")),
866 866 ]
867 867
868 868 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
869 869 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
870 870 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
871 871 revision = Column('revision', String(40), nullable=False)
872 872 status = Column('status', String(128), nullable=False, default=DEFAULT)
873 873 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
874 874 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
875 875 version = Column('version', Integer(), nullable=False, default=0)
876 876 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
877 877
878 878 author = relationship('User', lazy='joined')
879 879 repo = relationship('Repository')
880 880 comment = relationship('ChangesetComment', lazy='joined')
881 881 pull_request = relationship('PullRequest', lazy='joined')
882 882
883 883
884 884
885 885 class PullRequest(Base, BaseModel):
886 886 __tablename__ = 'pull_requests'
887 887 __table_args__ = (
888 888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
889 889 'mysql_charset': 'utf8'},
890 890 )
891 891
892 892 STATUS_NEW = u'new'
893 893 STATUS_OPEN = u'open'
894 894 STATUS_CLOSED = u'closed'
895 895
896 896 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
897 897 title = Column('title', Unicode(256), nullable=True)
898 898 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
899 899 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
900 900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
901 901 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
902 902 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
903 903 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
904 904 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
905 905 org_ref = Column('org_ref', Unicode(256), nullable=False)
906 906 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
907 907 other_ref = Column('other_ref', Unicode(256), nullable=False)
908 908
909 909 author = relationship('User', lazy='joined')
910 910 reviewers = relationship('PullRequestReviewers',
911 911 cascade="all, delete, delete-orphan")
912 912 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
913 913 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
914 914 statuses = relationship('ChangesetStatus')
915 915 comments = relationship('ChangesetComment',
916 916 cascade="all, delete, delete-orphan")
917 917
918 918
919 919 class PullRequestReviewers(Base, BaseModel):
920 920 __tablename__ = 'pull_request_reviewers'
921 921 __table_args__ = (
922 922 {'extend_existing': True, 'mysql_engine': 'InnoDB',
923 923 'mysql_charset': 'utf8'},
924 924 )
925 925
926 926 def __init__(self, user=None, pull_request=None):
927 927 self.user = user
928 928 self.pull_request = pull_request
929 929
930 930 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
931 931 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
932 932 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
933 933
934 934 user = relationship('User')
935 935 pull_request = relationship('PullRequest')
936 936
937 937
938 938 class Notification(Base, BaseModel):
939 939 __tablename__ = 'notifications'
940 940 __table_args__ = (
941 941 Index('notification_type_idx', 'type'),
942 942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
943 943 'mysql_charset': 'utf8'},
944 944 )
945 945
946 946 TYPE_CHANGESET_COMMENT = u'cs_comment'
947 947 TYPE_MESSAGE = u'message'
948 948 TYPE_MENTION = u'mention'
949 949 TYPE_REGISTRATION = u'registration'
950 950 TYPE_PULL_REQUEST = u'pull_request'
951 951 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
952 952
953 953 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
954 954 subject = Column('subject', Unicode(512), nullable=True)
955 955 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
956 956 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
957 957 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
958 958 type_ = Column('type', Unicode(256))
959 959
960 960 created_by_user = relationship('User')
961 961 notifications_to_users = relationship('UserNotification', lazy='joined',
962 962 cascade="all, delete, delete-orphan")
963 963
964 964
965 965 class UserNotification(Base, BaseModel):
966 966 __tablename__ = 'user_to_notification'
967 967 __table_args__ = (
968 968 UniqueConstraint('user_id', 'notification_id'),
969 969 {'extend_existing': True, 'mysql_engine': 'InnoDB',
970 970 'mysql_charset': 'utf8'}
971 971 )
972 972 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
973 973 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
974 974 read = Column('read', Boolean, default=False)
975 975 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
976 976
977 977 user = relationship('User', lazy="joined")
978 978 notification = relationship('Notification', lazy="joined",
979 979 order_by=lambda: Notification.created_on.desc(),)
980 980
981 981
982 982 class DbMigrateVersion(Base, BaseModel):
983 983 __tablename__ = 'db_migrate_version'
984 984 __table_args__ = (
985 985 {'extend_existing': True, 'mysql_engine': 'InnoDB',
986 986 'mysql_charset': 'utf8'},
987 987 )
988 988 repository_id = Column('repository_id', String(250), primary_key=True)
989 989 repository_path = Column('repository_path', Text)
990 990 version = Column('version', Integer)
@@ -1,999 +1,999 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27
28 28 from sqlalchemy import *
29 29 from sqlalchemy.ext.hybrid import hybrid_property
30 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
31 31 from sqlalchemy.exc import DatabaseError
32 32 from beaker.cache import cache_region, region_invalidate
33 33 from webob.exc import HTTPNotFound
34 34
35 35 from rhodecode.translation import _
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 zope.cachedescriptors.property import Lazy as LazyProperty
41 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, remove_suffix, remove_prefix
44 44 from rhodecode.lib.ext_json import json
45 45 from rhodecode.lib.caching_query import FromCache
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48
49 49 URL_SEP = '/'
50 50 log = logging.getLogger(__name__)
51 51
52 52 #==============================================================================
53 53 # BASE CLASSES
54 54 #==============================================================================
55 55
56 56 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
57 57
58 58
59 59 class BaseModel(object):
60 60 """
61 61 Base Model for all classes
62 62 """
63 63
64 64 @classmethod
65 65 def _get_keys(cls):
66 66 """return column names for this model """
67 67 return class_mapper(cls).c.keys()
68 68
69 69 def get_dict(self):
70 70 """
71 71 return dict with keys and values corresponding
72 72 to this model data """
73 73
74 74 d = {}
75 75 for k in self._get_keys():
76 76 d[k] = getattr(self, k)
77 77
78 78 # also use __json__() if present to get additional fields
79 79 _json_attr = getattr(self, '__json__', None)
80 80 if _json_attr:
81 81 # update with attributes from __json__
82 82 if callable(_json_attr):
83 83 _json_attr = _json_attr()
84 84 for k, val in _json_attr.items():
85 85 d[k] = val
86 86 return d
87 87
88 88 def get_appstruct(self):
89 89 """return list with keys and values tupples corresponding
90 90 to this model data """
91 91
92 92 l = []
93 93 for k in self._get_keys():
94 94 l.append((k, getattr(self, k),))
95 95 return l
96 96
97 97 def populate_obj(self, populate_dict):
98 98 """populate model with data from given populate_dict"""
99 99
100 100 for k in self._get_keys():
101 101 if k in populate_dict:
102 102 setattr(self, k, populate_dict[k])
103 103
104 104 @classmethod
105 105 def query(cls):
106 106 return Session().query(cls)
107 107
108 108 @classmethod
109 109 def get(cls, id_):
110 110 if id_:
111 111 return cls.query().get(id_)
112 112
113 113 @classmethod
114 114 def get_or_404(cls, id_):
115 115 try:
116 116 id_ = int(id_)
117 117 except (TypeError, ValueError):
118 118 raise HTTPNotFound
119 119
120 120 res = cls.query().get(id_)
121 121 if not res:
122 122 raise HTTPNotFound
123 123 return res
124 124
125 125 @classmethod
126 126 def getAll(cls):
127 127 # deprecated and left for backward compatibility
128 128 return cls.get_all()
129 129
130 130 @classmethod
131 131 def get_all(cls):
132 132 return cls.query().all()
133 133
134 134 @classmethod
135 135 def delete(cls, id_):
136 136 obj = cls.query().get(id_)
137 137 Session().delete(obj)
138 138
139 139 def __repr__(self):
140 140 if hasattr(self, '__unicode__'):
141 141 # python repr needs to return str
142 142 return safe_str(self.__unicode__())
143 143 return '<DB:%s>' % (self.__class__.__name__)
144 144
145 145
146 146 class RhodeCodeSetting(Base, BaseModel):
147 147 __tablename__ = 'rhodecode_settings'
148 148 __table_args__ = (
149 149 UniqueConstraint('app_settings_name'),
150 150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 151 'mysql_charset': 'utf8'}
152 152 )
153 153 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 154 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
155 155 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
156 156
157 157 def __init__(self, k='', v=''):
158 158 self.app_settings_name = k
159 159 self.app_settings_value = v
160 160
161 161 @validates('_app_settings_value')
162 162 def validate_settings_value(self, key, val):
163 163 assert type(val) == str
164 164 return val
165 165
166 166 @hybrid_property
167 167 def app_settings_value(self):
168 168 v = self._app_settings_value
169 169 if self.app_settings_name in ["ldap_active",
170 170 "default_repo_enable_statistics",
171 171 "default_repo_enable_locking",
172 172 "default_repo_private",
173 173 "default_repo_enable_downloads"]:
174 174 v = str2bool(v)
175 175 return v
176 176
177 177 @app_settings_value.setter
178 178 def app_settings_value(self, val):
179 179 """
180 180 Setter that will always make sure we use unicode in app_settings_value
181 181
182 182 :param val:
183 183 """
184 184 self._app_settings_value = safe_str(val)
185 185
186 186 def __unicode__(self):
187 187 return u"<%s('%s:%s')>" % (
188 188 self.__class__.__name__,
189 189 self.app_settings_name, self.app_settings_value
190 190 )
191 191
192 192
193 193 class RhodeCodeUi(Base, BaseModel):
194 194 __tablename__ = 'rhodecode_ui'
195 195 __table_args__ = (
196 196 UniqueConstraint('ui_key'),
197 197 {'extend_existing': True, 'mysql_engine': 'InnoDB',
198 198 'mysql_charset': 'utf8'}
199 199 )
200 200
201 201 HOOK_REPO_SIZE = 'changegroup.repo_size'
202 202 HOOK_PUSH = 'changegroup.push_logger'
203 203 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
204 204 HOOK_PULL = 'outgoing.pull_logger'
205 205 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
206 206
207 207 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
208 208 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
209 209 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
210 210 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
211 211 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
212 212
213 213
214 214
215 215 class User(Base, BaseModel):
216 216 __tablename__ = 'users'
217 217 __table_args__ = (
218 218 UniqueConstraint('username'), UniqueConstraint('email'),
219 219 Index('u_username_idx', 'username'),
220 220 Index('u_email_idx', 'email'),
221 221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 222 'mysql_charset': 'utf8'}
223 223 )
224 224 DEFAULT_USER = 'default'
225 225 DEFAULT_PERMISSIONS = [
226 226 'hg.register.manual_activate', 'hg.create.repository',
227 227 'hg.fork.repository', 'repository.read', 'group.read'
228 228 ]
229 229 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
230 230 username = Column("username", String(255), nullable=True, unique=None, default=None)
231 231 password = Column("password", String(255), nullable=True, unique=None, default=None)
232 232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
233 233 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
234 234 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
235 235 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
236 236 _email = Column("email", String(255), nullable=True, unique=None, default=None)
237 237 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
238 238 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
239 239 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
240 240 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
241 241
242 242 user_log = relationship('UserLog')
243 243 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
244 244
245 245 repositories = relationship('Repository')
246 246 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
247 247 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
248 248
249 249 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
250 250 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
251 251
252 252 group_member = relationship('UserGroupMember', cascade='all')
253 253
254 254 notifications = relationship('UserNotification', cascade='all')
255 255 # notifications assigned to this user
256 256 user_created_notifications = relationship('Notification', cascade='all')
257 257 # comments created by this user
258 258 user_comments = relationship('ChangesetComment', cascade='all')
259 259 user_emails = relationship('UserEmailMap', cascade='all')
260 260
261 261 @hybrid_property
262 262 def email(self):
263 263 return self._email
264 264
265 265 @email.setter
266 266 def email(self, val):
267 267 self._email = val.lower() if val else None
268 268
269 269 @property
270 270 def firstname(self):
271 271 # alias for future
272 272 return self.name
273 273
274 274 @property
275 275 def username_and_name(self):
276 276 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
277 277
278 278 @property
279 279 def full_name(self):
280 280 return '%s %s' % (self.firstname, self.lastname)
281 281
282 282 @property
283 283 def full_contact(self):
284 284 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
285 285
286 286 @property
287 287 def short_contact(self):
288 288 return '%s %s' % (self.firstname, self.lastname)
289 289
290 290 @property
291 291 def is_admin(self):
292 292 return self.admin
293 293
294 294 @classmethod
295 295 def get_by_username(cls, username, case_insensitive=False, cache=False):
296 296 if case_insensitive:
297 297 q = cls.query().filter(cls.username.ilike(username))
298 298 else:
299 299 q = cls.query().filter(cls.username == username)
300 300
301 301 if cache:
302 302 q = q.options(FromCache(
303 303 "sql_cache_short",
304 304 "get_user_%s" % _hash_key(username)
305 305 )
306 306 )
307 307 return q.scalar()
308 308
309 309 @classmethod
310 310 def get_by_auth_token(cls, auth_token, cache=False):
311 311 q = cls.query().filter(cls.api_key == auth_token)
312 312
313 313 if cache:
314 314 q = q.options(FromCache("sql_cache_short",
315 315 "get_auth_token_%s" % auth_token))
316 316 return q.scalar()
317 317
318 318 @classmethod
319 319 def get_by_email(cls, email, case_insensitive=False, cache=False):
320 320 if case_insensitive:
321 321 q = cls.query().filter(cls.email.ilike(email))
322 322 else:
323 323 q = cls.query().filter(cls.email == email)
324 324
325 325 if cache:
326 326 q = q.options(FromCache("sql_cache_short",
327 327 "get_email_key_%s" % email))
328 328
329 329 ret = q.scalar()
330 330 if ret is None:
331 331 q = UserEmailMap.query()
332 332 # try fetching in alternate email map
333 333 if case_insensitive:
334 334 q = q.filter(UserEmailMap.email.ilike(email))
335 335 else:
336 336 q = q.filter(UserEmailMap.email == email)
337 337 q = q.options(joinedload(UserEmailMap.user))
338 338 if cache:
339 339 q = q.options(FromCache("sql_cache_short",
340 340 "get_email_map_key_%s" % email))
341 341 ret = getattr(q.scalar(), 'user', None)
342 342
343 343 return ret
344 344
345 345
346 346 class UserEmailMap(Base, BaseModel):
347 347 __tablename__ = 'user_email_map'
348 348 __table_args__ = (
349 349 Index('uem_email_idx', 'email'),
350 350 UniqueConstraint('email'),
351 351 {'extend_existing': True, 'mysql_engine': 'InnoDB',
352 352 'mysql_charset': 'utf8'}
353 353 )
354 __mapper_args__ = {}
354
355 355
356 356 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
357 357 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
358 358 _email = Column("email", String(255), nullable=True, unique=False, default=None)
359 359 user = relationship('User', lazy='joined')
360 360
361 361 @validates('_email')
362 362 def validate_email(self, key, email):
363 363 # check if this email is not main one
364 364 main_email = Session().query(User).filter(User.email == email).scalar()
365 365 if main_email is not None:
366 366 raise AttributeError('email %s is present is user table' % email)
367 367 return email
368 368
369 369 @hybrid_property
370 370 def email(self):
371 371 return self._email
372 372
373 373 @email.setter
374 374 def email(self, val):
375 375 self._email = val.lower() if val else None
376 376
377 377
378 378 class UserIpMap(Base, BaseModel):
379 379 __tablename__ = 'user_ip_map'
380 380 __table_args__ = (
381 381 UniqueConstraint('user_id', 'ip_addr'),
382 382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
383 383 'mysql_charset': 'utf8'}
384 384 )
385 __mapper_args__ = {}
385
386 386
387 387 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
388 388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
389 389 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
390 390 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
391 391 user = relationship('User', lazy='joined')
392 392
393 393
394 394 class UserLog(Base, BaseModel):
395 395 __tablename__ = 'user_logs'
396 396 __table_args__ = (
397 397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
398 398 'mysql_charset': 'utf8'},
399 399 )
400 400 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
401 401 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
402 402 username = Column("username", String(255), nullable=True, unique=None, default=None)
403 403 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
404 404 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
405 405 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
406 406 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
407 407 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
408 408
409 409
410 410 user = relationship('User')
411 411 repository = relationship('Repository', cascade='')
412 412
413 413
414 414 class UserGroup(Base, BaseModel):
415 415 __tablename__ = 'users_groups'
416 416 __table_args__ = (
417 417 {'extend_existing': True, 'mysql_engine': 'InnoDB',
418 418 'mysql_charset': 'utf8'},
419 419 )
420 420
421 421 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
422 422 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
423 423 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
424 424 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
425 425
426 426 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
427 427 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
428 428 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
429 429
430 430 def __unicode__(self):
431 431 return u'<userGroup(%s)>' % (self.users_group_name)
432 432
433 433 @classmethod
434 434 def get_by_group_name(cls, group_name, cache=False,
435 435 case_insensitive=False):
436 436 if case_insensitive:
437 437 q = cls.query().filter(cls.users_group_name.ilike(group_name))
438 438 else:
439 439 q = cls.query().filter(cls.users_group_name == group_name)
440 440 if cache:
441 441 q = q.options(FromCache(
442 442 "sql_cache_short",
443 443 "get_user_%s" % _hash_key(group_name)
444 444 )
445 445 )
446 446 return q.scalar()
447 447
448 448 @classmethod
449 449 def get(cls, users_group_id, cache=False):
450 450 user_group = cls.query()
451 451 if cache:
452 452 user_group = user_group.options(FromCache("sql_cache_short",
453 453 "get_users_group_%s" % users_group_id))
454 454 return user_group.get(users_group_id)
455 455
456 456
457 457 class UserGroupMember(Base, BaseModel):
458 458 __tablename__ = 'users_groups_members'
459 459 __table_args__ = (
460 460 {'extend_existing': True, 'mysql_engine': 'InnoDB',
461 461 'mysql_charset': 'utf8'},
462 462 )
463 463
464 464 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
465 465 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
466 466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 467
468 468 user = relationship('User', lazy='joined')
469 469 users_group = relationship('UserGroup')
470 470
471 471 def __init__(self, gr_id='', u_id=''):
472 472 self.users_group_id = gr_id
473 473 self.user_id = u_id
474 474
475 475
476 476 class Repository(Base, BaseModel):
477 477 __tablename__ = 'repositories'
478 478 __table_args__ = (
479 479 UniqueConstraint('repo_name'),
480 480 Index('r_repo_name_idx', 'repo_name'),
481 481 {'extend_existing': True, 'mysql_engine': 'InnoDB',
482 482 'mysql_charset': 'utf8'},
483 483 )
484 484
485 485 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
486 486 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
487 487 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
488 488 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
489 489 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
490 490 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
491 491 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
492 492 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
493 493 description = Column("description", String(10000), nullable=True, unique=None, default=None)
494 494 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
495 495 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
496 496 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
497 497 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
498 498 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
499 499 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
500 500
501 501 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
502 502 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
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('UserGroupRepoToPerm', cascade='all')
509 509 stats = relationship('Statistics', cascade='all', uselist=False)
510 510
511 511 followers = relationship('UserFollowing',
512 512 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
513 513 cascade='all')
514 514
515 515 logs = relationship('UserLog')
516 516 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
517 517
518 518 pull_requests_org = relationship('PullRequest',
519 519 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
520 520 cascade="all, delete, delete-orphan")
521 521
522 522 pull_requests_other = relationship('PullRequest',
523 523 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
524 524 cascade="all, delete, delete-orphan")
525 525
526 526 def __unicode__(self):
527 527 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
528 528 safe_str(self.repo_name))
529 529
530 530
531 531 @classmethod
532 532 def get_by_repo_name(cls, repo_name):
533 533 q = Session().query(cls).filter(cls.repo_name == repo_name)
534 534 q = q.options(joinedload(Repository.fork))\
535 535 .options(joinedload(Repository.user))\
536 536 .options(joinedload(Repository.group))
537 537 return q.scalar()
538 538
539 539
540 540 class RepoGroup(Base, BaseModel):
541 541 __tablename__ = 'groups'
542 542 __table_args__ = (
543 543 UniqueConstraint('group_name', 'group_parent_id'),
544 544 {'extend_existing': True, 'mysql_engine': 'InnoDB',
545 545 'mysql_charset': 'utf8'},
546 546 )
547 __mapper_args__ = {'order_by': 'group_name'}
547
548 548
549 549 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
550 550 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
551 551 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
552 552 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
553 553 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
554 554
555 555 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
556 556 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
557 557 parent_group = relationship('RepoGroup', remote_side=group_id)
558 558
559 559 def __init__(self, group_name='', parent_group=None):
560 560 self.group_name = group_name
561 561 self.parent_group = parent_group
562 562
563 563 def __unicode__(self):
564 564 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
565 565 self.group_name)
566 566
567 567 @classmethod
568 568 def url_sep(cls):
569 569 return URL_SEP
570 570
571 571 @classmethod
572 572 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
573 573 if case_insensitive:
574 574 gr = cls.query()\
575 575 .filter(cls.group_name.ilike(group_name))
576 576 else:
577 577 gr = cls.query()\
578 578 .filter(cls.group_name == group_name)
579 579 if cache:
580 580 gr = gr.options(FromCache(
581 581 "sql_cache_short",
582 582 "get_group_%s" % _hash_key(group_name)
583 583 )
584 584 )
585 585 return gr.scalar()
586 586
587 587
588 588 class Permission(Base, BaseModel):
589 589 __tablename__ = 'permissions'
590 590 __table_args__ = (
591 591 Index('p_perm_name_idx', 'permission_name'),
592 592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
593 593 'mysql_charset': 'utf8'},
594 594 )
595 595 PERMS = [
596 596 ('repository.none', _('Repository no access')),
597 597 ('repository.read', _('Repository read access')),
598 598 ('repository.write', _('Repository write access')),
599 599 ('repository.admin', _('Repository admin access')),
600 600
601 601 ('group.none', _('Repositories Group no access')),
602 602 ('group.read', _('Repositories Group read access')),
603 603 ('group.write', _('Repositories Group write access')),
604 604 ('group.admin', _('Repositories Group admin access')),
605 605
606 606 ('hg.admin', _('RhodeCode Administrator')),
607 607 ('hg.create.none', _('Repository creation disabled')),
608 608 ('hg.create.repository', _('Repository creation enabled')),
609 609 ('hg.fork.none', _('Repository forking disabled')),
610 610 ('hg.fork.repository', _('Repository forking enabled')),
611 611 ('hg.register.none', _('Register disabled')),
612 612 ('hg.register.manual_activate', _('Register new user with RhodeCode '
613 613 'with manual activation')),
614 614
615 615 ('hg.register.auto_activate', _('Register new user with RhodeCode '
616 616 'with auto activation')),
617 617 ]
618 618
619 619 # defines which permissions are more important higher the more important
620 620 PERM_WEIGHTS = {
621 621 'repository.none': 0,
622 622 'repository.read': 1,
623 623 'repository.write': 3,
624 624 'repository.admin': 4,
625 625
626 626 'group.none': 0,
627 627 'group.read': 1,
628 628 'group.write': 3,
629 629 'group.admin': 4,
630 630
631 631 'hg.fork.none': 0,
632 632 'hg.fork.repository': 1,
633 633 'hg.create.none': 0,
634 634 'hg.create.repository':1
635 635 }
636 636
637 637 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
638 638 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
639 639 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
640 640
641 641 def __unicode__(self):
642 642 return u"<%s('%s:%s')>" % (
643 643 self.__class__.__name__, self.permission_id, self.permission_name
644 644 )
645 645
646 646 @classmethod
647 647 def get_by_key(cls, key):
648 648 return cls.query().filter(cls.permission_name == key).scalar()
649 649
650 650
651 651 class UserRepoToPerm(Base, BaseModel):
652 652 __tablename__ = 'repo_to_perm'
653 653 __table_args__ = (
654 654 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
655 655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 656 'mysql_charset': 'utf8'}
657 657 )
658 658 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
659 659 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
660 660 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
661 661 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
662 662
663 663 user = relationship('User')
664 664 repository = relationship('Repository')
665 665 permission = relationship('Permission')
666 666
667 667 def __unicode__(self):
668 668 return u'<user:%s => %s >' % (self.user, self.repository)
669 669
670 670
671 671 class UserToPerm(Base, BaseModel):
672 672 __tablename__ = 'user_to_perm'
673 673 __table_args__ = (
674 674 UniqueConstraint('user_id', 'permission_id'),
675 675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
676 676 'mysql_charset': 'utf8'}
677 677 )
678 678 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
679 679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
680 680 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
681 681
682 682 user = relationship('User')
683 683 permission = relationship('Permission', lazy='joined')
684 684
685 685
686 686 class UserGroupRepoToPerm(Base, BaseModel):
687 687 __tablename__ = 'users_group_repo_to_perm'
688 688 __table_args__ = (
689 689 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
690 690 {'extend_existing': True, 'mysql_engine': 'InnoDB',
691 691 'mysql_charset': 'utf8'}
692 692 )
693 693 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
694 694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
695 695 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
696 696 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
697 697
698 698 users_group = relationship('UserGroup')
699 699 permission = relationship('Permission')
700 700 repository = relationship('Repository')
701 701
702 702 def __unicode__(self):
703 703 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
704 704
705 705
706 706 class UserGroupToPerm(Base, BaseModel):
707 707 __tablename__ = 'users_group_to_perm'
708 708 __table_args__ = (
709 709 UniqueConstraint('users_group_id', 'permission_id',),
710 710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
711 711 'mysql_charset': 'utf8'}
712 712 )
713 713 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
714 714 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
715 715 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
716 716
717 717 users_group = relationship('UserGroup')
718 718 permission = relationship('Permission')
719 719
720 720
721 721 class UserRepoGroupToPerm(Base, BaseModel):
722 722 __tablename__ = 'user_repo_group_to_perm'
723 723 __table_args__ = (
724 724 UniqueConstraint('user_id', 'group_id', 'permission_id'),
725 725 {'extend_existing': True, 'mysql_engine': 'InnoDB',
726 726 'mysql_charset': 'utf8'}
727 727 )
728 728
729 729 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
730 730 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
731 731 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
732 732 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
733 733
734 734 user = relationship('User')
735 735 group = relationship('RepoGroup')
736 736 permission = relationship('Permission')
737 737
738 738
739 739 class UserGroupRepoGroupToPerm(Base, BaseModel):
740 740 __tablename__ = 'users_group_repo_group_to_perm'
741 741 __table_args__ = (
742 742 UniqueConstraint('users_group_id', 'group_id'),
743 743 {'extend_existing': True, 'mysql_engine': 'InnoDB',
744 744 'mysql_charset': 'utf8'}
745 745 )
746 746
747 747 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)
748 748 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
749 749 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
750 750 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
751 751
752 752 users_group = relationship('UserGroup')
753 753 permission = relationship('Permission')
754 754 group = relationship('RepoGroup')
755 755
756 756
757 757 class Statistics(Base, BaseModel):
758 758 __tablename__ = 'statistics'
759 759 __table_args__ = (
760 760 UniqueConstraint('repository_id'),
761 761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
762 762 'mysql_charset': 'utf8'}
763 763 )
764 764 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
765 765 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
766 766 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
767 767 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
768 768 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
769 769 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
770 770
771 771 repository = relationship('Repository', single_parent=True)
772 772
773 773
774 774 class UserFollowing(Base, BaseModel):
775 775 __tablename__ = 'user_followings'
776 776 __table_args__ = (
777 777 UniqueConstraint('user_id', 'follows_repository_id'),
778 778 UniqueConstraint('user_id', 'follows_user_id'),
779 779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
780 780 'mysql_charset': 'utf8'}
781 781 )
782 782
783 783 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
784 784 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
785 785 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
786 786 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
787 787 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
788 788
789 789 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
790 790
791 791 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
792 792 follows_repository = relationship('Repository', order_by='Repository.repo_name')
793 793
794 794
795 795 class CacheInvalidation(Base, BaseModel):
796 796 __tablename__ = 'cache_invalidation'
797 797 __table_args__ = (
798 798 UniqueConstraint('cache_key'),
799 799 Index('key_idx', 'cache_key'),
800 800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
801 801 'mysql_charset': 'utf8'},
802 802 )
803 803 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
804 804 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
805 805 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
806 806 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
807 807
808 808 def __init__(self, cache_key, cache_args=''):
809 809 self.cache_key = cache_key
810 810 self.cache_args = cache_args
811 811 self.cache_active = False
812 812
813 813
814 814 class ChangesetComment(Base, BaseModel):
815 815 __tablename__ = 'changeset_comments'
816 816 __table_args__ = (
817 817 Index('cc_revision_idx', 'revision'),
818 818 {'extend_existing': True, 'mysql_engine': 'InnoDB',
819 819 'mysql_charset': 'utf8'},
820 820 )
821 821 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
822 822 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
823 823 revision = Column('revision', String(40), nullable=True)
824 824 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
825 825 line_no = Column('line_no', Unicode(10), nullable=True)
826 826 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
827 827 f_path = Column('f_path', Unicode(1000), nullable=True)
828 828 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
829 829 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
830 830 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
831 831 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
832 832
833 833 author = relationship('User', lazy='joined')
834 834 repo = relationship('Repository')
835 835 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
836 836 pull_request = relationship('PullRequest', lazy='joined')
837 837
838 838 @classmethod
839 839 def get_users(cls, revision=None, pull_request_id=None):
840 840 """
841 841 Returns user associated with this ChangesetComment. ie those
842 842 who actually commented
843 843
844 844 :param cls:
845 845 :param revision:
846 846 """
847 847 q = Session().query(User)\
848 848 .join(ChangesetComment.author)
849 849 if revision:
850 850 q = q.filter(cls.revision == revision)
851 851 elif pull_request_id:
852 852 q = q.filter(cls.pull_request_id == pull_request_id)
853 853 return q.all()
854 854
855 855
856 856 class ChangesetStatus(Base, BaseModel):
857 857 __tablename__ = 'changeset_statuses'
858 858 __table_args__ = (
859 859 Index('cs_revision_idx', 'revision'),
860 860 Index('cs_version_idx', 'version'),
861 861 UniqueConstraint('repo_id', 'revision', 'version'),
862 862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
863 863 'mysql_charset': 'utf8'}
864 864 )
865 865 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
866 866 STATUS_APPROVED = 'approved'
867 867 STATUS_REJECTED = 'rejected'
868 868 STATUS_UNDER_REVIEW = 'under_review'
869 869
870 870 STATUSES = [
871 871 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
872 872 (STATUS_APPROVED, _("Approved")),
873 873 (STATUS_REJECTED, _("Rejected")),
874 874 (STATUS_UNDER_REVIEW, _("Under Review")),
875 875 ]
876 876
877 877 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
878 878 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
879 879 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
880 880 revision = Column('revision', String(40), nullable=False)
881 881 status = Column('status', String(128), nullable=False, default=DEFAULT)
882 882 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
883 883 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
884 884 version = Column('version', Integer(), nullable=False, default=0)
885 885 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
886 886
887 887 author = relationship('User', lazy='joined')
888 888 repo = relationship('Repository')
889 889 comment = relationship('ChangesetComment', lazy='joined')
890 890 pull_request = relationship('PullRequest', lazy='joined')
891 891
892 892
893 893
894 894 class PullRequest(Base, BaseModel):
895 895 __tablename__ = 'pull_requests'
896 896 __table_args__ = (
897 897 {'extend_existing': True, 'mysql_engine': 'InnoDB',
898 898 'mysql_charset': 'utf8'},
899 899 )
900 900
901 901 STATUS_NEW = u'new'
902 902 STATUS_OPEN = u'open'
903 903 STATUS_CLOSED = u'closed'
904 904
905 905 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
906 906 title = Column('title', Unicode(256), nullable=True)
907 907 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
908 908 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
909 909 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
910 910 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
911 911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
912 912 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
913 913 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
914 914 org_ref = Column('org_ref', Unicode(256), nullable=False)
915 915 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
916 916 other_ref = Column('other_ref', Unicode(256), nullable=False)
917 917
918 918 author = relationship('User', lazy='joined')
919 919 reviewers = relationship('PullRequestReviewers',
920 920 cascade="all, delete, delete-orphan")
921 921 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
922 922 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
923 923 statuses = relationship('ChangesetStatus')
924 924 comments = relationship('ChangesetComment',
925 925 cascade="all, delete, delete-orphan")
926 926
927 927
928 928 class PullRequestReviewers(Base, BaseModel):
929 929 __tablename__ = 'pull_request_reviewers'
930 930 __table_args__ = (
931 931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
932 932 'mysql_charset': 'utf8'},
933 933 )
934 934
935 935 def __init__(self, user=None, pull_request=None):
936 936 self.user = user
937 937 self.pull_request = pull_request
938 938
939 939 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
940 940 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
941 941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
942 942
943 943 user = relationship('User')
944 944 pull_request = relationship('PullRequest')
945 945
946 946
947 947 class Notification(Base, BaseModel):
948 948 __tablename__ = 'notifications'
949 949 __table_args__ = (
950 950 Index('notification_type_idx', 'type'),
951 951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
952 952 'mysql_charset': 'utf8'},
953 953 )
954 954
955 955 TYPE_CHANGESET_COMMENT = u'cs_comment'
956 956 TYPE_MESSAGE = u'message'
957 957 TYPE_MENTION = u'mention'
958 958 TYPE_REGISTRATION = u'registration'
959 959 TYPE_PULL_REQUEST = u'pull_request'
960 960 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
961 961
962 962 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
963 963 subject = Column('subject', Unicode(512), nullable=True)
964 964 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
965 965 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
966 966 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
967 967 type_ = Column('type', Unicode(256))
968 968
969 969 created_by_user = relationship('User')
970 970 notifications_to_users = relationship('UserNotification', lazy='joined',
971 971 cascade="all, delete, delete-orphan")
972 972
973 973
974 974 class UserNotification(Base, BaseModel):
975 975 __tablename__ = 'user_to_notification'
976 976 __table_args__ = (
977 977 UniqueConstraint('user_id', 'notification_id'),
978 978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
979 979 'mysql_charset': 'utf8'}
980 980 )
981 981 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
982 982 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
983 983 read = Column('read', Boolean, default=False)
984 984 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
985 985
986 986 user = relationship('User', lazy="joined")
987 987 notification = relationship('Notification', lazy="joined",
988 988 order_by=lambda: Notification.created_on.desc(),)
989 989
990 990
991 991 class DbMigrateVersion(Base, BaseModel):
992 992 __tablename__ = 'db_migrate_version'
993 993 __table_args__ = (
994 994 {'extend_existing': True, 'mysql_engine': 'InnoDB',
995 995 'mysql_charset': 'utf8'},
996 996 )
997 997 repository_id = Column('repository_id', String(250), primary_key=True)
998 998 repository_path = Column('repository_path', Text)
999 999 version = Column('version', Integer)
@@ -1,1083 +1,1083 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27
28 28 from sqlalchemy import *
29 29 from sqlalchemy.ext.hybrid import hybrid_property
30 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
31 31 from sqlalchemy.exc import DatabaseError
32 32 from beaker.cache import cache_region, region_invalidate
33 33 from webob.exc import HTTPNotFound
34 34
35 35 from rhodecode.translation import _
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 zope.cachedescriptors.property import Lazy as LazyProperty
41 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
44 44 remove_suffix, remove_prefix, time_to_datetime
45 45 from rhodecode.lib.ext_json import json
46 46 from rhodecode.lib.caching_query import FromCache
47 47
48 48 from rhodecode.model.meta import Base, Session
49 49
50 50 URL_SEP = '/'
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class BaseModel(object):
61 61 """
62 62 Base Model for all classes
63 63 """
64 64
65 65 @classmethod
66 66 def _get_keys(cls):
67 67 """return column names for this model """
68 68 return class_mapper(cls).c.keys()
69 69
70 70 def get_dict(self):
71 71 """
72 72 return dict with keys and values corresponding
73 73 to this model data """
74 74
75 75 d = {}
76 76 for k in self._get_keys():
77 77 d[k] = getattr(self, k)
78 78
79 79 # also use __json__() if present to get additional fields
80 80 _json_attr = getattr(self, '__json__', None)
81 81 if _json_attr:
82 82 # update with attributes from __json__
83 83 if callable(_json_attr):
84 84 _json_attr = _json_attr()
85 85 for k, val in _json_attr.items():
86 86 d[k] = val
87 87 return d
88 88
89 89 def get_appstruct(self):
90 90 """return list with keys and values tupples corresponding
91 91 to this model data """
92 92
93 93 l = []
94 94 for k in self._get_keys():
95 95 l.append((k, getattr(self, k),))
96 96 return l
97 97
98 98 def populate_obj(self, populate_dict):
99 99 """populate model with data from given populate_dict"""
100 100
101 101 for k in self._get_keys():
102 102 if k in populate_dict:
103 103 setattr(self, k, populate_dict[k])
104 104
105 105 @classmethod
106 106 def query(cls):
107 107 return Session().query(cls)
108 108
109 109 @classmethod
110 110 def get(cls, id_):
111 111 if id_:
112 112 return cls.query().get(id_)
113 113
114 114 @classmethod
115 115 def get_or_404(cls, id_):
116 116 try:
117 117 id_ = int(id_)
118 118 except (TypeError, ValueError):
119 119 raise HTTPNotFound
120 120
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 # deprecated and left for backward compatibility
129 129 return cls.get_all()
130 130
131 131 @classmethod
132 132 def get_all(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
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == str
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_str(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193
194 194 class RhodeCodeUi(Base, BaseModel):
195 195 __tablename__ = 'rhodecode_ui'
196 196 __table_args__ = (
197 197 UniqueConstraint('ui_key'),
198 198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
199 199 'mysql_charset': 'utf8'}
200 200 )
201 201
202 202 HOOK_REPO_SIZE = 'changegroup.repo_size'
203 203 HOOK_PUSH = 'changegroup.push_logger'
204 204 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
205 205 HOOK_PULL = 'outgoing.pull_logger'
206 206 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
207 207
208 208 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
209 209 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
210 210 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
211 211 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
212 212 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
213 213
214 214
215 215
216 216 class User(Base, BaseModel):
217 217 __tablename__ = 'users'
218 218 __table_args__ = (
219 219 UniqueConstraint('username'), UniqueConstraint('email'),
220 220 Index('u_username_idx', 'username'),
221 221 Index('u_email_idx', 'email'),
222 222 {'extend_existing': True, 'mysql_engine': 'InnoDB',
223 223 'mysql_charset': 'utf8'}
224 224 )
225 225 DEFAULT_USER = 'default'
226 226 DEFAULT_PERMISSIONS = [
227 227 'hg.register.manual_activate', 'hg.create.repository',
228 228 'hg.fork.repository', 'repository.read', 'group.read'
229 229 ]
230 230 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 231 username = Column("username", String(255), nullable=True, unique=None, default=None)
232 232 password = Column("password", String(255), nullable=True, unique=None, default=None)
233 233 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
234 234 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
235 235 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
236 236 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
237 237 _email = Column("email", String(255), nullable=True, unique=None, default=None)
238 238 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
239 239 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
240 240 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
241 241 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
242 242
243 243 user_log = relationship('UserLog')
244 244 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
245 245
246 246 repositories = relationship('Repository')
247 247 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
248 248 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
249 249
250 250 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
251 251 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
252 252
253 253 group_member = relationship('UserGroupMember', cascade='all')
254 254
255 255 notifications = relationship('UserNotification', cascade='all')
256 256 # notifications assigned to this user
257 257 user_created_notifications = relationship('Notification', cascade='all')
258 258 # comments created by this user
259 259 user_comments = relationship('ChangesetComment', cascade='all')
260 260 user_emails = relationship('UserEmailMap', cascade='all')
261 261
262 262 @hybrid_property
263 263 def email(self):
264 264 return self._email
265 265
266 266 @email.setter
267 267 def email(self, val):
268 268 self._email = val.lower() if val else None
269 269
270 270 @property
271 271 def firstname(self):
272 272 # alias for future
273 273 return self.name
274 274
275 275 @property
276 276 def username_and_name(self):
277 277 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
278 278
279 279 @property
280 280 def full_name(self):
281 281 return '%s %s' % (self.firstname, self.lastname)
282 282
283 283 @property
284 284 def full_contact(self):
285 285 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
286 286
287 287 @property
288 288 def short_contact(self):
289 289 return '%s %s' % (self.firstname, self.lastname)
290 290
291 291 @property
292 292 def is_admin(self):
293 293 return self.admin
294 294
295 295 @classmethod
296 296 def get_by_username(cls, username, case_insensitive=False, cache=False):
297 297 if case_insensitive:
298 298 q = cls.query().filter(cls.username.ilike(username))
299 299 else:
300 300 q = cls.query().filter(cls.username == username)
301 301
302 302 if cache:
303 303 q = q.options(FromCache(
304 304 "sql_cache_short",
305 305 "get_user_%s" % _hash_key(username)
306 306 )
307 307 )
308 308 return q.scalar()
309 309
310 310 @classmethod
311 311 def get_by_auth_token(cls, auth_token, cache=False):
312 312 q = cls.query().filter(cls.api_key == auth_token)
313 313
314 314 if cache:
315 315 q = q.options(FromCache("sql_cache_short",
316 316 "get_auth_token_%s" % auth_token))
317 317 return q.scalar()
318 318
319 319 @classmethod
320 320 def get_by_email(cls, email, case_insensitive=False, cache=False):
321 321 if case_insensitive:
322 322 q = cls.query().filter(cls.email.ilike(email))
323 323 else:
324 324 q = cls.query().filter(cls.email == email)
325 325
326 326 if cache:
327 327 q = q.options(FromCache("sql_cache_short",
328 328 "get_email_key_%s" % email))
329 329
330 330 ret = q.scalar()
331 331 if ret is None:
332 332 q = UserEmailMap.query()
333 333 # try fetching in alternate email map
334 334 if case_insensitive:
335 335 q = q.filter(UserEmailMap.email.ilike(email))
336 336 else:
337 337 q = q.filter(UserEmailMap.email == email)
338 338 q = q.options(joinedload(UserEmailMap.user))
339 339 if cache:
340 340 q = q.options(FromCache("sql_cache_short",
341 341 "get_email_map_key_%s" % email))
342 342 ret = getattr(q.scalar(), 'user', None)
343 343
344 344 return ret
345 345
346 346
347 347 class UserEmailMap(Base, BaseModel):
348 348 __tablename__ = 'user_email_map'
349 349 __table_args__ = (
350 350 Index('uem_email_idx', 'email'),
351 351 UniqueConstraint('email'),
352 352 {'extend_existing': True, 'mysql_engine': 'InnoDB',
353 353 'mysql_charset': 'utf8'}
354 354 )
355 __mapper_args__ = {}
355
356 356
357 357 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
358 358 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
359 359 _email = Column("email", String(255), nullable=True, unique=False, default=None)
360 360 user = relationship('User', lazy='joined')
361 361
362 362 @validates('_email')
363 363 def validate_email(self, key, email):
364 364 # check if this email is not main one
365 365 main_email = Session().query(User).filter(User.email == email).scalar()
366 366 if main_email is not None:
367 367 raise AttributeError('email %s is present is user table' % email)
368 368 return email
369 369
370 370 @hybrid_property
371 371 def email(self):
372 372 return self._email
373 373
374 374 @email.setter
375 375 def email(self, val):
376 376 self._email = val.lower() if val else None
377 377
378 378
379 379 class UserIpMap(Base, BaseModel):
380 380 __tablename__ = 'user_ip_map'
381 381 __table_args__ = (
382 382 UniqueConstraint('user_id', 'ip_addr'),
383 383 {'extend_existing': True, 'mysql_engine': 'InnoDB',
384 384 'mysql_charset': 'utf8'}
385 385 )
386 __mapper_args__ = {}
386
387 387
388 388 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
389 389 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
390 390 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
391 391 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
392 392 user = relationship('User', lazy='joined')
393 393
394 394
395 395 class UserLog(Base, BaseModel):
396 396 __tablename__ = 'user_logs'
397 397 __table_args__ = (
398 398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
399 399 'mysql_charset': 'utf8'},
400 400 )
401 401 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
402 402 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
403 403 username = Column("username", String(255), nullable=True, unique=None, default=None)
404 404 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
405 405 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
406 406 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
407 407 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
408 408 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
409 409
410 410
411 411 user = relationship('User')
412 412 repository = relationship('Repository', cascade='')
413 413
414 414
415 415 class UserGroup(Base, BaseModel):
416 416 __tablename__ = 'users_groups'
417 417 __table_args__ = (
418 418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
419 419 'mysql_charset': 'utf8'},
420 420 )
421 421
422 422 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
423 423 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
424 424 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
425 425 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
426 426
427 427 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
428 428 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
429 429 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
430 430
431 431 def __unicode__(self):
432 432 return u'<userGroup(%s)>' % (self.users_group_name)
433 433
434 434 @classmethod
435 435 def get_by_group_name(cls, group_name, cache=False,
436 436 case_insensitive=False):
437 437 if case_insensitive:
438 438 q = cls.query().filter(cls.users_group_name.ilike(group_name))
439 439 else:
440 440 q = cls.query().filter(cls.users_group_name == group_name)
441 441 if cache:
442 442 q = q.options(FromCache(
443 443 "sql_cache_short",
444 444 "get_user_%s" % _hash_key(group_name)
445 445 )
446 446 )
447 447 return q.scalar()
448 448
449 449 @classmethod
450 450 def get(cls, users_group_id, cache=False):
451 451 user_group = cls.query()
452 452 if cache:
453 453 user_group = user_group.options(FromCache("sql_cache_short",
454 454 "get_users_group_%s" % users_group_id))
455 455 return user_group.get(users_group_id)
456 456
457 457
458 458 class UserGroupMember(Base, BaseModel):
459 459 __tablename__ = 'users_groups_members'
460 460 __table_args__ = (
461 461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 462 'mysql_charset': 'utf8'},
463 463 )
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('UserGroup')
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
477 477 class RepositoryField(Base, BaseModel):
478 478 __tablename__ = 'repositories_fields'
479 479 __table_args__ = (
480 480 UniqueConstraint('repository_id', 'field_key'), # no-multi field
481 481 {'extend_existing': True, 'mysql_engine': 'InnoDB',
482 482 'mysql_charset': 'utf8'},
483 483 )
484 484 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
485 485
486 486 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
487 487 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
488 488 field_key = Column("field_key", String(250))
489 489 field_label = Column("field_label", String(1024), nullable=False)
490 490 field_value = Column("field_value", String(10000), nullable=False)
491 491 field_desc = Column("field_desc", String(1024), nullable=False)
492 492 field_type = Column("field_type", String(256), nullable=False, unique=None)
493 493 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
494 494
495 495 repository = relationship('Repository')
496 496
497 497 @classmethod
498 498 def get_by_key_name(cls, key, repo):
499 499 row = cls.query()\
500 500 .filter(cls.repository == repo)\
501 501 .filter(cls.field_key == key).scalar()
502 502 return row
503 503
504 504
505 505 class Repository(Base, BaseModel):
506 506 __tablename__ = 'repositories'
507 507 __table_args__ = (
508 508 UniqueConstraint('repo_name'),
509 509 Index('r_repo_name_idx', 'repo_name'),
510 510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
511 511 'mysql_charset': 'utf8'},
512 512 )
513 513
514 514 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
515 515 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
516 516 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
517 517 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
518 518 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
519 519 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
520 520 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
521 521 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
522 522 description = Column("description", String(10000), nullable=True, unique=None, default=None)
523 523 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
524 524 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
525 525 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
526 526 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
527 527 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
528 528 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
529 529
530 530 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
531 531 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
532 532
533 533 user = relationship('User')
534 534 fork = relationship('Repository', remote_side=repo_id)
535 535 group = relationship('RepoGroup')
536 536 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
537 537 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
538 538 stats = relationship('Statistics', cascade='all', uselist=False)
539 539
540 540 followers = relationship('UserFollowing',
541 541 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
542 542 cascade='all')
543 543 extra_fields = relationship('RepositoryField',
544 544 cascade="all, delete, delete-orphan")
545 545
546 546 logs = relationship('UserLog')
547 547 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
548 548
549 549 pull_requests_org = relationship('PullRequest',
550 550 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
551 551 cascade="all, delete, delete-orphan")
552 552
553 553 pull_requests_other = relationship('PullRequest',
554 554 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
555 555 cascade="all, delete, delete-orphan")
556 556
557 557 def __unicode__(self):
558 558 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
559 559 safe_str(self.repo_name))
560 560
561 561 #NOTE for this migration we are required tio have it
562 562 @hybrid_property
563 563 def changeset_cache(self):
564 564 from rhodecode.lib.vcs.backends.base import EmptyCommit
565 565 dummy = EmptyCommit().__json__()
566 566 if not self._changeset_cache:
567 567 return dummy
568 568 try:
569 569 return json.loads(self._changeset_cache)
570 570 except TypeError:
571 571 return dummy
572 572
573 573 @changeset_cache.setter
574 574 def changeset_cache(self, val):
575 575 try:
576 576 self._changeset_cache = json.dumps(val)
577 577 except Exception:
578 578 log.error(traceback.format_exc())
579 579
580 580 @classmethod
581 581 def get_by_repo_name(cls, repo_name):
582 582 q = Session().query(cls).filter(cls.repo_name == repo_name)
583 583 q = q.options(joinedload(Repository.fork))\
584 584 .options(joinedload(Repository.user))\
585 585 .options(joinedload(Repository.group))
586 586 return q.scalar()
587 587
588 588 #NOTE this is required for this migration to work
589 589 def update_commit_cache(self, cs_cache=None):
590 590 """
591 591 Update cache of last changeset for repository, keys should be::
592 592
593 593 short_id
594 594 raw_id
595 595 revision
596 596 message
597 597 date
598 598 author
599 599
600 600 :param cs_cache:
601 601 """
602 602 from rhodecode.lib.vcs.backends.base import BaseChangeset
603 603 if cs_cache is None:
604 604 cs_cache = EmptyCommit()
605 605 # Note: Using always the empty commit here in case we are
606 606 # upgrading towards version 3.0 and above. Reason is that in this
607 607 # case the vcsclient connection is not available and things
608 608 # would explode here.
609 609
610 610 if isinstance(cs_cache, BaseChangeset):
611 611 cs_cache = cs_cache.__json__()
612 612
613 613 if (cs_cache != self.changeset_cache or not self.changeset_cache):
614 614 _default = datetime.datetime.fromtimestamp(0)
615 615 last_change = cs_cache.get('date') or _default
616 616 log.debug('updated repo %s with new commit cache %s', self.repo_name, cs_cache)
617 617 self.updated_on = last_change
618 618 self.changeset_cache = cs_cache
619 619 Session().add(self)
620 620 Session().commit()
621 621 else:
622 622 log.debug('Skipping repo:%s already with latest changes', self.repo_name)
623 623
624 624 class RepoGroup(Base, BaseModel):
625 625 __tablename__ = 'groups'
626 626 __table_args__ = (
627 627 UniqueConstraint('group_name', 'group_parent_id'),
628 628 {'extend_existing': True, 'mysql_engine': 'InnoDB',
629 629 'mysql_charset': 'utf8'},
630 630 )
631 __mapper_args__ = {'order_by': 'group_name'}
631
632 632
633 633 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
634 634 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
635 635 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
636 636 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
637 637 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
638 638
639 639 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
640 640 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 641 parent_group = relationship('RepoGroup', remote_side=group_id)
642 642
643 643 def __init__(self, group_name='', parent_group=None):
644 644 self.group_name = group_name
645 645 self.parent_group = parent_group
646 646
647 647 def __unicode__(self):
648 648 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
649 649 self.group_name)
650 650
651 651 @classmethod
652 652 def url_sep(cls):
653 653 return URL_SEP
654 654
655 655 @classmethod
656 656 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
657 657 if case_insensitive:
658 658 gr = cls.query()\
659 659 .filter(cls.group_name.ilike(group_name))
660 660 else:
661 661 gr = cls.query()\
662 662 .filter(cls.group_name == group_name)
663 663 if cache:
664 664 gr = gr.options(FromCache(
665 665 "sql_cache_short",
666 666 "get_group_%s" % _hash_key(group_name)
667 667 )
668 668 )
669 669 return gr.scalar()
670 670
671 671
672 672 class Permission(Base, BaseModel):
673 673 __tablename__ = 'permissions'
674 674 __table_args__ = (
675 675 Index('p_perm_name_idx', 'permission_name'),
676 676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
677 677 'mysql_charset': 'utf8'},
678 678 )
679 679 PERMS = [
680 680 ('repository.none', _('Repository no access')),
681 681 ('repository.read', _('Repository read access')),
682 682 ('repository.write', _('Repository write access')),
683 683 ('repository.admin', _('Repository admin access')),
684 684
685 685 ('group.none', _('Repository group no access')),
686 686 ('group.read', _('Repository group read access')),
687 687 ('group.write', _('Repository group write access')),
688 688 ('group.admin', _('Repository group admin access')),
689 689
690 690 ('hg.admin', _('RhodeCode Administrator')),
691 691 ('hg.create.none', _('Repository creation disabled')),
692 692 ('hg.create.repository', _('Repository creation enabled')),
693 693 ('hg.fork.none', _('Repository forking disabled')),
694 694 ('hg.fork.repository', _('Repository forking enabled')),
695 695 ('hg.register.none', _('Register disabled')),
696 696 ('hg.register.manual_activate', _('Register new user with RhodeCode '
697 697 'with manual activation')),
698 698
699 699 ('hg.register.auto_activate', _('Register new user with RhodeCode '
700 700 'with auto activation')),
701 701 ]
702 702
703 703 # defines which permissions are more important higher the more important
704 704 PERM_WEIGHTS = {
705 705 'repository.none': 0,
706 706 'repository.read': 1,
707 707 'repository.write': 3,
708 708 'repository.admin': 4,
709 709
710 710 'group.none': 0,
711 711 'group.read': 1,
712 712 'group.write': 3,
713 713 'group.admin': 4,
714 714
715 715 'hg.fork.none': 0,
716 716 'hg.fork.repository': 1,
717 717 'hg.create.none': 0,
718 718 'hg.create.repository':1
719 719 }
720 720
721 721 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
722 722 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
723 723 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
724 724
725 725 def __unicode__(self):
726 726 return u"<%s('%s:%s')>" % (
727 727 self.__class__.__name__, self.permission_id, self.permission_name
728 728 )
729 729
730 730 @classmethod
731 731 def get_by_key(cls, key):
732 732 return cls.query().filter(cls.permission_name == key).scalar()
733 733
734 734
735 735 class UserRepoToPerm(Base, BaseModel):
736 736 __tablename__ = 'repo_to_perm'
737 737 __table_args__ = (
738 738 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
739 739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
740 740 'mysql_charset': 'utf8'}
741 741 )
742 742 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
743 743 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
744 744 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
745 745 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
746 746
747 747 user = relationship('User')
748 748 repository = relationship('Repository')
749 749 permission = relationship('Permission')
750 750
751 751 def __unicode__(self):
752 752 return u'<user:%s => %s >' % (self.user, self.repository)
753 753
754 754
755 755 class UserToPerm(Base, BaseModel):
756 756 __tablename__ = 'user_to_perm'
757 757 __table_args__ = (
758 758 UniqueConstraint('user_id', 'permission_id'),
759 759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
760 760 'mysql_charset': 'utf8'}
761 761 )
762 762 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
763 763 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
764 764 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
765 765
766 766 user = relationship('User')
767 767 permission = relationship('Permission', lazy='joined')
768 768
769 769
770 770 class UserGroupRepoToPerm(Base, BaseModel):
771 771 __tablename__ = 'users_group_repo_to_perm'
772 772 __table_args__ = (
773 773 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
774 774 {'extend_existing': True, 'mysql_engine': 'InnoDB',
775 775 'mysql_charset': 'utf8'}
776 776 )
777 777 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
778 778 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
779 779 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
780 780 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
781 781
782 782 users_group = relationship('UserGroup')
783 783 permission = relationship('Permission')
784 784 repository = relationship('Repository')
785 785
786 786 def __unicode__(self):
787 787 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
788 788
789 789
790 790 class UserGroupToPerm(Base, BaseModel):
791 791 __tablename__ = 'users_group_to_perm'
792 792 __table_args__ = (
793 793 UniqueConstraint('users_group_id', 'permission_id',),
794 794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
795 795 'mysql_charset': 'utf8'}
796 796 )
797 797 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
798 798 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
799 799 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
800 800
801 801 users_group = relationship('UserGroup')
802 802 permission = relationship('Permission')
803 803
804 804
805 805 class UserRepoGroupToPerm(Base, BaseModel):
806 806 __tablename__ = 'user_repo_group_to_perm'
807 807 __table_args__ = (
808 808 UniqueConstraint('user_id', 'group_id', 'permission_id'),
809 809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
810 810 'mysql_charset': 'utf8'}
811 811 )
812 812
813 813 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
814 814 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
815 815 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
816 816 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
817 817
818 818 user = relationship('User')
819 819 group = relationship('RepoGroup')
820 820 permission = relationship('Permission')
821 821
822 822
823 823 class UserGroupRepoGroupToPerm(Base, BaseModel):
824 824 __tablename__ = 'users_group_repo_group_to_perm'
825 825 __table_args__ = (
826 826 UniqueConstraint('users_group_id', 'group_id'),
827 827 {'extend_existing': True, 'mysql_engine': 'InnoDB',
828 828 'mysql_charset': 'utf8'}
829 829 )
830 830
831 831 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)
832 832 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
833 833 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
834 834 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
835 835
836 836 users_group = relationship('UserGroup')
837 837 permission = relationship('Permission')
838 838 group = relationship('RepoGroup')
839 839
840 840
841 841 class Statistics(Base, BaseModel):
842 842 __tablename__ = 'statistics'
843 843 __table_args__ = (
844 844 UniqueConstraint('repository_id'),
845 845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
846 846 'mysql_charset': 'utf8'}
847 847 )
848 848 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
849 849 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
850 850 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
851 851 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
852 852 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
853 853 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
854 854
855 855 repository = relationship('Repository', single_parent=True)
856 856
857 857
858 858 class UserFollowing(Base, BaseModel):
859 859 __tablename__ = 'user_followings'
860 860 __table_args__ = (
861 861 UniqueConstraint('user_id', 'follows_repository_id'),
862 862 UniqueConstraint('user_id', 'follows_user_id'),
863 863 {'extend_existing': True, 'mysql_engine': 'InnoDB',
864 864 'mysql_charset': 'utf8'}
865 865 )
866 866
867 867 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
868 868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
869 869 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
870 870 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
871 871 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
872 872
873 873 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
874 874
875 875 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
876 876 follows_repository = relationship('Repository', order_by='Repository.repo_name')
877 877
878 878
879 879 class CacheInvalidation(Base, BaseModel):
880 880 __tablename__ = 'cache_invalidation'
881 881 __table_args__ = (
882 882 UniqueConstraint('cache_key'),
883 883 Index('key_idx', 'cache_key'),
884 884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
885 885 'mysql_charset': 'utf8'},
886 886 )
887 887 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
888 888 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
889 889 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
890 890 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
891 891
892 892 def __init__(self, cache_key, cache_args=''):
893 893 self.cache_key = cache_key
894 894 self.cache_args = cache_args
895 895 self.cache_active = False
896 896
897 897
898 898 class ChangesetComment(Base, BaseModel):
899 899 __tablename__ = 'changeset_comments'
900 900 __table_args__ = (
901 901 Index('cc_revision_idx', 'revision'),
902 902 {'extend_existing': True, 'mysql_engine': 'InnoDB',
903 903 'mysql_charset': 'utf8'},
904 904 )
905 905 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
906 906 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
907 907 revision = Column('revision', String(40), nullable=True)
908 908 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
909 909 line_no = Column('line_no', Unicode(10), nullable=True)
910 910 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
911 911 f_path = Column('f_path', Unicode(1000), nullable=True)
912 912 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
913 913 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
914 914 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
915 915 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
916 916
917 917 author = relationship('User', lazy='joined')
918 918 repo = relationship('Repository')
919 919 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
920 920 pull_request = relationship('PullRequest', lazy='joined')
921 921
922 922 @classmethod
923 923 def get_users(cls, revision=None, pull_request_id=None):
924 924 """
925 925 Returns user associated with this ChangesetComment. ie those
926 926 who actually commented
927 927
928 928 :param cls:
929 929 :param revision:
930 930 """
931 931 q = Session().query(User)\
932 932 .join(ChangesetComment.author)
933 933 if revision:
934 934 q = q.filter(cls.revision == revision)
935 935 elif pull_request_id:
936 936 q = q.filter(cls.pull_request_id == pull_request_id)
937 937 return q.all()
938 938
939 939
940 940 class ChangesetStatus(Base, BaseModel):
941 941 __tablename__ = 'changeset_statuses'
942 942 __table_args__ = (
943 943 Index('cs_revision_idx', 'revision'),
944 944 Index('cs_version_idx', 'version'),
945 945 UniqueConstraint('repo_id', 'revision', 'version'),
946 946 {'extend_existing': True, 'mysql_engine': 'InnoDB',
947 947 'mysql_charset': 'utf8'}
948 948 )
949 949 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
950 950 STATUS_APPROVED = 'approved'
951 951 STATUS_REJECTED = 'rejected'
952 952 STATUS_UNDER_REVIEW = 'under_review'
953 953
954 954 STATUSES = [
955 955 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
956 956 (STATUS_APPROVED, _("Approved")),
957 957 (STATUS_REJECTED, _("Rejected")),
958 958 (STATUS_UNDER_REVIEW, _("Under Review")),
959 959 ]
960 960
961 961 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
962 962 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
963 963 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
964 964 revision = Column('revision', String(40), nullable=False)
965 965 status = Column('status', String(128), nullable=False, default=DEFAULT)
966 966 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
967 967 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
968 968 version = Column('version', Integer(), nullable=False, default=0)
969 969 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
970 970
971 971 author = relationship('User', lazy='joined')
972 972 repo = relationship('Repository')
973 973 comment = relationship('ChangesetComment', lazy='joined')
974 974 pull_request = relationship('PullRequest', lazy='joined')
975 975
976 976
977 977
978 978 class PullRequest(Base, BaseModel):
979 979 __tablename__ = 'pull_requests'
980 980 __table_args__ = (
981 981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
982 982 'mysql_charset': 'utf8'},
983 983 )
984 984
985 985 STATUS_NEW = u'new'
986 986 STATUS_OPEN = u'open'
987 987 STATUS_CLOSED = u'closed'
988 988
989 989 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
990 990 title = Column('title', Unicode(256), nullable=True)
991 991 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
992 992 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
993 993 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
994 994 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
995 995 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
996 996 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
997 997 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
998 998 org_ref = Column('org_ref', Unicode(256), nullable=False)
999 999 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1000 1000 other_ref = Column('other_ref', Unicode(256), nullable=False)
1001 1001
1002 1002 author = relationship('User', lazy='joined')
1003 1003 reviewers = relationship('PullRequestReviewers',
1004 1004 cascade="all, delete, delete-orphan")
1005 1005 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1006 1006 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1007 1007 statuses = relationship('ChangesetStatus')
1008 1008 comments = relationship('ChangesetComment',
1009 1009 cascade="all, delete, delete-orphan")
1010 1010
1011 1011
1012 1012 class PullRequestReviewers(Base, BaseModel):
1013 1013 __tablename__ = 'pull_request_reviewers'
1014 1014 __table_args__ = (
1015 1015 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1016 1016 'mysql_charset': 'utf8'},
1017 1017 )
1018 1018
1019 1019 def __init__(self, user=None, pull_request=None):
1020 1020 self.user = user
1021 1021 self.pull_request = pull_request
1022 1022
1023 1023 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1024 1024 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1025 1025 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1026 1026
1027 1027 user = relationship('User')
1028 1028 pull_request = relationship('PullRequest')
1029 1029
1030 1030
1031 1031 class Notification(Base, BaseModel):
1032 1032 __tablename__ = 'notifications'
1033 1033 __table_args__ = (
1034 1034 Index('notification_type_idx', 'type'),
1035 1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1036 1036 'mysql_charset': 'utf8'},
1037 1037 )
1038 1038
1039 1039 TYPE_CHANGESET_COMMENT = u'cs_comment'
1040 1040 TYPE_MESSAGE = u'message'
1041 1041 TYPE_MENTION = u'mention'
1042 1042 TYPE_REGISTRATION = u'registration'
1043 1043 TYPE_PULL_REQUEST = u'pull_request'
1044 1044 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1045 1045
1046 1046 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1047 1047 subject = Column('subject', Unicode(512), nullable=True)
1048 1048 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1049 1049 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1050 1050 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1051 1051 type_ = Column('type', Unicode(256))
1052 1052
1053 1053 created_by_user = relationship('User')
1054 1054 notifications_to_users = relationship('UserNotification', lazy='joined',
1055 1055 cascade="all, delete, delete-orphan")
1056 1056
1057 1057
1058 1058 class UserNotification(Base, BaseModel):
1059 1059 __tablename__ = 'user_to_notification'
1060 1060 __table_args__ = (
1061 1061 UniqueConstraint('user_id', 'notification_id'),
1062 1062 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1063 1063 'mysql_charset': 'utf8'}
1064 1064 )
1065 1065 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1066 1066 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1067 1067 read = Column('read', Boolean, default=False)
1068 1068 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1069 1069
1070 1070 user = relationship('User', lazy="joined")
1071 1071 notification = relationship('Notification', lazy="joined",
1072 1072 order_by=lambda: Notification.created_on.desc(),)
1073 1073
1074 1074
1075 1075 class DbMigrateVersion(Base, BaseModel):
1076 1076 __tablename__ = 'db_migrate_version'
1077 1077 __table_args__ = (
1078 1078 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1079 1079 'mysql_charset': 'utf8'},
1080 1080 )
1081 1081 repository_id = Column('repository_id', String(250), primary_key=True)
1082 1082 repository_path = Column('repository_path', Text)
1083 1083 version = Column('version', Integer)
@@ -1,1144 +1,1144 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27
28 28 from sqlalchemy import *
29 29 from sqlalchemy.ext.hybrid import hybrid_property
30 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
31 31 from sqlalchemy.exc import DatabaseError
32 32 from beaker.cache import cache_region, region_invalidate
33 33 from webob.exc import HTTPNotFound
34 34
35 35 from rhodecode.translation import _
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 zope.cachedescriptors.property import Lazy as LazyProperty
41 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
44 44 remove_suffix, remove_prefix, time_to_datetime
45 45 from rhodecode.lib.ext_json import json
46 46 from rhodecode.lib.caching_query import FromCache
47 47
48 48 from rhodecode.model.meta import Base, Session
49 49
50 50 URL_SEP = '/'
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class BaseModel(object):
61 61 """
62 62 Base Model for all classes
63 63 """
64 64
65 65 @classmethod
66 66 def _get_keys(cls):
67 67 """return column names for this model """
68 68 return class_mapper(cls).c.keys()
69 69
70 70 def get_dict(self):
71 71 """
72 72 return dict with keys and values corresponding
73 73 to this model data """
74 74
75 75 d = {}
76 76 for k in self._get_keys():
77 77 d[k] = getattr(self, k)
78 78
79 79 # also use __json__() if present to get additional fields
80 80 _json_attr = getattr(self, '__json__', None)
81 81 if _json_attr:
82 82 # update with attributes from __json__
83 83 if callable(_json_attr):
84 84 _json_attr = _json_attr()
85 85 for k, val in _json_attr.items():
86 86 d[k] = val
87 87 return d
88 88
89 89 def get_appstruct(self):
90 90 """return list with keys and values tupples corresponding
91 91 to this model data """
92 92
93 93 l = []
94 94 for k in self._get_keys():
95 95 l.append((k, getattr(self, k),))
96 96 return l
97 97
98 98 def populate_obj(self, populate_dict):
99 99 """populate model with data from given populate_dict"""
100 100
101 101 for k in self._get_keys():
102 102 if k in populate_dict:
103 103 setattr(self, k, populate_dict[k])
104 104
105 105 @classmethod
106 106 def query(cls):
107 107 return Session().query(cls)
108 108
109 109 @classmethod
110 110 def get(cls, id_):
111 111 if id_:
112 112 return cls.query().get(id_)
113 113
114 114 @classmethod
115 115 def get_or_404(cls, id_):
116 116 try:
117 117 id_ = int(id_)
118 118 except (TypeError, ValueError):
119 119 raise HTTPNotFound
120 120
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 # deprecated and left for backward compatibility
129 129 return cls.get_all()
130 130
131 131 @classmethod
132 132 def get_all(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
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == str
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_str(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193
194 194 class RhodeCodeUi(Base, BaseModel):
195 195 __tablename__ = 'rhodecode_ui'
196 196 __table_args__ = (
197 197 UniqueConstraint('ui_key'),
198 198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
199 199 'mysql_charset': 'utf8'}
200 200 )
201 201
202 202 HOOK_REPO_SIZE = 'changegroup.repo_size'
203 203 HOOK_PUSH = 'changegroup.push_logger'
204 204 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
205 205 HOOK_PULL = 'outgoing.pull_logger'
206 206 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
207 207
208 208 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
209 209 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
210 210 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
211 211 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
212 212 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
213 213
214 214
215 215
216 216 class User(Base, BaseModel):
217 217 __tablename__ = 'users'
218 218 __table_args__ = (
219 219 UniqueConstraint('username'), UniqueConstraint('email'),
220 220 Index('u_username_idx', 'username'),
221 221 Index('u_email_idx', 'email'),
222 222 {'extend_existing': True, 'mysql_engine': 'InnoDB',
223 223 'mysql_charset': 'utf8'}
224 224 )
225 225 DEFAULT_USER = 'default'
226 226
227 227 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
228 228 username = Column("username", String(255), nullable=True, unique=None, default=None)
229 229 password = Column("password", String(255), nullable=True, unique=None, default=None)
230 230 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
231 231 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
232 232 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
233 233 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
234 234 _email = Column("email", String(255), nullable=True, unique=None, default=None)
235 235 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
236 236 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
237 237 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
238 238 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
239 239
240 240 user_log = relationship('UserLog')
241 241 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
242 242
243 243 repositories = relationship('Repository')
244 244 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
245 245 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
246 246
247 247 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
248 248 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
249 249
250 250 group_member = relationship('UserGroupMember', cascade='all')
251 251
252 252 notifications = relationship('UserNotification', cascade='all')
253 253 # notifications assigned to this user
254 254 user_created_notifications = relationship('Notification', cascade='all')
255 255 # comments created by this user
256 256 user_comments = relationship('ChangesetComment', cascade='all')
257 257 user_emails = relationship('UserEmailMap', cascade='all')
258 258
259 259 @hybrid_property
260 260 def email(self):
261 261 return self._email
262 262
263 263 @email.setter
264 264 def email(self, val):
265 265 self._email = val.lower() if val else None
266 266
267 267 @property
268 268 def firstname(self):
269 269 # alias for future
270 270 return self.name
271 271
272 272 @property
273 273 def username_and_name(self):
274 274 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
275 275
276 276 @property
277 277 def full_name(self):
278 278 return '%s %s' % (self.firstname, self.lastname)
279 279
280 280 @property
281 281 def full_contact(self):
282 282 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
283 283
284 284 @property
285 285 def short_contact(self):
286 286 return '%s %s' % (self.firstname, self.lastname)
287 287
288 288 @property
289 289 def is_admin(self):
290 290 return self.admin
291 291
292 292 @classmethod
293 293 def get_by_username(cls, username, case_insensitive=False, cache=False):
294 294 if case_insensitive:
295 295 q = cls.query().filter(cls.username.ilike(username))
296 296 else:
297 297 q = cls.query().filter(cls.username == username)
298 298
299 299 if cache:
300 300 q = q.options(FromCache(
301 301 "sql_cache_short",
302 302 "get_user_%s" % _hash_key(username)
303 303 )
304 304 )
305 305 return q.scalar()
306 306
307 307 @classmethod
308 308 def get_by_auth_token(cls, auth_token, cache=False):
309 309 q = cls.query().filter(cls.api_key == auth_token)
310 310
311 311 if cache:
312 312 q = q.options(FromCache("sql_cache_short",
313 313 "get_auth_token_%s" % auth_token))
314 314 return q.scalar()
315 315
316 316 @classmethod
317 317 def get_by_email(cls, email, case_insensitive=False, cache=False):
318 318 if case_insensitive:
319 319 q = cls.query().filter(cls.email.ilike(email))
320 320 else:
321 321 q = cls.query().filter(cls.email == email)
322 322
323 323 if cache:
324 324 q = q.options(FromCache("sql_cache_short",
325 325 "get_email_key_%s" % email))
326 326
327 327 ret = q.scalar()
328 328 if ret is None:
329 329 q = UserEmailMap.query()
330 330 # try fetching in alternate email map
331 331 if case_insensitive:
332 332 q = q.filter(UserEmailMap.email.ilike(email))
333 333 else:
334 334 q = q.filter(UserEmailMap.email == email)
335 335 q = q.options(joinedload(UserEmailMap.user))
336 336 if cache:
337 337 q = q.options(FromCache("sql_cache_short",
338 338 "get_email_map_key_%s" % email))
339 339 ret = getattr(q.scalar(), 'user', None)
340 340
341 341 return ret
342 342
343 343 @classmethod
344 344 def get_first_admin(cls):
345 345 user = User.query().filter(User.admin == True).first()
346 346 if user is None:
347 347 raise Exception('Missing administrative account!')
348 348 return user
349 349
350 350 @classmethod
351 351 def get_default_user(cls, cache=False):
352 352 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
353 353 if user is None:
354 354 raise Exception('Missing default account!')
355 355 return user
356 356
357 357
358 358
359 359
360 360 class UserEmailMap(Base, BaseModel):
361 361 __tablename__ = 'user_email_map'
362 362 __table_args__ = (
363 363 Index('uem_email_idx', 'email'),
364 364 UniqueConstraint('email'),
365 365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
366 366 'mysql_charset': 'utf8'}
367 367 )
368 __mapper_args__ = {}
368
369 369
370 370 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
371 371 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
372 372 _email = Column("email", String(255), nullable=True, unique=False, default=None)
373 373 user = relationship('User', lazy='joined')
374 374
375 375 @validates('_email')
376 376 def validate_email(self, key, email):
377 377 # check if this email is not main one
378 378 main_email = Session().query(User).filter(User.email == email).scalar()
379 379 if main_email is not None:
380 380 raise AttributeError('email %s is present is user table' % email)
381 381 return email
382 382
383 383 @hybrid_property
384 384 def email(self):
385 385 return self._email
386 386
387 387 @email.setter
388 388 def email(self, val):
389 389 self._email = val.lower() if val else None
390 390
391 391
392 392 class UserIpMap(Base, BaseModel):
393 393 __tablename__ = 'user_ip_map'
394 394 __table_args__ = (
395 395 UniqueConstraint('user_id', 'ip_addr'),
396 396 {'extend_existing': True, 'mysql_engine': 'InnoDB',
397 397 'mysql_charset': 'utf8'}
398 398 )
399 __mapper_args__ = {}
399
400 400
401 401 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
402 402 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
403 403 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
404 404 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
405 405 user = relationship('User', lazy='joined')
406 406
407 407
408 408 class UserLog(Base, BaseModel):
409 409 __tablename__ = 'user_logs'
410 410 __table_args__ = (
411 411 {'extend_existing': True, 'mysql_engine': 'InnoDB',
412 412 'mysql_charset': 'utf8'},
413 413 )
414 414 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
415 415 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
416 416 username = Column("username", String(255), nullable=True, unique=None, default=None)
417 417 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
418 418 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
419 419 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
420 420 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
421 421 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
422 422
423 423 def __unicode__(self):
424 424 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
425 425 self.repository_name,
426 426 self.action)
427 427
428 428 user = relationship('User')
429 429 repository = relationship('Repository', cascade='')
430 430
431 431
432 432 class UserGroup(Base, BaseModel):
433 433 __tablename__ = 'users_groups'
434 434 __table_args__ = (
435 435 {'extend_existing': True, 'mysql_engine': 'InnoDB',
436 436 'mysql_charset': 'utf8'},
437 437 )
438 438
439 439 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
440 440 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
441 441 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
442 442 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
443 443 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
444 444
445 445 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
446 446 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
447 447 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
448 448 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
449 449 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
450 450 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
451 451
452 452 user = relationship('User')
453 453
454 454 def __unicode__(self):
455 455 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
456 456 self.users_group_id,
457 457 self.users_group_name)
458 458
459 459 @classmethod
460 460 def get_by_group_name(cls, group_name, cache=False,
461 461 case_insensitive=False):
462 462 if case_insensitive:
463 463 q = cls.query().filter(cls.users_group_name.ilike(group_name))
464 464 else:
465 465 q = cls.query().filter(cls.users_group_name == group_name)
466 466 if cache:
467 467 q = q.options(FromCache(
468 468 "sql_cache_short",
469 469 "get_user_%s" % _hash_key(group_name)
470 470 )
471 471 )
472 472 return q.scalar()
473 473
474 474 @classmethod
475 475 def get(cls, users_group_id, cache=False):
476 476 user_group = cls.query()
477 477 if cache:
478 478 user_group = user_group.options(FromCache("sql_cache_short",
479 479 "get_users_group_%s" % users_group_id))
480 480 return user_group.get(users_group_id)
481 481
482 482
483 483 class UserGroupMember(Base, BaseModel):
484 484 __tablename__ = 'users_groups_members'
485 485 __table_args__ = (
486 486 {'extend_existing': True, 'mysql_engine': 'InnoDB',
487 487 'mysql_charset': 'utf8'},
488 488 )
489 489
490 490 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
491 491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
492 492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
493 493
494 494 user = relationship('User', lazy='joined')
495 495 users_group = relationship('UserGroup')
496 496
497 497 def __init__(self, gr_id='', u_id=''):
498 498 self.users_group_id = gr_id
499 499 self.user_id = u_id
500 500
501 501
502 502 class RepositoryField(Base, BaseModel):
503 503 __tablename__ = 'repositories_fields'
504 504 __table_args__ = (
505 505 UniqueConstraint('repository_id', 'field_key'), # no-multi field
506 506 {'extend_existing': True, 'mysql_engine': 'InnoDB',
507 507 'mysql_charset': 'utf8'},
508 508 )
509 509 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
510 510
511 511 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
512 512 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
513 513 field_key = Column("field_key", String(250))
514 514 field_label = Column("field_label", String(1024), nullable=False)
515 515 field_value = Column("field_value", String(10000), nullable=False)
516 516 field_desc = Column("field_desc", String(1024), nullable=False)
517 517 field_type = Column("field_type", String(256), nullable=False, unique=None)
518 518 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
519 519
520 520 repository = relationship('Repository')
521 521
522 522 @classmethod
523 523 def get_by_key_name(cls, key, repo):
524 524 row = cls.query()\
525 525 .filter(cls.repository == repo)\
526 526 .filter(cls.field_key == key).scalar()
527 527 return row
528 528
529 529
530 530 class Repository(Base, BaseModel):
531 531 __tablename__ = 'repositories'
532 532 __table_args__ = (
533 533 UniqueConstraint('repo_name'),
534 534 Index('r_repo_name_idx', 'repo_name'),
535 535 {'extend_existing': True, 'mysql_engine': 'InnoDB',
536 536 'mysql_charset': 'utf8'},
537 537 )
538 538
539 539 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
540 540 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
541 541 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
542 542 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
543 543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
544 544 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
545 545 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
546 546 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
547 547 description = Column("description", String(10000), nullable=True, unique=None, default=None)
548 548 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
549 549 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
550 550 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
551 551 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
552 552 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
553 553 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
554 554
555 555 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
556 556 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
557 557
558 558 user = relationship('User')
559 559 fork = relationship('Repository', remote_side=repo_id)
560 560 group = relationship('RepoGroup')
561 561 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
562 562 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
563 563 stats = relationship('Statistics', cascade='all', uselist=False)
564 564
565 565 followers = relationship('UserFollowing',
566 566 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
567 567 cascade='all')
568 568 extra_fields = relationship('RepositoryField',
569 569 cascade="all, delete, delete-orphan")
570 570
571 571 logs = relationship('UserLog')
572 572 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
573 573
574 574 pull_requests_org = relationship('PullRequest',
575 575 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
576 576 cascade="all, delete, delete-orphan")
577 577
578 578 pull_requests_other = relationship('PullRequest',
579 579 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
580 580 cascade="all, delete, delete-orphan")
581 581
582 582 def __unicode__(self):
583 583 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
584 584 safe_str(self.repo_name))
585 585
586 586 @classmethod
587 587 def get_by_repo_name(cls, repo_name):
588 588 q = Session().query(cls).filter(cls.repo_name == repo_name)
589 589 q = q.options(joinedload(Repository.fork))\
590 590 .options(joinedload(Repository.user))\
591 591 .options(joinedload(Repository.group))
592 592 return q.scalar()
593 593
594 594
595 595 class RepoGroup(Base, BaseModel):
596 596 __tablename__ = 'groups'
597 597 __table_args__ = (
598 598 UniqueConstraint('group_name', 'group_parent_id'),
599 599 {'extend_existing': True, 'mysql_engine': 'InnoDB',
600 600 'mysql_charset': 'utf8'},
601 601 )
602 __mapper_args__ = {'order_by': 'group_name'}
602
603 603
604 604 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
605 605 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
606 606 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
607 607 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
608 608 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
609 609 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
610 610
611 611 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
612 612 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
613 613 parent_group = relationship('RepoGroup', remote_side=group_id)
614 614 user = relationship('User')
615 615
616 616 def __init__(self, group_name='', parent_group=None):
617 617 self.group_name = group_name
618 618 self.parent_group = parent_group
619 619
620 620 def __unicode__(self):
621 621 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
622 622 self.group_name)
623 623
624 624 @classmethod
625 625 def url_sep(cls):
626 626 return URL_SEP
627 627
628 628 @classmethod
629 629 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
630 630 if case_insensitive:
631 631 gr = cls.query()\
632 632 .filter(cls.group_name.ilike(group_name))
633 633 else:
634 634 gr = cls.query()\
635 635 .filter(cls.group_name == group_name)
636 636 if cache:
637 637 gr = gr.options(FromCache(
638 638 "sql_cache_short",
639 639 "get_group_%s" % _hash_key(group_name)
640 640 )
641 641 )
642 642 return gr.scalar()
643 643
644 644
645 645 class Permission(Base, BaseModel):
646 646 __tablename__ = 'permissions'
647 647 __table_args__ = (
648 648 Index('p_perm_name_idx', 'permission_name'),
649 649 {'extend_existing': True, 'mysql_engine': 'InnoDB',
650 650 'mysql_charset': 'utf8'},
651 651 )
652 652 PERMS = [
653 653 ('hg.admin', _('RhodeCode Administrator')),
654 654
655 655 ('repository.none', _('Repository no access')),
656 656 ('repository.read', _('Repository read access')),
657 657 ('repository.write', _('Repository write access')),
658 658 ('repository.admin', _('Repository admin access')),
659 659
660 660 ('group.none', _('Repository group no access')),
661 661 ('group.read', _('Repository group read access')),
662 662 ('group.write', _('Repository group write access')),
663 663 ('group.admin', _('Repository group admin access')),
664 664
665 665 ('usergroup.none', _('User group no access')),
666 666 ('usergroup.read', _('User group read access')),
667 667 ('usergroup.write', _('User group write access')),
668 668 ('usergroup.admin', _('User group admin access')),
669 669
670 670 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
671 671 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
672 672
673 673 ('hg.usergroup.create.false', _('User Group creation disabled')),
674 674 ('hg.usergroup.create.true', _('User Group creation enabled')),
675 675
676 676 ('hg.create.none', _('Repository creation disabled')),
677 677 ('hg.create.repository', _('Repository creation enabled')),
678 678
679 679 ('hg.fork.none', _('Repository forking disabled')),
680 680 ('hg.fork.repository', _('Repository forking enabled')),
681 681
682 682 ('hg.register.none', _('Registration disabled')),
683 683 ('hg.register.manual_activate', _('User Registration with manual account activation')),
684 684 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
685 685
686 686 ('hg.extern_activate.manual', _('Manual activation of external account')),
687 687 ('hg.extern_activate.auto', _('Automatic activation of external account')),
688 688
689 689 ]
690 690
691 691 #definition of system default permissions for DEFAULT user
692 692 DEFAULT_USER_PERMISSIONS = [
693 693 'repository.read',
694 694 'group.read',
695 695 'usergroup.read',
696 696 'hg.create.repository',
697 697 'hg.fork.repository',
698 698 'hg.register.manual_activate',
699 699 'hg.extern_activate.auto',
700 700 ]
701 701
702 702 # defines which permissions are more important higher the more important
703 703 # Weight defines which permissions are more important.
704 704 # The higher number the more important.
705 705 PERM_WEIGHTS = {
706 706 'repository.none': 0,
707 707 'repository.read': 1,
708 708 'repository.write': 3,
709 709 'repository.admin': 4,
710 710
711 711 'group.none': 0,
712 712 'group.read': 1,
713 713 'group.write': 3,
714 714 'group.admin': 4,
715 715
716 716 'usergroup.none': 0,
717 717 'usergroup.read': 1,
718 718 'usergroup.write': 3,
719 719 'usergroup.admin': 4,
720 720 'hg.repogroup.create.false': 0,
721 721 'hg.repogroup.create.true': 1,
722 722
723 723 'hg.usergroup.create.false': 0,
724 724 'hg.usergroup.create.true': 1,
725 725
726 726 'hg.fork.none': 0,
727 727 'hg.fork.repository': 1,
728 728 'hg.create.none': 0,
729 729 'hg.create.repository': 1
730 730 }
731 731
732 732 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
733 733 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
734 734 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
735 735
736 736 def __unicode__(self):
737 737 return u"<%s('%s:%s')>" % (
738 738 self.__class__.__name__, self.permission_id, self.permission_name
739 739 )
740 740
741 741 @classmethod
742 742 def get_by_key(cls, key):
743 743 return cls.query().filter(cls.permission_name == key).scalar()
744 744
745 745
746 746 class UserRepoToPerm(Base, BaseModel):
747 747 __tablename__ = 'repo_to_perm'
748 748 __table_args__ = (
749 749 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
750 750 {'extend_existing': True, 'mysql_engine': 'InnoDB',
751 751 'mysql_charset': 'utf8'}
752 752 )
753 753 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
754 754 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
755 755 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
756 756 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
757 757
758 758 user = relationship('User')
759 759 repository = relationship('Repository')
760 760 permission = relationship('Permission')
761 761
762 762 def __unicode__(self):
763 763 return u'<%s => %s >' % (self.user, self.repository)
764 764
765 765
766 766 class UserUserGroupToPerm(Base, BaseModel):
767 767 __tablename__ = 'user_user_group_to_perm'
768 768 __table_args__ = (
769 769 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
770 770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
771 771 'mysql_charset': 'utf8'}
772 772 )
773 773 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
774 774 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
775 775 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
776 776 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
777 777
778 778 user = relationship('User')
779 779 user_group = relationship('UserGroup')
780 780 permission = relationship('Permission')
781 781
782 782 def __unicode__(self):
783 783 return u'<%s => %s >' % (self.user, self.user_group)
784 784
785 785
786 786 class UserToPerm(Base, BaseModel):
787 787 __tablename__ = 'user_to_perm'
788 788 __table_args__ = (
789 789 UniqueConstraint('user_id', 'permission_id'),
790 790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
791 791 'mysql_charset': 'utf8'}
792 792 )
793 793 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
794 794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
795 795 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
796 796
797 797 user = relationship('User')
798 798 permission = relationship('Permission', lazy='joined')
799 799
800 800 def __unicode__(self):
801 801 return u'<%s => %s >' % (self.user, self.permission)
802 802
803 803
804 804 class UserGroupRepoToPerm(Base, BaseModel):
805 805 __tablename__ = 'users_group_repo_to_perm'
806 806 __table_args__ = (
807 807 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
808 808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
809 809 'mysql_charset': 'utf8'}
810 810 )
811 811 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
812 812 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
813 813 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
814 814 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
815 815
816 816 users_group = relationship('UserGroup')
817 817 permission = relationship('Permission')
818 818 repository = relationship('Repository')
819 819
820 820 def __unicode__(self):
821 821 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
822 822
823 823
824 824 class UserGroupUserGroupToPerm(Base, BaseModel):
825 825 __tablename__ = 'user_group_user_group_to_perm'
826 826 __table_args__ = (
827 827 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
828 828 CheckConstraint('target_user_group_id != user_group_id'),
829 829 {'extend_existing': True, 'mysql_engine': 'InnoDB',
830 830 'mysql_charset': 'utf8'}
831 831 )
832 832 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
833 833 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
834 834 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
835 835 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
836 836
837 837 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
838 838 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
839 839 permission = relationship('Permission')
840 840
841 841 def __unicode__(self):
842 842 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
843 843
844 844
845 845 class UserGroupToPerm(Base, BaseModel):
846 846 __tablename__ = 'users_group_to_perm'
847 847 __table_args__ = (
848 848 UniqueConstraint('users_group_id', 'permission_id',),
849 849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
850 850 'mysql_charset': 'utf8'}
851 851 )
852 852 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
853 853 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
854 854 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
855 855
856 856 users_group = relationship('UserGroup')
857 857 permission = relationship('Permission')
858 858
859 859
860 860 class UserRepoGroupToPerm(Base, BaseModel):
861 861 __tablename__ = 'user_repo_group_to_perm'
862 862 __table_args__ = (
863 863 UniqueConstraint('user_id', 'group_id', 'permission_id'),
864 864 {'extend_existing': True, 'mysql_engine': 'InnoDB',
865 865 'mysql_charset': 'utf8'}
866 866 )
867 867
868 868 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
869 869 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
870 870 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
871 871 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
872 872
873 873 user = relationship('User')
874 874 group = relationship('RepoGroup')
875 875 permission = relationship('Permission')
876 876
877 877
878 878 class UserGroupRepoGroupToPerm(Base, BaseModel):
879 879 __tablename__ = 'users_group_repo_group_to_perm'
880 880 __table_args__ = (
881 881 UniqueConstraint('users_group_id', 'group_id'),
882 882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
883 883 'mysql_charset': 'utf8'}
884 884 )
885 885
886 886 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)
887 887 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
888 888 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
889 889 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
890 890
891 891 users_group = relationship('UserGroup')
892 892 permission = relationship('Permission')
893 893 group = relationship('RepoGroup')
894 894
895 895
896 896 class Statistics(Base, BaseModel):
897 897 __tablename__ = 'statistics'
898 898 __table_args__ = (
899 899 UniqueConstraint('repository_id'),
900 900 {'extend_existing': True, 'mysql_engine': 'InnoDB',
901 901 'mysql_charset': 'utf8'}
902 902 )
903 903 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
904 904 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
905 905 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
906 906 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
907 907 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
908 908 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
909 909
910 910 repository = relationship('Repository', single_parent=True)
911 911
912 912
913 913 class UserFollowing(Base, BaseModel):
914 914 __tablename__ = 'user_followings'
915 915 __table_args__ = (
916 916 UniqueConstraint('user_id', 'follows_repository_id'),
917 917 UniqueConstraint('user_id', 'follows_user_id'),
918 918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
919 919 'mysql_charset': 'utf8'}
920 920 )
921 921
922 922 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
923 923 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
924 924 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
925 925 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
926 926 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
927 927
928 928 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
929 929
930 930 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
931 931 follows_repository = relationship('Repository', order_by='Repository.repo_name')
932 932
933 933
934 934 class CacheInvalidation(Base, BaseModel):
935 935 __tablename__ = 'cache_invalidation'
936 936 __table_args__ = (
937 937 UniqueConstraint('cache_key'),
938 938 Index('key_idx', 'cache_key'),
939 939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
940 940 'mysql_charset': 'utf8'},
941 941 )
942 942 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
943 943 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
944 944 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
945 945 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
946 946
947 947 def __init__(self, cache_key, cache_args=''):
948 948 self.cache_key = cache_key
949 949 self.cache_args = cache_args
950 950 self.cache_active = False
951 951
952 952
953 953 class ChangesetComment(Base, BaseModel):
954 954 __tablename__ = 'changeset_comments'
955 955 __table_args__ = (
956 956 Index('cc_revision_idx', 'revision'),
957 957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
958 958 'mysql_charset': 'utf8'},
959 959 )
960 960 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
961 961 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
962 962 revision = Column('revision', String(40), nullable=True)
963 963 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
964 964 line_no = Column('line_no', Unicode(10), nullable=True)
965 965 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
966 966 f_path = Column('f_path', Unicode(1000), nullable=True)
967 967 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
968 968 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
969 969 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
970 970 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
971 971
972 972 author = relationship('User', lazy='joined')
973 973 repo = relationship('Repository')
974 974 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
975 975 pull_request = relationship('PullRequest', lazy='joined')
976 976
977 977
978 978 class ChangesetStatus(Base, BaseModel):
979 979 __tablename__ = 'changeset_statuses'
980 980 __table_args__ = (
981 981 Index('cs_revision_idx', 'revision'),
982 982 Index('cs_version_idx', 'version'),
983 983 UniqueConstraint('repo_id', 'revision', 'version'),
984 984 {'extend_existing': True, 'mysql_engine': 'InnoDB',
985 985 'mysql_charset': 'utf8'}
986 986 )
987 987 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
988 988 STATUS_APPROVED = 'approved'
989 989 STATUS_REJECTED = 'rejected'
990 990 STATUS_UNDER_REVIEW = 'under_review'
991 991
992 992 STATUSES = [
993 993 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
994 994 (STATUS_APPROVED, _("Approved")),
995 995 (STATUS_REJECTED, _("Rejected")),
996 996 (STATUS_UNDER_REVIEW, _("Under Review")),
997 997 ]
998 998
999 999 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1000 1000 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1001 1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1002 1002 revision = Column('revision', String(40), nullable=False)
1003 1003 status = Column('status', String(128), nullable=False, default=DEFAULT)
1004 1004 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1005 1005 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1006 1006 version = Column('version', Integer(), nullable=False, default=0)
1007 1007 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1008 1008
1009 1009 author = relationship('User', lazy='joined')
1010 1010 repo = relationship('Repository')
1011 1011 comment = relationship('ChangesetComment', lazy='joined')
1012 1012 pull_request = relationship('PullRequest', lazy='joined')
1013 1013
1014 1014
1015 1015
1016 1016 class PullRequest(Base, BaseModel):
1017 1017 __tablename__ = 'pull_requests'
1018 1018 __table_args__ = (
1019 1019 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1020 1020 'mysql_charset': 'utf8'},
1021 1021 )
1022 1022
1023 1023 STATUS_NEW = u'new'
1024 1024 STATUS_OPEN = u'open'
1025 1025 STATUS_CLOSED = u'closed'
1026 1026
1027 1027 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1028 1028 title = Column('title', Unicode(256), nullable=True)
1029 1029 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1030 1030 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1031 1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1032 1032 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1033 1033 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1034 1034 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1035 1035 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1036 1036 org_ref = Column('org_ref', Unicode(256), nullable=False)
1037 1037 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1038 1038 other_ref = Column('other_ref', Unicode(256), nullable=False)
1039 1039
1040 1040 author = relationship('User', lazy='joined')
1041 1041 reviewers = relationship('PullRequestReviewers',
1042 1042 cascade="all, delete, delete-orphan")
1043 1043 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1044 1044 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1045 1045 statuses = relationship('ChangesetStatus')
1046 1046 comments = relationship('ChangesetComment',
1047 1047 cascade="all, delete, delete-orphan")
1048 1048
1049 1049
1050 1050 class PullRequestReviewers(Base, BaseModel):
1051 1051 __tablename__ = 'pull_request_reviewers'
1052 1052 __table_args__ = (
1053 1053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1054 1054 'mysql_charset': 'utf8'},
1055 1055 )
1056 1056
1057 1057 def __init__(self, user=None, pull_request=None):
1058 1058 self.user = user
1059 1059 self.pull_request = pull_request
1060 1060
1061 1061 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1062 1062 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1063 1063 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1064 1064
1065 1065 user = relationship('User')
1066 1066 pull_request = relationship('PullRequest')
1067 1067
1068 1068
1069 1069 class Notification(Base, BaseModel):
1070 1070 __tablename__ = 'notifications'
1071 1071 __table_args__ = (
1072 1072 Index('notification_type_idx', 'type'),
1073 1073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1074 1074 'mysql_charset': 'utf8'},
1075 1075 )
1076 1076
1077 1077 TYPE_CHANGESET_COMMENT = u'cs_comment'
1078 1078 TYPE_MESSAGE = u'message'
1079 1079 TYPE_MENTION = u'mention'
1080 1080 TYPE_REGISTRATION = u'registration'
1081 1081 TYPE_PULL_REQUEST = u'pull_request'
1082 1082 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1083 1083
1084 1084 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1085 1085 subject = Column('subject', Unicode(512), nullable=True)
1086 1086 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1087 1087 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1088 1088 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1089 1089 type_ = Column('type', Unicode(256))
1090 1090
1091 1091 created_by_user = relationship('User')
1092 1092 notifications_to_users = relationship('UserNotification', lazy='joined',
1093 1093 cascade="all, delete, delete-orphan")
1094 1094
1095 1095
1096 1096 class UserNotification(Base, BaseModel):
1097 1097 __tablename__ = 'user_to_notification'
1098 1098 __table_args__ = (
1099 1099 UniqueConstraint('user_id', 'notification_id'),
1100 1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1101 1101 'mysql_charset': 'utf8'}
1102 1102 )
1103 1103 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1104 1104 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1105 1105 read = Column('read', Boolean, default=False)
1106 1106 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1107 1107
1108 1108 user = relationship('User', lazy="joined")
1109 1109 notification = relationship('Notification', lazy="joined",
1110 1110 order_by=lambda: Notification.created_on.desc(),)
1111 1111
1112 1112
1113 1113 class Gist(Base, BaseModel):
1114 1114 __tablename__ = 'gists'
1115 1115 __table_args__ = (
1116 1116 Index('g_gist_access_id_idx', 'gist_access_id'),
1117 1117 Index('g_created_on_idx', 'created_on'),
1118 1118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1119 1119 'mysql_charset': 'utf8'}
1120 1120 )
1121 1121 GIST_PUBLIC = u'public'
1122 1122 GIST_PRIVATE = u'private'
1123 1123
1124 1124 gist_id = Column('gist_id', Integer(), primary_key=True)
1125 1125 gist_access_id = Column('gist_access_id', Unicode(250))
1126 1126 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1127 1127 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1128 1128 gist_expires = Column('gist_expires', Float(), nullable=False)
1129 1129 gist_type = Column('gist_type', Unicode(128), nullable=False)
1130 1130 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1131 1131 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1132 1132
1133 1133 owner = relationship('User')
1134 1134
1135 1135
1136 1136 class DbMigrateVersion(Base, BaseModel):
1137 1137 __tablename__ = 'db_migrate_version'
1138 1138 __table_args__ = (
1139 1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 1140 'mysql_charset': 'utf8'},
1141 1141 )
1142 1142 repository_id = Column('repository_id', String(250), primary_key=True)
1143 1143 repository_path = Column('repository_path', Text)
1144 1144 version = Column('version', Integer)
@@ -1,1146 +1,1146 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27
28 28 from sqlalchemy import *
29 29 from sqlalchemy.ext.hybrid import hybrid_property
30 30 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
31 31 from sqlalchemy.exc import DatabaseError
32 32 from beaker.cache import cache_region, region_invalidate
33 33 from webob.exc import HTTPNotFound
34 34
35 35 from rhodecode.translation import _
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 zope.cachedescriptors.property import Lazy as LazyProperty
41 41 from rhodecode.lib.vcs.backends.base import EmptyCommit
42 42
43 43 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
44 44 remove_suffix, remove_prefix, time_to_datetime
45 45 from rhodecode.lib.ext_json import json
46 46 from rhodecode.lib.caching_query import FromCache
47 47
48 48 from rhodecode.model.meta import Base, Session
49 49
50 50 URL_SEP = '/'
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class BaseModel(object):
61 61 """
62 62 Base Model for all classes
63 63 """
64 64
65 65 @classmethod
66 66 def _get_keys(cls):
67 67 """return column names for this model """
68 68 return class_mapper(cls).c.keys()
69 69
70 70 def get_dict(self):
71 71 """
72 72 return dict with keys and values corresponding
73 73 to this model data """
74 74
75 75 d = {}
76 76 for k in self._get_keys():
77 77 d[k] = getattr(self, k)
78 78
79 79 # also use __json__() if present to get additional fields
80 80 _json_attr = getattr(self, '__json__', None)
81 81 if _json_attr:
82 82 # update with attributes from __json__
83 83 if callable(_json_attr):
84 84 _json_attr = _json_attr()
85 85 for k, val in _json_attr.items():
86 86 d[k] = val
87 87 return d
88 88
89 89 def get_appstruct(self):
90 90 """return list with keys and values tupples corresponding
91 91 to this model data """
92 92
93 93 l = []
94 94 for k in self._get_keys():
95 95 l.append((k, getattr(self, k),))
96 96 return l
97 97
98 98 def populate_obj(self, populate_dict):
99 99 """populate model with data from given populate_dict"""
100 100
101 101 for k in self._get_keys():
102 102 if k in populate_dict:
103 103 setattr(self, k, populate_dict[k])
104 104
105 105 @classmethod
106 106 def query(cls):
107 107 return Session().query(cls)
108 108
109 109 @classmethod
110 110 def get(cls, id_):
111 111 if id_:
112 112 return cls.query().get(id_)
113 113
114 114 @classmethod
115 115 def get_or_404(cls, id_):
116 116 try:
117 117 id_ = int(id_)
118 118 except (TypeError, ValueError):
119 119 raise HTTPNotFound
120 120
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 # deprecated and left for backward compatibility
129 129 return cls.get_all()
130 130
131 131 @classmethod
132 132 def get_all(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
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255), nullable=True, unique=None, default=None)
157 157 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, key='', val='', type='unicode'):
160 160 self.app_settings_name = key
161 161 self.app_settings_value = val
162 162 self.app_settings_type = type
163 163
164 164 @validates('_app_settings_value')
165 165 def validate_settings_value(self, key, val):
166 166 assert type(val) == str
167 167 return val
168 168
169 169 @hybrid_property
170 170 def app_settings_value(self):
171 171 v = self._app_settings_value
172 172 if self.app_settings_name in ["ldap_active",
173 173 "default_repo_enable_statistics",
174 174 "default_repo_enable_locking",
175 175 "default_repo_private",
176 176 "default_repo_enable_downloads"]:
177 177 v = str2bool(v)
178 178 return v
179 179
180 180 @app_settings_value.setter
181 181 def app_settings_value(self, val):
182 182 """
183 183 Setter that will always make sure we use unicode in app_settings_value
184 184
185 185 :param val:
186 186 """
187 187 self._app_settings_value = safe_str(val)
188 188
189 189 def __unicode__(self):
190 190 return u"<%s('%s:%s')>" % (
191 191 self.__class__.__name__,
192 192 self.app_settings_name, self.app_settings_value
193 193 )
194 194
195 195
196 196 class RhodeCodeUi(Base, BaseModel):
197 197 __tablename__ = 'rhodecode_ui'
198 198 __table_args__ = (
199 199 UniqueConstraint('ui_key'),
200 200 {'extend_existing': True, 'mysql_engine': 'InnoDB',
201 201 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
202 202 )
203 203
204 204 HOOK_REPO_SIZE = 'changegroup.repo_size'
205 205 HOOK_PUSH = 'changegroup.push_logger'
206 206 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
207 207 HOOK_PULL = 'outgoing.pull_logger'
208 208 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
209 209
210 210 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
211 211 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
212 212 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
213 213 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
214 214 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
215 215
216 216
217 217
218 218 class User(Base, BaseModel):
219 219 __tablename__ = 'users'
220 220 __table_args__ = (
221 221 UniqueConstraint('username'), UniqueConstraint('email'),
222 222 Index('u_username_idx', 'username'),
223 223 Index('u_email_idx', 'email'),
224 224 {'extend_existing': True, 'mysql_engine': 'InnoDB',
225 225 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
226 226 )
227 227 DEFAULT_USER = 'default'
228 228
229 229 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
230 230 username = Column("username", String(255), nullable=True, unique=None, default=None)
231 231 password = Column("password", String(255), nullable=True, unique=None, default=None)
232 232 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
233 233 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
234 234 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
235 235 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
236 236 _email = Column("email", String(255), nullable=True, unique=None, default=None)
237 237 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
238 238 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
239 239 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
240 240 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
241 241
242 242 user_log = relationship('UserLog')
243 243 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
244 244
245 245 repositories = relationship('Repository')
246 246 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
247 247 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
248 248
249 249 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
250 250 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
251 251
252 252 group_member = relationship('UserGroupMember', cascade='all')
253 253
254 254 notifications = relationship('UserNotification', cascade='all')
255 255 # notifications assigned to this user
256 256 user_created_notifications = relationship('Notification', cascade='all')
257 257 # comments created by this user
258 258 user_comments = relationship('ChangesetComment', cascade='all')
259 259 user_emails = relationship('UserEmailMap', cascade='all')
260 260
261 261 @hybrid_property
262 262 def email(self):
263 263 return self._email
264 264
265 265 @email.setter
266 266 def email(self, val):
267 267 self._email = val.lower() if val else None
268 268
269 269 @property
270 270 def firstname(self):
271 271 # alias for future
272 272 return self.name
273 273
274 274 @property
275 275 def username_and_name(self):
276 276 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
277 277
278 278 @property
279 279 def full_name(self):
280 280 return '%s %s' % (self.firstname, self.lastname)
281 281
282 282 @property
283 283 def full_contact(self):
284 284 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
285 285
286 286 @property
287 287 def short_contact(self):
288 288 return '%s %s' % (self.firstname, self.lastname)
289 289
290 290 @property
291 291 def is_admin(self):
292 292 return self.admin
293 293
294 294 @classmethod
295 295 def get_by_username(cls, username, case_insensitive=False, cache=False):
296 296 if case_insensitive:
297 297 q = cls.query().filter(cls.username.ilike(username))
298 298 else:
299 299 q = cls.query().filter(cls.username == username)
300 300
301 301 if cache:
302 302 q = q.options(FromCache(
303 303 "sql_cache_short",
304 304 "get_user_%s" % _hash_key(username)
305 305 )
306 306 )
307 307 return q.scalar()
308 308
309 309 @classmethod
310 310 def get_by_auth_token(cls, auth_token, cache=False):
311 311 q = cls.query().filter(cls.api_key == auth_token)
312 312
313 313 if cache:
314 314 q = q.options(FromCache("sql_cache_short",
315 315 "get_auth_token_%s" % auth_token))
316 316 return q.scalar()
317 317
318 318 @classmethod
319 319 def get_by_email(cls, email, case_insensitive=False, cache=False):
320 320 if case_insensitive:
321 321 q = cls.query().filter(cls.email.ilike(email))
322 322 else:
323 323 q = cls.query().filter(cls.email == email)
324 324
325 325 if cache:
326 326 q = q.options(FromCache("sql_cache_short",
327 327 "get_email_key_%s" % email))
328 328
329 329 ret = q.scalar()
330 330 if ret is None:
331 331 q = UserEmailMap.query()
332 332 # try fetching in alternate email map
333 333 if case_insensitive:
334 334 q = q.filter(UserEmailMap.email.ilike(email))
335 335 else:
336 336 q = q.filter(UserEmailMap.email == email)
337 337 q = q.options(joinedload(UserEmailMap.user))
338 338 if cache:
339 339 q = q.options(FromCache("sql_cache_short",
340 340 "get_email_map_key_%s" % email))
341 341 ret = getattr(q.scalar(), 'user', None)
342 342
343 343 return ret
344 344
345 345 @classmethod
346 346 def get_first_admin(cls):
347 347 user = User.query().filter(User.admin == True).first()
348 348 if user is None:
349 349 raise Exception('Missing administrative account!')
350 350 return user
351 351
352 352 @classmethod
353 353 def get_default_user(cls, cache=False):
354 354 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
355 355 if user is None:
356 356 raise Exception('Missing default account!')
357 357 return user
358 358
359 359
360 360
361 361
362 362 class UserEmailMap(Base, BaseModel):
363 363 __tablename__ = 'user_email_map'
364 364 __table_args__ = (
365 365 Index('uem_email_idx', 'email'),
366 366 UniqueConstraint('email'),
367 367 {'extend_existing': True, 'mysql_engine': 'InnoDB',
368 368 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
369 369 )
370 __mapper_args__ = {}
370
371 371
372 372 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
373 373 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
374 374 _email = Column("email", String(255), nullable=True, unique=False, default=None)
375 375 user = relationship('User', lazy='joined')
376 376
377 377 @validates('_email')
378 378 def validate_email(self, key, email):
379 379 # check if this email is not main one
380 380 main_email = Session().query(User).filter(User.email == email).scalar()
381 381 if main_email is not None:
382 382 raise AttributeError('email %s is present is user table' % email)
383 383 return email
384 384
385 385 @hybrid_property
386 386 def email(self):
387 387 return self._email
388 388
389 389 @email.setter
390 390 def email(self, val):
391 391 self._email = val.lower() if val else None
392 392
393 393
394 394 class UserIpMap(Base, BaseModel):
395 395 __tablename__ = 'user_ip_map'
396 396 __table_args__ = (
397 397 UniqueConstraint('user_id', 'ip_addr'),
398 398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
399 399 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
400 400 )
401 __mapper_args__ = {}
401
402 402
403 403 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 404 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
405 405 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
406 406 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
407 407 user = relationship('User', lazy='joined')
408 408
409 409
410 410 class UserLog(Base, BaseModel):
411 411 __tablename__ = 'user_logs'
412 412 __table_args__ = (
413 413 {'extend_existing': True, 'mysql_engine': 'InnoDB',
414 414 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
415 415 )
416 416 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
417 417 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
418 418 username = Column("username", String(255), nullable=True, unique=None, default=None)
419 419 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
420 420 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
421 421 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
422 422 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
423 423 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
424 424
425 425 def __unicode__(self):
426 426 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
427 427 self.repository_name,
428 428 self.action)
429 429
430 430 user = relationship('User')
431 431 repository = relationship('Repository', cascade='')
432 432
433 433
434 434 class UserGroup(Base, BaseModel):
435 435 __tablename__ = 'users_groups'
436 436 __table_args__ = (
437 437 {'extend_existing': True, 'mysql_engine': 'InnoDB',
438 438 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
439 439 )
440 440
441 441 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
442 442 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
443 443 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
444 444 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
445 445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
446 446
447 447 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
448 448 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
449 449 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
450 450 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
451 451 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
452 452 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
453 453
454 454 user = relationship('User')
455 455
456 456 def __unicode__(self):
457 457 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
458 458 self.users_group_id,
459 459 self.users_group_name)
460 460
461 461 @classmethod
462 462 def get_by_group_name(cls, group_name, cache=False,
463 463 case_insensitive=False):
464 464 if case_insensitive:
465 465 q = cls.query().filter(cls.users_group_name.ilike(group_name))
466 466 else:
467 467 q = cls.query().filter(cls.users_group_name == group_name)
468 468 if cache:
469 469 q = q.options(FromCache(
470 470 "sql_cache_short",
471 471 "get_user_%s" % _hash_key(group_name)
472 472 )
473 473 )
474 474 return q.scalar()
475 475
476 476 @classmethod
477 477 def get(cls, user_group_id, cache=False):
478 478 user_group = cls.query()
479 479 if cache:
480 480 user_group = user_group.options(FromCache("sql_cache_short",
481 481 "get_users_group_%s" % user_group_id))
482 482 return user_group.get(user_group_id)
483 483
484 484
485 485 class UserGroupMember(Base, BaseModel):
486 486 __tablename__ = 'users_groups_members'
487 487 __table_args__ = (
488 488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
489 489 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
490 490 )
491 491
492 492 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
493 493 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
494 494 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
495 495
496 496 user = relationship('User', lazy='joined')
497 497 users_group = relationship('UserGroup')
498 498
499 499 def __init__(self, gr_id='', u_id=''):
500 500 self.users_group_id = gr_id
501 501 self.user_id = u_id
502 502
503 503
504 504 class RepositoryField(Base, BaseModel):
505 505 __tablename__ = 'repositories_fields'
506 506 __table_args__ = (
507 507 UniqueConstraint('repository_id', 'field_key'), # no-multi field
508 508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
509 509 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
510 510 )
511 511 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
512 512
513 513 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
514 514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
515 515 field_key = Column("field_key", String(250))
516 516 field_label = Column("field_label", String(1024), nullable=False)
517 517 field_value = Column("field_value", String(10000), nullable=False)
518 518 field_desc = Column("field_desc", String(1024), nullable=False)
519 519 field_type = Column("field_type", String(256), nullable=False, unique=None)
520 520 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
521 521
522 522 repository = relationship('Repository')
523 523
524 524 @classmethod
525 525 def get_by_key_name(cls, key, repo):
526 526 row = cls.query()\
527 527 .filter(cls.repository == repo)\
528 528 .filter(cls.field_key == key).scalar()
529 529 return row
530 530
531 531
532 532 class Repository(Base, BaseModel):
533 533 __tablename__ = 'repositories'
534 534 __table_args__ = (
535 535 UniqueConstraint('repo_name'),
536 536 Index('r_repo_name_idx', 'repo_name'),
537 537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
538 538 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
539 539 )
540 540
541 541 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
542 542 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
543 543 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
544 544 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
545 545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
546 546 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
547 547 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
548 548 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
549 549 description = Column("description", String(10000), nullable=True, unique=None, default=None)
550 550 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
551 551 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
552 552 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
553 553 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
554 554 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
555 555 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
556 556
557 557 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
558 558 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
559 559
560 560 user = relationship('User')
561 561 fork = relationship('Repository', remote_side=repo_id)
562 562 group = relationship('RepoGroup')
563 563 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
564 564 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
565 565 stats = relationship('Statistics', cascade='all', uselist=False)
566 566
567 567 followers = relationship('UserFollowing',
568 568 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
569 569 cascade='all')
570 570 extra_fields = relationship('RepositoryField',
571 571 cascade="all, delete, delete-orphan")
572 572
573 573 logs = relationship('UserLog')
574 574 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
575 575
576 576 pull_requests_org = relationship('PullRequest',
577 577 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
578 578 cascade="all, delete, delete-orphan")
579 579
580 580 pull_requests_other = relationship('PullRequest',
581 581 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
582 582 cascade="all, delete, delete-orphan")
583 583
584 584 def __unicode__(self):
585 585 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
586 586 safe_str(self.repo_name))
587 587
588 588 @classmethod
589 589 def get_by_repo_name(cls, repo_name):
590 590 q = Session().query(cls).filter(cls.repo_name == repo_name)
591 591 q = q.options(joinedload(Repository.fork))\
592 592 .options(joinedload(Repository.user))\
593 593 .options(joinedload(Repository.group))
594 594 return q.scalar()
595 595
596 596
597 597 class RepoGroup(Base, BaseModel):
598 598 __tablename__ = 'groups'
599 599 __table_args__ = (
600 600 UniqueConstraint('group_name', 'group_parent_id'),
601 601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 602 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
603 603 )
604 __mapper_args__ = {'order_by': 'group_name'}
604
605 605
606 606 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 607 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
608 608 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
609 609 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
610 610 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
611 611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
612 612
613 613 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
614 614 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
615 615 parent_group = relationship('RepoGroup', remote_side=group_id)
616 616 user = relationship('User')
617 617
618 618 def __init__(self, group_name='', parent_group=None):
619 619 self.group_name = group_name
620 620 self.parent_group = parent_group
621 621
622 622 def __unicode__(self):
623 623 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
624 624 self.group_name)
625 625
626 626 @classmethod
627 627 def url_sep(cls):
628 628 return URL_SEP
629 629
630 630 @classmethod
631 631 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
632 632 if case_insensitive:
633 633 gr = cls.query()\
634 634 .filter(cls.group_name.ilike(group_name))
635 635 else:
636 636 gr = cls.query()\
637 637 .filter(cls.group_name == group_name)
638 638 if cache:
639 639 gr = gr.options(FromCache(
640 640 "sql_cache_short",
641 641 "get_group_%s" % _hash_key(group_name)
642 642 )
643 643 )
644 644 return gr.scalar()
645 645
646 646
647 647 class Permission(Base, BaseModel):
648 648 __tablename__ = 'permissions'
649 649 __table_args__ = (
650 650 Index('p_perm_name_idx', 'permission_name'),
651 651 {'extend_existing': True, 'mysql_engine': 'InnoDB',
652 652 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
653 653 )
654 654 PERMS = [
655 655 ('hg.admin', _('RhodeCode Administrator')),
656 656
657 657 ('repository.none', _('Repository no access')),
658 658 ('repository.read', _('Repository read access')),
659 659 ('repository.write', _('Repository write access')),
660 660 ('repository.admin', _('Repository admin access')),
661 661
662 662 ('group.none', _('Repository group no access')),
663 663 ('group.read', _('Repository group read access')),
664 664 ('group.write', _('Repository group write access')),
665 665 ('group.admin', _('Repository group admin access')),
666 666
667 667 ('usergroup.none', _('User group no access')),
668 668 ('usergroup.read', _('User group read access')),
669 669 ('usergroup.write', _('User group write access')),
670 670 ('usergroup.admin', _('User group admin access')),
671 671
672 672 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
673 673 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
674 674
675 675 ('hg.usergroup.create.false', _('User Group creation disabled')),
676 676 ('hg.usergroup.create.true', _('User Group creation enabled')),
677 677
678 678 ('hg.create.none', _('Repository creation disabled')),
679 679 ('hg.create.repository', _('Repository creation enabled')),
680 680
681 681 ('hg.fork.none', _('Repository forking disabled')),
682 682 ('hg.fork.repository', _('Repository forking enabled')),
683 683
684 684 ('hg.register.none', _('Registration disabled')),
685 685 ('hg.register.manual_activate', _('User Registration with manual account activation')),
686 686 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
687 687
688 688 ('hg.extern_activate.manual', _('Manual activation of external account')),
689 689 ('hg.extern_activate.auto', _('Automatic activation of external account')),
690 690
691 691 ]
692 692
693 693 #definition of system default permissions for DEFAULT user
694 694 DEFAULT_USER_PERMISSIONS = [
695 695 'repository.read',
696 696 'group.read',
697 697 'usergroup.read',
698 698 'hg.create.repository',
699 699 'hg.fork.repository',
700 700 'hg.register.manual_activate',
701 701 'hg.extern_activate.auto',
702 702 ]
703 703
704 704 # defines which permissions are more important higher the more important
705 705 # Weight defines which permissions are more important.
706 706 # The higher number the more important.
707 707 PERM_WEIGHTS = {
708 708 'repository.none': 0,
709 709 'repository.read': 1,
710 710 'repository.write': 3,
711 711 'repository.admin': 4,
712 712
713 713 'group.none': 0,
714 714 'group.read': 1,
715 715 'group.write': 3,
716 716 'group.admin': 4,
717 717
718 718 'usergroup.none': 0,
719 719 'usergroup.read': 1,
720 720 'usergroup.write': 3,
721 721 'usergroup.admin': 4,
722 722 'hg.repogroup.create.false': 0,
723 723 'hg.repogroup.create.true': 1,
724 724
725 725 'hg.usergroup.create.false': 0,
726 726 'hg.usergroup.create.true': 1,
727 727
728 728 'hg.fork.none': 0,
729 729 'hg.fork.repository': 1,
730 730 'hg.create.none': 0,
731 731 'hg.create.repository': 1
732 732 }
733 733
734 734 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
735 735 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
736 736 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
737 737
738 738 def __unicode__(self):
739 739 return u"<%s('%s:%s')>" % (
740 740 self.__class__.__name__, self.permission_id, self.permission_name
741 741 )
742 742
743 743 @classmethod
744 744 def get_by_key(cls, key):
745 745 return cls.query().filter(cls.permission_name == key).scalar()
746 746
747 747
748 748 class UserRepoToPerm(Base, BaseModel):
749 749 __tablename__ = 'repo_to_perm'
750 750 __table_args__ = (
751 751 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
752 752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
753 753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
754 754 )
755 755 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
756 756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
757 757 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
758 758 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
759 759
760 760 user = relationship('User')
761 761 repository = relationship('Repository')
762 762 permission = relationship('Permission')
763 763
764 764 def __unicode__(self):
765 765 return u'<%s => %s >' % (self.user, self.repository)
766 766
767 767
768 768 class UserUserGroupToPerm(Base, BaseModel):
769 769 __tablename__ = 'user_user_group_to_perm'
770 770 __table_args__ = (
771 771 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
772 772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
773 773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
774 774 )
775 775 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
776 776 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
777 777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
778 778 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
779 779
780 780 user = relationship('User')
781 781 user_group = relationship('UserGroup')
782 782 permission = relationship('Permission')
783 783
784 784 def __unicode__(self):
785 785 return u'<%s => %s >' % (self.user, self.user_group)
786 786
787 787
788 788 class UserToPerm(Base, BaseModel):
789 789 __tablename__ = 'user_to_perm'
790 790 __table_args__ = (
791 791 UniqueConstraint('user_id', 'permission_id'),
792 792 {'extend_existing': True, 'mysql_engine': 'InnoDB',
793 793 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
794 794 )
795 795 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
796 796 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
797 797 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
798 798
799 799 user = relationship('User')
800 800 permission = relationship('Permission', lazy='joined')
801 801
802 802 def __unicode__(self):
803 803 return u'<%s => %s >' % (self.user, self.permission)
804 804
805 805
806 806 class UserGroupRepoToPerm(Base, BaseModel):
807 807 __tablename__ = 'users_group_repo_to_perm'
808 808 __table_args__ = (
809 809 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
810 810 {'extend_existing': True, 'mysql_engine': 'InnoDB',
811 811 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
812 812 )
813 813 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
814 814 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
815 815 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
816 816 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
817 817
818 818 users_group = relationship('UserGroup')
819 819 permission = relationship('Permission')
820 820 repository = relationship('Repository')
821 821
822 822 def __unicode__(self):
823 823 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
824 824
825 825
826 826 class UserGroupUserGroupToPerm(Base, BaseModel):
827 827 __tablename__ = 'user_group_user_group_to_perm'
828 828 __table_args__ = (
829 829 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
830 830 CheckConstraint('target_user_group_id != user_group_id'),
831 831 {'extend_existing': True, 'mysql_engine': 'InnoDB',
832 832 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
833 833 )
834 834 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
835 835 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
836 836 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
837 837 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
838 838
839 839 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
840 840 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
841 841 permission = relationship('Permission')
842 842
843 843 def __unicode__(self):
844 844 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
845 845
846 846
847 847 class UserGroupToPerm(Base, BaseModel):
848 848 __tablename__ = 'users_group_to_perm'
849 849 __table_args__ = (
850 850 UniqueConstraint('users_group_id', 'permission_id',),
851 851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
852 852 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
853 853 )
854 854 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
855 855 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
856 856 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
857 857
858 858 users_group = relationship('UserGroup')
859 859 permission = relationship('Permission')
860 860
861 861
862 862 class UserRepoGroupToPerm(Base, BaseModel):
863 863 __tablename__ = 'user_repo_group_to_perm'
864 864 __table_args__ = (
865 865 UniqueConstraint('user_id', 'group_id', 'permission_id'),
866 866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
867 867 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
868 868 )
869 869
870 870 group_to_perm_id = Column("group_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 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
873 873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
874 874
875 875 user = relationship('User')
876 876 group = relationship('RepoGroup')
877 877 permission = relationship('Permission')
878 878
879 879
880 880 class UserGroupRepoGroupToPerm(Base, BaseModel):
881 881 __tablename__ = 'users_group_repo_group_to_perm'
882 882 __table_args__ = (
883 883 UniqueConstraint('users_group_id', 'group_id'),
884 884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
885 885 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
886 886 )
887 887
888 888 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)
889 889 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
890 890 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
891 891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
892 892
893 893 users_group = relationship('UserGroup')
894 894 permission = relationship('Permission')
895 895 group = relationship('RepoGroup')
896 896
897 897
898 898 class Statistics(Base, BaseModel):
899 899 __tablename__ = 'statistics'
900 900 __table_args__ = (
901 901 UniqueConstraint('repository_id'),
902 902 {'extend_existing': True, 'mysql_engine': 'InnoDB',
903 903 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
904 904 )
905 905 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
906 906 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
907 907 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
908 908 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
909 909 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
910 910 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
911 911
912 912 repository = relationship('Repository', single_parent=True)
913 913
914 914
915 915 class UserFollowing(Base, BaseModel):
916 916 __tablename__ = 'user_followings'
917 917 __table_args__ = (
918 918 UniqueConstraint('user_id', 'follows_repository_id'),
919 919 UniqueConstraint('user_id', 'follows_user_id'),
920 920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
921 921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
922 922 )
923 923
924 924 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
925 925 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
926 926 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
927 927 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
928 928 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
929 929
930 930 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
931 931
932 932 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
933 933 follows_repository = relationship('Repository', order_by='Repository.repo_name')
934 934
935 935
936 936 class CacheInvalidation(Base, BaseModel):
937 937 __tablename__ = 'cache_invalidation'
938 938 __table_args__ = (
939 939 UniqueConstraint('cache_key'),
940 940 Index('key_idx', 'cache_key'),
941 941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
942 942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
943 943 )
944 944 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
945 945 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
946 946 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
947 947 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
948 948
949 949 def __init__(self, cache_key, cache_args=''):
950 950 self.cache_key = cache_key
951 951 self.cache_args = cache_args
952 952 self.cache_active = False
953 953
954 954
955 955 class ChangesetComment(Base, BaseModel):
956 956 __tablename__ = 'changeset_comments'
957 957 __table_args__ = (
958 958 Index('cc_revision_idx', 'revision'),
959 959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
960 960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
961 961 )
962 962 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
963 963 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
964 964 revision = Column('revision', String(40), nullable=True)
965 965 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
966 966 line_no = Column('line_no', Unicode(10), nullable=True)
967 967 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
968 968 f_path = Column('f_path', Unicode(1000), nullable=True)
969 969 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
970 970 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
971 971 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
972 972 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
973 973
974 974 author = relationship('User', lazy='joined')
975 975 repo = relationship('Repository')
976 976 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
977 977 pull_request = relationship('PullRequest', lazy='joined')
978 978
979 979
980 980 class ChangesetStatus(Base, BaseModel):
981 981 __tablename__ = 'changeset_statuses'
982 982 __table_args__ = (
983 983 Index('cs_revision_idx', 'revision'),
984 984 Index('cs_version_idx', 'version'),
985 985 UniqueConstraint('repo_id', 'revision', 'version'),
986 986 {'extend_existing': True, 'mysql_engine': 'InnoDB',
987 987 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
988 988 )
989 989 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
990 990 STATUS_APPROVED = 'approved'
991 991 STATUS_REJECTED = 'rejected'
992 992 STATUS_UNDER_REVIEW = 'under_review'
993 993
994 994 STATUSES = [
995 995 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
996 996 (STATUS_APPROVED, _("Approved")),
997 997 (STATUS_REJECTED, _("Rejected")),
998 998 (STATUS_UNDER_REVIEW, _("Under Review")),
999 999 ]
1000 1000
1001 1001 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1002 1002 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1003 1003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1004 1004 revision = Column('revision', String(40), nullable=False)
1005 1005 status = Column('status', String(128), nullable=False, default=DEFAULT)
1006 1006 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1007 1007 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1008 1008 version = Column('version', Integer(), nullable=False, default=0)
1009 1009 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1010 1010
1011 1011 author = relationship('User', lazy='joined')
1012 1012 repo = relationship('Repository')
1013 1013 comment = relationship('ChangesetComment', lazy='joined')
1014 1014 pull_request = relationship('PullRequest', lazy='joined')
1015 1015
1016 1016
1017 1017
1018 1018 class PullRequest(Base, BaseModel):
1019 1019 __tablename__ = 'pull_requests'
1020 1020 __table_args__ = (
1021 1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1022 1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1023 1023 )
1024 1024
1025 1025 STATUS_NEW = u'new'
1026 1026 STATUS_OPEN = u'open'
1027 1027 STATUS_CLOSED = u'closed'
1028 1028
1029 1029 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1030 1030 title = Column('title', Unicode(256), nullable=True)
1031 1031 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1032 1032 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1033 1033 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1034 1034 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1035 1035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1036 1036 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1037 1037 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1038 1038 org_ref = Column('org_ref', Unicode(256), nullable=False)
1039 1039 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1040 1040 other_ref = Column('other_ref', Unicode(256), nullable=False)
1041 1041
1042 1042 author = relationship('User', lazy='joined')
1043 1043 reviewers = relationship('PullRequestReviewers',
1044 1044 cascade="all, delete, delete-orphan")
1045 1045 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1046 1046 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1047 1047 statuses = relationship('ChangesetStatus')
1048 1048 comments = relationship('ChangesetComment',
1049 1049 cascade="all, delete, delete-orphan")
1050 1050
1051 1051
1052 1052 class PullRequestReviewers(Base, BaseModel):
1053 1053 __tablename__ = 'pull_request_reviewers'
1054 1054 __table_args__ = (
1055 1055 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1056 1056 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1057 1057 )
1058 1058
1059 1059 def __init__(self, user=None, pull_request=None):
1060 1060 self.user = user
1061 1061 self.pull_request = pull_request
1062 1062
1063 1063 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1064 1064 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1065 1065 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1066 1066
1067 1067 user = relationship('User')
1068 1068 pull_request = relationship('PullRequest')
1069 1069
1070 1070
1071 1071 class Notification(Base, BaseModel):
1072 1072 __tablename__ = 'notifications'
1073 1073 __table_args__ = (
1074 1074 Index('notification_type_idx', 'type'),
1075 1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1076 1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1077 1077 )
1078 1078
1079 1079 TYPE_CHANGESET_COMMENT = u'cs_comment'
1080 1080 TYPE_MESSAGE = u'message'
1081 1081 TYPE_MENTION = u'mention'
1082 1082 TYPE_REGISTRATION = u'registration'
1083 1083 TYPE_PULL_REQUEST = u'pull_request'
1084 1084 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1085 1085
1086 1086 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1087 1087 subject = Column('subject', Unicode(512), nullable=True)
1088 1088 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1089 1089 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1090 1090 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1091 1091 type_ = Column('type', Unicode(256))
1092 1092
1093 1093 created_by_user = relationship('User')
1094 1094 notifications_to_users = relationship('UserNotification', lazy='joined',
1095 1095 cascade="all, delete, delete-orphan")
1096 1096
1097 1097
1098 1098 class UserNotification(Base, BaseModel):
1099 1099 __tablename__ = 'user_to_notification'
1100 1100 __table_args__ = (
1101 1101 UniqueConstraint('user_id', 'notification_id'),
1102 1102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1103 1103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1104 1104 )
1105 1105 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1106 1106 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1107 1107 read = Column('read', Boolean, default=False)
1108 1108 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1109 1109
1110 1110 user = relationship('User', lazy="joined")
1111 1111 notification = relationship('Notification', lazy="joined",
1112 1112 order_by=lambda: Notification.created_on.desc(),)
1113 1113
1114 1114
1115 1115 class Gist(Base, BaseModel):
1116 1116 __tablename__ = 'gists'
1117 1117 __table_args__ = (
1118 1118 Index('g_gist_access_id_idx', 'gist_access_id'),
1119 1119 Index('g_created_on_idx', 'created_on'),
1120 1120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1121 1121 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1122 1122 )
1123 1123 GIST_PUBLIC = u'public'
1124 1124 GIST_PRIVATE = u'private'
1125 1125
1126 1126 gist_id = Column('gist_id', Integer(), primary_key=True)
1127 1127 gist_access_id = Column('gist_access_id', Unicode(250))
1128 1128 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1129 1129 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1130 1130 gist_expires = Column('gist_expires', Float(53), nullable=False)
1131 1131 gist_type = Column('gist_type', Unicode(128), nullable=False)
1132 1132 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1133 1133 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1134 1134
1135 1135 owner = relationship('User')
1136 1136
1137 1137
1138 1138 class DbMigrateVersion(Base, BaseModel):
1139 1139 __tablename__ = 'db_migrate_version'
1140 1140 __table_args__ = (
1141 1141 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1142 1142 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1143 1143 )
1144 1144 repository_id = Column('repository_id', String(250), primary_key=True)
1145 1145 repository_path = Column('repository_path', Text)
1146 1146 version = Column('version', Integer)
@@ -1,1169 +1,1169 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27 import functools
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 remove_prefix, time_to_datetime, aslist, Optional, safe_int
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 SETTINGS_TYPES = {
150 150 'str': safe_str,
151 151 'int': safe_int,
152 152 'unicode': safe_str,
153 153 'bool': str2bool,
154 154 'list': functools.partial(aslist, sep=',')
155 155 }
156 156 __tablename__ = 'rhodecode_settings'
157 157 __table_args__ = (
158 158 UniqueConstraint('app_settings_name'),
159 159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
160 160 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
161 161 )
162 162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
163 163 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
164 164 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
165 165 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
166 166
167 167 def __init__(self, key='', val='', type='unicode'):
168 168 self.app_settings_name = key
169 169 self.app_settings_value = val
170 170 self.app_settings_type = type
171 171
172 172 @validates('_app_settings_value')
173 173 def validate_settings_value(self, key, val):
174 174 assert type(val) == str
175 175 return val
176 176
177 177 @hybrid_property
178 178 def app_settings_value(self):
179 179 v = self._app_settings_value
180 180 _type = self.app_settings_type
181 181 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
182 182 return converter(v)
183 183
184 184 @app_settings_value.setter
185 185 def app_settings_value(self, val):
186 186 """
187 187 Setter that will always make sure we use unicode in app_settings_value
188 188
189 189 :param val:
190 190 """
191 191 self._app_settings_value = safe_str(val)
192 192
193 193 @hybrid_property
194 194 def app_settings_type(self):
195 195 return self._app_settings_type
196 196
197 197 @app_settings_type.setter
198 198 def app_settings_type(self, val):
199 199 if val not in self.SETTINGS_TYPES:
200 200 raise Exception('type must be one of %s got %s'
201 201 % (self.SETTINGS_TYPES.keys(), val))
202 202 self._app_settings_type = val
203 203
204 204 def __unicode__(self):
205 205 return u"<%s('%s:%s[%s]')>" % (
206 206 self.__class__.__name__,
207 207 self.app_settings_name, self.app_settings_value, self.app_settings_type
208 208 )
209 209
210 210
211 211 class RhodeCodeUi(Base, BaseModel):
212 212 __tablename__ = 'rhodecode_ui'
213 213 __table_args__ = (
214 214 UniqueConstraint('ui_key'),
215 215 {'extend_existing': True, 'mysql_engine': 'InnoDB',
216 216 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
217 217 )
218 218
219 219 HOOK_REPO_SIZE = 'changegroup.repo_size'
220 220 HOOK_PUSH = 'changegroup.push_logger'
221 221 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
222 222 HOOK_PULL = 'outgoing.pull_logger'
223 223 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
224 224
225 225 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
226 226 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
227 227 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
228 228 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
229 229 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
230 230
231 231
232 232
233 233 class User(Base, BaseModel):
234 234 __tablename__ = 'users'
235 235 __table_args__ = (
236 236 UniqueConstraint('username'), UniqueConstraint('email'),
237 237 Index('u_username_idx', 'username'),
238 238 Index('u_email_idx', 'email'),
239 239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
240 240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
241 241 )
242 242 DEFAULT_USER = 'default'
243 243
244 244 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 245 username = Column("username", String(255), nullable=True, unique=None, default=None)
246 246 password = Column("password", String(255), nullable=True, unique=None, default=None)
247 247 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
248 248 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
249 249 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
250 250 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
251 251 _email = Column("email", String(255), nullable=True, unique=None, default=None)
252 252 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
253 253 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
254 254 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
255 255 #for migration reasons, this is going to be later deleted
256 256 ldap_dn = Column("ldap_dn", String(255), nullable=True, unique=None, default=None)
257 257
258 258 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
259 259 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
260 260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
261 261
262 262 user_log = relationship('UserLog')
263 263 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
264 264
265 265 repositories = relationship('Repository')
266 266 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
267 267 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
268 268
269 269 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
270 270 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
271 271
272 272 group_member = relationship('UserGroupMember', cascade='all')
273 273
274 274 notifications = relationship('UserNotification', cascade='all')
275 275 # notifications assigned to this user
276 276 user_created_notifications = relationship('Notification', cascade='all')
277 277 # comments created by this user
278 278 user_comments = relationship('ChangesetComment', cascade='all')
279 279 user_emails = relationship('UserEmailMap', cascade='all')
280 280
281 281 @hybrid_property
282 282 def email(self):
283 283 return self._email
284 284
285 285 @email.setter
286 286 def email(self, val):
287 287 self._email = val.lower() if val else None
288 288
289 289 @property
290 290 def firstname(self):
291 291 # alias for future
292 292 return self.name
293 293
294 294 @property
295 295 def username_and_name(self):
296 296 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
297 297
298 298 @property
299 299 def full_name(self):
300 300 return '%s %s' % (self.firstname, self.lastname)
301 301
302 302 @property
303 303 def full_contact(self):
304 304 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
305 305
306 306 @property
307 307 def short_contact(self):
308 308 return '%s %s' % (self.firstname, self.lastname)
309 309
310 310 @property
311 311 def is_admin(self):
312 312 return self.admin
313 313
314 314 @classmethod
315 315 def get_by_username(cls, username, case_insensitive=False, cache=False):
316 316 if case_insensitive:
317 317 q = cls.query().filter(cls.username.ilike(username))
318 318 else:
319 319 q = cls.query().filter(cls.username == username)
320 320
321 321 if cache:
322 322 q = q.options(FromCache(
323 323 "sql_cache_short",
324 324 "get_user_%s" % _hash_key(username)
325 325 )
326 326 )
327 327 return q.scalar()
328 328
329 329 @classmethod
330 330 def get_by_auth_token(cls, auth_token, cache=False):
331 331 q = cls.query().filter(cls.api_key == auth_token)
332 332
333 333 if cache:
334 334 q = q.options(FromCache("sql_cache_short",
335 335 "get_auth_token_%s" % auth_token))
336 336 return q.scalar()
337 337
338 338 @classmethod
339 339 def get_by_email(cls, email, case_insensitive=False, cache=False):
340 340 if case_insensitive:
341 341 q = cls.query().filter(cls.email.ilike(email))
342 342 else:
343 343 q = cls.query().filter(cls.email == email)
344 344
345 345 if cache:
346 346 q = q.options(FromCache("sql_cache_short",
347 347 "get_email_key_%s" % email))
348 348
349 349 ret = q.scalar()
350 350 if ret is None:
351 351 q = UserEmailMap.query()
352 352 # try fetching in alternate email map
353 353 if case_insensitive:
354 354 q = q.filter(UserEmailMap.email.ilike(email))
355 355 else:
356 356 q = q.filter(UserEmailMap.email == email)
357 357 q = q.options(joinedload(UserEmailMap.user))
358 358 if cache:
359 359 q = q.options(FromCache("sql_cache_short",
360 360 "get_email_map_key_%s" % email))
361 361 ret = getattr(q.scalar(), 'user', None)
362 362
363 363 return ret
364 364
365 365 @classmethod
366 366 def get_first_admin(cls):
367 367 user = User.query().filter(User.admin == True).first()
368 368 if user is None:
369 369 raise Exception('Missing administrative account!')
370 370 return user
371 371
372 372 @classmethod
373 373 def get_default_user(cls, cache=False):
374 374 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
375 375 if user is None:
376 376 raise Exception('Missing default account!')
377 377 return user
378 378
379 379
380 380
381 381
382 382 class UserEmailMap(Base, BaseModel):
383 383 __tablename__ = 'user_email_map'
384 384 __table_args__ = (
385 385 Index('uem_email_idx', 'email'),
386 386 UniqueConstraint('email'),
387 387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
388 388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
389 389 )
390 __mapper_args__ = {}
390
391 391
392 392 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
393 393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
394 394 _email = Column("email", String(255), nullable=True, unique=False, default=None)
395 395 user = relationship('User', lazy='joined')
396 396
397 397 @validates('_email')
398 398 def validate_email(self, key, email):
399 399 # check if this email is not main one
400 400 main_email = Session().query(User).filter(User.email == email).scalar()
401 401 if main_email is not None:
402 402 raise AttributeError('email %s is present is user table' % email)
403 403 return email
404 404
405 405 @hybrid_property
406 406 def email(self):
407 407 return self._email
408 408
409 409 @email.setter
410 410 def email(self, val):
411 411 self._email = val.lower() if val else None
412 412
413 413
414 414 class UserIpMap(Base, BaseModel):
415 415 __tablename__ = 'user_ip_map'
416 416 __table_args__ = (
417 417 UniqueConstraint('user_id', 'ip_addr'),
418 418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
419 419 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
420 420 )
421 __mapper_args__ = {}
421
422 422
423 423 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
425 425 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
426 426 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
427 427 user = relationship('User', lazy='joined')
428 428
429 429
430 430 class UserLog(Base, BaseModel):
431 431 __tablename__ = 'user_logs'
432 432 __table_args__ = (
433 433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
434 434 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
435 435 )
436 436 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
437 437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
438 438 username = Column("username", String(255), nullable=True, unique=None, default=None)
439 439 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
440 440 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
441 441 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
442 442 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
443 443 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
444 444
445 445 def __unicode__(self):
446 446 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
447 447 self.repository_name,
448 448 self.action)
449 449
450 450 user = relationship('User')
451 451 repository = relationship('Repository', cascade='')
452 452
453 453
454 454 class UserGroup(Base, BaseModel):
455 455 __tablename__ = 'users_groups'
456 456 __table_args__ = (
457 457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
458 458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
459 459 )
460 460
461 461 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
462 462 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
463 463 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
464 464 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
465 465 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
466 466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
467 467 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
468 468
469 469 # don't trigger lazy load for migrations
470 470 #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
471 471 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
472 472 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
473 473 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
474 474 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
475 475 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
476 476
477 477 user = relationship('User')
478 478
479 479 def __unicode__(self):
480 480 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
481 481 self.users_group_id,
482 482 self.users_group_name)
483 483
484 484 @classmethod
485 485 def get_by_group_name(cls, group_name, cache=False,
486 486 case_insensitive=False):
487 487 if case_insensitive:
488 488 q = cls.query().filter(cls.users_group_name.ilike(group_name))
489 489 else:
490 490 q = cls.query().filter(cls.users_group_name == group_name)
491 491 if cache:
492 492 q = q.options(FromCache(
493 493 "sql_cache_short",
494 494 "get_user_%s" % _hash_key(group_name)
495 495 )
496 496 )
497 497 return q.scalar()
498 498
499 499 @classmethod
500 500 def get(cls, user_group_id, cache=False):
501 501 user_group = cls.query()
502 502 if cache:
503 503 user_group = user_group.options(FromCache("sql_cache_short",
504 504 "get_users_group_%s" % user_group_id))
505 505 return user_group.get(user_group_id)
506 506
507 507
508 508 class UserGroupMember(Base, BaseModel):
509 509 __tablename__ = 'users_groups_members'
510 510 __table_args__ = (
511 511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
512 512 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
513 513 )
514 514
515 515 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
516 516 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
517 517 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
518 518
519 519 user = relationship('User', lazy='joined')
520 520 users_group = relationship('UserGroup')
521 521
522 522 def __init__(self, gr_id='', u_id=''):
523 523 self.users_group_id = gr_id
524 524 self.user_id = u_id
525 525
526 526
527 527 class RepositoryField(Base, BaseModel):
528 528 __tablename__ = 'repositories_fields'
529 529 __table_args__ = (
530 530 UniqueConstraint('repository_id', 'field_key'), # no-multi field
531 531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
532 532 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
533 533 )
534 534 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
535 535
536 536 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
537 537 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
538 538 field_key = Column("field_key", String(250))
539 539 field_label = Column("field_label", String(1024), nullable=False)
540 540 field_value = Column("field_value", String(10000), nullable=False)
541 541 field_desc = Column("field_desc", String(1024), nullable=False)
542 542 field_type = Column("field_type", String(256), nullable=False, unique=None)
543 543 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
544 544
545 545 repository = relationship('Repository')
546 546
547 547 @classmethod
548 548 def get_by_key_name(cls, key, repo):
549 549 row = cls.query()\
550 550 .filter(cls.repository == repo)\
551 551 .filter(cls.field_key == key).scalar()
552 552 return row
553 553
554 554
555 555 class Repository(Base, BaseModel):
556 556 __tablename__ = 'repositories'
557 557 __table_args__ = (
558 558 UniqueConstraint('repo_name'),
559 559 Index('r_repo_name_idx', 'repo_name'),
560 560 {'extend_existing': True, 'mysql_engine': 'InnoDB',
561 561 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
562 562 )
563 563
564 564 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
565 565 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
566 566 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
567 567 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
568 568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
569 569 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
570 570 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
571 571 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
572 572 description = Column("description", String(10000), nullable=True, unique=None, default=None)
573 573 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
574 574 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
575 575 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
576 576 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
577 577 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
578 578 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
579 579
580 580 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
581 581 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
582 582
583 583 user = relationship('User')
584 584 fork = relationship('Repository', remote_side=repo_id)
585 585 group = relationship('RepoGroup')
586 586 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
587 587 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
588 588 stats = relationship('Statistics', cascade='all', uselist=False)
589 589
590 590 followers = relationship('UserFollowing',
591 591 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
592 592 cascade='all')
593 593 extra_fields = relationship('RepositoryField',
594 594 cascade="all, delete, delete-orphan")
595 595
596 596 logs = relationship('UserLog')
597 597 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
598 598
599 599 pull_requests_org = relationship('PullRequest',
600 600 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
601 601 cascade="all, delete, delete-orphan")
602 602
603 603 pull_requests_other = relationship('PullRequest',
604 604 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
605 605 cascade="all, delete, delete-orphan")
606 606
607 607 def __unicode__(self):
608 608 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
609 609 safe_str(self.repo_name))
610 610
611 611 @classmethod
612 612 def get_by_repo_name(cls, repo_name):
613 613 q = Session().query(cls).filter(cls.repo_name == repo_name)
614 614 q = q.options(joinedload(Repository.fork))\
615 615 .options(joinedload(Repository.user))\
616 616 .options(joinedload(Repository.group))
617 617 return q.scalar()
618 618
619 619
620 620 class RepoGroup(Base, BaseModel):
621 621 __tablename__ = 'groups'
622 622 __table_args__ = (
623 623 UniqueConstraint('group_name', 'group_parent_id'),
624 624 {'extend_existing': True, 'mysql_engine': 'InnoDB',
625 625 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
626 626 )
627 __mapper_args__ = {'order_by': 'group_name'}
627
628 628
629 629 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
630 630 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
631 631 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
632 632 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
633 633 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
634 634 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
635 635
636 636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
637 637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
638 638 parent_group = relationship('RepoGroup', remote_side=group_id)
639 639 user = relationship('User')
640 640
641 641 def __init__(self, group_name='', parent_group=None):
642 642 self.group_name = group_name
643 643 self.parent_group = parent_group
644 644
645 645 def __unicode__(self):
646 646 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
647 647 self.group_name)
648 648
649 649 @classmethod
650 650 def url_sep(cls):
651 651 return URL_SEP
652 652
653 653 @classmethod
654 654 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
655 655 if case_insensitive:
656 656 gr = cls.query()\
657 657 .filter(cls.group_name.ilike(group_name))
658 658 else:
659 659 gr = cls.query()\
660 660 .filter(cls.group_name == group_name)
661 661 if cache:
662 662 gr = gr.options(FromCache(
663 663 "sql_cache_short",
664 664 "get_group_%s" % _hash_key(group_name)
665 665 )
666 666 )
667 667 return gr.scalar()
668 668
669 669
670 670 class Permission(Base, BaseModel):
671 671 __tablename__ = 'permissions'
672 672 __table_args__ = (
673 673 Index('p_perm_name_idx', 'permission_name'),
674 674 {'extend_existing': True, 'mysql_engine': 'InnoDB',
675 675 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
676 676 )
677 677 PERMS = [
678 678 ('hg.admin', _('RhodeCode Administrator')),
679 679
680 680 ('repository.none', _('Repository no access')),
681 681 ('repository.read', _('Repository read access')),
682 682 ('repository.write', _('Repository write access')),
683 683 ('repository.admin', _('Repository admin access')),
684 684
685 685 ('group.none', _('Repository group no access')),
686 686 ('group.read', _('Repository group read access')),
687 687 ('group.write', _('Repository group write access')),
688 688 ('group.admin', _('Repository group admin access')),
689 689
690 690 ('usergroup.none', _('User group no access')),
691 691 ('usergroup.read', _('User group read access')),
692 692 ('usergroup.write', _('User group write access')),
693 693 ('usergroup.admin', _('User group admin access')),
694 694
695 695 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
696 696 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
697 697
698 698 ('hg.usergroup.create.false', _('User Group creation disabled')),
699 699 ('hg.usergroup.create.true', _('User Group creation enabled')),
700 700
701 701 ('hg.create.none', _('Repository creation disabled')),
702 702 ('hg.create.repository', _('Repository creation enabled')),
703 703
704 704 ('hg.fork.none', _('Repository forking disabled')),
705 705 ('hg.fork.repository', _('Repository forking enabled')),
706 706
707 707 ('hg.register.none', _('Registration disabled')),
708 708 ('hg.register.manual_activate', _('User Registration with manual account activation')),
709 709 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
710 710
711 711 ('hg.extern_activate.manual', _('Manual activation of external account')),
712 712 ('hg.extern_activate.auto', _('Automatic activation of external account')),
713 713
714 714 ]
715 715
716 716 #definition of system default permissions for DEFAULT user
717 717 DEFAULT_USER_PERMISSIONS = [
718 718 'repository.read',
719 719 'group.read',
720 720 'usergroup.read',
721 721 'hg.create.repository',
722 722 'hg.fork.repository',
723 723 'hg.register.manual_activate',
724 724 'hg.extern_activate.auto',
725 725 ]
726 726
727 727 # defines which permissions are more important higher the more important
728 728 # Weight defines which permissions are more important.
729 729 # The higher number the more important.
730 730 PERM_WEIGHTS = {
731 731 'repository.none': 0,
732 732 'repository.read': 1,
733 733 'repository.write': 3,
734 734 'repository.admin': 4,
735 735
736 736 'group.none': 0,
737 737 'group.read': 1,
738 738 'group.write': 3,
739 739 'group.admin': 4,
740 740
741 741 'usergroup.none': 0,
742 742 'usergroup.read': 1,
743 743 'usergroup.write': 3,
744 744 'usergroup.admin': 4,
745 745 'hg.repogroup.create.false': 0,
746 746 'hg.repogroup.create.true': 1,
747 747
748 748 'hg.usergroup.create.false': 0,
749 749 'hg.usergroup.create.true': 1,
750 750
751 751 'hg.fork.none': 0,
752 752 'hg.fork.repository': 1,
753 753 'hg.create.none': 0,
754 754 'hg.create.repository': 1
755 755 }
756 756
757 757 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
758 758 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
759 759 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
760 760
761 761 def __unicode__(self):
762 762 return u"<%s('%s:%s')>" % (
763 763 self.__class__.__name__, self.permission_id, self.permission_name
764 764 )
765 765
766 766 @classmethod
767 767 def get_by_key(cls, key):
768 768 return cls.query().filter(cls.permission_name == key).scalar()
769 769
770 770
771 771 class UserRepoToPerm(Base, BaseModel):
772 772 __tablename__ = 'repo_to_perm'
773 773 __table_args__ = (
774 774 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
775 775 {'extend_existing': True, 'mysql_engine': 'InnoDB',
776 776 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
777 777 )
778 778 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
779 779 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
780 780 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
781 781 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
782 782
783 783 user = relationship('User')
784 784 repository = relationship('Repository')
785 785 permission = relationship('Permission')
786 786
787 787 def __unicode__(self):
788 788 return u'<%s => %s >' % (self.user, self.repository)
789 789
790 790
791 791 class UserUserGroupToPerm(Base, BaseModel):
792 792 __tablename__ = 'user_user_group_to_perm'
793 793 __table_args__ = (
794 794 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
795 795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
796 796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
797 797 )
798 798 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
799 799 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
800 800 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
801 801 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
802 802
803 803 user = relationship('User')
804 804 user_group = relationship('UserGroup')
805 805 permission = relationship('Permission')
806 806
807 807 def __unicode__(self):
808 808 return u'<%s => %s >' % (self.user, self.user_group)
809 809
810 810
811 811 class UserToPerm(Base, BaseModel):
812 812 __tablename__ = 'user_to_perm'
813 813 __table_args__ = (
814 814 UniqueConstraint('user_id', 'permission_id'),
815 815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
816 816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
817 817 )
818 818 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
819 819 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
820 820 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
821 821
822 822 user = relationship('User')
823 823 permission = relationship('Permission', lazy='joined')
824 824
825 825 def __unicode__(self):
826 826 return u'<%s => %s >' % (self.user, self.permission)
827 827
828 828
829 829 class UserGroupRepoToPerm(Base, BaseModel):
830 830 __tablename__ = 'users_group_repo_to_perm'
831 831 __table_args__ = (
832 832 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
833 833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
834 834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
835 835 )
836 836 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
837 837 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
838 838 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
839 839 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
840 840
841 841 users_group = relationship('UserGroup')
842 842 permission = relationship('Permission')
843 843 repository = relationship('Repository')
844 844
845 845 def __unicode__(self):
846 846 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
847 847
848 848
849 849 class UserGroupUserGroupToPerm(Base, BaseModel):
850 850 __tablename__ = 'user_group_user_group_to_perm'
851 851 __table_args__ = (
852 852 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
853 853 CheckConstraint('target_user_group_id != user_group_id'),
854 854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
855 855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
856 856 )
857 857 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
858 858 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
859 859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
860 860 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
861 861
862 862 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
863 863 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
864 864 permission = relationship('Permission')
865 865
866 866 def __unicode__(self):
867 867 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
868 868
869 869
870 870 class UserGroupToPerm(Base, BaseModel):
871 871 __tablename__ = 'users_group_to_perm'
872 872 __table_args__ = (
873 873 UniqueConstraint('users_group_id', 'permission_id',),
874 874 {'extend_existing': True, 'mysql_engine': 'InnoDB',
875 875 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
876 876 )
877 877 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
878 878 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
879 879 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
880 880
881 881 users_group = relationship('UserGroup')
882 882 permission = relationship('Permission')
883 883
884 884
885 885 class UserRepoGroupToPerm(Base, BaseModel):
886 886 __tablename__ = 'user_repo_group_to_perm'
887 887 __table_args__ = (
888 888 UniqueConstraint('user_id', 'group_id', 'permission_id'),
889 889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
890 890 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
891 891 )
892 892
893 893 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
894 894 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
895 895 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
896 896 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
897 897
898 898 user = relationship('User')
899 899 group = relationship('RepoGroup')
900 900 permission = relationship('Permission')
901 901
902 902
903 903 class UserGroupRepoGroupToPerm(Base, BaseModel):
904 904 __tablename__ = 'users_group_repo_group_to_perm'
905 905 __table_args__ = (
906 906 UniqueConstraint('users_group_id', 'group_id'),
907 907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
908 908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
909 909 )
910 910
911 911 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)
912 912 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
913 913 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
914 914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
915 915
916 916 users_group = relationship('UserGroup')
917 917 permission = relationship('Permission')
918 918 group = relationship('RepoGroup')
919 919
920 920
921 921 class Statistics(Base, BaseModel):
922 922 __tablename__ = 'statistics'
923 923 __table_args__ = (
924 924 UniqueConstraint('repository_id'),
925 925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
926 926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
927 927 )
928 928 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
929 929 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
930 930 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
931 931 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
932 932 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
933 933 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
934 934
935 935 repository = relationship('Repository', single_parent=True)
936 936
937 937
938 938 class UserFollowing(Base, BaseModel):
939 939 __tablename__ = 'user_followings'
940 940 __table_args__ = (
941 941 UniqueConstraint('user_id', 'follows_repository_id'),
942 942 UniqueConstraint('user_id', 'follows_user_id'),
943 943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
944 944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
945 945 )
946 946
947 947 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
948 948 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
949 949 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
950 950 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
951 951 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
952 952
953 953 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
954 954
955 955 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
956 956 follows_repository = relationship('Repository', order_by='Repository.repo_name')
957 957
958 958
959 959 class CacheInvalidation(Base, BaseModel):
960 960 __tablename__ = 'cache_invalidation'
961 961 __table_args__ = (
962 962 UniqueConstraint('cache_key'),
963 963 Index('key_idx', 'cache_key'),
964 964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
965 965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
966 966 )
967 967 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
968 968 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
969 969 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
970 970 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
971 971
972 972 def __init__(self, cache_key, cache_args=''):
973 973 self.cache_key = cache_key
974 974 self.cache_args = cache_args
975 975 self.cache_active = False
976 976
977 977
978 978 class ChangesetComment(Base, BaseModel):
979 979 __tablename__ = 'changeset_comments'
980 980 __table_args__ = (
981 981 Index('cc_revision_idx', 'revision'),
982 982 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 983 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
984 984 )
985 985 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
986 986 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
987 987 revision = Column('revision', String(40), nullable=True)
988 988 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
989 989 line_no = Column('line_no', Unicode(10), nullable=True)
990 990 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
991 991 f_path = Column('f_path', Unicode(1000), nullable=True)
992 992 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
993 993 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
994 994 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
995 995 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
996 996
997 997 author = relationship('User', lazy='joined')
998 998 repo = relationship('Repository')
999 999 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1000 1000 pull_request = relationship('PullRequest', lazy='joined')
1001 1001
1002 1002
1003 1003 class ChangesetStatus(Base, BaseModel):
1004 1004 __tablename__ = 'changeset_statuses'
1005 1005 __table_args__ = (
1006 1006 Index('cs_revision_idx', 'revision'),
1007 1007 Index('cs_version_idx', 'version'),
1008 1008 UniqueConstraint('repo_id', 'revision', 'version'),
1009 1009 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1010 1010 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1011 1011 )
1012 1012 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1013 1013 STATUS_APPROVED = 'approved'
1014 1014 STATUS_REJECTED = 'rejected'
1015 1015 STATUS_UNDER_REVIEW = 'under_review'
1016 1016
1017 1017 STATUSES = [
1018 1018 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1019 1019 (STATUS_APPROVED, _("Approved")),
1020 1020 (STATUS_REJECTED, _("Rejected")),
1021 1021 (STATUS_UNDER_REVIEW, _("Under Review")),
1022 1022 ]
1023 1023
1024 1024 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1025 1025 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1026 1026 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1027 1027 revision = Column('revision', String(40), nullable=False)
1028 1028 status = Column('status', String(128), nullable=False, default=DEFAULT)
1029 1029 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1030 1030 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1031 1031 version = Column('version', Integer(), nullable=False, default=0)
1032 1032 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1033 1033
1034 1034 author = relationship('User', lazy='joined')
1035 1035 repo = relationship('Repository')
1036 1036 comment = relationship('ChangesetComment', lazy='joined')
1037 1037 pull_request = relationship('PullRequest', lazy='joined')
1038 1038
1039 1039
1040 1040
1041 1041 class PullRequest(Base, BaseModel):
1042 1042 __tablename__ = 'pull_requests'
1043 1043 __table_args__ = (
1044 1044 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1045 1045 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1046 1046 )
1047 1047
1048 1048 STATUS_NEW = u'new'
1049 1049 STATUS_OPEN = u'open'
1050 1050 STATUS_CLOSED = u'closed'
1051 1051
1052 1052 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1053 1053 title = Column('title', Unicode(256), nullable=True)
1054 1054 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1055 1055 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1056 1056 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1057 1057 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 1058 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1059 1059 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1060 1060 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1061 1061 org_ref = Column('org_ref', Unicode(256), nullable=False)
1062 1062 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1063 1063 other_ref = Column('other_ref', Unicode(256), nullable=False)
1064 1064
1065 1065 author = relationship('User', lazy='joined')
1066 1066 reviewers = relationship('PullRequestReviewers',
1067 1067 cascade="all, delete, delete-orphan")
1068 1068 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1069 1069 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1070 1070 statuses = relationship('ChangesetStatus')
1071 1071 comments = relationship('ChangesetComment',
1072 1072 cascade="all, delete, delete-orphan")
1073 1073
1074 1074
1075 1075 class PullRequestReviewers(Base, BaseModel):
1076 1076 __tablename__ = 'pull_request_reviewers'
1077 1077 __table_args__ = (
1078 1078 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1079 1079 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1080 1080 )
1081 1081
1082 1082 def __init__(self, user=None, pull_request=None):
1083 1083 self.user = user
1084 1084 self.pull_request = pull_request
1085 1085
1086 1086 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1087 1087 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1088 1088 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1089 1089
1090 1090 user = relationship('User')
1091 1091 pull_request = relationship('PullRequest')
1092 1092
1093 1093
1094 1094 class Notification(Base, BaseModel):
1095 1095 __tablename__ = 'notifications'
1096 1096 __table_args__ = (
1097 1097 Index('notification_type_idx', 'type'),
1098 1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1099 1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1100 1100 )
1101 1101
1102 1102 TYPE_CHANGESET_COMMENT = u'cs_comment'
1103 1103 TYPE_MESSAGE = u'message'
1104 1104 TYPE_MENTION = u'mention'
1105 1105 TYPE_REGISTRATION = u'registration'
1106 1106 TYPE_PULL_REQUEST = u'pull_request'
1107 1107 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1108 1108
1109 1109 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1110 1110 subject = Column('subject', Unicode(512), nullable=True)
1111 1111 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1112 1112 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1113 1113 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1114 1114 type_ = Column('type', Unicode(256))
1115 1115
1116 1116 created_by_user = relationship('User')
1117 1117 notifications_to_users = relationship('UserNotification', lazy='joined',
1118 1118 cascade="all, delete, delete-orphan")
1119 1119
1120 1120
1121 1121 class UserNotification(Base, BaseModel):
1122 1122 __tablename__ = 'user_to_notification'
1123 1123 __table_args__ = (
1124 1124 UniqueConstraint('user_id', 'notification_id'),
1125 1125 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1126 1126 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1127 1127 )
1128 1128 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1129 1129 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1130 1130 read = Column('read', Boolean, default=False)
1131 1131 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1132 1132
1133 1133 user = relationship('User', lazy="joined")
1134 1134 notification = relationship('Notification', lazy="joined",
1135 1135 order_by=lambda: Notification.created_on.desc(),)
1136 1136
1137 1137
1138 1138 class Gist(Base, BaseModel):
1139 1139 __tablename__ = 'gists'
1140 1140 __table_args__ = (
1141 1141 Index('g_gist_access_id_idx', 'gist_access_id'),
1142 1142 Index('g_created_on_idx', 'created_on'),
1143 1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1145 1145 )
1146 1146 GIST_PUBLIC = u'public'
1147 1147 GIST_PRIVATE = u'private'
1148 1148
1149 1149 gist_id = Column('gist_id', Integer(), primary_key=True)
1150 1150 gist_access_id = Column('gist_access_id', Unicode(250))
1151 1151 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1152 1152 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1153 1153 gist_expires = Column('gist_expires', Float(53), nullable=False)
1154 1154 gist_type = Column('gist_type', Unicode(128), nullable=False)
1155 1155 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1156 1156 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1157 1157
1158 1158 owner = relationship('User')
1159 1159
1160 1160
1161 1161 class DbMigrateVersion(Base, BaseModel):
1162 1162 __tablename__ = 'db_migrate_version'
1163 1163 __table_args__ = (
1164 1164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1165 1165 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1166 1166 )
1167 1167 repository_id = Column('repository_id', String(250), primary_key=True)
1168 1168 repository_path = Column('repository_path', Text)
1169 1169 version = Column('version', Integer)
@@ -1,1170 +1,1170 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27 import functools
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 remove_prefix, time_to_datetime, aslist, Optional, safe_int
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 try:
145 145 return safe_str(self.__unicode__())
146 146 except UnicodeDecodeError:
147 147 pass
148 148 return '<DB:%s>' % (self.__class__.__name__)
149 149
150 150
151 151 class RhodeCodeSetting(Base, BaseModel):
152 152 SETTINGS_TYPES = {
153 153 'str': safe_str,
154 154 'int': safe_int,
155 155 'unicode': safe_str,
156 156 'bool': str2bool,
157 157 'list': functools.partial(aslist, sep=',')
158 158 }
159 159 __tablename__ = 'rhodecode_settings'
160 160 __table_args__ = (
161 161 UniqueConstraint('app_settings_name'),
162 162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
163 163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
164 164 )
165 165 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
166 166 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
167 167 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
168 168 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
169 169
170 170 def __init__(self, key='', val='', type='unicode'):
171 171 self.app_settings_name = key
172 172 self.app_settings_value = val
173 173 self.app_settings_type = type
174 174
175 175 @validates('_app_settings_value')
176 176 def validate_settings_value(self, key, val):
177 177 assert type(val) == str
178 178 return val
179 179
180 180 @hybrid_property
181 181 def app_settings_value(self):
182 182 v = self._app_settings_value
183 183 _type = self.app_settings_type
184 184 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
185 185 return converter(v)
186 186
187 187 @app_settings_value.setter
188 188 def app_settings_value(self, val):
189 189 """
190 190 Setter that will always make sure we use unicode in app_settings_value
191 191
192 192 :param val:
193 193 """
194 194 self._app_settings_value = safe_str(val)
195 195
196 196 @hybrid_property
197 197 def app_settings_type(self):
198 198 return self._app_settings_type
199 199
200 200 @app_settings_type.setter
201 201 def app_settings_type(self, val):
202 202 if val not in self.SETTINGS_TYPES:
203 203 raise Exception('type must be one of %s got %s'
204 204 % (self.SETTINGS_TYPES.keys(), val))
205 205 self._app_settings_type = val
206 206
207 207 def __unicode__(self):
208 208 return u"<%s('%s:%s[%s]')>" % (
209 209 self.__class__.__name__,
210 210 self.app_settings_name, self.app_settings_value, self.app_settings_type
211 211 )
212 212
213 213
214 214 class RhodeCodeUi(Base, BaseModel):
215 215 __tablename__ = 'rhodecode_ui'
216 216 __table_args__ = (
217 217 UniqueConstraint('ui_key'),
218 218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
219 219 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
220 220 )
221 221
222 222 HOOK_REPO_SIZE = 'changegroup.repo_size'
223 223 HOOK_PUSH = 'changegroup.push_logger'
224 224 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
225 225 HOOK_PULL = 'outgoing.pull_logger'
226 226 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
227 227
228 228 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
229 229 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
230 230 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
231 231 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
232 232 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
233 233
234 234
235 235
236 236 class User(Base, BaseModel):
237 237 __tablename__ = 'users'
238 238 __table_args__ = (
239 239 UniqueConstraint('username'), UniqueConstraint('email'),
240 240 Index('u_username_idx', 'username'),
241 241 Index('u_email_idx', 'email'),
242 242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
243 243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
244 244 )
245 245 DEFAULT_USER = 'default'
246 246
247 247 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
248 248 username = Column("username", String(255), nullable=True, unique=None, default=None)
249 249 password = Column("password", String(255), nullable=True, unique=None, default=None)
250 250 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
251 251 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
252 252 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
253 253 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
254 254 _email = Column("email", String(255), nullable=True, unique=None, default=None)
255 255 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
256 256 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
257 257 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
258 258 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
259 259 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
260 260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
261 261
262 262 user_log = relationship('UserLog')
263 263 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
264 264
265 265 repositories = relationship('Repository')
266 266 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
267 267 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
268 268
269 269 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
270 270 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
271 271
272 272 group_member = relationship('UserGroupMember', cascade='all')
273 273
274 274 notifications = relationship('UserNotification', cascade='all')
275 275 # notifications assigned to this user
276 276 user_created_notifications = relationship('Notification', cascade='all')
277 277 # comments created by this user
278 278 user_comments = relationship('ChangesetComment', cascade='all')
279 279 user_emails = relationship('UserEmailMap', cascade='all')
280 280
281 281 @hybrid_property
282 282 def email(self):
283 283 return self._email
284 284
285 285 @email.setter
286 286 def email(self, val):
287 287 self._email = val.lower() if val else None
288 288
289 289 @property
290 290 def firstname(self):
291 291 # alias for future
292 292 return self.name
293 293
294 294 @property
295 295 def username_and_name(self):
296 296 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
297 297
298 298 @property
299 299 def full_name(self):
300 300 return '%s %s' % (self.firstname, self.lastname)
301 301
302 302 @property
303 303 def full_contact(self):
304 304 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
305 305
306 306 @property
307 307 def short_contact(self):
308 308 return '%s %s' % (self.firstname, self.lastname)
309 309
310 310 @property
311 311 def is_admin(self):
312 312 return self.admin
313 313
314 314 @classmethod
315 315 def get_by_username(cls, username, case_insensitive=False, cache=False):
316 316 if case_insensitive:
317 317 q = cls.query().filter(cls.username.ilike(username))
318 318 else:
319 319 q = cls.query().filter(cls.username == username)
320 320
321 321 if cache:
322 322 q = q.options(FromCache(
323 323 "sql_cache_short",
324 324 "get_user_%s" % _hash_key(username)
325 325 )
326 326 )
327 327 return q.scalar()
328 328
329 329 @classmethod
330 330 def get_by_auth_token(cls, auth_token, cache=False):
331 331 q = cls.query().filter(cls.api_key == auth_token)
332 332
333 333 if cache:
334 334 q = q.options(FromCache("sql_cache_short",
335 335 "get_auth_token_%s" % auth_token))
336 336 return q.scalar()
337 337
338 338 @classmethod
339 339 def get_by_email(cls, email, case_insensitive=False, cache=False):
340 340 if case_insensitive:
341 341 q = cls.query().filter(cls.email.ilike(email))
342 342 else:
343 343 q = cls.query().filter(cls.email == email)
344 344
345 345 if cache:
346 346 q = q.options(FromCache("sql_cache_short",
347 347 "get_email_key_%s" % email))
348 348
349 349 ret = q.scalar()
350 350 if ret is None:
351 351 q = UserEmailMap.query()
352 352 # try fetching in alternate email map
353 353 if case_insensitive:
354 354 q = q.filter(UserEmailMap.email.ilike(email))
355 355 else:
356 356 q = q.filter(UserEmailMap.email == email)
357 357 q = q.options(joinedload(UserEmailMap.user))
358 358 if cache:
359 359 q = q.options(FromCache("sql_cache_short",
360 360 "get_email_map_key_%s" % email))
361 361 ret = getattr(q.scalar(), 'user', None)
362 362
363 363 return ret
364 364
365 365 @classmethod
366 366 def get_first_admin(cls):
367 367 user = User.query().filter(User.admin == True).first()
368 368 if user is None:
369 369 raise Exception('Missing administrative account!')
370 370 return user
371 371
372 372 @classmethod
373 373 def get_default_user(cls, cache=False):
374 374 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
375 375 if user is None:
376 376 raise Exception('Missing default account!')
377 377 return user
378 378
379 379
380 380
381 381
382 382 class UserEmailMap(Base, BaseModel):
383 383 __tablename__ = 'user_email_map'
384 384 __table_args__ = (
385 385 Index('uem_email_idx', 'email'),
386 386 UniqueConstraint('email'),
387 387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
388 388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
389 389 )
390 __mapper_args__ = {}
390
391 391
392 392 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
393 393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
394 394 _email = Column("email", String(255), nullable=True, unique=False, default=None)
395 395 user = relationship('User', lazy='joined')
396 396
397 397 @validates('_email')
398 398 def validate_email(self, key, email):
399 399 # check if this email is not main one
400 400 main_email = Session().query(User).filter(User.email == email).scalar()
401 401 if main_email is not None:
402 402 raise AttributeError('email %s is present is user table' % email)
403 403 return email
404 404
405 405 @hybrid_property
406 406 def email(self):
407 407 return self._email
408 408
409 409 @email.setter
410 410 def email(self, val):
411 411 self._email = val.lower() if val else None
412 412
413 413
414 414 class UserIpMap(Base, BaseModel):
415 415 __tablename__ = 'user_ip_map'
416 416 __table_args__ = (
417 417 UniqueConstraint('user_id', 'ip_addr'),
418 418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
419 419 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
420 420 )
421 __mapper_args__ = {}
421
422 422
423 423 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
425 425 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
426 426 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
427 427 user = relationship('User', lazy='joined')
428 428
429 429
430 430 class UserLog(Base, BaseModel):
431 431 __tablename__ = 'user_logs'
432 432 __table_args__ = (
433 433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
434 434 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
435 435 )
436 436 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
437 437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
438 438 username = Column("username", String(255), nullable=True, unique=None, default=None)
439 439 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
440 440 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
441 441 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
442 442 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
443 443 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
444 444
445 445 def __unicode__(self):
446 446 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
447 447 self.repository_name,
448 448 self.action)
449 449
450 450 user = relationship('User')
451 451 repository = relationship('Repository', cascade='')
452 452
453 453
454 454 class UserGroup(Base, BaseModel):
455 455 __tablename__ = 'users_groups'
456 456 __table_args__ = (
457 457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
458 458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
459 459 )
460 460
461 461 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
462 462 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
463 463 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
464 464 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
465 465 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
466 466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
467 467 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
468 468
469 469 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
470 470 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
471 471 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
472 472 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
473 473 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
474 474 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
475 475
476 476 user = relationship('User')
477 477
478 478 def __unicode__(self):
479 479 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
480 480 self.users_group_id,
481 481 self.users_group_name)
482 482
483 483 @classmethod
484 484 def get_by_group_name(cls, group_name, cache=False,
485 485 case_insensitive=False):
486 486 if case_insensitive:
487 487 q = cls.query().filter(cls.users_group_name.ilike(group_name))
488 488 else:
489 489 q = cls.query().filter(cls.users_group_name == group_name)
490 490 if cache:
491 491 q = q.options(FromCache(
492 492 "sql_cache_short",
493 493 "get_user_%s" % _hash_key(group_name)
494 494 )
495 495 )
496 496 return q.scalar()
497 497
498 498 @classmethod
499 499 def get(cls, user_group_id, cache=False):
500 500 user_group = cls.query()
501 501 if cache:
502 502 user_group = user_group.options(FromCache("sql_cache_short",
503 503 "get_users_group_%s" % user_group_id))
504 504 return user_group.get(user_group_id)
505 505
506 506
507 507 class UserGroupMember(Base, BaseModel):
508 508 __tablename__ = 'users_groups_members'
509 509 __table_args__ = (
510 510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
511 511 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
512 512 )
513 513
514 514 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
515 515 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
516 516 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
517 517
518 518 user = relationship('User', lazy='joined')
519 519 users_group = relationship('UserGroup')
520 520
521 521 def __init__(self, gr_id='', u_id=''):
522 522 self.users_group_id = gr_id
523 523 self.user_id = u_id
524 524
525 525
526 526 class RepositoryField(Base, BaseModel):
527 527 __tablename__ = 'repositories_fields'
528 528 __table_args__ = (
529 529 UniqueConstraint('repository_id', 'field_key'), # no-multi field
530 530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 531 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
532 532 )
533 533 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
534 534
535 535 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 536 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
537 537 field_key = Column("field_key", String(250))
538 538 field_label = Column("field_label", String(1024), nullable=False)
539 539 field_value = Column("field_value", String(10000), nullable=False)
540 540 field_desc = Column("field_desc", String(1024), nullable=False)
541 541 field_type = Column("field_type", String(256), nullable=False, unique=None)
542 542 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
543 543
544 544 repository = relationship('Repository')
545 545
546 546 @classmethod
547 547 def get_by_key_name(cls, key, repo):
548 548 row = cls.query()\
549 549 .filter(cls.repository == repo)\
550 550 .filter(cls.field_key == key).scalar()
551 551 return row
552 552
553 553
554 554 class Repository(Base, BaseModel):
555 555 __tablename__ = 'repositories'
556 556 __table_args__ = (
557 557 UniqueConstraint('repo_name'),
558 558 Index('r_repo_name_idx', 'repo_name'),
559 559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
560 560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
561 561 )
562 562
563 563 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
564 564 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
565 565 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
566 566 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
567 567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
568 568 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
569 569 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
570 570 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
571 571 description = Column("description", String(10000), nullable=True, unique=None, default=None)
572 572 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
573 573 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
574 574 landing_rev = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
575 575 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
576 576 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
577 577 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
578 578
579 579 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
580 580 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
581 581
582 582 user = relationship('User')
583 583 fork = relationship('Repository', remote_side=repo_id)
584 584 group = relationship('RepoGroup')
585 585 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
586 586 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
587 587 stats = relationship('Statistics', cascade='all', uselist=False)
588 588
589 589 followers = relationship('UserFollowing',
590 590 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
591 591 cascade='all')
592 592 extra_fields = relationship('RepositoryField',
593 593 cascade="all, delete, delete-orphan")
594 594
595 595 logs = relationship('UserLog')
596 596 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
597 597
598 598 pull_requests_org = relationship('PullRequest',
599 599 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
600 600 cascade="all, delete, delete-orphan")
601 601
602 602 pull_requests_other = relationship('PullRequest',
603 603 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
604 604 cascade="all, delete, delete-orphan")
605 605
606 606 def __unicode__(self):
607 607 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
608 608 safe_str(self.repo_name))
609 609
610 610 @classmethod
611 611 def get_by_repo_name(cls, repo_name):
612 612 q = Session().query(cls).filter(cls.repo_name == repo_name)
613 613 q = q.options(joinedload(Repository.fork))\
614 614 .options(joinedload(Repository.user))\
615 615 .options(joinedload(Repository.group))
616 616 return q.scalar()
617 617
618 618
619 619 class RepoGroup(Base, BaseModel):
620 620 __tablename__ = 'groups'
621 621 __table_args__ = (
622 622 UniqueConstraint('group_name', 'group_parent_id'),
623 623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
624 624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
625 625 )
626 __mapper_args__ = {'order_by': 'group_name'}
626
627 627
628 628 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
629 629 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
630 630 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
631 631 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
632 632 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
633 633 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
634 634 #TODO: create this field in migrations
635 635 #created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
636 636
637 637 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
638 638 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
639 639 parent_group = relationship('RepoGroup', remote_side=group_id)
640 640 user = relationship('User')
641 641
642 642 def __init__(self, group_name='', parent_group=None):
643 643 self.group_name = group_name
644 644 self.parent_group = parent_group
645 645
646 646 def __unicode__(self):
647 647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
648 648 self.group_name)
649 649
650 650 @classmethod
651 651 def url_sep(cls):
652 652 return URL_SEP
653 653
654 654 @classmethod
655 655 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
656 656 if case_insensitive:
657 657 gr = cls.query()\
658 658 .filter(cls.group_name.ilike(group_name))
659 659 else:
660 660 gr = cls.query()\
661 661 .filter(cls.group_name == group_name)
662 662 if cache:
663 663 gr = gr.options(FromCache(
664 664 "sql_cache_short",
665 665 "get_group_%s" % _hash_key(group_name)
666 666 )
667 667 )
668 668 return gr.scalar()
669 669
670 670
671 671 class Permission(Base, BaseModel):
672 672 __tablename__ = 'permissions'
673 673 __table_args__ = (
674 674 Index('p_perm_name_idx', 'permission_name'),
675 675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
676 676 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
677 677 )
678 678 PERMS = [
679 679 ('hg.admin', _('RhodeCode Administrator')),
680 680
681 681 ('repository.none', _('Repository no access')),
682 682 ('repository.read', _('Repository read access')),
683 683 ('repository.write', _('Repository write access')),
684 684 ('repository.admin', _('Repository admin access')),
685 685
686 686 ('group.none', _('Repository group no access')),
687 687 ('group.read', _('Repository group read access')),
688 688 ('group.write', _('Repository group write access')),
689 689 ('group.admin', _('Repository group admin access')),
690 690
691 691 ('usergroup.none', _('User group no access')),
692 692 ('usergroup.read', _('User group read access')),
693 693 ('usergroup.write', _('User group write access')),
694 694 ('usergroup.admin', _('User group admin access')),
695 695
696 696 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
697 697 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
698 698
699 699 ('hg.usergroup.create.false', _('User Group creation disabled')),
700 700 ('hg.usergroup.create.true', _('User Group creation enabled')),
701 701
702 702 ('hg.create.none', _('Repository creation disabled')),
703 703 ('hg.create.repository', _('Repository creation enabled')),
704 704
705 705 ('hg.fork.none', _('Repository forking disabled')),
706 706 ('hg.fork.repository', _('Repository forking enabled')),
707 707
708 708 ('hg.register.none', _('Registration disabled')),
709 709 ('hg.register.manual_activate', _('User Registration with manual account activation')),
710 710 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
711 711
712 712 ('hg.extern_activate.manual', _('Manual activation of external account')),
713 713 ('hg.extern_activate.auto', _('Automatic activation of external account')),
714 714
715 715 ]
716 716
717 717 #definition of system default permissions for DEFAULT user
718 718 DEFAULT_USER_PERMISSIONS = [
719 719 'repository.read',
720 720 'group.read',
721 721 'usergroup.read',
722 722 'hg.create.repository',
723 723 'hg.fork.repository',
724 724 'hg.register.manual_activate',
725 725 'hg.extern_activate.auto',
726 726 ]
727 727
728 728 # defines which permissions are more important higher the more important
729 729 # Weight defines which permissions are more important.
730 730 # The higher number the more important.
731 731 PERM_WEIGHTS = {
732 732 'repository.none': 0,
733 733 'repository.read': 1,
734 734 'repository.write': 3,
735 735 'repository.admin': 4,
736 736
737 737 'group.none': 0,
738 738 'group.read': 1,
739 739 'group.write': 3,
740 740 'group.admin': 4,
741 741
742 742 'usergroup.none': 0,
743 743 'usergroup.read': 1,
744 744 'usergroup.write': 3,
745 745 'usergroup.admin': 4,
746 746 'hg.repogroup.create.false': 0,
747 747 'hg.repogroup.create.true': 1,
748 748
749 749 'hg.usergroup.create.false': 0,
750 750 'hg.usergroup.create.true': 1,
751 751
752 752 'hg.fork.none': 0,
753 753 'hg.fork.repository': 1,
754 754 'hg.create.none': 0,
755 755 'hg.create.repository': 1
756 756 }
757 757
758 758 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
759 759 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
760 760 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
761 761
762 762 def __unicode__(self):
763 763 return u"<%s('%s:%s')>" % (
764 764 self.__class__.__name__, self.permission_id, self.permission_name
765 765 )
766 766
767 767 @classmethod
768 768 def get_by_key(cls, key):
769 769 return cls.query().filter(cls.permission_name == key).scalar()
770 770
771 771
772 772 class UserRepoToPerm(Base, BaseModel):
773 773 __tablename__ = 'repo_to_perm'
774 774 __table_args__ = (
775 775 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
776 776 {'extend_existing': True, 'mysql_engine': 'InnoDB',
777 777 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
778 778 )
779 779 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
780 780 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
781 781 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
782 782 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
783 783
784 784 user = relationship('User')
785 785 repository = relationship('Repository')
786 786 permission = relationship('Permission')
787 787
788 788 def __unicode__(self):
789 789 return u'<%s => %s >' % (self.user, self.repository)
790 790
791 791
792 792 class UserUserGroupToPerm(Base, BaseModel):
793 793 __tablename__ = 'user_user_group_to_perm'
794 794 __table_args__ = (
795 795 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
796 796 {'extend_existing': True, 'mysql_engine': 'InnoDB',
797 797 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
798 798 )
799 799 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
800 800 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
801 801 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
802 802 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
803 803
804 804 user = relationship('User')
805 805 user_group = relationship('UserGroup')
806 806 permission = relationship('Permission')
807 807
808 808 def __unicode__(self):
809 809 return u'<%s => %s >' % (self.user, self.user_group)
810 810
811 811
812 812 class UserToPerm(Base, BaseModel):
813 813 __tablename__ = 'user_to_perm'
814 814 __table_args__ = (
815 815 UniqueConstraint('user_id', 'permission_id'),
816 816 {'extend_existing': True, 'mysql_engine': 'InnoDB',
817 817 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
818 818 )
819 819 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
820 820 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
821 821 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
822 822
823 823 user = relationship('User')
824 824 permission = relationship('Permission', lazy='joined')
825 825
826 826 def __unicode__(self):
827 827 return u'<%s => %s >' % (self.user, self.permission)
828 828
829 829
830 830 class UserGroupRepoToPerm(Base, BaseModel):
831 831 __tablename__ = 'users_group_repo_to_perm'
832 832 __table_args__ = (
833 833 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
834 834 {'extend_existing': True, 'mysql_engine': 'InnoDB',
835 835 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
836 836 )
837 837 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
838 838 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
839 839 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
840 840 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
841 841
842 842 users_group = relationship('UserGroup')
843 843 permission = relationship('Permission')
844 844 repository = relationship('Repository')
845 845
846 846 def __unicode__(self):
847 847 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
848 848
849 849
850 850 class UserGroupUserGroupToPerm(Base, BaseModel):
851 851 __tablename__ = 'user_group_user_group_to_perm'
852 852 __table_args__ = (
853 853 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
854 854 CheckConstraint('target_user_group_id != user_group_id'),
855 855 {'extend_existing': True, 'mysql_engine': 'InnoDB',
856 856 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
857 857 )
858 858 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 859 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_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 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
862 862
863 863 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
864 864 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
865 865 permission = relationship('Permission')
866 866
867 867 def __unicode__(self):
868 868 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
869 869
870 870
871 871 class UserGroupToPerm(Base, BaseModel):
872 872 __tablename__ = 'users_group_to_perm'
873 873 __table_args__ = (
874 874 UniqueConstraint('users_group_id', 'permission_id',),
875 875 {'extend_existing': True, 'mysql_engine': 'InnoDB',
876 876 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
877 877 )
878 878 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
879 879 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
880 880 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
881 881
882 882 users_group = relationship('UserGroup')
883 883 permission = relationship('Permission')
884 884
885 885
886 886 class UserRepoGroupToPerm(Base, BaseModel):
887 887 __tablename__ = 'user_repo_group_to_perm'
888 888 __table_args__ = (
889 889 UniqueConstraint('user_id', 'group_id', 'permission_id'),
890 890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
891 891 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
892 892 )
893 893
894 894 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
895 895 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
896 896 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
897 897 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
898 898
899 899 user = relationship('User')
900 900 group = relationship('RepoGroup')
901 901 permission = relationship('Permission')
902 902
903 903
904 904 class UserGroupRepoGroupToPerm(Base, BaseModel):
905 905 __tablename__ = 'users_group_repo_group_to_perm'
906 906 __table_args__ = (
907 907 UniqueConstraint('users_group_id', 'group_id'),
908 908 {'extend_existing': True, 'mysql_engine': 'InnoDB',
909 909 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
910 910 )
911 911
912 912 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)
913 913 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
914 914 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
915 915 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
916 916
917 917 users_group = relationship('UserGroup')
918 918 permission = relationship('Permission')
919 919 group = relationship('RepoGroup')
920 920
921 921
922 922 class Statistics(Base, BaseModel):
923 923 __tablename__ = 'statistics'
924 924 __table_args__ = (
925 925 UniqueConstraint('repository_id'),
926 926 {'extend_existing': True, 'mysql_engine': 'InnoDB',
927 927 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
928 928 )
929 929 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
930 930 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
931 931 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
932 932 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
933 933 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
934 934 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
935 935
936 936 repository = relationship('Repository', single_parent=True)
937 937
938 938
939 939 class UserFollowing(Base, BaseModel):
940 940 __tablename__ = 'user_followings'
941 941 __table_args__ = (
942 942 UniqueConstraint('user_id', 'follows_repository_id'),
943 943 UniqueConstraint('user_id', 'follows_user_id'),
944 944 {'extend_existing': True, 'mysql_engine': 'InnoDB',
945 945 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
946 946 )
947 947
948 948 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
950 950 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
951 951 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
952 952 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
953 953
954 954 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
955 955
956 956 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
957 957 follows_repository = relationship('Repository', order_by='Repository.repo_name')
958 958
959 959
960 960 class CacheInvalidation(Base, BaseModel):
961 961 __tablename__ = 'cache_invalidation'
962 962 __table_args__ = (
963 963 UniqueConstraint('cache_key'),
964 964 Index('key_idx', 'cache_key'),
965 965 {'extend_existing': True, 'mysql_engine': 'InnoDB',
966 966 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
967 967 )
968 968 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 969 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
970 970 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
971 971 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
972 972
973 973 def __init__(self, cache_key, cache_args=''):
974 974 self.cache_key = cache_key
975 975 self.cache_args = cache_args
976 976 self.cache_active = False
977 977
978 978
979 979 class ChangesetComment(Base, BaseModel):
980 980 __tablename__ = 'changeset_comments'
981 981 __table_args__ = (
982 982 Index('cc_revision_idx', 'revision'),
983 983 {'extend_existing': True, 'mysql_engine': 'InnoDB',
984 984 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
985 985 )
986 986 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
987 987 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
988 988 revision = Column('revision', String(40), nullable=True)
989 989 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
990 990 line_no = Column('line_no', Unicode(10), nullable=True)
991 991 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
992 992 f_path = Column('f_path', Unicode(1000), nullable=True)
993 993 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
994 994 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
995 995 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
996 996 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
997 997
998 998 author = relationship('User', lazy='joined')
999 999 repo = relationship('Repository')
1000 1000 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1001 1001 pull_request = relationship('PullRequest', lazy='joined')
1002 1002
1003 1003
1004 1004 class ChangesetStatus(Base, BaseModel):
1005 1005 __tablename__ = 'changeset_statuses'
1006 1006 __table_args__ = (
1007 1007 Index('cs_revision_idx', 'revision'),
1008 1008 Index('cs_version_idx', 'version'),
1009 1009 UniqueConstraint('repo_id', 'revision', 'version'),
1010 1010 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1011 1011 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1012 1012 )
1013 1013 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1014 1014 STATUS_APPROVED = 'approved'
1015 1015 STATUS_REJECTED = 'rejected'
1016 1016 STATUS_UNDER_REVIEW = 'under_review'
1017 1017
1018 1018 STATUSES = [
1019 1019 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1020 1020 (STATUS_APPROVED, _("Approved")),
1021 1021 (STATUS_REJECTED, _("Rejected")),
1022 1022 (STATUS_UNDER_REVIEW, _("Under Review")),
1023 1023 ]
1024 1024
1025 1025 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1026 1026 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1027 1027 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1028 1028 revision = Column('revision', String(40), nullable=False)
1029 1029 status = Column('status', String(128), nullable=False, default=DEFAULT)
1030 1030 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1031 1031 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1032 1032 version = Column('version', Integer(), nullable=False, default=0)
1033 1033 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1034 1034
1035 1035 author = relationship('User', lazy='joined')
1036 1036 repo = relationship('Repository')
1037 1037 comment = relationship('ChangesetComment', lazy='joined')
1038 1038 pull_request = relationship('PullRequest', lazy='joined')
1039 1039
1040 1040
1041 1041
1042 1042 class PullRequest(Base, BaseModel):
1043 1043 __tablename__ = 'pull_requests'
1044 1044 __table_args__ = (
1045 1045 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1046 1046 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1047 1047 )
1048 1048
1049 1049 STATUS_NEW = u'new'
1050 1050 STATUS_OPEN = u'open'
1051 1051 STATUS_CLOSED = u'closed'
1052 1052
1053 1053 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1054 1054 title = Column('title', Unicode(256), nullable=True)
1055 1055 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1056 1056 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1057 1057 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1058 1058 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1059 1059 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1060 1060 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1061 1061 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1062 1062 org_ref = Column('org_ref', Unicode(256), nullable=False)
1063 1063 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1064 1064 other_ref = Column('other_ref', Unicode(256), nullable=False)
1065 1065
1066 1066 author = relationship('User', lazy='joined')
1067 1067 reviewers = relationship('PullRequestReviewers',
1068 1068 cascade="all, delete, delete-orphan")
1069 1069 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1070 1070 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1071 1071 statuses = relationship('ChangesetStatus')
1072 1072 comments = relationship('ChangesetComment',
1073 1073 cascade="all, delete, delete-orphan")
1074 1074
1075 1075
1076 1076 class PullRequestReviewers(Base, BaseModel):
1077 1077 __tablename__ = 'pull_request_reviewers'
1078 1078 __table_args__ = (
1079 1079 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1080 1080 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1081 1081 )
1082 1082
1083 1083 def __init__(self, user=None, pull_request=None):
1084 1084 self.user = user
1085 1085 self.pull_request = pull_request
1086 1086
1087 1087 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1088 1088 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1089 1089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1090 1090
1091 1091 user = relationship('User')
1092 1092 pull_request = relationship('PullRequest')
1093 1093
1094 1094
1095 1095 class Notification(Base, BaseModel):
1096 1096 __tablename__ = 'notifications'
1097 1097 __table_args__ = (
1098 1098 Index('notification_type_idx', 'type'),
1099 1099 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1100 1100 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1101 1101 )
1102 1102
1103 1103 TYPE_CHANGESET_COMMENT = u'cs_comment'
1104 1104 TYPE_MESSAGE = u'message'
1105 1105 TYPE_MENTION = u'mention'
1106 1106 TYPE_REGISTRATION = u'registration'
1107 1107 TYPE_PULL_REQUEST = u'pull_request'
1108 1108 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1109 1109
1110 1110 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1111 1111 subject = Column('subject', Unicode(512), nullable=True)
1112 1112 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1113 1113 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1114 1114 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1115 1115 type_ = Column('type', Unicode(256))
1116 1116
1117 1117 created_by_user = relationship('User')
1118 1118 notifications_to_users = relationship('UserNotification', lazy='joined',
1119 1119 cascade="all, delete, delete-orphan")
1120 1120
1121 1121
1122 1122 class UserNotification(Base, BaseModel):
1123 1123 __tablename__ = 'user_to_notification'
1124 1124 __table_args__ = (
1125 1125 UniqueConstraint('user_id', 'notification_id'),
1126 1126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1127 1127 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1128 1128 )
1129 1129 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1130 1130 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1131 1131 read = Column('read', Boolean, default=False)
1132 1132 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1133 1133
1134 1134 user = relationship('User', lazy="joined")
1135 1135 notification = relationship('Notification', lazy="joined",
1136 1136 order_by=lambda: Notification.created_on.desc(),)
1137 1137
1138 1138
1139 1139 class Gist(Base, BaseModel):
1140 1140 __tablename__ = 'gists'
1141 1141 __table_args__ = (
1142 1142 Index('g_gist_access_id_idx', 'gist_access_id'),
1143 1143 Index('g_created_on_idx', 'created_on'),
1144 1144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1145 1145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1146 1146 )
1147 1147 GIST_PUBLIC = u'public'
1148 1148 GIST_PRIVATE = u'private'
1149 1149
1150 1150 gist_id = Column('gist_id', Integer(), primary_key=True)
1151 1151 gist_access_id = Column('gist_access_id', Unicode(250))
1152 1152 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1153 1153 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1154 1154 gist_expires = Column('gist_expires', Float(53), nullable=False)
1155 1155 gist_type = Column('gist_type', Unicode(128), nullable=False)
1156 1156 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1157 1157 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1158 1158
1159 1159 owner = relationship('User')
1160 1160
1161 1161
1162 1162 class DbMigrateVersion(Base, BaseModel):
1163 1163 __tablename__ = 'db_migrate_version'
1164 1164 __table_args__ = (
1165 1165 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1166 1166 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1167 1167 )
1168 1168 repository_id = Column('repository_id', String(250), primary_key=True)
1169 1169 repository_path = Column('repository_path', Text)
1170 1170 version = Column('version', Integer)
@@ -1,1187 +1,1187 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import time
22 22 import logging
23 23 import datetime
24 24 import traceback
25 25 import hashlib
26 26 import collections
27 27 import functools
28 28
29 29 from sqlalchemy import *
30 30 from sqlalchemy.ext.hybrid import hybrid_property
31 31 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
32 32 from sqlalchemy.exc import DatabaseError
33 33 from beaker.cache import cache_region, region_invalidate
34 34 from webob.exc import HTTPNotFound
35 35
36 36 from rhodecode.translation import _
37 37
38 38 from rhodecode.lib.vcs import get_backend
39 39 from rhodecode.lib.vcs.utils.helpers import get_scm
40 40 from rhodecode.lib.vcs.exceptions import VCSError
41 41 from zope.cachedescriptors.property import Lazy as LazyProperty
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 43
44 44 from rhodecode.lib.utils2 import str2bool, safe_str, get_commit_safe, \
45 45 remove_prefix, time_to_datetime, aslist, Optional, safe_int
46 46 from rhodecode.lib.ext_json import json
47 47 from rhodecode.lib.caching_query import FromCache
48 48
49 49 from rhodecode.model.meta import Base, Session
50 50
51 51 URL_SEP = '/'
52 52 log = logging.getLogger(__name__)
53 53
54 54 #==============================================================================
55 55 # BASE CLASSES
56 56 #==============================================================================
57 57
58 58 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 59
60 60
61 61 class BaseModel(object):
62 62 """
63 63 Base Model for all classes
64 64 """
65 65
66 66 @classmethod
67 67 def _get_keys(cls):
68 68 """return column names for this model """
69 69 return class_mapper(cls).c.keys()
70 70
71 71 def get_dict(self):
72 72 """
73 73 return dict with keys and values corresponding
74 74 to this model data """
75 75
76 76 d = {}
77 77 for k in self._get_keys():
78 78 d[k] = getattr(self, k)
79 79
80 80 # also use __json__() if present to get additional fields
81 81 _json_attr = getattr(self, '__json__', None)
82 82 if _json_attr:
83 83 # update with attributes from __json__
84 84 if callable(_json_attr):
85 85 _json_attr = _json_attr()
86 86 for k, val in _json_attr.items():
87 87 d[k] = val
88 88 return d
89 89
90 90 def get_appstruct(self):
91 91 """return list with keys and values tupples corresponding
92 92 to this model data """
93 93
94 94 l = []
95 95 for k in self._get_keys():
96 96 l.append((k, getattr(self, k),))
97 97 return l
98 98
99 99 def populate_obj(self, populate_dict):
100 100 """populate model with data from given populate_dict"""
101 101
102 102 for k in self._get_keys():
103 103 if k in populate_dict:
104 104 setattr(self, k, populate_dict[k])
105 105
106 106 @classmethod
107 107 def query(cls):
108 108 return Session().query(cls)
109 109
110 110 @classmethod
111 111 def get(cls, id_):
112 112 if id_:
113 113 return cls.query().get(id_)
114 114
115 115 @classmethod
116 116 def get_or_404(cls, id_):
117 117 try:
118 118 id_ = int(id_)
119 119 except (TypeError, ValueError):
120 120 raise HTTPNotFound
121 121
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 # deprecated and left for backward compatibility
130 130 return cls.get_all()
131 131
132 132 @classmethod
133 133 def get_all(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 try:
145 145 return safe_str(self.__unicode__())
146 146 except UnicodeDecodeError:
147 147 pass
148 148 return '<DB:%s>' % (self.__class__.__name__)
149 149
150 150
151 151 class RhodeCodeSetting(Base, BaseModel):
152 152 SETTINGS_TYPES = {
153 153 'str': safe_str,
154 154 'int': safe_int,
155 155 'unicode': safe_str,
156 156 'bool': str2bool,
157 157 'list': functools.partial(aslist, sep=',')
158 158 }
159 159 __tablename__ = 'rhodecode_settings'
160 160 __table_args__ = (
161 161 UniqueConstraint('app_settings_name'),
162 162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
163 163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
164 164 )
165 165 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
166 166 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
167 167 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
168 168 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
169 169
170 170 def __init__(self, key='', val='', type='unicode'):
171 171 self.app_settings_name = key
172 172 self.app_settings_value = val
173 173 self.app_settings_type = type
174 174
175 175 @validates('_app_settings_value')
176 176 def validate_settings_value(self, key, val):
177 177 assert type(val) == str
178 178 return val
179 179
180 180 @hybrid_property
181 181 def app_settings_value(self):
182 182 v = self._app_settings_value
183 183 _type = self.app_settings_type
184 184 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
185 185 return converter(v)
186 186
187 187 @app_settings_value.setter
188 188 def app_settings_value(self, val):
189 189 """
190 190 Setter that will always make sure we use unicode in app_settings_value
191 191
192 192 :param val:
193 193 """
194 194 self._app_settings_value = safe_str(val)
195 195
196 196 @hybrid_property
197 197 def app_settings_type(self):
198 198 return self._app_settings_type
199 199
200 200 @app_settings_type.setter
201 201 def app_settings_type(self, val):
202 202 if val not in self.SETTINGS_TYPES:
203 203 raise Exception('type must be one of %s got %s'
204 204 % (self.SETTINGS_TYPES.keys(), val))
205 205 self._app_settings_type = val
206 206
207 207 def __unicode__(self):
208 208 return u"<%s('%s:%s[%s]')>" % (
209 209 self.__class__.__name__,
210 210 self.app_settings_name, self.app_settings_value, self.app_settings_type
211 211 )
212 212
213 213
214 214 class RhodeCodeUi(Base, BaseModel):
215 215 __tablename__ = 'rhodecode_ui'
216 216 __table_args__ = (
217 217 UniqueConstraint('ui_key'),
218 218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
219 219 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
220 220 )
221 221
222 222 HOOK_REPO_SIZE = 'changegroup.repo_size'
223 223 HOOK_PUSH = 'changegroup.push_logger'
224 224 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
225 225 HOOK_PULL = 'outgoing.pull_logger'
226 226 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
227 227
228 228 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
229 229 ui_section = Column("ui_section", String(255), nullable=True, unique=None, default=None)
230 230 ui_key = Column("ui_key", String(255), nullable=True, unique=None, default=None)
231 231 ui_value = Column("ui_value", String(255), nullable=True, unique=None, default=None)
232 232 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
233 233
234 234
235 235
236 236 class User(Base, BaseModel):
237 237 __tablename__ = 'users'
238 238 __table_args__ = (
239 239 UniqueConstraint('username'), UniqueConstraint('email'),
240 240 Index('u_username_idx', 'username'),
241 241 Index('u_email_idx', 'email'),
242 242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
243 243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
244 244 )
245 245 DEFAULT_USER = 'default'
246 246
247 247 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
248 248 username = Column("username", String(255), nullable=True, unique=None, default=None)
249 249 password = Column("password", String(255), nullable=True, unique=None, default=None)
250 250 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
251 251 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
252 252 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
253 253 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
254 254 _email = Column("email", String(255), nullable=True, unique=None, default=None)
255 255 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
256 256 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
257 257 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
258 258 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
259 259 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
260 260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
261 261
262 262 user_log = relationship('UserLog')
263 263 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
264 264
265 265 repositories = relationship('Repository')
266 266 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
267 267 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
268 268
269 269 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
270 270 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
271 271
272 272 group_member = relationship('UserGroupMember', cascade='all')
273 273
274 274 notifications = relationship('UserNotification', cascade='all')
275 275 # notifications assigned to this user
276 276 user_created_notifications = relationship('Notification', cascade='all')
277 277 # comments created by this user
278 278 user_comments = relationship('ChangesetComment', cascade='all')
279 279 user_emails = relationship('UserEmailMap', cascade='all')
280 280
281 281 @hybrid_property
282 282 def email(self):
283 283 return self._email
284 284
285 285 @email.setter
286 286 def email(self, val):
287 287 self._email = val.lower() if val else None
288 288
289 289 @property
290 290 def firstname(self):
291 291 # alias for future
292 292 return self.name
293 293
294 294 @property
295 295 def username_and_name(self):
296 296 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
297 297
298 298 @property
299 299 def full_name(self):
300 300 return '%s %s' % (self.firstname, self.lastname)
301 301
302 302 @property
303 303 def full_contact(self):
304 304 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
305 305
306 306 @property
307 307 def short_contact(self):
308 308 return '%s %s' % (self.firstname, self.lastname)
309 309
310 310 @property
311 311 def is_admin(self):
312 312 return self.admin
313 313
314 314 @classmethod
315 315 def get_by_username(cls, username, case_insensitive=False, cache=False):
316 316 if case_insensitive:
317 317 q = cls.query().filter(cls.username.ilike(username))
318 318 else:
319 319 q = cls.query().filter(cls.username == username)
320 320
321 321 if cache:
322 322 q = q.options(FromCache(
323 323 "sql_cache_short",
324 324 "get_user_%s" % _hash_key(username)
325 325 )
326 326 )
327 327 return q.scalar()
328 328
329 329 @classmethod
330 330 def get_by_auth_token(cls, auth_token, cache=False):
331 331 q = cls.query().filter(cls.api_key == auth_token)
332 332
333 333 if cache:
334 334 q = q.options(FromCache("sql_cache_short",
335 335 "get_auth_token_%s" % auth_token))
336 336 return q.scalar()
337 337
338 338 @classmethod
339 339 def get_by_email(cls, email, case_insensitive=False, cache=False):
340 340 if case_insensitive:
341 341 q = cls.query().filter(cls.email.ilike(email))
342 342 else:
343 343 q = cls.query().filter(cls.email == email)
344 344
345 345 if cache:
346 346 q = q.options(FromCache("sql_cache_short",
347 347 "get_email_key_%s" % email))
348 348
349 349 ret = q.scalar()
350 350 if ret is None:
351 351 q = UserEmailMap.query()
352 352 # try fetching in alternate email map
353 353 if case_insensitive:
354 354 q = q.filter(UserEmailMap.email.ilike(email))
355 355 else:
356 356 q = q.filter(UserEmailMap.email == email)
357 357 q = q.options(joinedload(UserEmailMap.user))
358 358 if cache:
359 359 q = q.options(FromCache("sql_cache_short",
360 360 "get_email_map_key_%s" % email))
361 361 ret = getattr(q.scalar(), 'user', None)
362 362
363 363 return ret
364 364
365 365 @classmethod
366 366 def get_first_admin(cls):
367 367 user = User.query().filter(User.admin == True).first()
368 368 if user is None:
369 369 raise Exception('Missing administrative account!')
370 370 return user
371 371
372 372 @classmethod
373 373 def get_default_user(cls, cache=False):
374 374 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
375 375 if user is None:
376 376 raise Exception('Missing default account!')
377 377 return user
378 378
379 379
380 380
381 381
382 382 class UserEmailMap(Base, BaseModel):
383 383 __tablename__ = 'user_email_map'
384 384 __table_args__ = (
385 385 Index('uem_email_idx', 'email'),
386 386 UniqueConstraint('email'),
387 387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
388 388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
389 389 )
390 __mapper_args__ = {}
390
391 391
392 392 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
393 393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
394 394 _email = Column("email", String(255), nullable=True, unique=False, default=None)
395 395 user = relationship('User', lazy='joined')
396 396
397 397 @validates('_email')
398 398 def validate_email(self, key, email):
399 399 # check if this email is not main one
400 400 main_email = Session().query(User).filter(User.email == email).scalar()
401 401 if main_email is not None:
402 402 raise AttributeError('email %s is present is user table' % email)
403 403 return email
404 404
405 405 @hybrid_property
406 406 def email(self):
407 407 return self._email
408 408
409 409 @email.setter
410 410 def email(self, val):
411 411 self._email = val.lower() if val else None
412 412
413 413
414 414 class UserIpMap(Base, BaseModel):
415 415 __tablename__ = 'user_ip_map'
416 416 __table_args__ = (
417 417 UniqueConstraint('user_id', 'ip_addr'),
418 418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
419 419 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
420 420 )
421 __mapper_args__ = {}
421
422 422
423 423 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
424 424 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
425 425 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
426 426 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
427 427 user = relationship('User', lazy='joined')
428 428
429 429
430 430 class UserLog(Base, BaseModel):
431 431 __tablename__ = 'user_logs'
432 432 __table_args__ = (
433 433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
434 434 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
435 435 )
436 436 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
437 437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
438 438 username = Column("username", String(255), nullable=True, unique=None, default=None)
439 439 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
440 440 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
441 441 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
442 442 action = Column("action", String(1200000), nullable=True, unique=None, default=None)
443 443 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
444 444
445 445 def __unicode__(self):
446 446 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
447 447 self.repository_name,
448 448 self.action)
449 449
450 450 user = relationship('User')
451 451 repository = relationship('Repository', cascade='')
452 452
453 453
454 454 class UserGroup(Base, BaseModel):
455 455 __tablename__ = 'users_groups'
456 456 __table_args__ = (
457 457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
458 458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
459 459 )
460 460
461 461 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
462 462 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
463 463 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
464 464 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
465 465 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
466 466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
467 467 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
468 468
469 469 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
470 470 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
471 471 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
472 472 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
473 473 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
474 474 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
475 475
476 476 user = relationship('User')
477 477
478 478 def __unicode__(self):
479 479 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
480 480 self.users_group_id,
481 481 self.users_group_name)
482 482
483 483 @classmethod
484 484 def get_by_group_name(cls, group_name, cache=False,
485 485 case_insensitive=False):
486 486 if case_insensitive:
487 487 q = cls.query().filter(cls.users_group_name.ilike(group_name))
488 488 else:
489 489 q = cls.query().filter(cls.users_group_name == group_name)
490 490 if cache:
491 491 q = q.options(FromCache(
492 492 "sql_cache_short",
493 493 "get_user_%s" % _hash_key(group_name)
494 494 )
495 495 )
496 496 return q.scalar()
497 497
498 498 @classmethod
499 499 def get(cls, user_group_id, cache=False):
500 500 user_group = cls.query()
501 501 if cache:
502 502 user_group = user_group.options(FromCache("sql_cache_short",
503 503 "get_users_group_%s" % user_group_id))
504 504 return user_group.get(user_group_id)
505 505
506 506
507 507 class UserGroupMember(Base, BaseModel):
508 508 __tablename__ = 'users_groups_members'
509 509 __table_args__ = (
510 510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
511 511 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
512 512 )
513 513
514 514 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
515 515 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
516 516 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
517 517
518 518 user = relationship('User', lazy='joined')
519 519 users_group = relationship('UserGroup')
520 520
521 521 def __init__(self, gr_id='', u_id=''):
522 522 self.users_group_id = gr_id
523 523 self.user_id = u_id
524 524
525 525
526 526 class RepositoryField(Base, BaseModel):
527 527 __tablename__ = 'repositories_fields'
528 528 __table_args__ = (
529 529 UniqueConstraint('repository_id', 'field_key'), # no-multi field
530 530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 531 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
532 532 )
533 533 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
534 534
535 535 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 536 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
537 537 field_key = Column("field_key", String(250))
538 538 field_label = Column("field_label", String(1024), nullable=False)
539 539 field_value = Column("field_value", String(10000), nullable=False)
540 540 field_desc = Column("field_desc", String(1024), nullable=False)
541 541 field_type = Column("field_type", String(256), nullable=False, unique=None)
542 542 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
543 543
544 544 repository = relationship('Repository')
545 545
546 546 @classmethod
547 547 def get_by_key_name(cls, key, repo):
548 548 row = cls.query()\
549 549 .filter(cls.repository == repo)\
550 550 .filter(cls.field_key == key).scalar()
551 551 return row
552 552
553 553
554 554 class Repository(Base, BaseModel):
555 555 __tablename__ = 'repositories'
556 556 __table_args__ = (
557 557 UniqueConstraint('repo_name'),
558 558 Index('r_repo_name_idx', 'repo_name'),
559 559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
560 560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
561 561 )
562 562
563 563 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
564 564 repo_name = Column("repo_name", String(255), nullable=False, unique=True, default=None)
565 565 clone_uri = Column("clone_uri", String(255), nullable=True, unique=False, default=None)
566 566 repo_type = Column("repo_type", String(255), nullable=False, unique=False, default=None)
567 567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
568 568 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
569 569 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
570 570 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
571 571 description = Column("description", String(10000), nullable=True, unique=None, default=None)
572 572 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
573 573 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
574 574 _landing_revision = Column("landing_revision", String(255), nullable=False, unique=False, default=None)
575 575 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
576 576 _locked = Column("locked", String(255), nullable=True, unique=False, default=None)
577 577 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
578 578
579 579 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
580 580 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
581 581
582 582 user = relationship('User')
583 583 fork = relationship('Repository', remote_side=repo_id)
584 584 group = relationship('RepoGroup')
585 585 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
586 586 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
587 587 stats = relationship('Statistics', cascade='all', uselist=False)
588 588
589 589 followers = relationship('UserFollowing',
590 590 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
591 591 cascade='all')
592 592 extra_fields = relationship('RepositoryField',
593 593 cascade="all, delete, delete-orphan")
594 594
595 595 logs = relationship('UserLog')
596 596 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
597 597
598 598 pull_requests_org = relationship('PullRequest',
599 599 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
600 600 cascade="all, delete, delete-orphan")
601 601
602 602 pull_requests_other = relationship('PullRequest',
603 603 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
604 604 cascade="all, delete, delete-orphan")
605 605
606 606 #NOTE: marcink, DO NOT REMOVE THOSE
607 607 @hybrid_property
608 608 def landing_rev(self):
609 609 # always should return [rev_type, rev]
610 610 if self._landing_revision:
611 611 _rev_info = self._landing_revision.split(':')
612 612 if len(_rev_info) < 2:
613 613 _rev_info.insert(0, 'rev')
614 614 return [_rev_info[0], _rev_info[1]]
615 615 return [None, None]
616 616
617 617 @landing_rev.setter
618 618 def landing_rev(self, val):
619 619 if ':' not in val:
620 620 raise ValueError('value must be delimited with `:` and consist '
621 621 'of <rev_type>:<rev>, got %s instead' % val)
622 622 self._landing_revision = val
623 623
624 624 def __unicode__(self):
625 625 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
626 626 safe_str(self.repo_name))
627 627
628 628 @classmethod
629 629 def get_by_repo_name(cls, repo_name):
630 630 q = Session().query(cls).filter(cls.repo_name == repo_name)
631 631 q = q.options(joinedload(Repository.fork))\
632 632 .options(joinedload(Repository.user))\
633 633 .options(joinedload(Repository.group))
634 634 return q.scalar()
635 635
636 636
637 637 class RepoGroup(Base, BaseModel):
638 638 __tablename__ = 'groups'
639 639 __table_args__ = (
640 640 UniqueConstraint('group_name', 'group_parent_id'),
641 641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
642 642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
643 643 )
644 __mapper_args__ = {'order_by': 'group_name'}
644
645 645
646 646 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
647 647 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
648 648 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
649 649 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
650 650 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
651 651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
652 652 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
653 653
654 654 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
655 655 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
656 656 parent_group = relationship('RepoGroup', remote_side=group_id)
657 657 user = relationship('User')
658 658
659 659 def __init__(self, group_name='', parent_group=None):
660 660 self.group_name = group_name
661 661 self.parent_group = parent_group
662 662
663 663 def __unicode__(self):
664 664 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
665 665 self.group_name)
666 666
667 667 @classmethod
668 668 def url_sep(cls):
669 669 return URL_SEP
670 670
671 671 @classmethod
672 672 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
673 673 if case_insensitive:
674 674 gr = cls.query()\
675 675 .filter(cls.group_name.ilike(group_name))
676 676 else:
677 677 gr = cls.query()\
678 678 .filter(cls.group_name == group_name)
679 679 if cache:
680 680 gr = gr.options(FromCache(
681 681 "sql_cache_short",
682 682 "get_group_%s" % _hash_key(group_name)
683 683 )
684 684 )
685 685 return gr.scalar()
686 686
687 687
688 688 class Permission(Base, BaseModel):
689 689 __tablename__ = 'permissions'
690 690 __table_args__ = (
691 691 Index('p_perm_name_idx', 'permission_name'),
692 692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
693 693 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
694 694 )
695 695 PERMS = [
696 696 ('hg.admin', _('RhodeCode Administrator')),
697 697
698 698 ('repository.none', _('Repository no access')),
699 699 ('repository.read', _('Repository read access')),
700 700 ('repository.write', _('Repository write access')),
701 701 ('repository.admin', _('Repository admin access')),
702 702
703 703 ('group.none', _('Repository group no access')),
704 704 ('group.read', _('Repository group read access')),
705 705 ('group.write', _('Repository group write access')),
706 706 ('group.admin', _('Repository group admin access')),
707 707
708 708 ('usergroup.none', _('User group no access')),
709 709 ('usergroup.read', _('User group read access')),
710 710 ('usergroup.write', _('User group write access')),
711 711 ('usergroup.admin', _('User group admin access')),
712 712
713 713 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
714 714 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
715 715
716 716 ('hg.usergroup.create.false', _('User Group creation disabled')),
717 717 ('hg.usergroup.create.true', _('User Group creation enabled')),
718 718
719 719 ('hg.create.none', _('Repository creation disabled')),
720 720 ('hg.create.repository', _('Repository creation enabled')),
721 721
722 722 ('hg.fork.none', _('Repository forking disabled')),
723 723 ('hg.fork.repository', _('Repository forking enabled')),
724 724
725 725 ('hg.register.none', _('Registration disabled')),
726 726 ('hg.register.manual_activate', _('User Registration with manual account activation')),
727 727 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
728 728
729 729 ('hg.extern_activate.manual', _('Manual activation of external account')),
730 730 ('hg.extern_activate.auto', _('Automatic activation of external account')),
731 731
732 732 ]
733 733
734 734 #definition of system default permissions for DEFAULT user
735 735 DEFAULT_USER_PERMISSIONS = [
736 736 'repository.read',
737 737 'group.read',
738 738 'usergroup.read',
739 739 'hg.create.repository',
740 740 'hg.fork.repository',
741 741 'hg.register.manual_activate',
742 742 'hg.extern_activate.auto',
743 743 ]
744 744
745 745 # defines which permissions are more important higher the more important
746 746 # Weight defines which permissions are more important.
747 747 # The higher number the more important.
748 748 PERM_WEIGHTS = {
749 749 'repository.none': 0,
750 750 'repository.read': 1,
751 751 'repository.write': 3,
752 752 'repository.admin': 4,
753 753
754 754 'group.none': 0,
755 755 'group.read': 1,
756 756 'group.write': 3,
757 757 'group.admin': 4,
758 758
759 759 'usergroup.none': 0,
760 760 'usergroup.read': 1,
761 761 'usergroup.write': 3,
762 762 'usergroup.admin': 4,
763 763 'hg.repogroup.create.false': 0,
764 764 'hg.repogroup.create.true': 1,
765 765
766 766 'hg.usergroup.create.false': 0,
767 767 'hg.usergroup.create.true': 1,
768 768
769 769 'hg.fork.none': 0,
770 770 'hg.fork.repository': 1,
771 771 'hg.create.none': 0,
772 772 'hg.create.repository': 1
773 773 }
774 774
775 775 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
776 776 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
777 777 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
778 778
779 779 def __unicode__(self):
780 780 return u"<%s('%s:%s')>" % (
781 781 self.__class__.__name__, self.permission_id, self.permission_name
782 782 )
783 783
784 784 @classmethod
785 785 def get_by_key(cls, key):
786 786 return cls.query().filter(cls.permission_name == key).scalar()
787 787
788 788
789 789 class UserRepoToPerm(Base, BaseModel):
790 790 __tablename__ = 'repo_to_perm'
791 791 __table_args__ = (
792 792 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
793 793 {'extend_existing': True, 'mysql_engine': 'InnoDB',
794 794 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
795 795 )
796 796 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
797 797 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
798 798 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
799 799 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
800 800
801 801 user = relationship('User')
802 802 repository = relationship('Repository')
803 803 permission = relationship('Permission')
804 804
805 805 def __unicode__(self):
806 806 return u'<%s => %s >' % (self.user, self.repository)
807 807
808 808
809 809 class UserUserGroupToPerm(Base, BaseModel):
810 810 __tablename__ = 'user_user_group_to_perm'
811 811 __table_args__ = (
812 812 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
813 813 {'extend_existing': True, 'mysql_engine': 'InnoDB',
814 814 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
815 815 )
816 816 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
817 817 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
818 818 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
819 819 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
820 820
821 821 user = relationship('User')
822 822 user_group = relationship('UserGroup')
823 823 permission = relationship('Permission')
824 824
825 825 def __unicode__(self):
826 826 return u'<%s => %s >' % (self.user, self.user_group)
827 827
828 828
829 829 class UserToPerm(Base, BaseModel):
830 830 __tablename__ = 'user_to_perm'
831 831 __table_args__ = (
832 832 UniqueConstraint('user_id', 'permission_id'),
833 833 {'extend_existing': True, 'mysql_engine': 'InnoDB',
834 834 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
835 835 )
836 836 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
837 837 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
838 838 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
839 839
840 840 user = relationship('User')
841 841 permission = relationship('Permission', lazy='joined')
842 842
843 843 def __unicode__(self):
844 844 return u'<%s => %s >' % (self.user, self.permission)
845 845
846 846
847 847 class UserGroupRepoToPerm(Base, BaseModel):
848 848 __tablename__ = 'users_group_repo_to_perm'
849 849 __table_args__ = (
850 850 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
851 851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
852 852 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
853 853 )
854 854 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
855 855 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
856 856 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
857 857 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
858 858
859 859 users_group = relationship('UserGroup')
860 860 permission = relationship('Permission')
861 861 repository = relationship('Repository')
862 862
863 863 def __unicode__(self):
864 864 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
865 865
866 866
867 867 class UserGroupUserGroupToPerm(Base, BaseModel):
868 868 __tablename__ = 'user_group_user_group_to_perm'
869 869 __table_args__ = (
870 870 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
871 871 CheckConstraint('target_user_group_id != user_group_id'),
872 872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
873 873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
874 874 )
875 875 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
876 876 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
877 877 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
878 878 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
879 879
880 880 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
881 881 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
882 882 permission = relationship('Permission')
883 883
884 884 def __unicode__(self):
885 885 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
886 886
887 887
888 888 class UserGroupToPerm(Base, BaseModel):
889 889 __tablename__ = 'users_group_to_perm'
890 890 __table_args__ = (
891 891 UniqueConstraint('users_group_id', 'permission_id',),
892 892 {'extend_existing': True, 'mysql_engine': 'InnoDB',
893 893 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
894 894 )
895 895 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
896 896 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
897 897 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
898 898
899 899 users_group = relationship('UserGroup')
900 900 permission = relationship('Permission')
901 901
902 902
903 903 class UserRepoGroupToPerm(Base, BaseModel):
904 904 __tablename__ = 'user_repo_group_to_perm'
905 905 __table_args__ = (
906 906 UniqueConstraint('user_id', 'group_id', 'permission_id'),
907 907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
908 908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
909 909 )
910 910
911 911 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
912 912 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
913 913 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
914 914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
915 915
916 916 user = relationship('User')
917 917 group = relationship('RepoGroup')
918 918 permission = relationship('Permission')
919 919
920 920
921 921 class UserGroupRepoGroupToPerm(Base, BaseModel):
922 922 __tablename__ = 'users_group_repo_group_to_perm'
923 923 __table_args__ = (
924 924 UniqueConstraint('users_group_id', 'group_id'),
925 925 {'extend_existing': True, 'mysql_engine': 'InnoDB',
926 926 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
927 927 )
928 928
929 929 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)
930 930 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
931 931 group_id = Column("group_id", Integer(), ForeignKey('groups.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('UserGroup')
935 935 permission = relationship('Permission')
936 936 group = relationship('RepoGroup')
937 937
938 938
939 939 class Statistics(Base, BaseModel):
940 940 __tablename__ = 'statistics'
941 941 __table_args__ = (
942 942 UniqueConstraint('repository_id'),
943 943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
944 944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
945 945 )
946 946 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
947 947 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
948 948 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
949 949 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
950 950 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
951 951 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
952 952
953 953 repository = relationship('Repository', single_parent=True)
954 954
955 955
956 956 class UserFollowing(Base, BaseModel):
957 957 __tablename__ = 'user_followings'
958 958 __table_args__ = (
959 959 UniqueConstraint('user_id', 'follows_repository_id'),
960 960 UniqueConstraint('user_id', 'follows_user_id'),
961 961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
962 962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
963 963 )
964 964
965 965 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
966 966 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
967 967 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
968 968 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
969 969 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
970 970
971 971 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
972 972
973 973 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
974 974 follows_repository = relationship('Repository', order_by='Repository.repo_name')
975 975
976 976
977 977 class CacheInvalidation(Base, BaseModel):
978 978 __tablename__ = 'cache_invalidation'
979 979 __table_args__ = (
980 980 UniqueConstraint('cache_key'),
981 981 Index('key_idx', 'cache_key'),
982 982 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 983 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
984 984 )
985 985 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 986 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
987 987 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
988 988 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
989 989
990 990 def __init__(self, cache_key, cache_args=''):
991 991 self.cache_key = cache_key
992 992 self.cache_args = cache_args
993 993 self.cache_active = False
994 994
995 995
996 996 class ChangesetComment(Base, BaseModel):
997 997 __tablename__ = 'changeset_comments'
998 998 __table_args__ = (
999 999 Index('cc_revision_idx', 'revision'),
1000 1000 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1001 1001 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1002 1002 )
1003 1003 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1004 1004 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1005 1005 revision = Column('revision', String(40), nullable=True)
1006 1006 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1007 1007 line_no = Column('line_no', Unicode(10), nullable=True)
1008 1008 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1009 1009 f_path = Column('f_path', Unicode(1000), nullable=True)
1010 1010 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1011 1011 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
1012 1012 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1013 1013 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1014 1014
1015 1015 author = relationship('User', lazy='joined')
1016 1016 repo = relationship('Repository')
1017 1017 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1018 1018 pull_request = relationship('PullRequest', lazy='joined')
1019 1019
1020 1020
1021 1021 class ChangesetStatus(Base, BaseModel):
1022 1022 __tablename__ = 'changeset_statuses'
1023 1023 __table_args__ = (
1024 1024 Index('cs_revision_idx', 'revision'),
1025 1025 Index('cs_version_idx', 'version'),
1026 1026 UniqueConstraint('repo_id', 'revision', 'version'),
1027 1027 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1028 1028 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1029 1029 )
1030 1030 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1031 1031 STATUS_APPROVED = 'approved'
1032 1032 STATUS_REJECTED = 'rejected'
1033 1033 STATUS_UNDER_REVIEW = 'under_review'
1034 1034
1035 1035 STATUSES = [
1036 1036 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1037 1037 (STATUS_APPROVED, _("Approved")),
1038 1038 (STATUS_REJECTED, _("Rejected")),
1039 1039 (STATUS_UNDER_REVIEW, _("Under Review")),
1040 1040 ]
1041 1041
1042 1042 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1043 1043 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1044 1044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1045 1045 revision = Column('revision', String(40), nullable=False)
1046 1046 status = Column('status', String(128), nullable=False, default=DEFAULT)
1047 1047 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1048 1048 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1049 1049 version = Column('version', Integer(), nullable=False, default=0)
1050 1050 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1051 1051
1052 1052 author = relationship('User', lazy='joined')
1053 1053 repo = relationship('Repository')
1054 1054 comment = relationship('ChangesetComment', lazy='joined')
1055 1055 pull_request = relationship('PullRequest', lazy='joined')
1056 1056
1057 1057
1058 1058
1059 1059 class PullRequest(Base, BaseModel):
1060 1060 __tablename__ = 'pull_requests'
1061 1061 __table_args__ = (
1062 1062 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1063 1063 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1064 1064 )
1065 1065
1066 1066 STATUS_NEW = u'new'
1067 1067 STATUS_OPEN = u'open'
1068 1068 STATUS_CLOSED = u'closed'
1069 1069
1070 1070 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1071 1071 title = Column('title', Unicode(256), nullable=True)
1072 1072 description = Column('description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
1073 1073 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1074 1074 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1075 1075 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1076 1076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1077 1077 _revisions = Column('revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
1078 1078 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1079 1079 org_ref = Column('org_ref', Unicode(256), nullable=False)
1080 1080 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1081 1081 other_ref = Column('other_ref', Unicode(256), nullable=False)
1082 1082
1083 1083 author = relationship('User', lazy='joined')
1084 1084 reviewers = relationship('PullRequestReviewers',
1085 1085 cascade="all, delete, delete-orphan")
1086 1086 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1087 1087 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1088 1088 statuses = relationship('ChangesetStatus')
1089 1089 comments = relationship('ChangesetComment',
1090 1090 cascade="all, delete, delete-orphan")
1091 1091
1092 1092
1093 1093 class PullRequestReviewers(Base, BaseModel):
1094 1094 __tablename__ = 'pull_request_reviewers'
1095 1095 __table_args__ = (
1096 1096 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1097 1097 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1098 1098 )
1099 1099
1100 1100 def __init__(self, user=None, pull_request=None):
1101 1101 self.user = user
1102 1102 self.pull_request = pull_request
1103 1103
1104 1104 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1105 1105 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1106 1106 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1107 1107
1108 1108 user = relationship('User')
1109 1109 pull_request = relationship('PullRequest')
1110 1110
1111 1111
1112 1112 class Notification(Base, BaseModel):
1113 1113 __tablename__ = 'notifications'
1114 1114 __table_args__ = (
1115 1115 Index('notification_type_idx', 'type'),
1116 1116 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1117 1117 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1118 1118 )
1119 1119
1120 1120 TYPE_CHANGESET_COMMENT = u'cs_comment'
1121 1121 TYPE_MESSAGE = u'message'
1122 1122 TYPE_MENTION = u'mention'
1123 1123 TYPE_REGISTRATION = u'registration'
1124 1124 TYPE_PULL_REQUEST = u'pull_request'
1125 1125 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1126 1126
1127 1127 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1128 1128 subject = Column('subject', Unicode(512), nullable=True)
1129 1129 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
1130 1130 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1131 1131 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1132 1132 type_ = Column('type', Unicode(256))
1133 1133
1134 1134 created_by_user = relationship('User')
1135 1135 notifications_to_users = relationship('UserNotification', lazy='joined',
1136 1136 cascade="all, delete, delete-orphan")
1137 1137
1138 1138
1139 1139 class UserNotification(Base, BaseModel):
1140 1140 __tablename__ = 'user_to_notification'
1141 1141 __table_args__ = (
1142 1142 UniqueConstraint('user_id', 'notification_id'),
1143 1143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 1144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1145 1145 )
1146 1146 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1147 1147 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1148 1148 read = Column('read', Boolean, default=False)
1149 1149 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1150 1150
1151 1151 user = relationship('User', lazy="joined")
1152 1152 notification = relationship('Notification', lazy="joined",
1153 1153 order_by=lambda: Notification.created_on.desc(),)
1154 1154
1155 1155
1156 1156 class Gist(Base, BaseModel):
1157 1157 __tablename__ = 'gists'
1158 1158 __table_args__ = (
1159 1159 Index('g_gist_access_id_idx', 'gist_access_id'),
1160 1160 Index('g_created_on_idx', 'created_on'),
1161 1161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1162 1162 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1163 1163 )
1164 1164 GIST_PUBLIC = u'public'
1165 1165 GIST_PRIVATE = u'private'
1166 1166
1167 1167 gist_id = Column('gist_id', Integer(), primary_key=True)
1168 1168 gist_access_id = Column('gist_access_id', Unicode(250))
1169 1169 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1170 1170 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
1171 1171 gist_expires = Column('gist_expires', Float(53), nullable=False)
1172 1172 gist_type = Column('gist_type', Unicode(128), nullable=False)
1173 1173 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1174 1174 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1175 1175
1176 1176 owner = relationship('User')
1177 1177
1178 1178
1179 1179 class DbMigrateVersion(Base, BaseModel):
1180 1180 __tablename__ = 'db_migrate_version'
1181 1181 __table_args__ = (
1182 1182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1183 1183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1184 1184 )
1185 1185 repository_id = Column('repository_id', String(250), primary_key=True)
1186 1186 repository_path = Column('repository_path', Text)
1187 1187 version = Column('version', Integer)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now