comparison rhodecode/lib/dbmigrate/schema/db_1_5_0.py @ 3148:b31984972e95 beta

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