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