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