comparison rhodecode/lib/dbmigrate/schema/db_2_0_0.py @ 4116:ffd45b185016 rhodecode-2.2.5-gpl

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