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