comparison rhodecode/lib/dbmigrate/schema/db_2_0_1.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 try:
151 return safe_str(self.__unicode__())
152 except UnicodeDecodeError:
153 pass
154 return '<DB:%s>' % (self.__class__.__name__)
155
156
157 class RhodeCodeSetting(Base, BaseModel):
158 SETTINGS_TYPES = {
159 'str': safe_str,
160 'int': safe_int,
161 'unicode': safe_unicode,
162 'bool': str2bool,
163 'list': functools.partial(aslist, sep=',')
164 }
165 __tablename__ = 'rhodecode_settings'
166 __table_args__ = (
167 UniqueConstraint('app_settings_name'),
168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
170 )
171 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
172 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
173 _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
174 _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
175
176 def __init__(self, key='', val='', type='unicode'):
177 self.app_settings_name = key
178 self.app_settings_value = val
179 self.app_settings_type = type
180
181 @validates('_app_settings_value')
182 def validate_settings_value(self, key, val):
183 assert type(val) == unicode
184 return val
185
186 @hybrid_property
187 def app_settings_value(self):
188 v = self._app_settings_value
189 _type = self.app_settings_type
190 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
191 return converter(v)
192
193 @app_settings_value.setter
194 def app_settings_value(self, val):
195 """
196 Setter that will always make sure we use unicode in app_settings_value
197
198 :param val:
199 """
200 self._app_settings_value = safe_unicode(val)
201
202 @hybrid_property
203 def app_settings_type(self):
204 return self._app_settings_type
205
206 @app_settings_type.setter
207 def app_settings_type(self, val):
208 if val not in self.SETTINGS_TYPES:
209 raise Exception('type must be one of %s got %s'
210 % (self.SETTINGS_TYPES.keys(), val))
211 self._app_settings_type = val
212
213 def __unicode__(self):
214 return u"<%s('%s:%s[%s]')>" % (
215 self.__class__.__name__,
216 self.app_settings_name, self.app_settings_value, self.app_settings_type
217 )
218
219 @classmethod
220 def get_by_name(cls, key):
221 return cls.query()\
222 .filter(cls.app_settings_name == key).scalar()
223
224 @classmethod
225 def get_by_name_or_create(cls, key, val='', type='unicode'):
226 res = cls.get_by_name(key)
227 if not res:
228 res = cls(key, val, type)
229 return res
230
231 @classmethod
232 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')):
233 """
234 Creates or updates RhodeCode setting. If updates is triggered it will only
235 update parameters that are explicityl set Optional instance will be skipped
236
237 :param key:
238 :param val:
239 :param type:
240 :return:
241 """
242 res = cls.get_by_name(key)
243 if not res:
244 val = Optional.extract(val)
245 type = Optional.extract(type)
246 res = cls(key, val, type)
247 else:
248 res.app_settings_name = key
249 if not isinstance(val, Optional):
250 # update if set
251 res.app_settings_value = val
252 if not isinstance(type, Optional):
253 # update if set
254 res.app_settings_type = type
255 return res
256
257 @classmethod
258 def get_app_settings(cls, cache=False):
259
260 ret = cls.query()
261
262 if cache:
263 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
264
265 if not ret:
266 raise Exception('Could not get application settings !')
267 settings = {}
268 for each in ret:
269 settings['rhodecode_' + each.app_settings_name] = \
270 each.app_settings_value
271
272 return settings
273
274 @classmethod
275 def get_auth_plugins(cls, cache=False):
276 auth_plugins = cls.get_by_name("auth_plugins").app_settings_value
277 return auth_plugins
278
279 @classmethod
280 def get_auth_settings(cls, cache=False):
281 ret = cls.query()\
282 .filter(cls.app_settings_name.startswith('auth_')).all()
283 fd = {}
284 for row in ret:
285 fd.update({row.app_settings_name: row.app_settings_value})
286
287 return fd
288
289 @classmethod
290 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
291 ret = cls.query()\
292 .filter(cls.app_settings_name.startswith('default_')).all()
293 fd = {}
294 for row in ret:
295 key = row.app_settings_name
296 if strip_prefix:
297 key = remove_prefix(key, prefix='default_')
298 fd.update({key: row.app_settings_value})
299
300 return fd
301
302 @classmethod
303 def get_server_info(cls):
304 import pkg_resources
305 import platform
306 import rhodecode
307 from rhodecode.lib.utils import check_git_version
308 mods = [(p.project_name, p.version) for p in pkg_resources.working_set]
309 info = {
310 'modules': sorted(mods, key=lambda k: k[0].lower()),
311 'py_version': platform.python_version(),
312 'platform': safe_unicode(platform.platform()),
313 'rhodecode_version': rhodecode.__version__,
314 'git_version': safe_unicode(check_git_version()),
315 'git_path': rhodecode.CONFIG.get('git_path')
316 }
317 return info
318
319
320 class RhodeCodeUi(Base, BaseModel):
321 __tablename__ = 'rhodecode_ui'
322 __table_args__ = (
323 UniqueConstraint('ui_key'),
324 {'extend_existing': True, 'mysql_engine': 'InnoDB',
325 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
326 )
327
328 HOOK_UPDATE = 'changegroup.update'
329 HOOK_REPO_SIZE = 'changegroup.repo_size'
330 HOOK_PUSH = 'changegroup.push_logger'
331 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
332 HOOK_PULL = 'outgoing.pull_logger'
333 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
334
335 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
336 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
337 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
338 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
339 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
340
341 # def __init__(self, section='', key='', value=''):
342 # self.ui_section = section
343 # self.ui_key = key
344 # self.ui_value = value
345
346 @classmethod
347 def get_by_key(cls, key):
348 return cls.query().filter(cls.ui_key == key).scalar()
349
350 @classmethod
351 def get_builtin_hooks(cls):
352 q = cls.query()
353 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
354 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
355 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
356 return q.all()
357
358 @classmethod
359 def get_custom_hooks(cls):
360 q = cls.query()
361 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
362 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
363 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
364 q = q.filter(cls.ui_section == 'hooks')
365 return q.all()
366
367 @classmethod
368 def get_repos_location(cls):
369 return cls.get_by_key('/').ui_value
370
371 @classmethod
372 def create_or_update_hook(cls, key, val):
373 new_ui = cls.get_by_key(key) or cls()
374 new_ui.ui_section = 'hooks'
375 new_ui.ui_active = True
376 new_ui.ui_key = key
377 new_ui.ui_value = val
378
379 Session().add(new_ui)
380
381 def __repr__(self):
382 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
383 self.ui_value)
384
385
386 class User(Base, BaseModel):
387 __tablename__ = 'users'
388 __table_args__ = (
389 UniqueConstraint('username'), UniqueConstraint('email'),
390 Index('u_username_idx', 'username'),
391 Index('u_email_idx', 'email'),
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 )
395 DEFAULT_USER = 'default'
396
397 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
398 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
399 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
400 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
401 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
402 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
403 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
404 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
405 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
406 extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
407 extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
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 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
721 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
722 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
723 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
724 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
725 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
726
727 user = relationship('User')
728
729 def __unicode__(self):
730 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
731 self.users_group_id,
732 self.users_group_name)
733
734 @classmethod
735 def get_by_group_name(cls, group_name, cache=False,
736 case_insensitive=False):
737 if case_insensitive:
738 q = cls.query().filter(cls.users_group_name.ilike(group_name))
739 else:
740 q = cls.query().filter(cls.users_group_name == group_name)
741 if cache:
742 q = q.options(FromCache(
743 "sql_cache_short",
744 "get_user_%s" % _hash_key(group_name)
745 )
746 )
747 return q.scalar()
748
749 @classmethod
750 def get(cls, user_group_id, cache=False):
751 user_group = cls.query()
752 if cache:
753 user_group = user_group.options(FromCache("sql_cache_short",
754 "get_users_group_%s" % user_group_id))
755 return user_group.get(user_group_id)
756
757 def get_api_data(self, with_members=True):
758 user_group = self
759
760 data = dict(
761 users_group_id=user_group.users_group_id,
762 group_name=user_group.users_group_name,
763 group_description=user_group.user_group_description,
764 active=user_group.users_group_active,
765 owner=user_group.user.username,
766 )
767 if with_members:
768 members = []
769 for user in user_group.members:
770 user = user.user
771 members.append(user.get_api_data())
772 data['members'] = members
773
774 return data
775
776
777 class UserGroupMember(Base, BaseModel):
778 __tablename__ = 'users_groups_members'
779 __table_args__ = (
780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
782 )
783
784 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
785 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
786 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
787
788 user = relationship('User', lazy='joined')
789 users_group = relationship('UserGroup')
790
791 def __init__(self, gr_id='', u_id=''):
792 self.users_group_id = gr_id
793 self.user_id = u_id
794
795
796 class RepositoryField(Base, BaseModel):
797 __tablename__ = 'repositories_fields'
798 __table_args__ = (
799 UniqueConstraint('repository_id', 'field_key'), # no-multi field
800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
801 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
802 )
803 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
804
805 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
806 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
807 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
808 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
809 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
810 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
811 field_type = Column("field_type", String(256), nullable=False, unique=None)
812 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
813
814 repository = relationship('Repository')
815
816 @property
817 def field_key_prefixed(self):
818 return 'ex_%s' % self.field_key
819
820 @classmethod
821 def un_prefix_key(cls, key):
822 if key.startswith(cls.PREFIX):
823 return key[len(cls.PREFIX):]
824 return key
825
826 @classmethod
827 def get_by_key_name(cls, key, repo):
828 row = cls.query()\
829 .filter(cls.repository == repo)\
830 .filter(cls.field_key == key).scalar()
831 return row
832
833
834 class Repository(Base, BaseModel):
835 __tablename__ = 'repositories'
836 __table_args__ = (
837 UniqueConstraint('repo_name'),
838 Index('r_repo_name_idx', 'repo_name'),
839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
841 )
842
843 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
844 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
845 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
846 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
848 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
849 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
850 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
851 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
852 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
853 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
854 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
855 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
856 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
857 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
858
859 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
860 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
861
862 user = relationship('User')
863 fork = relationship('Repository', remote_side=repo_id)
864 group = relationship('RepoGroup')
865 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
866 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
867 stats = relationship('Statistics', cascade='all', uselist=False)
868
869 followers = relationship('UserFollowing',
870 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
871 cascade='all')
872 extra_fields = relationship('RepositoryField',
873 cascade="all, delete, delete-orphan")
874
875 logs = relationship('UserLog')
876 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
877
878 pull_requests_org = relationship('PullRequest',
879 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
880 cascade="all, delete, delete-orphan")
881
882 pull_requests_other = relationship('PullRequest',
883 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
884 cascade="all, delete, delete-orphan")
885
886 def __unicode__(self):
887 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
888 safe_unicode(self.repo_name))
889
890 @hybrid_property
891 def locked(self):
892 # always should return [user_id, timelocked]
893 if self._locked:
894 _lock_info = self._locked.split(':')
895 return int(_lock_info[0]), _lock_info[1]
896 return [None, None]
897
898 @locked.setter
899 def locked(self, val):
900 if val and isinstance(val, (list, tuple)):
901 self._locked = ':'.join(map(str, val))
902 else:
903 self._locked = None
904
905 @hybrid_property
906 def changeset_cache(self):
907 from rhodecode.lib.vcs.backends.base import EmptyChangeset
908 dummy = EmptyChangeset().__json__()
909 if not self._changeset_cache:
910 return dummy
911 try:
912 return json.loads(self._changeset_cache)
913 except TypeError:
914 return dummy
915
916 @changeset_cache.setter
917 def changeset_cache(self, val):
918 try:
919 self._changeset_cache = json.dumps(val)
920 except Exception:
921 log.error(traceback.format_exc())
922
923 @classmethod
924 def url_sep(cls):
925 return URL_SEP
926
927 @classmethod
928 def normalize_repo_name(cls, repo_name):
929 """
930 Normalizes os specific repo_name to the format internally stored inside
931 dabatabase using URL_SEP
932
933 :param cls:
934 :param repo_name:
935 """
936 return cls.url_sep().join(repo_name.split(os.sep))
937
938 @classmethod
939 def get_by_repo_name(cls, repo_name):
940 q = Session().query(cls).filter(cls.repo_name == repo_name)
941 q = q.options(joinedload(Repository.fork))\
942 .options(joinedload(Repository.user))\
943 .options(joinedload(Repository.group))
944 return q.scalar()
945
946 @classmethod
947 def get_by_full_path(cls, repo_full_path):
948 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
949 repo_name = cls.normalize_repo_name(repo_name)
950 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
951
952 @classmethod
953 def get_repo_forks(cls, repo_id):
954 return cls.query().filter(Repository.fork_id == repo_id)
955
956 @classmethod
957 def base_path(cls):
958 """
959 Returns base path when all repos are stored
960
961 :param cls:
962 """
963 q = Session().query(RhodeCodeUi)\
964 .filter(RhodeCodeUi.ui_key == cls.url_sep())
965 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
966 return q.one().ui_value
967
968 @property
969 def forks(self):
970 """
971 Return forks of this repo
972 """
973 return Repository.get_repo_forks(self.repo_id)
974
975 @property
976 def parent(self):
977 """
978 Returns fork parent
979 """
980 return self.fork
981
982 @property
983 def just_name(self):
984 return self.repo_name.split(Repository.url_sep())[-1]
985
986 @property
987 def groups_with_parents(self):
988 groups = []
989 if self.group is None:
990 return groups
991
992 cur_gr = self.group
993 groups.insert(0, cur_gr)
994 while 1:
995 gr = getattr(cur_gr, 'parent_group', None)
996 cur_gr = cur_gr.parent_group
997 if gr is None:
998 break
999 groups.insert(0, gr)
1000
1001 return groups
1002
1003 @property
1004 def groups_and_repo(self):
1005 return self.groups_with_parents, self.just_name, self.repo_name
1006
1007 @LazyProperty
1008 def repo_path(self):
1009 """
1010 Returns base full path for that repository means where it actually
1011 exists on a filesystem
1012 """
1013 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
1014 Repository.url_sep())
1015 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1016 return q.one().ui_value
1017
1018 @property
1019 def repo_full_path(self):
1020 p = [self.repo_path]
1021 # we need to split the name by / since this is how we store the
1022 # names in the database, but that eventually needs to be converted
1023 # into a valid system path
1024 p += self.repo_name.split(Repository.url_sep())
1025 return os.path.join(*map(safe_unicode, p))
1026
1027 @property
1028 def cache_keys(self):
1029 """
1030 Returns associated cache keys for that repo
1031 """
1032 return CacheInvalidation.query()\
1033 .filter(CacheInvalidation.cache_args == self.repo_name)\
1034 .order_by(CacheInvalidation.cache_key)\
1035 .all()
1036
1037 def get_new_name(self, repo_name):
1038 """
1039 returns new full repository name based on assigned group and new new
1040
1041 :param group_name:
1042 """
1043 path_prefix = self.group.full_path_splitted if self.group else []
1044 return Repository.url_sep().join(path_prefix + [repo_name])
1045
1046 @property
1047 def _ui(self):
1048 """
1049 Creates an db based ui object for this repository
1050 """
1051 from rhodecode.lib.utils import make_ui
1052 return make_ui('db', clear_session=False)
1053
1054 @classmethod
1055 def is_valid(cls, repo_name):
1056 """
1057 returns True if given repo name is a valid filesystem repository
1058
1059 :param cls:
1060 :param repo_name:
1061 """
1062 from rhodecode.lib.utils import is_valid_repo
1063
1064 return is_valid_repo(repo_name, cls.base_path())
1065
1066 def get_api_data(self):
1067 """
1068 Common function for generating repo api data
1069
1070 """
1071 repo = self
1072 data = dict(
1073 repo_id=repo.repo_id,
1074 repo_name=repo.repo_name,
1075 repo_type=repo.repo_type,
1076 clone_uri=repo.clone_uri,
1077 private=repo.private,
1078 created_on=repo.created_on,
1079 description=repo.description,
1080 landing_rev=repo.landing_rev,
1081 owner=repo.user.username,
1082 fork_of=repo.fork.repo_name if repo.fork else None,
1083 enable_statistics=repo.enable_statistics,
1084 enable_locking=repo.enable_locking,
1085 enable_downloads=repo.enable_downloads,
1086 last_changeset=repo.changeset_cache,
1087 locked_by=User.get(self.locked[0]).get_api_data() \
1088 if self.locked[0] else None,
1089 locked_date=time_to_datetime(self.locked[1]) \
1090 if self.locked[1] else None
1091 )
1092 rc_config = RhodeCodeSetting.get_app_settings()
1093 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1094 if repository_fields:
1095 for f in self.extra_fields:
1096 data[f.field_key_prefixed] = f.field_value
1097
1098 return data
1099
1100 @classmethod
1101 def lock(cls, repo, user_id, lock_time=None):
1102 if not lock_time:
1103 lock_time = time.time()
1104 repo.locked = [user_id, lock_time]
1105 Session().add(repo)
1106 Session().commit()
1107
1108 @classmethod
1109 def unlock(cls, repo):
1110 repo.locked = None
1111 Session().add(repo)
1112 Session().commit()
1113
1114 @classmethod
1115 def getlock(cls, repo):
1116 return repo.locked
1117
1118 @property
1119 def last_db_change(self):
1120 return self.updated_on
1121
1122 def clone_url(self, **override):
1123 from pylons import url
1124 from urlparse import urlparse
1125 import urllib
1126 parsed_url = urlparse(url('home', qualified=True))
1127 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1128 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1129 args = {
1130 'user': '',
1131 'pass': '',
1132 'scheme': parsed_url.scheme,
1133 'netloc': parsed_url.netloc,
1134 'prefix': decoded_path,
1135 'path': self.repo_name
1136 }
1137
1138 args.update(override)
1139 return default_clone_uri % args
1140
1141 #==========================================================================
1142 # SCM PROPERTIES
1143 #==========================================================================
1144
1145 def get_changeset(self, rev=None):
1146 return get_changeset_safe(self.scm_instance, rev)
1147
1148 def get_landing_changeset(self):
1149 """
1150 Returns landing changeset, or if that doesn't exist returns the tip
1151 """
1152 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1153 return cs
1154
1155 def update_changeset_cache(self, cs_cache=None):
1156 """
1157 Update cache of last changeset for repository, keys should be::
1158
1159 short_id
1160 raw_id
1161 revision
1162 message
1163 date
1164 author
1165
1166 :param cs_cache:
1167 """
1168 from rhodecode.lib.vcs.backends.base import BaseChangeset
1169 if cs_cache is None:
1170 cs_cache = EmptyChangeset()
1171 # use no-cache version here
1172 scm_repo = self.scm_instance_no_cache()
1173 if scm_repo:
1174 cs_cache = scm_repo.get_changeset()
1175
1176 if isinstance(cs_cache, BaseChangeset):
1177 cs_cache = cs_cache.__json__()
1178
1179 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1180 _default = datetime.datetime.fromtimestamp(0)
1181 last_change = cs_cache.get('date') or _default
1182 log.debug('updated repo %s with new cs cache %s'
1183 % (self.repo_name, cs_cache))
1184 self.updated_on = last_change
1185 self.changeset_cache = cs_cache
1186 Session().add(self)
1187 Session().commit()
1188 else:
1189 log.debug('Skipping repo:%s already with latest changes'
1190 % self.repo_name)
1191
1192 @property
1193 def tip(self):
1194 return self.get_changeset('tip')
1195
1196 @property
1197 def author(self):
1198 return self.tip.author
1199
1200 @property
1201 def last_change(self):
1202 return self.scm_instance.last_change
1203
1204 def get_comments(self, revisions=None):
1205 """
1206 Returns comments for this repository grouped by revisions
1207
1208 :param revisions: filter query by revisions only
1209 """
1210 cmts = ChangesetComment.query()\
1211 .filter(ChangesetComment.repo == self)
1212 if revisions:
1213 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1214 grouped = collections.defaultdict(list)
1215 for cmt in cmts.all():
1216 grouped[cmt.revision].append(cmt)
1217 return grouped
1218
1219 def statuses(self, revisions=None):
1220 """
1221 Returns statuses for this repository
1222
1223 :param revisions: list of revisions to get statuses for
1224 """
1225
1226 statuses = ChangesetStatus.query()\
1227 .filter(ChangesetStatus.repo == self)\
1228 .filter(ChangesetStatus.version == 0)
1229 if revisions:
1230 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1231 grouped = {}
1232
1233 #maybe we have open new pullrequest without a status ?
1234 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1235 status_lbl = ChangesetStatus.get_status_lbl(stat)
1236 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1237 for rev in pr.revisions:
1238 pr_id = pr.pull_request_id
1239 pr_repo = pr.other_repo.repo_name
1240 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1241
1242 for stat in statuses.all():
1243 pr_id = pr_repo = None
1244 if stat.pull_request:
1245 pr_id = stat.pull_request.pull_request_id
1246 pr_repo = stat.pull_request.other_repo.repo_name
1247 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1248 pr_id, pr_repo]
1249 return grouped
1250
1251 def _repo_size(self):
1252 from rhodecode.lib import helpers as h
1253 log.debug('calculating repository size...')
1254 return h.format_byte_size(self.scm_instance.size)
1255
1256 #==========================================================================
1257 # SCM CACHE INSTANCE
1258 #==========================================================================
1259
1260 def set_invalidate(self):
1261 """
1262 Mark caches of this repo as invalid.
1263 """
1264 CacheInvalidation.set_invalidate(self.repo_name)
1265
1266 def scm_instance_no_cache(self):
1267 return self.__get_instance()
1268
1269 @property
1270 def scm_instance(self):
1271 import rhodecode
1272 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1273 if full_cache:
1274 return self.scm_instance_cached()
1275 return self.__get_instance()
1276
1277 def scm_instance_cached(self, valid_cache_keys=None):
1278 @cache_region('long_term')
1279 def _c(repo_name):
1280 return self.__get_instance()
1281 rn = self.repo_name
1282
1283 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1284 if not valid:
1285 log.debug('Cache for %s invalidated, getting new object' % (rn))
1286 region_invalidate(_c, None, rn)
1287 else:
1288 log.debug('Getting obj for %s from cache' % (rn))
1289 return _c(rn)
1290
1291 def __get_instance(self):
1292 repo_full_path = self.repo_full_path
1293 try:
1294 alias = get_scm(repo_full_path)[0]
1295 log.debug('Creating instance of %s repository from %s'
1296 % (alias, repo_full_path))
1297 backend = get_backend(alias)
1298 except VCSError:
1299 log.error(traceback.format_exc())
1300 log.error('Perhaps this repository is in db and not in '
1301 'filesystem run rescan repositories with '
1302 '"destroy old data " option from admin panel')
1303 return
1304
1305 if alias == 'hg':
1306
1307 repo = backend(safe_str(repo_full_path), create=False,
1308 baseui=self._ui)
1309 # skip hidden web repository
1310 if repo._get_hidden():
1311 return
1312 else:
1313 repo = backend(repo_full_path, create=False)
1314
1315 return repo
1316
1317
1318 class RepoGroup(Base, BaseModel):
1319 __tablename__ = 'groups'
1320 __table_args__ = (
1321 UniqueConstraint('group_name', 'group_parent_id'),
1322 CheckConstraint('group_id != group_parent_id'),
1323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1324 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1325 )
1326 __mapper_args__ = {'order_by': 'group_name'}
1327
1328 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1329 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1330 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1331 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1332 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1333 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1334 #TODO: create this field in migrations
1335 #created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1336
1337 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1338 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1339 parent_group = relationship('RepoGroup', remote_side=group_id)
1340 user = relationship('User')
1341
1342 def __init__(self, group_name='', parent_group=None):
1343 self.group_name = group_name
1344 self.parent_group = parent_group
1345
1346 def __unicode__(self):
1347 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1348 self.group_name)
1349
1350 @classmethod
1351 def groups_choices(cls, groups=None, show_empty_group=True):
1352 from webhelpers.html import literal as _literal
1353 if not groups:
1354 groups = cls.query().all()
1355
1356 repo_groups = []
1357 if show_empty_group:
1358 repo_groups = [('-1', u'-- %s --' % _('top level'))]
1359 sep = ' &raquo; '
1360 _name = lambda k: _literal(sep.join(k))
1361
1362 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1363 for x in groups])
1364
1365 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1366 return repo_groups
1367
1368 @classmethod
1369 def url_sep(cls):
1370 return URL_SEP
1371
1372 @classmethod
1373 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1374 if case_insensitive:
1375 gr = cls.query()\
1376 .filter(cls.group_name.ilike(group_name))
1377 else:
1378 gr = cls.query()\
1379 .filter(cls.group_name == group_name)
1380 if cache:
1381 gr = gr.options(FromCache(
1382 "sql_cache_short",
1383 "get_group_%s" % _hash_key(group_name)
1384 )
1385 )
1386 return gr.scalar()
1387
1388 @property
1389 def parents(self):
1390 parents_recursion_limit = 5
1391 groups = []
1392 if self.parent_group is None:
1393 return groups
1394 cur_gr = self.parent_group
1395 groups.insert(0, cur_gr)
1396 cnt = 0
1397 while 1:
1398 cnt += 1
1399 gr = getattr(cur_gr, 'parent_group', None)
1400 cur_gr = cur_gr.parent_group
1401 if gr is None:
1402 break
1403 if cnt == parents_recursion_limit:
1404 # this will prevent accidental infinit loops
1405 log.error('group nested more than %s' %
1406 parents_recursion_limit)
1407 break
1408
1409 groups.insert(0, gr)
1410 return groups
1411
1412 @property
1413 def children(self):
1414 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1415
1416 @property
1417 def name(self):
1418 return self.group_name.split(RepoGroup.url_sep())[-1]
1419
1420 @property
1421 def full_path(self):
1422 return self.group_name
1423
1424 @property
1425 def full_path_splitted(self):
1426 return self.group_name.split(RepoGroup.url_sep())
1427
1428 @property
1429 def repositories(self):
1430 return Repository.query()\
1431 .filter(Repository.group == self)\
1432 .order_by(Repository.repo_name)
1433
1434 @property
1435 def repositories_recursive_count(self):
1436 cnt = self.repositories.count()
1437
1438 def children_count(group):
1439 cnt = 0
1440 for child in group.children:
1441 cnt += child.repositories.count()
1442 cnt += children_count(child)
1443 return cnt
1444
1445 return cnt + children_count(self)
1446
1447 def _recursive_objects(self, include_repos=True):
1448 all_ = []
1449
1450 def _get_members(root_gr):
1451 if include_repos:
1452 for r in root_gr.repositories:
1453 all_.append(r)
1454 childs = root_gr.children.all()
1455 if childs:
1456 for gr in childs:
1457 all_.append(gr)
1458 _get_members(gr)
1459
1460 _get_members(self)
1461 return [self] + all_
1462
1463 def recursive_groups_and_repos(self):
1464 """
1465 Recursive return all groups, with repositories in those groups
1466 """
1467 return self._recursive_objects()
1468
1469 def recursive_groups(self):
1470 """
1471 Returns all children groups for this group including children of children
1472 """
1473 return self._recursive_objects(include_repos=False)
1474
1475 def get_new_name(self, group_name):
1476 """
1477 returns new full group name based on parent and new name
1478
1479 :param group_name:
1480 """
1481 path_prefix = (self.parent_group.full_path_splitted if
1482 self.parent_group else [])
1483 return RepoGroup.url_sep().join(path_prefix + [group_name])
1484
1485 def get_api_data(self):
1486 """
1487 Common function for generating api data
1488
1489 """
1490 group = self
1491 data = dict(
1492 group_id=group.group_id,
1493 group_name=group.group_name,
1494 group_description=group.group_description,
1495 parent_group=group.parent_group.group_name if group.parent_group else None,
1496 repositories=[x.repo_name for x in group.repositories],
1497 owner=group.user.username
1498 )
1499 return data
1500
1501
1502 class Permission(Base, BaseModel):
1503 __tablename__ = 'permissions'
1504 __table_args__ = (
1505 Index('p_perm_name_idx', 'permission_name'),
1506 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1507 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1508 )
1509 PERMS = [
1510 ('hg.admin', _('RhodeCode Administrator')),
1511
1512 ('repository.none', _('Repository no access')),
1513 ('repository.read', _('Repository read access')),
1514 ('repository.write', _('Repository write access')),
1515 ('repository.admin', _('Repository admin access')),
1516
1517 ('group.none', _('Repository group no access')),
1518 ('group.read', _('Repository group read access')),
1519 ('group.write', _('Repository group write access')),
1520 ('group.admin', _('Repository group admin access')),
1521
1522 ('usergroup.none', _('User group no access')),
1523 ('usergroup.read', _('User group read access')),
1524 ('usergroup.write', _('User group write access')),
1525 ('usergroup.admin', _('User group admin access')),
1526
1527 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1528 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1529
1530 ('hg.usergroup.create.false', _('User Group creation disabled')),
1531 ('hg.usergroup.create.true', _('User Group creation enabled')),
1532
1533 ('hg.create.none', _('Repository creation disabled')),
1534 ('hg.create.repository', _('Repository creation enabled')),
1535
1536 ('hg.fork.none', _('Repository forking disabled')),
1537 ('hg.fork.repository', _('Repository forking enabled')),
1538
1539 ('hg.register.none', _('Registration disabled')),
1540 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1541 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1542
1543 ('hg.extern_activate.manual', _('Manual activation of external account')),
1544 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1545
1546 ]
1547
1548 #definition of system default permissions for DEFAULT user
1549 DEFAULT_USER_PERMISSIONS = [
1550 'repository.read',
1551 'group.read',
1552 'usergroup.read',
1553 'hg.create.repository',
1554 'hg.fork.repository',
1555 'hg.register.manual_activate',
1556 'hg.extern_activate.auto',
1557 ]
1558
1559 # defines which permissions are more important higher the more important
1560 # Weight defines which permissions are more important.
1561 # The higher number the more important.
1562 PERM_WEIGHTS = {
1563 'repository.none': 0,
1564 'repository.read': 1,
1565 'repository.write': 3,
1566 'repository.admin': 4,
1567
1568 'group.none': 0,
1569 'group.read': 1,
1570 'group.write': 3,
1571 'group.admin': 4,
1572
1573 'usergroup.none': 0,
1574 'usergroup.read': 1,
1575 'usergroup.write': 3,
1576 'usergroup.admin': 4,
1577 'hg.repogroup.create.false': 0,
1578 'hg.repogroup.create.true': 1,
1579
1580 'hg.usergroup.create.false': 0,
1581 'hg.usergroup.create.true': 1,
1582
1583 'hg.fork.none': 0,
1584 'hg.fork.repository': 1,
1585 'hg.create.none': 0,
1586 'hg.create.repository': 1
1587 }
1588
1589 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1590 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1591 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1592
1593 def __unicode__(self):
1594 return u"<%s('%s:%s')>" % (
1595 self.__class__.__name__, self.permission_id, self.permission_name
1596 )
1597
1598 @classmethod
1599 def get_by_key(cls, key):
1600 return cls.query().filter(cls.permission_name == key).scalar()
1601
1602 @classmethod
1603 def get_default_perms(cls, default_user_id):
1604 q = Session().query(UserRepoToPerm, Repository, cls)\
1605 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1606 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1607 .filter(UserRepoToPerm.user_id == default_user_id)
1608
1609 return q.all()
1610
1611 @classmethod
1612 def get_default_group_perms(cls, default_user_id):
1613 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1614 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1615 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1616 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1617
1618 return q.all()
1619
1620 @classmethod
1621 def get_default_user_group_perms(cls, default_user_id):
1622 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1623 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1624 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1625 .filter(UserUserGroupToPerm.user_id == default_user_id)
1626
1627 return q.all()
1628
1629
1630 class UserRepoToPerm(Base, BaseModel):
1631 __tablename__ = 'repo_to_perm'
1632 __table_args__ = (
1633 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1634 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1635 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1636 )
1637 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1638 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1639 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1640 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1641
1642 user = relationship('User')
1643 repository = relationship('Repository')
1644 permission = relationship('Permission')
1645
1646 @classmethod
1647 def create(cls, user, repository, permission):
1648 n = cls()
1649 n.user = user
1650 n.repository = repository
1651 n.permission = permission
1652 Session().add(n)
1653 return n
1654
1655 def __unicode__(self):
1656 return u'<%s => %s >' % (self.user, self.repository)
1657
1658
1659 class UserUserGroupToPerm(Base, BaseModel):
1660 __tablename__ = 'user_user_group_to_perm'
1661 __table_args__ = (
1662 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1663 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1664 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1665 )
1666 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1667 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1668 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1669 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1670
1671 user = relationship('User')
1672 user_group = relationship('UserGroup')
1673 permission = relationship('Permission')
1674
1675 @classmethod
1676 def create(cls, user, user_group, permission):
1677 n = cls()
1678 n.user = user
1679 n.user_group = user_group
1680 n.permission = permission
1681 Session().add(n)
1682 return n
1683
1684 def __unicode__(self):
1685 return u'<%s => %s >' % (self.user, self.user_group)
1686
1687
1688 class UserToPerm(Base, BaseModel):
1689 __tablename__ = 'user_to_perm'
1690 __table_args__ = (
1691 UniqueConstraint('user_id', 'permission_id'),
1692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1693 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1694 )
1695 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1696 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1697 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1698
1699 user = relationship('User')
1700 permission = relationship('Permission', lazy='joined')
1701
1702 def __unicode__(self):
1703 return u'<%s => %s >' % (self.user, self.permission)
1704
1705
1706 class UserGroupRepoToPerm(Base, BaseModel):
1707 __tablename__ = 'users_group_repo_to_perm'
1708 __table_args__ = (
1709 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1711 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1712 )
1713 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1714 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1715 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1717
1718 users_group = relationship('UserGroup')
1719 permission = relationship('Permission')
1720 repository = relationship('Repository')
1721
1722 @classmethod
1723 def create(cls, users_group, repository, permission):
1724 n = cls()
1725 n.users_group = users_group
1726 n.repository = repository
1727 n.permission = permission
1728 Session().add(n)
1729 return n
1730
1731 def __unicode__(self):
1732 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1733
1734
1735 class UserGroupUserGroupToPerm(Base, BaseModel):
1736 __tablename__ = 'user_group_user_group_to_perm'
1737 __table_args__ = (
1738 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1739 CheckConstraint('target_user_group_id != user_group_id'),
1740 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1741 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1742 )
1743 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)
1744 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1745 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1746 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1747
1748 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1749 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1750 permission = relationship('Permission')
1751
1752 @classmethod
1753 def create(cls, target_user_group, user_group, permission):
1754 n = cls()
1755 n.target_user_group = target_user_group
1756 n.user_group = user_group
1757 n.permission = permission
1758 Session().add(n)
1759 return n
1760
1761 def __unicode__(self):
1762 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1763
1764
1765 class UserGroupToPerm(Base, BaseModel):
1766 __tablename__ = 'users_group_to_perm'
1767 __table_args__ = (
1768 UniqueConstraint('users_group_id', 'permission_id',),
1769 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1770 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1771 )
1772 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1773 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1774 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1775
1776 users_group = relationship('UserGroup')
1777 permission = relationship('Permission')
1778
1779
1780 class UserRepoGroupToPerm(Base, BaseModel):
1781 __tablename__ = 'user_repo_group_to_perm'
1782 __table_args__ = (
1783 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1784 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1785 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1786 )
1787
1788 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1789 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1790 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1791 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1792
1793 user = relationship('User')
1794 group = relationship('RepoGroup')
1795 permission = relationship('Permission')
1796
1797
1798 class UserGroupRepoGroupToPerm(Base, BaseModel):
1799 __tablename__ = 'users_group_repo_group_to_perm'
1800 __table_args__ = (
1801 UniqueConstraint('users_group_id', 'group_id'),
1802 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1803 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1804 )
1805
1806 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)
1807 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1808 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1809 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1810
1811 users_group = relationship('UserGroup')
1812 permission = relationship('Permission')
1813 group = relationship('RepoGroup')
1814
1815
1816 class Statistics(Base, BaseModel):
1817 __tablename__ = 'statistics'
1818 __table_args__ = (
1819 UniqueConstraint('repository_id'),
1820 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1821 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1822 )
1823 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1824 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1825 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1826 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1827 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1828 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1829
1830 repository = relationship('Repository', single_parent=True)
1831
1832
1833 class UserFollowing(Base, BaseModel):
1834 __tablename__ = 'user_followings'
1835 __table_args__ = (
1836 UniqueConstraint('user_id', 'follows_repository_id'),
1837 UniqueConstraint('user_id', 'follows_user_id'),
1838 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1839 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1840 )
1841
1842 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1843 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1844 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1845 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1846 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1847
1848 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1849
1850 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1851 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1852
1853 @classmethod
1854 def get_repo_followers(cls, repo_id):
1855 return cls.query().filter(cls.follows_repo_id == repo_id)
1856
1857
1858 class CacheInvalidation(Base, BaseModel):
1859 __tablename__ = 'cache_invalidation'
1860 __table_args__ = (
1861 UniqueConstraint('cache_key'),
1862 Index('key_idx', 'cache_key'),
1863 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1864 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1865 )
1866 # cache_id, not used
1867 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1868 # cache_key as created by _get_cache_key
1869 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1870 # cache_args is a repo_name
1871 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1872 # instance sets cache_active True when it is caching,
1873 # other instances set cache_active to False to indicate that this cache is invalid
1874 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1875
1876 def __init__(self, cache_key, repo_name=''):
1877 self.cache_key = cache_key
1878 self.cache_args = repo_name
1879 self.cache_active = False
1880
1881 def __unicode__(self):
1882 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1883 self.cache_id, self.cache_key, self.cache_active)
1884
1885 def _cache_key_partition(self):
1886 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1887 return prefix, repo_name, suffix
1888
1889 def get_prefix(self):
1890 """
1891 get prefix that might have been used in _get_cache_key to
1892 generate self.cache_key. Only used for informational purposes
1893 in repo_edit.html.
1894 """
1895 # prefix, repo_name, suffix
1896 return self._cache_key_partition()[0]
1897
1898 def get_suffix(self):
1899 """
1900 get suffix that might have been used in _get_cache_key to
1901 generate self.cache_key. Only used for informational purposes
1902 in repo_edit.html.
1903 """
1904 # prefix, repo_name, suffix
1905 return self._cache_key_partition()[2]
1906
1907 @classmethod
1908 def clear_cache(cls):
1909 """
1910 Delete all cache keys from database.
1911 Should only be run when all instances are down and all entries thus stale.
1912 """
1913 cls.query().delete()
1914 Session().commit()
1915
1916 @classmethod
1917 def _get_cache_key(cls, key):
1918 """
1919 Wrapper for generating a unique cache key for this instance and "key".
1920 key must / will start with a repo_name which will be stored in .cache_args .
1921 """
1922 import rhodecode
1923 prefix = rhodecode.CONFIG.get('instance_id', '')
1924 return "%s%s" % (prefix, key)
1925
1926 @classmethod
1927 def set_invalidate(cls, repo_name, delete=False):
1928 """
1929 Mark all caches of a repo as invalid in the database.
1930 """
1931 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1932
1933 try:
1934 for inv_obj in inv_objs:
1935 log.debug('marking %s key for invalidation based on repo_name=%s'
1936 % (inv_obj, safe_str(repo_name)))
1937 if delete:
1938 Session().delete(inv_obj)
1939 else:
1940 inv_obj.cache_active = False
1941 Session().add(inv_obj)
1942 Session().commit()
1943 except Exception:
1944 log.error(traceback.format_exc())
1945 Session().rollback()
1946
1947 @classmethod
1948 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1949 """
1950 Mark this cache key as active and currently cached.
1951 Return True if the existing cache registration still was valid.
1952 Return False to indicate that it had been invalidated and caches should be refreshed.
1953 """
1954
1955 key = (repo_name + '_' + kind) if kind else repo_name
1956 cache_key = cls._get_cache_key(key)
1957
1958 if valid_cache_keys and cache_key in valid_cache_keys:
1959 return True
1960
1961 try:
1962 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1963 if not inv_obj:
1964 inv_obj = CacheInvalidation(cache_key, repo_name)
1965 was_valid = inv_obj.cache_active
1966 inv_obj.cache_active = True
1967 Session().add(inv_obj)
1968 Session().commit()
1969 return was_valid
1970 except Exception:
1971 log.error(traceback.format_exc())
1972 Session().rollback()
1973 return False
1974
1975 @classmethod
1976 def get_valid_cache_keys(cls):
1977 """
1978 Return opaque object with information of which caches still are valid
1979 and can be used without checking for invalidation.
1980 """
1981 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1982
1983
1984 class ChangesetComment(Base, BaseModel):
1985 __tablename__ = 'changeset_comments'
1986 __table_args__ = (
1987 Index('cc_revision_idx', 'revision'),
1988 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1989 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1990 )
1991 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1992 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1993 revision = Column('revision', String(40), nullable=True)
1994 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1995 line_no = Column('line_no', Unicode(10), nullable=True)
1996 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1997 f_path = Column('f_path', Unicode(1000), nullable=True)
1998 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1999 text = Column('text', UnicodeText(25000), nullable=False)
2000 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2001 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2002
2003 author = relationship('User', lazy='joined')
2004 repo = relationship('Repository')
2005 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2006 pull_request = relationship('PullRequest', lazy='joined')
2007
2008 @classmethod
2009 def get_users(cls, revision=None, pull_request_id=None):
2010 """
2011 Returns user associated with this ChangesetComment. ie those
2012 who actually commented
2013
2014 :param cls:
2015 :param revision:
2016 """
2017 q = Session().query(User)\
2018 .join(ChangesetComment.author)
2019 if revision:
2020 q = q.filter(cls.revision == revision)
2021 elif pull_request_id:
2022 q = q.filter(cls.pull_request_id == pull_request_id)
2023 return q.all()
2024
2025
2026 class ChangesetStatus(Base, BaseModel):
2027 __tablename__ = 'changeset_statuses'
2028 __table_args__ = (
2029 Index('cs_revision_idx', 'revision'),
2030 Index('cs_version_idx', 'version'),
2031 UniqueConstraint('repo_id', 'revision', 'version'),
2032 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2033 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2034 )
2035 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2036 STATUS_APPROVED = 'approved'
2037 STATUS_REJECTED = 'rejected'
2038 STATUS_UNDER_REVIEW = 'under_review'
2039
2040 STATUSES = [
2041 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2042 (STATUS_APPROVED, _("Approved")),
2043 (STATUS_REJECTED, _("Rejected")),
2044 (STATUS_UNDER_REVIEW, _("Under Review")),
2045 ]
2046
2047 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2048 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2049 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2050 revision = Column('revision', String(40), nullable=False)
2051 status = Column('status', String(128), nullable=False, default=DEFAULT)
2052 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2053 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2054 version = Column('version', Integer(), nullable=False, default=0)
2055 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2056
2057 author = relationship('User', lazy='joined')
2058 repo = relationship('Repository')
2059 comment = relationship('ChangesetComment', lazy='joined')
2060 pull_request = relationship('PullRequest', lazy='joined')
2061
2062 def __unicode__(self):
2063 return u"<%s('%s:%s')>" % (
2064 self.__class__.__name__,
2065 self.status, self.author
2066 )
2067
2068 @classmethod
2069 def get_status_lbl(cls, value):
2070 return dict(cls.STATUSES).get(value)
2071
2072 @property
2073 def status_lbl(self):
2074 return ChangesetStatus.get_status_lbl(self.status)
2075
2076
2077 class PullRequest(Base, BaseModel):
2078 __tablename__ = 'pull_requests'
2079 __table_args__ = (
2080 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2081 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2082 )
2083
2084 # values for .status
2085 STATUS_NEW = u'new'
2086 STATUS_OPEN = u'open'
2087 STATUS_CLOSED = u'closed'
2088
2089 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
2090 title = Column('title', Unicode(256), nullable=True)
2091 description = Column('description', UnicodeText(10240), nullable=True)
2092 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc
2093 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2094 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2096 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
2097 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2098 org_ref = Column('org_ref', Unicode(256), nullable=False)
2099 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2100 other_ref = Column('other_ref', Unicode(256), nullable=False)
2101
2102 @hybrid_property
2103 def revisions(self):
2104 return self._revisions.split(':')
2105
2106 @revisions.setter
2107 def revisions(self, val):
2108 self._revisions = ':'.join(val)
2109
2110 @property
2111 def org_ref_parts(self):
2112 return self.org_ref.split(':')
2113
2114 @property
2115 def other_ref_parts(self):
2116 return self.other_ref.split(':')
2117
2118 author = relationship('User', lazy='joined')
2119 reviewers = relationship('PullRequestReviewers',
2120 cascade="all, delete, delete-orphan")
2121 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2122 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2123 statuses = relationship('ChangesetStatus')
2124 comments = relationship('ChangesetComment',
2125 cascade="all, delete, delete-orphan")
2126
2127 def is_closed(self):
2128 return self.status == self.STATUS_CLOSED
2129
2130 @property
2131 def last_review_status(self):
2132 return self.statuses[-1].status if self.statuses else ''
2133
2134 def __json__(self):
2135 return dict(
2136 revisions=self.revisions
2137 )
2138
2139
2140 class PullRequestReviewers(Base, BaseModel):
2141 __tablename__ = 'pull_request_reviewers'
2142 __table_args__ = (
2143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2145 )
2146
2147 def __init__(self, user=None, pull_request=None):
2148 self.user = user
2149 self.pull_request = pull_request
2150
2151 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2152 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2153 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2154
2155 user = relationship('User')
2156 pull_request = relationship('PullRequest')
2157
2158
2159 class Notification(Base, BaseModel):
2160 __tablename__ = 'notifications'
2161 __table_args__ = (
2162 Index('notification_type_idx', 'type'),
2163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2165 )
2166
2167 TYPE_CHANGESET_COMMENT = u'cs_comment'
2168 TYPE_MESSAGE = u'message'
2169 TYPE_MENTION = u'mention'
2170 TYPE_REGISTRATION = u'registration'
2171 TYPE_PULL_REQUEST = u'pull_request'
2172 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2173
2174 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2175 subject = Column('subject', Unicode(512), nullable=True)
2176 body = Column('body', UnicodeText(50000), nullable=True)
2177 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2178 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2179 type_ = Column('type', Unicode(256))
2180
2181 created_by_user = relationship('User')
2182 notifications_to_users = relationship('UserNotification', lazy='joined',
2183 cascade="all, delete, delete-orphan")
2184
2185 @property
2186 def recipients(self):
2187 return [x.user for x in UserNotification.query()\
2188 .filter(UserNotification.notification == self)\
2189 .order_by(UserNotification.user_id.asc()).all()]
2190
2191 @classmethod
2192 def create(cls, created_by, subject, body, recipients, type_=None):
2193 if type_ is None:
2194 type_ = Notification.TYPE_MESSAGE
2195
2196 notification = cls()
2197 notification.created_by_user = created_by
2198 notification.subject = subject
2199 notification.body = body
2200 notification.type_ = type_
2201 notification.created_on = datetime.datetime.now()
2202
2203 for u in recipients:
2204 assoc = UserNotification()
2205 assoc.notification = notification
2206 u.notifications.append(assoc)
2207 Session().add(notification)
2208 return notification
2209
2210 @property
2211 def description(self):
2212 from rhodecode.model.notification import NotificationModel
2213 return NotificationModel().make_description(self)
2214
2215
2216 class UserNotification(Base, BaseModel):
2217 __tablename__ = 'user_to_notification'
2218 __table_args__ = (
2219 UniqueConstraint('user_id', 'notification_id'),
2220 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2221 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2222 )
2223 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2224 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2225 read = Column('read', Boolean, default=False)
2226 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2227
2228 user = relationship('User', lazy="joined")
2229 notification = relationship('Notification', lazy="joined",
2230 order_by=lambda: Notification.created_on.desc(),)
2231
2232 def mark_as_read(self):
2233 self.read = True
2234 Session().add(self)
2235
2236
2237 class Gist(Base, BaseModel):
2238 __tablename__ = 'gists'
2239 __table_args__ = (
2240 Index('g_gist_access_id_idx', 'gist_access_id'),
2241 Index('g_created_on_idx', 'created_on'),
2242 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2243 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2244 )
2245 GIST_PUBLIC = u'public'
2246 GIST_PRIVATE = u'private'
2247
2248 gist_id = Column('gist_id', Integer(), primary_key=True)
2249 gist_access_id = Column('gist_access_id', Unicode(250))
2250 gist_description = Column('gist_description', UnicodeText(1024))
2251 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2252 gist_expires = Column('gist_expires', Float(53), nullable=False)
2253 gist_type = Column('gist_type', Unicode(128), nullable=False)
2254 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2255 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2256
2257 owner = relationship('User')
2258
2259 @classmethod
2260 def get_or_404(cls, id_):
2261 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2262 if not res:
2263 raise HTTPNotFound
2264 return res
2265
2266 @classmethod
2267 def get_by_access_id(cls, gist_access_id):
2268 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
2269
2270 def gist_url(self):
2271 import rhodecode
2272 alias_url = rhodecode.CONFIG.get('gist_alias_url')
2273 if alias_url:
2274 return alias_url.replace('{gistid}', self.gist_access_id)
2275
2276 from pylons import url
2277 return url('gist', gist_id=self.gist_access_id, qualified=True)
2278
2279 @classmethod
2280 def base_path(cls):
2281 """
2282 Returns base path when all gists are stored
2283
2284 :param cls:
2285 """
2286 from rhodecode.model.gist import GIST_STORE_LOC
2287 q = Session().query(RhodeCodeUi)\
2288 .filter(RhodeCodeUi.ui_key == URL_SEP)
2289 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2290 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
2291
2292 def get_api_data(self):
2293 """
2294 Common function for generating gist related data for API
2295 """
2296 gist = self
2297 data = dict(
2298 gist_id=gist.gist_id,
2299 type=gist.gist_type,
2300 access_id=gist.gist_access_id,
2301 description=gist.gist_description,
2302 url=gist.gist_url(),
2303 expires=gist.gist_expires,
2304 created_on=gist.created_on,
2305 )
2306 return data
2307
2308 def __json__(self):
2309 data = dict(
2310 )
2311 data.update(self.get_api_data())
2312 return data
2313 ## SCM functions
2314
2315 @property
2316 def scm_instance(self):
2317 from rhodecode.lib.vcs import get_repo
2318 base_path = self.base_path()
2319 return get_repo(os.path.join(*map(safe_str,
2320 [base_path, self.gist_access_id])))
2321
2322
2323 class DbMigrateVersion(Base, BaseModel):
2324 __tablename__ = 'db_migrate_version'
2325 __table_args__ = (
2326 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2327 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2328 )
2329 repository_id = Column('repository_id', String(250), primary_key=True)
2330 repository_path = Column('repository_path', Text)
2331 version = Column('version', Integer)