comparison rhodecode/lib/dbmigrate/schema/db_1_6_0.py @ 3710:ec65d8b2fccc beta

freeze schema for 1.6 + fixed migration from version 1.4.X+
author Marcin Kuzminski <marcin@python-works.com>
date Sun, 07 Apr 2013 23:51:53 +0200
parents b3cf4539d1bd
children 08cf7741e5e1
comparison
equal deleted inserted replaced
3707:93de03499097 3710:ec65d8b2fccc
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 next 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 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)
1030
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