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