##// END OF EJS Templates
users: migrate last_activity column so it's part of user table....
marcink -
r1545:e40bd0a7 default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (3903 lines changed) Show them Hide them
@@ -0,0 +1,3903 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Database Models for RhodeCode Enterprise
23 """
24
25 import re
26 import os
27 import time
28 import hashlib
29 import logging
30 import datetime
31 import warnings
32 import ipaddress
33 import functools
34 import traceback
35 import collections
36
37
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
50
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
61
62 from rhodecode.model.meta import Base, Session
63
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
66
67 # =============================================================================
68 # BASE CLASSES
69 # =============================================================================
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
80 'write': '###',
81 'read': '##',
82 'none': '#',
83 }
84
85
86 def display_sort(obj):
87 """
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
91 """
92
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
97
98
99 def _hash_key(k):
100 return md5_safe(k)
101
102
103 class EncryptedTextValue(TypeDecorator):
104 """
105 Special column for encrypted long text data, use like::
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
111 """
112 impl = Text
113
114 def process_bind_param(self, value, dialect):
115 if not value:
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
125 def process_result_value(self, value, dialect):
126 import rhodecode
127
128 if not value:
129 return value
130
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
133 # probably not encrypted values
134 return value
135 else:
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
153
154
155 class BaseModel(object):
156 """
157 Base Model for all classes
158 """
159
160 @classmethod
161 def _get_keys(cls):
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
164
165 def get_dict(self):
166 """
167 return dict with keys and values corresponding
168 to this model data """
169
170 d = {}
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
173
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
177 # update with attributes from __json__
178 if callable(_json_attr):
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
181 d[k] = val
182 return d
183
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
186 to this model data """
187
188 l = []
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
191 return l
192
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
195
196 for k in self._get_keys():
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
199
200 @classmethod
201 def query(cls):
202 return Session().query(cls)
203
204 @classmethod
205 def get(cls, id_):
206 if id_:
207 return cls.query().get(id_)
208
209 @classmethod
210 def get_or_404(cls, id_):
211 try:
212 id_ = int(id_)
213 except (TypeError, ValueError):
214 raise HTTPNotFound
215
216 res = cls.query().get(id_)
217 if not res:
218 raise HTTPNotFound
219 return res
220
221 @classmethod
222 def getAll(cls):
223 # deprecated and left for backward compatibility
224 return cls.get_all()
225
226 @classmethod
227 def get_all(cls):
228 return cls.query().all()
229
230 @classmethod
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
233 Session().delete(obj)
234
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
241 if exist_in_session:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
244 log.exception(
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
248
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
252 try:
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
257
258
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
266
267 SETTINGS_TYPES = {
268 'str': safe_str,
269 'int': safe_int,
270 'unicode': safe_unicode,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
284 self.app_settings_type = type
285 self.app_settings_value = val
286
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
290 return val
291
292 @hybrid_property
293 def app_settings_value(self):
294 v = self._app_settings_value
295 _type = self.app_settings_type
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
306
307 @app_settings_value.setter
308 def app_settings_value(self, val):
309 """
310 Setter that will always make sure we use unicode in app_settings_value
311
312 :param val:
313 """
314 val = safe_unicode(val)
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
320
321 @hybrid_property
322 def app_settings_type(self):
323 return self._app_settings_type
324
325 @app_settings_type.setter
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
331
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
337 )
338
339
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
354 HOOK_PUSH = 'changegroup.push_logger'
355
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # git part is currently hardcoded.
358
359 # SVN PATTERNS
360 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_TAG_ID = 'vcs_svn_tag'
362
363 ui_id = Column(
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 primary_key=True)
366 ui_section = Column(
367 "ui_section", String(255), nullable=True, unique=None, default=None)
368 ui_key = Column(
369 "ui_key", String(255), nullable=True, unique=None, default=None)
370 ui_value = Column(
371 "ui_value", String(255), nullable=True, unique=None, default=None)
372 ui_active = Column(
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374
375 def __repr__(self):
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 self.ui_key, self.ui_value)
378
379
380 class RepoRhodeCodeSetting(Base, BaseModel):
381 __tablename__ = 'repo_rhodecode_settings'
382 __table_args__ = (
383 UniqueConstraint(
384 'app_settings_name', 'repository_id',
385 name='uq_repo_rhodecode_setting_name_repo_id'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
389
390 repository_id = Column(
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 nullable=False)
393 app_settings_id = Column(
394 "app_settings_id", Integer(), nullable=False, unique=True,
395 default=None, primary_key=True)
396 app_settings_name = Column(
397 "app_settings_name", String(255), nullable=True, unique=None,
398 default=None)
399 _app_settings_value = Column(
400 "app_settings_value", String(4096), nullable=True, unique=None,
401 default=None)
402 _app_settings_type = Column(
403 "app_settings_type", String(255), nullable=True, unique=None,
404 default=None)
405
406 repository = relationship('Repository')
407
408 def __init__(self, repository_id, key='', val='', type='unicode'):
409 self.repository_id = repository_id
410 self.app_settings_name = key
411 self.app_settings_type = type
412 self.app_settings_value = val
413
414 @validates('_app_settings_value')
415 def validate_settings_value(self, key, val):
416 assert type(val) == unicode
417 return val
418
419 @hybrid_property
420 def app_settings_value(self):
421 v = self._app_settings_value
422 type_ = self.app_settings_type
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 return converter(v)
426
427 @app_settings_value.setter
428 def app_settings_value(self, val):
429 """
430 Setter that will always make sure we use unicode in app_settings_value
431
432 :param val:
433 """
434 self._app_settings_value = safe_unicode(val)
435
436 @hybrid_property
437 def app_settings_type(self):
438 return self._app_settings_type
439
440 @app_settings_type.setter
441 def app_settings_type(self, val):
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 if val not in SETTINGS_TYPES:
444 raise Exception('type must be one of %s got %s'
445 % (SETTINGS_TYPES.keys(), val))
446 self._app_settings_type = val
447
448 def __unicode__(self):
449 return u"<%s('%s:%s:%s[%s]')>" % (
450 self.__class__.__name__, self.repository.repo_name,
451 self.app_settings_name, self.app_settings_value,
452 self.app_settings_type
453 )
454
455
456 class RepoRhodeCodeUi(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_ui'
458 __table_args__ = (
459 UniqueConstraint(
460 'repository_id', 'ui_section', 'ui_key',
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 )
465
466 repository_id = Column(
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 nullable=False)
469 ui_id = Column(
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 primary_key=True)
472 ui_section = Column(
473 "ui_section", String(255), nullable=True, unique=None, default=None)
474 ui_key = Column(
475 "ui_key", String(255), nullable=True, unique=None, default=None)
476 ui_value = Column(
477 "ui_value", String(255), nullable=True, unique=None, default=None)
478 ui_active = Column(
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480
481 repository = relationship('Repository')
482
483 def __repr__(self):
484 return '<%s[%s:%s]%s=>%s]>' % (
485 self.__class__.__name__, self.repository.repo_name,
486 self.ui_section, self.ui_key, self.ui_value)
487
488
489 class User(Base, BaseModel):
490 __tablename__ = 'users'
491 __table_args__ = (
492 UniqueConstraint('username'), UniqueConstraint('email'),
493 Index('u_username_idx', 'username'),
494 Index('u_email_idx', 'email'),
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 )
498 DEFAULT_USER = 'default'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
518
519 user_log = relationship('UserLog')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
521
522 repositories = relationship('Repository')
523 repository_groups = relationship('RepoGroup')
524 user_groups = relationship('UserGroup')
525
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
528
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
532
533 group_member = relationship('UserGroupMember', cascade='all')
534
535 notifications = relationship('UserNotification', cascade='all')
536 # notifications assigned to this user
537 user_created_notifications = relationship('Notification', cascade='all')
538 # comments created by this user
539 user_comments = relationship('ChangesetComment', cascade='all')
540 # user profile extra info
541 user_emails = relationship('UserEmailMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
544 # gists
545 user_gists = relationship('Gist', cascade='all')
546 # user pull requests
547 user_pull_requests = relationship('PullRequest', cascade='all')
548 # external identities
549 extenal_identities = relationship(
550 'ExternalIdentity',
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
552 cascade='all')
553
554 def __unicode__(self):
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
556 self.user_id, self.username)
557
558 @hybrid_property
559 def email(self):
560 return self._email
561
562 @email.setter
563 def email(self, val):
564 self._email = val.lower() if val else None
565
566 @property
567 def firstname(self):
568 # alias for future
569 return self.name
570
571 @property
572 def emails(self):
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
574 return [self.email] + [x.email for x in other]
575
576 @property
577 def auth_tokens(self):
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
579
580 @property
581 def extra_auth_tokens(self):
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
583
584 @property
585 def feed_token(self):
586 return self.get_feed_token()
587
588 def get_feed_token(self):
589 feed_tokens = UserApiKeys.query()\
590 .filter(UserApiKeys.user == self)\
591 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
592 .all()
593 if feed_tokens:
594 return feed_tokens[0].api_key
595 return 'NO_FEED_TOKEN_AVAILABLE'
596
597 @classmethod
598 def extra_valid_auth_tokens(cls, user, role=None):
599 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
600 .filter(or_(UserApiKeys.expires == -1,
601 UserApiKeys.expires >= time.time()))
602 if role:
603 tokens = tokens.filter(or_(UserApiKeys.role == role,
604 UserApiKeys.role == UserApiKeys.ROLE_ALL))
605 return tokens.all()
606
607 def authenticate_by_token(self, auth_token, roles=None,
608 include_builtin_token=False):
609 from rhodecode.lib import auth
610
611 log.debug('Trying to authenticate user: %s via auth-token, '
612 'and roles: %s', self, roles)
613
614 if not auth_token:
615 return False
616
617 crypto_backend = auth.crypto_backend()
618
619 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
620 tokens_q = UserApiKeys.query()\
621 .filter(UserApiKeys.user_id == self.user_id)\
622 .filter(or_(UserApiKeys.expires == -1,
623 UserApiKeys.expires >= time.time()))
624
625 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
626
627 maybe_builtin = []
628 if include_builtin_token:
629 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
630
631 plain_tokens = []
632 hash_tokens = []
633
634 for token in tokens_q.all() + maybe_builtin:
635 if token.api_key.startswith(crypto_backend.ENC_PREF):
636 hash_tokens.append(token.api_key)
637 else:
638 plain_tokens.append(token.api_key)
639
640 is_plain_match = auth_token in plain_tokens
641 if is_plain_match:
642 return True
643
644 for hashed in hash_tokens:
645 # marcink: this is expensive to calculate, but the most secure
646 match = crypto_backend.hash_check(auth_token, hashed)
647 if match:
648 return True
649
650 return False
651
652 @property
653 def ip_addresses(self):
654 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
655 return [x.ip_addr for x in ret]
656
657 @property
658 def username_and_name(self):
659 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
660
661 @property
662 def username_or_name_or_email(self):
663 full_name = self.full_name if self.full_name is not ' ' else None
664 return self.username or full_name or self.email
665
666 @property
667 def full_name(self):
668 return '%s %s' % (self.firstname, self.lastname)
669
670 @property
671 def full_name_or_username(self):
672 return ('%s %s' % (self.firstname, self.lastname)
673 if (self.firstname and self.lastname) else self.username)
674
675 @property
676 def full_contact(self):
677 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
678
679 @property
680 def short_contact(self):
681 return '%s %s' % (self.firstname, self.lastname)
682
683 @property
684 def is_admin(self):
685 return self.admin
686
687 @property
688 def AuthUser(self):
689 """
690 Returns instance of AuthUser for this user
691 """
692 from rhodecode.lib.auth import AuthUser
693 return AuthUser(user_id=self.user_id, api_key=self.api_key,
694 username=self.username)
695
696 @hybrid_property
697 def user_data(self):
698 if not self._user_data:
699 return {}
700
701 try:
702 return json.loads(self._user_data)
703 except TypeError:
704 return {}
705
706 @user_data.setter
707 def user_data(self, val):
708 if not isinstance(val, dict):
709 raise Exception('user_data must be dict, got %s' % type(val))
710 try:
711 self._user_data = json.dumps(val)
712 except Exception:
713 log.error(traceback.format_exc())
714
715 @classmethod
716 def get_by_username(cls, username, case_insensitive=False,
717 cache=False, identity_cache=False):
718 session = Session()
719
720 if case_insensitive:
721 q = cls.query().filter(
722 func.lower(cls.username) == func.lower(username))
723 else:
724 q = cls.query().filter(cls.username == username)
725
726 if cache:
727 if identity_cache:
728 val = cls.identity_cache(session, 'username', username)
729 if val:
730 return val
731 else:
732 q = q.options(
733 FromCache("sql_cache_short",
734 "get_user_by_name_%s" % _hash_key(username)))
735
736 return q.scalar()
737
738 @classmethod
739 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
740 q = cls.query().filter(cls.api_key == auth_token)
741
742 if cache:
743 q = q.options(FromCache("sql_cache_short",
744 "get_auth_token_%s" % auth_token))
745 res = q.scalar()
746
747 if fallback and not res:
748 #fallback to additional keys
749 _res = UserApiKeys.query()\
750 .filter(UserApiKeys.api_key == auth_token)\
751 .filter(or_(UserApiKeys.expires == -1,
752 UserApiKeys.expires >= time.time()))\
753 .first()
754 if _res:
755 res = _res.user
756 return res
757
758 @classmethod
759 def get_by_email(cls, email, case_insensitive=False, cache=False):
760
761 if case_insensitive:
762 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
763
764 else:
765 q = cls.query().filter(cls.email == email)
766
767 if cache:
768 q = q.options(FromCache("sql_cache_short",
769 "get_email_key_%s" % _hash_key(email)))
770
771 ret = q.scalar()
772 if ret is None:
773 q = UserEmailMap.query()
774 # try fetching in alternate email map
775 if case_insensitive:
776 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
777 else:
778 q = q.filter(UserEmailMap.email == email)
779 q = q.options(joinedload(UserEmailMap.user))
780 if cache:
781 q = q.options(FromCache("sql_cache_short",
782 "get_email_map_key_%s" % email))
783 ret = getattr(q.scalar(), 'user', None)
784
785 return ret
786
787 @classmethod
788 def get_from_cs_author(cls, author):
789 """
790 Tries to get User objects out of commit author string
791
792 :param author:
793 """
794 from rhodecode.lib.helpers import email, author_name
795 # Valid email in the attribute passed, see if they're in the system
796 _email = email(author)
797 if _email:
798 user = cls.get_by_email(_email, case_insensitive=True)
799 if user:
800 return user
801 # Maybe we can match by username?
802 _author = author_name(author)
803 user = cls.get_by_username(_author, case_insensitive=True)
804 if user:
805 return user
806
807 def update_userdata(self, **kwargs):
808 usr = self
809 old = usr.user_data
810 old.update(**kwargs)
811 usr.user_data = old
812 Session().add(usr)
813 log.debug('updated userdata with ', kwargs)
814
815 def update_lastlogin(self):
816 """Update user lastlogin"""
817 self.last_login = datetime.datetime.now()
818 Session().add(self)
819 log.debug('updated user %s lastlogin', self.username)
820
821 def update_lastactivity(self):
822 """Update user lastactivity"""
823 usr = self
824 old = usr.user_data
825 old.update({'last_activity': time.time()})
826 usr.user_data = old
827 Session().add(usr)
828 log.debug('updated user %s lastactivity', usr.username)
829
830 def update_password(self, new_password, change_api_key=False):
831 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
832
833 self.password = get_crypt_password(new_password)
834 if change_api_key:
835 self.api_key = generate_auth_token(self.username)
836 Session().add(self)
837
838 @classmethod
839 def get_first_super_admin(cls):
840 user = User.query().filter(User.admin == true()).first()
841 if user is None:
842 raise Exception('FATAL: Missing administrative account!')
843 return user
844
845 @classmethod
846 def get_all_super_admins(cls):
847 """
848 Returns all admin accounts sorted by username
849 """
850 return User.query().filter(User.admin == true())\
851 .order_by(User.username.asc()).all()
852
853 @classmethod
854 def get_default_user(cls, cache=False):
855 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
856 if user is None:
857 raise Exception('FATAL: Missing default account!')
858 return user
859
860 def _get_default_perms(self, user, suffix=''):
861 from rhodecode.model.permission import PermissionModel
862 return PermissionModel().get_default_perms(user.user_perms, suffix)
863
864 def get_default_perms(self, suffix=''):
865 return self._get_default_perms(self, suffix)
866
867 def get_api_data(self, include_secrets=False, details='full'):
868 """
869 Common function for generating user related data for API
870
871 :param include_secrets: By default secrets in the API data will be replaced
872 by a placeholder value to prevent exposing this data by accident. In case
873 this data shall be exposed, set this flag to ``True``.
874
875 :param details: details can be 'basic|full' basic gives only a subset of
876 the available user information that includes user_id, name and emails.
877 """
878 user = self
879 user_data = self.user_data
880 data = {
881 'user_id': user.user_id,
882 'username': user.username,
883 'firstname': user.name,
884 'lastname': user.lastname,
885 'email': user.email,
886 'emails': user.emails,
887 }
888 if details == 'basic':
889 return data
890
891 api_key_length = 40
892 api_key_replacement = '*' * api_key_length
893
894 extras = {
895 'api_key': api_key_replacement,
896 'api_keys': [api_key_replacement],
897 'active': user.active,
898 'admin': user.admin,
899 'extern_type': user.extern_type,
900 'extern_name': user.extern_name,
901 'last_login': user.last_login,
902 'ip_addresses': user.ip_addresses,
903 'language': user_data.get('language')
904 }
905 data.update(extras)
906
907 if include_secrets:
908 data['api_key'] = user.api_key
909 data['api_keys'] = user.auth_tokens
910 return data
911
912 def __json__(self):
913 data = {
914 'full_name': self.full_name,
915 'full_name_or_username': self.full_name_or_username,
916 'short_contact': self.short_contact,
917 'full_contact': self.full_contact,
918 }
919 data.update(self.get_api_data())
920 return data
921
922
923 class UserApiKeys(Base, BaseModel):
924 __tablename__ = 'user_api_keys'
925 __table_args__ = (
926 Index('uak_api_key_idx', 'api_key'),
927 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
928 UniqueConstraint('api_key'),
929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
931 )
932 __mapper_args__ = {}
933
934 # ApiKey role
935 ROLE_ALL = 'token_role_all'
936 ROLE_HTTP = 'token_role_http'
937 ROLE_VCS = 'token_role_vcs'
938 ROLE_API = 'token_role_api'
939 ROLE_FEED = 'token_role_feed'
940 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
941
942 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
943 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
944 api_key = Column("api_key", String(255), nullable=False, unique=True)
945 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
946 expires = Column('expires', Float(53), nullable=False)
947 role = Column('role', String(255), nullable=True)
948 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
949
950 user = relationship('User', lazy='joined')
951
952 @classmethod
953 def _get_role_name(cls, role):
954 return {
955 cls.ROLE_ALL: _('all'),
956 cls.ROLE_HTTP: _('http/web interface'),
957 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
958 cls.ROLE_API: _('api calls'),
959 cls.ROLE_FEED: _('feed access'),
960 }.get(role, role)
961
962 @property
963 def expired(self):
964 if self.expires == -1:
965 return False
966 return time.time() > self.expires
967
968 @property
969 def role_humanized(self):
970 return self._get_role_name(self.role)
971
972
973 class UserEmailMap(Base, BaseModel):
974 __tablename__ = 'user_email_map'
975 __table_args__ = (
976 Index('uem_email_idx', 'email'),
977 UniqueConstraint('email'),
978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
979 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
980 )
981 __mapper_args__ = {}
982
983 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
985 _email = Column("email", String(255), nullable=True, unique=False, default=None)
986 user = relationship('User', lazy='joined')
987
988 @validates('_email')
989 def validate_email(self, key, email):
990 # check if this email is not main one
991 main_email = Session().query(User).filter(User.email == email).scalar()
992 if main_email is not None:
993 raise AttributeError('email %s is present is user table' % email)
994 return email
995
996 @hybrid_property
997 def email(self):
998 return self._email
999
1000 @email.setter
1001 def email(self, val):
1002 self._email = val.lower() if val else None
1003
1004
1005 class UserIpMap(Base, BaseModel):
1006 __tablename__ = 'user_ip_map'
1007 __table_args__ = (
1008 UniqueConstraint('user_id', 'ip_addr'),
1009 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1010 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1011 )
1012 __mapper_args__ = {}
1013
1014 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1015 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1016 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1017 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1018 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1019 user = relationship('User', lazy='joined')
1020
1021 @classmethod
1022 def _get_ip_range(cls, ip_addr):
1023 net = ipaddress.ip_network(ip_addr, strict=False)
1024 return [str(net.network_address), str(net.broadcast_address)]
1025
1026 def __json__(self):
1027 return {
1028 'ip_addr': self.ip_addr,
1029 'ip_range': self._get_ip_range(self.ip_addr),
1030 }
1031
1032 def __unicode__(self):
1033 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1034 self.user_id, self.ip_addr)
1035
1036 class UserLog(Base, BaseModel):
1037 __tablename__ = 'user_logs'
1038 __table_args__ = (
1039 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1040 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1041 )
1042 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1043 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1044 username = Column("username", String(255), nullable=True, unique=None, default=None)
1045 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1046 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1047 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1048 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1049 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1050
1051 def __unicode__(self):
1052 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1053 self.repository_name,
1054 self.action)
1055
1056 @property
1057 def action_as_day(self):
1058 return datetime.date(*self.action_date.timetuple()[:3])
1059
1060 user = relationship('User')
1061 repository = relationship('Repository', cascade='')
1062
1063
1064 class UserGroup(Base, BaseModel):
1065 __tablename__ = 'users_groups'
1066 __table_args__ = (
1067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1069 )
1070
1071 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1072 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1073 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1074 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1075 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1077 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1078 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1079
1080 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1081 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1082 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1083 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1084 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1085 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1086
1087 user = relationship('User')
1088
1089 @hybrid_property
1090 def group_data(self):
1091 if not self._group_data:
1092 return {}
1093
1094 try:
1095 return json.loads(self._group_data)
1096 except TypeError:
1097 return {}
1098
1099 @group_data.setter
1100 def group_data(self, val):
1101 try:
1102 self._group_data = json.dumps(val)
1103 except Exception:
1104 log.error(traceback.format_exc())
1105
1106 def __unicode__(self):
1107 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1108 self.users_group_id,
1109 self.users_group_name)
1110
1111 @classmethod
1112 def get_by_group_name(cls, group_name, cache=False,
1113 case_insensitive=False):
1114 if case_insensitive:
1115 q = cls.query().filter(func.lower(cls.users_group_name) ==
1116 func.lower(group_name))
1117
1118 else:
1119 q = cls.query().filter(cls.users_group_name == group_name)
1120 if cache:
1121 q = q.options(FromCache(
1122 "sql_cache_short",
1123 "get_group_%s" % _hash_key(group_name)))
1124 return q.scalar()
1125
1126 @classmethod
1127 def get(cls, user_group_id, cache=False):
1128 user_group = cls.query()
1129 if cache:
1130 user_group = user_group.options(FromCache("sql_cache_short",
1131 "get_users_group_%s" % user_group_id))
1132 return user_group.get(user_group_id)
1133
1134 def permissions(self, with_admins=True, with_owner=True):
1135 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1136 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1137 joinedload(UserUserGroupToPerm.user),
1138 joinedload(UserUserGroupToPerm.permission),)
1139
1140 # get owners and admins and permissions. We do a trick of re-writing
1141 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1142 # has a global reference and changing one object propagates to all
1143 # others. This means if admin is also an owner admin_row that change
1144 # would propagate to both objects
1145 perm_rows = []
1146 for _usr in q.all():
1147 usr = AttributeDict(_usr.user.get_dict())
1148 usr.permission = _usr.permission.permission_name
1149 perm_rows.append(usr)
1150
1151 # filter the perm rows by 'default' first and then sort them by
1152 # admin,write,read,none permissions sorted again alphabetically in
1153 # each group
1154 perm_rows = sorted(perm_rows, key=display_sort)
1155
1156 _admin_perm = 'usergroup.admin'
1157 owner_row = []
1158 if with_owner:
1159 usr = AttributeDict(self.user.get_dict())
1160 usr.owner_row = True
1161 usr.permission = _admin_perm
1162 owner_row.append(usr)
1163
1164 super_admin_rows = []
1165 if with_admins:
1166 for usr in User.get_all_super_admins():
1167 # if this admin is also owner, don't double the record
1168 if usr.user_id == owner_row[0].user_id:
1169 owner_row[0].admin_row = True
1170 else:
1171 usr = AttributeDict(usr.get_dict())
1172 usr.admin_row = True
1173 usr.permission = _admin_perm
1174 super_admin_rows.append(usr)
1175
1176 return super_admin_rows + owner_row + perm_rows
1177
1178 def permission_user_groups(self):
1179 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1180 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1181 joinedload(UserGroupUserGroupToPerm.target_user_group),
1182 joinedload(UserGroupUserGroupToPerm.permission),)
1183
1184 perm_rows = []
1185 for _user_group in q.all():
1186 usr = AttributeDict(_user_group.user_group.get_dict())
1187 usr.permission = _user_group.permission.permission_name
1188 perm_rows.append(usr)
1189
1190 return perm_rows
1191
1192 def _get_default_perms(self, user_group, suffix=''):
1193 from rhodecode.model.permission import PermissionModel
1194 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1195
1196 def get_default_perms(self, suffix=''):
1197 return self._get_default_perms(self, suffix)
1198
1199 def get_api_data(self, with_group_members=True, include_secrets=False):
1200 """
1201 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1202 basically forwarded.
1203
1204 """
1205 user_group = self
1206
1207 data = {
1208 'users_group_id': user_group.users_group_id,
1209 'group_name': user_group.users_group_name,
1210 'group_description': user_group.user_group_description,
1211 'active': user_group.users_group_active,
1212 'owner': user_group.user.username,
1213 }
1214 if with_group_members:
1215 users = []
1216 for user in user_group.members:
1217 user = user.user
1218 users.append(user.get_api_data(include_secrets=include_secrets))
1219 data['users'] = users
1220
1221 return data
1222
1223
1224 class UserGroupMember(Base, BaseModel):
1225 __tablename__ = 'users_groups_members'
1226 __table_args__ = (
1227 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1228 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1229 )
1230
1231 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1232 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1233 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1234
1235 user = relationship('User', lazy='joined')
1236 users_group = relationship('UserGroup')
1237
1238 def __init__(self, gr_id='', u_id=''):
1239 self.users_group_id = gr_id
1240 self.user_id = u_id
1241
1242
1243 class RepositoryField(Base, BaseModel):
1244 __tablename__ = 'repositories_fields'
1245 __table_args__ = (
1246 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1248 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1249 )
1250 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1251
1252 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1253 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1254 field_key = Column("field_key", String(250))
1255 field_label = Column("field_label", String(1024), nullable=False)
1256 field_value = Column("field_value", String(10000), nullable=False)
1257 field_desc = Column("field_desc", String(1024), nullable=False)
1258 field_type = Column("field_type", String(255), nullable=False, unique=None)
1259 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1260
1261 repository = relationship('Repository')
1262
1263 @property
1264 def field_key_prefixed(self):
1265 return 'ex_%s' % self.field_key
1266
1267 @classmethod
1268 def un_prefix_key(cls, key):
1269 if key.startswith(cls.PREFIX):
1270 return key[len(cls.PREFIX):]
1271 return key
1272
1273 @classmethod
1274 def get_by_key_name(cls, key, repo):
1275 row = cls.query()\
1276 .filter(cls.repository == repo)\
1277 .filter(cls.field_key == key).scalar()
1278 return row
1279
1280
1281 class Repository(Base, BaseModel):
1282 __tablename__ = 'repositories'
1283 __table_args__ = (
1284 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1287 )
1288 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1289 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1290
1291 STATE_CREATED = 'repo_state_created'
1292 STATE_PENDING = 'repo_state_pending'
1293 STATE_ERROR = 'repo_state_error'
1294
1295 LOCK_AUTOMATIC = 'lock_auto'
1296 LOCK_API = 'lock_api'
1297 LOCK_WEB = 'lock_web'
1298 LOCK_PULL = 'lock_pull'
1299
1300 NAME_SEP = URL_SEP
1301
1302 repo_id = Column(
1303 "repo_id", Integer(), nullable=False, unique=True, default=None,
1304 primary_key=True)
1305 _repo_name = Column(
1306 "repo_name", Text(), nullable=False, default=None)
1307 _repo_name_hash = Column(
1308 "repo_name_hash", String(255), nullable=False, unique=True)
1309 repo_state = Column("repo_state", String(255), nullable=True)
1310
1311 clone_uri = Column(
1312 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1313 default=None)
1314 repo_type = Column(
1315 "repo_type", String(255), nullable=False, unique=False, default=None)
1316 user_id = Column(
1317 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1318 unique=False, default=None)
1319 private = Column(
1320 "private", Boolean(), nullable=True, unique=None, default=None)
1321 enable_statistics = Column(
1322 "statistics", Boolean(), nullable=True, unique=None, default=True)
1323 enable_downloads = Column(
1324 "downloads", Boolean(), nullable=True, unique=None, default=True)
1325 description = Column(
1326 "description", String(10000), nullable=True, unique=None, default=None)
1327 created_on = Column(
1328 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1329 default=datetime.datetime.now)
1330 updated_on = Column(
1331 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1332 default=datetime.datetime.now)
1333 _landing_revision = Column(
1334 "landing_revision", String(255), nullable=False, unique=False,
1335 default=None)
1336 enable_locking = Column(
1337 "enable_locking", Boolean(), nullable=False, unique=None,
1338 default=False)
1339 _locked = Column(
1340 "locked", String(255), nullable=True, unique=False, default=None)
1341 _changeset_cache = Column(
1342 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1343
1344 fork_id = Column(
1345 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1346 nullable=True, unique=False, default=None)
1347 group_id = Column(
1348 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1349 unique=False, default=None)
1350
1351 user = relationship('User', lazy='joined')
1352 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1353 group = relationship('RepoGroup', lazy='joined')
1354 repo_to_perm = relationship(
1355 'UserRepoToPerm', cascade='all',
1356 order_by='UserRepoToPerm.repo_to_perm_id')
1357 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1358 stats = relationship('Statistics', cascade='all', uselist=False)
1359
1360 followers = relationship(
1361 'UserFollowing',
1362 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1363 cascade='all')
1364 extra_fields = relationship(
1365 'RepositoryField', cascade="all, delete, delete-orphan")
1366 logs = relationship('UserLog')
1367 comments = relationship(
1368 'ChangesetComment', cascade="all, delete, delete-orphan")
1369 pull_requests_source = relationship(
1370 'PullRequest',
1371 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1372 cascade="all, delete, delete-orphan")
1373 pull_requests_target = relationship(
1374 'PullRequest',
1375 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1376 cascade="all, delete, delete-orphan")
1377 ui = relationship('RepoRhodeCodeUi', cascade="all")
1378 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1379 integrations = relationship('Integration',
1380 cascade="all, delete, delete-orphan")
1381
1382 def __unicode__(self):
1383 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1384 safe_unicode(self.repo_name))
1385
1386 @hybrid_property
1387 def landing_rev(self):
1388 # always should return [rev_type, rev]
1389 if self._landing_revision:
1390 _rev_info = self._landing_revision.split(':')
1391 if len(_rev_info) < 2:
1392 _rev_info.insert(0, 'rev')
1393 return [_rev_info[0], _rev_info[1]]
1394 return [None, None]
1395
1396 @landing_rev.setter
1397 def landing_rev(self, val):
1398 if ':' not in val:
1399 raise ValueError('value must be delimited with `:` and consist '
1400 'of <rev_type>:<rev>, got %s instead' % val)
1401 self._landing_revision = val
1402
1403 @hybrid_property
1404 def locked(self):
1405 if self._locked:
1406 user_id, timelocked, reason = self._locked.split(':')
1407 lock_values = int(user_id), timelocked, reason
1408 else:
1409 lock_values = [None, None, None]
1410 return lock_values
1411
1412 @locked.setter
1413 def locked(self, val):
1414 if val and isinstance(val, (list, tuple)):
1415 self._locked = ':'.join(map(str, val))
1416 else:
1417 self._locked = None
1418
1419 @hybrid_property
1420 def changeset_cache(self):
1421 from rhodecode.lib.vcs.backends.base import EmptyCommit
1422 dummy = EmptyCommit().__json__()
1423 if not self._changeset_cache:
1424 return dummy
1425 try:
1426 return json.loads(self._changeset_cache)
1427 except TypeError:
1428 return dummy
1429 except Exception:
1430 log.error(traceback.format_exc())
1431 return dummy
1432
1433 @changeset_cache.setter
1434 def changeset_cache(self, val):
1435 try:
1436 self._changeset_cache = json.dumps(val)
1437 except Exception:
1438 log.error(traceback.format_exc())
1439
1440 @hybrid_property
1441 def repo_name(self):
1442 return self._repo_name
1443
1444 @repo_name.setter
1445 def repo_name(self, value):
1446 self._repo_name = value
1447 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1448
1449 @classmethod
1450 def normalize_repo_name(cls, repo_name):
1451 """
1452 Normalizes os specific repo_name to the format internally stored inside
1453 database using URL_SEP
1454
1455 :param cls:
1456 :param repo_name:
1457 """
1458 return cls.NAME_SEP.join(repo_name.split(os.sep))
1459
1460 @classmethod
1461 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1462 session = Session()
1463 q = session.query(cls).filter(cls.repo_name == repo_name)
1464
1465 if cache:
1466 if identity_cache:
1467 val = cls.identity_cache(session, 'repo_name', repo_name)
1468 if val:
1469 return val
1470 else:
1471 q = q.options(
1472 FromCache("sql_cache_short",
1473 "get_repo_by_name_%s" % _hash_key(repo_name)))
1474
1475 return q.scalar()
1476
1477 @classmethod
1478 def get_by_full_path(cls, repo_full_path):
1479 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1480 repo_name = cls.normalize_repo_name(repo_name)
1481 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1482
1483 @classmethod
1484 def get_repo_forks(cls, repo_id):
1485 return cls.query().filter(Repository.fork_id == repo_id)
1486
1487 @classmethod
1488 def base_path(cls):
1489 """
1490 Returns base path when all repos are stored
1491
1492 :param cls:
1493 """
1494 q = Session().query(RhodeCodeUi)\
1495 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1496 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1497 return q.one().ui_value
1498
1499 @classmethod
1500 def is_valid(cls, repo_name):
1501 """
1502 returns True if given repo name is a valid filesystem repository
1503
1504 :param cls:
1505 :param repo_name:
1506 """
1507 from rhodecode.lib.utils import is_valid_repo
1508
1509 return is_valid_repo(repo_name, cls.base_path())
1510
1511 @classmethod
1512 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1513 case_insensitive=True):
1514 q = Repository.query()
1515
1516 if not isinstance(user_id, Optional):
1517 q = q.filter(Repository.user_id == user_id)
1518
1519 if not isinstance(group_id, Optional):
1520 q = q.filter(Repository.group_id == group_id)
1521
1522 if case_insensitive:
1523 q = q.order_by(func.lower(Repository.repo_name))
1524 else:
1525 q = q.order_by(Repository.repo_name)
1526 return q.all()
1527
1528 @property
1529 def forks(self):
1530 """
1531 Return forks of this repo
1532 """
1533 return Repository.get_repo_forks(self.repo_id)
1534
1535 @property
1536 def parent(self):
1537 """
1538 Returns fork parent
1539 """
1540 return self.fork
1541
1542 @property
1543 def just_name(self):
1544 return self.repo_name.split(self.NAME_SEP)[-1]
1545
1546 @property
1547 def groups_with_parents(self):
1548 groups = []
1549 if self.group is None:
1550 return groups
1551
1552 cur_gr = self.group
1553 groups.insert(0, cur_gr)
1554 while 1:
1555 gr = getattr(cur_gr, 'parent_group', None)
1556 cur_gr = cur_gr.parent_group
1557 if gr is None:
1558 break
1559 groups.insert(0, gr)
1560
1561 return groups
1562
1563 @property
1564 def groups_and_repo(self):
1565 return self.groups_with_parents, self
1566
1567 @LazyProperty
1568 def repo_path(self):
1569 """
1570 Returns base full path for that repository means where it actually
1571 exists on a filesystem
1572 """
1573 q = Session().query(RhodeCodeUi).filter(
1574 RhodeCodeUi.ui_key == self.NAME_SEP)
1575 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1576 return q.one().ui_value
1577
1578 @property
1579 def repo_full_path(self):
1580 p = [self.repo_path]
1581 # we need to split the name by / since this is how we store the
1582 # names in the database, but that eventually needs to be converted
1583 # into a valid system path
1584 p += self.repo_name.split(self.NAME_SEP)
1585 return os.path.join(*map(safe_unicode, p))
1586
1587 @property
1588 def cache_keys(self):
1589 """
1590 Returns associated cache keys for that repo
1591 """
1592 return CacheKey.query()\
1593 .filter(CacheKey.cache_args == self.repo_name)\
1594 .order_by(CacheKey.cache_key)\
1595 .all()
1596
1597 def get_new_name(self, repo_name):
1598 """
1599 returns new full repository name based on assigned group and new new
1600
1601 :param group_name:
1602 """
1603 path_prefix = self.group.full_path_splitted if self.group else []
1604 return self.NAME_SEP.join(path_prefix + [repo_name])
1605
1606 @property
1607 def _config(self):
1608 """
1609 Returns db based config object.
1610 """
1611 from rhodecode.lib.utils import make_db_config
1612 return make_db_config(clear_session=False, repo=self)
1613
1614 def permissions(self, with_admins=True, with_owner=True):
1615 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1616 q = q.options(joinedload(UserRepoToPerm.repository),
1617 joinedload(UserRepoToPerm.user),
1618 joinedload(UserRepoToPerm.permission),)
1619
1620 # get owners and admins and permissions. We do a trick of re-writing
1621 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1622 # has a global reference and changing one object propagates to all
1623 # others. This means if admin is also an owner admin_row that change
1624 # would propagate to both objects
1625 perm_rows = []
1626 for _usr in q.all():
1627 usr = AttributeDict(_usr.user.get_dict())
1628 usr.permission = _usr.permission.permission_name
1629 perm_rows.append(usr)
1630
1631 # filter the perm rows by 'default' first and then sort them by
1632 # admin,write,read,none permissions sorted again alphabetically in
1633 # each group
1634 perm_rows = sorted(perm_rows, key=display_sort)
1635
1636 _admin_perm = 'repository.admin'
1637 owner_row = []
1638 if with_owner:
1639 usr = AttributeDict(self.user.get_dict())
1640 usr.owner_row = True
1641 usr.permission = _admin_perm
1642 owner_row.append(usr)
1643
1644 super_admin_rows = []
1645 if with_admins:
1646 for usr in User.get_all_super_admins():
1647 # if this admin is also owner, don't double the record
1648 if usr.user_id == owner_row[0].user_id:
1649 owner_row[0].admin_row = True
1650 else:
1651 usr = AttributeDict(usr.get_dict())
1652 usr.admin_row = True
1653 usr.permission = _admin_perm
1654 super_admin_rows.append(usr)
1655
1656 return super_admin_rows + owner_row + perm_rows
1657
1658 def permission_user_groups(self):
1659 q = UserGroupRepoToPerm.query().filter(
1660 UserGroupRepoToPerm.repository == self)
1661 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1662 joinedload(UserGroupRepoToPerm.users_group),
1663 joinedload(UserGroupRepoToPerm.permission),)
1664
1665 perm_rows = []
1666 for _user_group in q.all():
1667 usr = AttributeDict(_user_group.users_group.get_dict())
1668 usr.permission = _user_group.permission.permission_name
1669 perm_rows.append(usr)
1670
1671 return perm_rows
1672
1673 def get_api_data(self, include_secrets=False):
1674 """
1675 Common function for generating repo api data
1676
1677 :param include_secrets: See :meth:`User.get_api_data`.
1678
1679 """
1680 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1681 # move this methods on models level.
1682 from rhodecode.model.settings import SettingsModel
1683
1684 repo = self
1685 _user_id, _time, _reason = self.locked
1686
1687 data = {
1688 'repo_id': repo.repo_id,
1689 'repo_name': repo.repo_name,
1690 'repo_type': repo.repo_type,
1691 'clone_uri': repo.clone_uri or '',
1692 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1693 'private': repo.private,
1694 'created_on': repo.created_on,
1695 'description': repo.description,
1696 'landing_rev': repo.landing_rev,
1697 'owner': repo.user.username,
1698 'fork_of': repo.fork.repo_name if repo.fork else None,
1699 'enable_statistics': repo.enable_statistics,
1700 'enable_locking': repo.enable_locking,
1701 'enable_downloads': repo.enable_downloads,
1702 'last_changeset': repo.changeset_cache,
1703 'locked_by': User.get(_user_id).get_api_data(
1704 include_secrets=include_secrets) if _user_id else None,
1705 'locked_date': time_to_datetime(_time) if _time else None,
1706 'lock_reason': _reason if _reason else None,
1707 }
1708
1709 # TODO: mikhail: should be per-repo settings here
1710 rc_config = SettingsModel().get_all_settings()
1711 repository_fields = str2bool(
1712 rc_config.get('rhodecode_repository_fields'))
1713 if repository_fields:
1714 for f in self.extra_fields:
1715 data[f.field_key_prefixed] = f.field_value
1716
1717 return data
1718
1719 @classmethod
1720 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1721 if not lock_time:
1722 lock_time = time.time()
1723 if not lock_reason:
1724 lock_reason = cls.LOCK_AUTOMATIC
1725 repo.locked = [user_id, lock_time, lock_reason]
1726 Session().add(repo)
1727 Session().commit()
1728
1729 @classmethod
1730 def unlock(cls, repo):
1731 repo.locked = None
1732 Session().add(repo)
1733 Session().commit()
1734
1735 @classmethod
1736 def getlock(cls, repo):
1737 return repo.locked
1738
1739 def is_user_lock(self, user_id):
1740 if self.lock[0]:
1741 lock_user_id = safe_int(self.lock[0])
1742 user_id = safe_int(user_id)
1743 # both are ints, and they are equal
1744 return all([lock_user_id, user_id]) and lock_user_id == user_id
1745
1746 return False
1747
1748 def get_locking_state(self, action, user_id, only_when_enabled=True):
1749 """
1750 Checks locking on this repository, if locking is enabled and lock is
1751 present returns a tuple of make_lock, locked, locked_by.
1752 make_lock can have 3 states None (do nothing) True, make lock
1753 False release lock, This value is later propagated to hooks, which
1754 do the locking. Think about this as signals passed to hooks what to do.
1755
1756 """
1757 # TODO: johbo: This is part of the business logic and should be moved
1758 # into the RepositoryModel.
1759
1760 if action not in ('push', 'pull'):
1761 raise ValueError("Invalid action value: %s" % repr(action))
1762
1763 # defines if locked error should be thrown to user
1764 currently_locked = False
1765 # defines if new lock should be made, tri-state
1766 make_lock = None
1767 repo = self
1768 user = User.get(user_id)
1769
1770 lock_info = repo.locked
1771
1772 if repo and (repo.enable_locking or not only_when_enabled):
1773 if action == 'push':
1774 # check if it's already locked !, if it is compare users
1775 locked_by_user_id = lock_info[0]
1776 if user.user_id == locked_by_user_id:
1777 log.debug(
1778 'Got `push` action from user %s, now unlocking', user)
1779 # unlock if we have push from user who locked
1780 make_lock = False
1781 else:
1782 # we're not the same user who locked, ban with
1783 # code defined in settings (default is 423 HTTP Locked) !
1784 log.debug('Repo %s is currently locked by %s', repo, user)
1785 currently_locked = True
1786 elif action == 'pull':
1787 # [0] user [1] date
1788 if lock_info[0] and lock_info[1]:
1789 log.debug('Repo %s is currently locked by %s', repo, user)
1790 currently_locked = True
1791 else:
1792 log.debug('Setting lock on repo %s by %s', repo, user)
1793 make_lock = True
1794
1795 else:
1796 log.debug('Repository %s do not have locking enabled', repo)
1797
1798 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1799 make_lock, currently_locked, lock_info)
1800
1801 from rhodecode.lib.auth import HasRepoPermissionAny
1802 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1803 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1804 # if we don't have at least write permission we cannot make a lock
1805 log.debug('lock state reset back to FALSE due to lack '
1806 'of at least read permission')
1807 make_lock = False
1808
1809 return make_lock, currently_locked, lock_info
1810
1811 @property
1812 def last_db_change(self):
1813 return self.updated_on
1814
1815 @property
1816 def clone_uri_hidden(self):
1817 clone_uri = self.clone_uri
1818 if clone_uri:
1819 import urlobject
1820 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1821 if url_obj.password:
1822 clone_uri = url_obj.with_password('*****')
1823 return clone_uri
1824
1825 def clone_url(self, **override):
1826 qualified_home_url = url('home', qualified=True)
1827
1828 uri_tmpl = None
1829 if 'with_id' in override:
1830 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1831 del override['with_id']
1832
1833 if 'uri_tmpl' in override:
1834 uri_tmpl = override['uri_tmpl']
1835 del override['uri_tmpl']
1836
1837 # we didn't override our tmpl from **overrides
1838 if not uri_tmpl:
1839 uri_tmpl = self.DEFAULT_CLONE_URI
1840 try:
1841 from pylons import tmpl_context as c
1842 uri_tmpl = c.clone_uri_tmpl
1843 except Exception:
1844 # in any case if we call this outside of request context,
1845 # ie, not having tmpl_context set up
1846 pass
1847
1848 return get_clone_url(uri_tmpl=uri_tmpl,
1849 qualifed_home_url=qualified_home_url,
1850 repo_name=self.repo_name,
1851 repo_id=self.repo_id, **override)
1852
1853 def set_state(self, state):
1854 self.repo_state = state
1855 Session().add(self)
1856 #==========================================================================
1857 # SCM PROPERTIES
1858 #==========================================================================
1859
1860 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1861 return get_commit_safe(
1862 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1863
1864 def get_changeset(self, rev=None, pre_load=None):
1865 warnings.warn("Use get_commit", DeprecationWarning)
1866 commit_id = None
1867 commit_idx = None
1868 if isinstance(rev, basestring):
1869 commit_id = rev
1870 else:
1871 commit_idx = rev
1872 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1873 pre_load=pre_load)
1874
1875 def get_landing_commit(self):
1876 """
1877 Returns landing commit, or if that doesn't exist returns the tip
1878 """
1879 _rev_type, _rev = self.landing_rev
1880 commit = self.get_commit(_rev)
1881 if isinstance(commit, EmptyCommit):
1882 return self.get_commit()
1883 return commit
1884
1885 def update_commit_cache(self, cs_cache=None, config=None):
1886 """
1887 Update cache of last changeset for repository, keys should be::
1888
1889 short_id
1890 raw_id
1891 revision
1892 parents
1893 message
1894 date
1895 author
1896
1897 :param cs_cache:
1898 """
1899 from rhodecode.lib.vcs.backends.base import BaseChangeset
1900 if cs_cache is None:
1901 # use no-cache version here
1902 scm_repo = self.scm_instance(cache=False, config=config)
1903 if scm_repo:
1904 cs_cache = scm_repo.get_commit(
1905 pre_load=["author", "date", "message", "parents"])
1906 else:
1907 cs_cache = EmptyCommit()
1908
1909 if isinstance(cs_cache, BaseChangeset):
1910 cs_cache = cs_cache.__json__()
1911
1912 def is_outdated(new_cs_cache):
1913 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1914 new_cs_cache['revision'] != self.changeset_cache['revision']):
1915 return True
1916 return False
1917
1918 # check if we have maybe already latest cached revision
1919 if is_outdated(cs_cache) or not self.changeset_cache:
1920 _default = datetime.datetime.fromtimestamp(0)
1921 last_change = cs_cache.get('date') or _default
1922 log.debug('updated repo %s with new cs cache %s',
1923 self.repo_name, cs_cache)
1924 self.updated_on = last_change
1925 self.changeset_cache = cs_cache
1926 Session().add(self)
1927 Session().commit()
1928 else:
1929 log.debug('Skipping update_commit_cache for repo:`%s` '
1930 'commit already with latest changes', self.repo_name)
1931
1932 @property
1933 def tip(self):
1934 return self.get_commit('tip')
1935
1936 @property
1937 def author(self):
1938 return self.tip.author
1939
1940 @property
1941 def last_change(self):
1942 return self.scm_instance().last_change
1943
1944 def get_comments(self, revisions=None):
1945 """
1946 Returns comments for this repository grouped by revisions
1947
1948 :param revisions: filter query by revisions only
1949 """
1950 cmts = ChangesetComment.query()\
1951 .filter(ChangesetComment.repo == self)
1952 if revisions:
1953 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1954 grouped = collections.defaultdict(list)
1955 for cmt in cmts.all():
1956 grouped[cmt.revision].append(cmt)
1957 return grouped
1958
1959 def statuses(self, revisions=None):
1960 """
1961 Returns statuses for this repository
1962
1963 :param revisions: list of revisions to get statuses for
1964 """
1965 statuses = ChangesetStatus.query()\
1966 .filter(ChangesetStatus.repo == self)\
1967 .filter(ChangesetStatus.version == 0)
1968
1969 if revisions:
1970 # Try doing the filtering in chunks to avoid hitting limits
1971 size = 500
1972 status_results = []
1973 for chunk in xrange(0, len(revisions), size):
1974 status_results += statuses.filter(
1975 ChangesetStatus.revision.in_(
1976 revisions[chunk: chunk+size])
1977 ).all()
1978 else:
1979 status_results = statuses.all()
1980
1981 grouped = {}
1982
1983 # maybe we have open new pullrequest without a status?
1984 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1985 status_lbl = ChangesetStatus.get_status_lbl(stat)
1986 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1987 for rev in pr.revisions:
1988 pr_id = pr.pull_request_id
1989 pr_repo = pr.target_repo.repo_name
1990 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1991
1992 for stat in status_results:
1993 pr_id = pr_repo = None
1994 if stat.pull_request:
1995 pr_id = stat.pull_request.pull_request_id
1996 pr_repo = stat.pull_request.target_repo.repo_name
1997 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1998 pr_id, pr_repo]
1999 return grouped
2000
2001 # ==========================================================================
2002 # SCM CACHE INSTANCE
2003 # ==========================================================================
2004
2005 def scm_instance(self, **kwargs):
2006 import rhodecode
2007
2008 # Passing a config will not hit the cache currently only used
2009 # for repo2dbmapper
2010 config = kwargs.pop('config', None)
2011 cache = kwargs.pop('cache', None)
2012 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2013 # if cache is NOT defined use default global, else we have a full
2014 # control over cache behaviour
2015 if cache is None and full_cache and not config:
2016 return self._get_instance_cached()
2017 return self._get_instance(cache=bool(cache), config=config)
2018
2019 def _get_instance_cached(self):
2020 @cache_region('long_term')
2021 def _get_repo(cache_key):
2022 return self._get_instance()
2023
2024 invalidator_context = CacheKey.repo_context_cache(
2025 _get_repo, self.repo_name, None, thread_scoped=True)
2026
2027 with invalidator_context as context:
2028 context.invalidate()
2029 repo = context.compute()
2030
2031 return repo
2032
2033 def _get_instance(self, cache=True, config=None):
2034 config = config or self._config
2035 custom_wire = {
2036 'cache': cache # controls the vcs.remote cache
2037 }
2038 repo = get_vcs_instance(
2039 repo_path=safe_str(self.repo_full_path),
2040 config=config,
2041 with_wire=custom_wire,
2042 create=False,
2043 _vcs_alias=self.repo_type)
2044
2045 return repo
2046
2047 def __json__(self):
2048 return {'landing_rev': self.landing_rev}
2049
2050 def get_dict(self):
2051
2052 # Since we transformed `repo_name` to a hybrid property, we need to
2053 # keep compatibility with the code which uses `repo_name` field.
2054
2055 result = super(Repository, self).get_dict()
2056 result['repo_name'] = result.pop('_repo_name', None)
2057 return result
2058
2059
2060 class RepoGroup(Base, BaseModel):
2061 __tablename__ = 'groups'
2062 __table_args__ = (
2063 UniqueConstraint('group_name', 'group_parent_id'),
2064 CheckConstraint('group_id != group_parent_id'),
2065 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2066 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2067 )
2068 __mapper_args__ = {'order_by': 'group_name'}
2069
2070 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2071
2072 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2073 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2074 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2075 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2076 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2077 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2078 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2079 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2080
2081 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2082 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2083 parent_group = relationship('RepoGroup', remote_side=group_id)
2084 user = relationship('User')
2085 integrations = relationship('Integration',
2086 cascade="all, delete, delete-orphan")
2087
2088 def __init__(self, group_name='', parent_group=None):
2089 self.group_name = group_name
2090 self.parent_group = parent_group
2091
2092 def __unicode__(self):
2093 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2094 self.group_name)
2095
2096 @classmethod
2097 def _generate_choice(cls, repo_group):
2098 from webhelpers.html import literal as _literal
2099 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2100 return repo_group.group_id, _name(repo_group.full_path_splitted)
2101
2102 @classmethod
2103 def groups_choices(cls, groups=None, show_empty_group=True):
2104 if not groups:
2105 groups = cls.query().all()
2106
2107 repo_groups = []
2108 if show_empty_group:
2109 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2110
2111 repo_groups.extend([cls._generate_choice(x) for x in groups])
2112
2113 repo_groups = sorted(
2114 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2115 return repo_groups
2116
2117 @classmethod
2118 def url_sep(cls):
2119 return URL_SEP
2120
2121 @classmethod
2122 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2123 if case_insensitive:
2124 gr = cls.query().filter(func.lower(cls.group_name)
2125 == func.lower(group_name))
2126 else:
2127 gr = cls.query().filter(cls.group_name == group_name)
2128 if cache:
2129 gr = gr.options(FromCache(
2130 "sql_cache_short",
2131 "get_group_%s" % _hash_key(group_name)))
2132 return gr.scalar()
2133
2134 @classmethod
2135 def get_user_personal_repo_group(cls, user_id):
2136 user = User.get(user_id)
2137 return cls.query()\
2138 .filter(cls.personal == true())\
2139 .filter(cls.user == user).scalar()
2140
2141 @classmethod
2142 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2143 case_insensitive=True):
2144 q = RepoGroup.query()
2145
2146 if not isinstance(user_id, Optional):
2147 q = q.filter(RepoGroup.user_id == user_id)
2148
2149 if not isinstance(group_id, Optional):
2150 q = q.filter(RepoGroup.group_parent_id == group_id)
2151
2152 if case_insensitive:
2153 q = q.order_by(func.lower(RepoGroup.group_name))
2154 else:
2155 q = q.order_by(RepoGroup.group_name)
2156 return q.all()
2157
2158 @property
2159 def parents(self):
2160 parents_recursion_limit = 10
2161 groups = []
2162 if self.parent_group is None:
2163 return groups
2164 cur_gr = self.parent_group
2165 groups.insert(0, cur_gr)
2166 cnt = 0
2167 while 1:
2168 cnt += 1
2169 gr = getattr(cur_gr, 'parent_group', None)
2170 cur_gr = cur_gr.parent_group
2171 if gr is None:
2172 break
2173 if cnt == parents_recursion_limit:
2174 # this will prevent accidental infinit loops
2175 log.error(('more than %s parents found for group %s, stopping '
2176 'recursive parent fetching' % (parents_recursion_limit, self)))
2177 break
2178
2179 groups.insert(0, gr)
2180 return groups
2181
2182 @property
2183 def children(self):
2184 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2185
2186 @property
2187 def name(self):
2188 return self.group_name.split(RepoGroup.url_sep())[-1]
2189
2190 @property
2191 def full_path(self):
2192 return self.group_name
2193
2194 @property
2195 def full_path_splitted(self):
2196 return self.group_name.split(RepoGroup.url_sep())
2197
2198 @property
2199 def repositories(self):
2200 return Repository.query()\
2201 .filter(Repository.group == self)\
2202 .order_by(Repository.repo_name)
2203
2204 @property
2205 def repositories_recursive_count(self):
2206 cnt = self.repositories.count()
2207
2208 def children_count(group):
2209 cnt = 0
2210 for child in group.children:
2211 cnt += child.repositories.count()
2212 cnt += children_count(child)
2213 return cnt
2214
2215 return cnt + children_count(self)
2216
2217 def _recursive_objects(self, include_repos=True):
2218 all_ = []
2219
2220 def _get_members(root_gr):
2221 if include_repos:
2222 for r in root_gr.repositories:
2223 all_.append(r)
2224 childs = root_gr.children.all()
2225 if childs:
2226 for gr in childs:
2227 all_.append(gr)
2228 _get_members(gr)
2229
2230 _get_members(self)
2231 return [self] + all_
2232
2233 def recursive_groups_and_repos(self):
2234 """
2235 Recursive return all groups, with repositories in those groups
2236 """
2237 return self._recursive_objects()
2238
2239 def recursive_groups(self):
2240 """
2241 Returns all children groups for this group including children of children
2242 """
2243 return self._recursive_objects(include_repos=False)
2244
2245 def get_new_name(self, group_name):
2246 """
2247 returns new full group name based on parent and new name
2248
2249 :param group_name:
2250 """
2251 path_prefix = (self.parent_group.full_path_splitted if
2252 self.parent_group else [])
2253 return RepoGroup.url_sep().join(path_prefix + [group_name])
2254
2255 def permissions(self, with_admins=True, with_owner=True):
2256 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2257 q = q.options(joinedload(UserRepoGroupToPerm.group),
2258 joinedload(UserRepoGroupToPerm.user),
2259 joinedload(UserRepoGroupToPerm.permission),)
2260
2261 # get owners and admins and permissions. We do a trick of re-writing
2262 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2263 # has a global reference and changing one object propagates to all
2264 # others. This means if admin is also an owner admin_row that change
2265 # would propagate to both objects
2266 perm_rows = []
2267 for _usr in q.all():
2268 usr = AttributeDict(_usr.user.get_dict())
2269 usr.permission = _usr.permission.permission_name
2270 perm_rows.append(usr)
2271
2272 # filter the perm rows by 'default' first and then sort them by
2273 # admin,write,read,none permissions sorted again alphabetically in
2274 # each group
2275 perm_rows = sorted(perm_rows, key=display_sort)
2276
2277 _admin_perm = 'group.admin'
2278 owner_row = []
2279 if with_owner:
2280 usr = AttributeDict(self.user.get_dict())
2281 usr.owner_row = True
2282 usr.permission = _admin_perm
2283 owner_row.append(usr)
2284
2285 super_admin_rows = []
2286 if with_admins:
2287 for usr in User.get_all_super_admins():
2288 # if this admin is also owner, don't double the record
2289 if usr.user_id == owner_row[0].user_id:
2290 owner_row[0].admin_row = True
2291 else:
2292 usr = AttributeDict(usr.get_dict())
2293 usr.admin_row = True
2294 usr.permission = _admin_perm
2295 super_admin_rows.append(usr)
2296
2297 return super_admin_rows + owner_row + perm_rows
2298
2299 def permission_user_groups(self):
2300 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2301 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2302 joinedload(UserGroupRepoGroupToPerm.users_group),
2303 joinedload(UserGroupRepoGroupToPerm.permission),)
2304
2305 perm_rows = []
2306 for _user_group in q.all():
2307 usr = AttributeDict(_user_group.users_group.get_dict())
2308 usr.permission = _user_group.permission.permission_name
2309 perm_rows.append(usr)
2310
2311 return perm_rows
2312
2313 def get_api_data(self):
2314 """
2315 Common function for generating api data
2316
2317 """
2318 group = self
2319 data = {
2320 'group_id': group.group_id,
2321 'group_name': group.group_name,
2322 'group_description': group.group_description,
2323 'parent_group': group.parent_group.group_name if group.parent_group else None,
2324 'repositories': [x.repo_name for x in group.repositories],
2325 'owner': group.user.username,
2326 }
2327 return data
2328
2329
2330 class Permission(Base, BaseModel):
2331 __tablename__ = 'permissions'
2332 __table_args__ = (
2333 Index('p_perm_name_idx', 'permission_name'),
2334 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2335 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2336 )
2337 PERMS = [
2338 ('hg.admin', _('RhodeCode Super Administrator')),
2339
2340 ('repository.none', _('Repository no access')),
2341 ('repository.read', _('Repository read access')),
2342 ('repository.write', _('Repository write access')),
2343 ('repository.admin', _('Repository admin access')),
2344
2345 ('group.none', _('Repository group no access')),
2346 ('group.read', _('Repository group read access')),
2347 ('group.write', _('Repository group write access')),
2348 ('group.admin', _('Repository group admin access')),
2349
2350 ('usergroup.none', _('User group no access')),
2351 ('usergroup.read', _('User group read access')),
2352 ('usergroup.write', _('User group write access')),
2353 ('usergroup.admin', _('User group admin access')),
2354
2355 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2356 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2357
2358 ('hg.usergroup.create.false', _('User Group creation disabled')),
2359 ('hg.usergroup.create.true', _('User Group creation enabled')),
2360
2361 ('hg.create.none', _('Repository creation disabled')),
2362 ('hg.create.repository', _('Repository creation enabled')),
2363 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2364 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2365
2366 ('hg.fork.none', _('Repository forking disabled')),
2367 ('hg.fork.repository', _('Repository forking enabled')),
2368
2369 ('hg.register.none', _('Registration disabled')),
2370 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2371 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2372
2373 ('hg.password_reset.enabled', _('Password reset enabled')),
2374 ('hg.password_reset.hidden', _('Password reset hidden')),
2375 ('hg.password_reset.disabled', _('Password reset disabled')),
2376
2377 ('hg.extern_activate.manual', _('Manual activation of external account')),
2378 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2379
2380 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2381 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2382 ]
2383
2384 # definition of system default permissions for DEFAULT user
2385 DEFAULT_USER_PERMISSIONS = [
2386 'repository.read',
2387 'group.read',
2388 'usergroup.read',
2389 'hg.create.repository',
2390 'hg.repogroup.create.false',
2391 'hg.usergroup.create.false',
2392 'hg.create.write_on_repogroup.true',
2393 'hg.fork.repository',
2394 'hg.register.manual_activate',
2395 'hg.password_reset.enabled',
2396 'hg.extern_activate.auto',
2397 'hg.inherit_default_perms.true',
2398 ]
2399
2400 # defines which permissions are more important higher the more important
2401 # Weight defines which permissions are more important.
2402 # The higher number the more important.
2403 PERM_WEIGHTS = {
2404 'repository.none': 0,
2405 'repository.read': 1,
2406 'repository.write': 3,
2407 'repository.admin': 4,
2408
2409 'group.none': 0,
2410 'group.read': 1,
2411 'group.write': 3,
2412 'group.admin': 4,
2413
2414 'usergroup.none': 0,
2415 'usergroup.read': 1,
2416 'usergroup.write': 3,
2417 'usergroup.admin': 4,
2418
2419 'hg.repogroup.create.false': 0,
2420 'hg.repogroup.create.true': 1,
2421
2422 'hg.usergroup.create.false': 0,
2423 'hg.usergroup.create.true': 1,
2424
2425 'hg.fork.none': 0,
2426 'hg.fork.repository': 1,
2427 'hg.create.none': 0,
2428 'hg.create.repository': 1
2429 }
2430
2431 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2432 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2433 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2434
2435 def __unicode__(self):
2436 return u"<%s('%s:%s')>" % (
2437 self.__class__.__name__, self.permission_id, self.permission_name
2438 )
2439
2440 @classmethod
2441 def get_by_key(cls, key):
2442 return cls.query().filter(cls.permission_name == key).scalar()
2443
2444 @classmethod
2445 def get_default_repo_perms(cls, user_id, repo_id=None):
2446 q = Session().query(UserRepoToPerm, Repository, Permission)\
2447 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2448 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2449 .filter(UserRepoToPerm.user_id == user_id)
2450 if repo_id:
2451 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2452 return q.all()
2453
2454 @classmethod
2455 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2456 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2457 .join(
2458 Permission,
2459 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2460 .join(
2461 Repository,
2462 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2463 .join(
2464 UserGroup,
2465 UserGroupRepoToPerm.users_group_id ==
2466 UserGroup.users_group_id)\
2467 .join(
2468 UserGroupMember,
2469 UserGroupRepoToPerm.users_group_id ==
2470 UserGroupMember.users_group_id)\
2471 .filter(
2472 UserGroupMember.user_id == user_id,
2473 UserGroup.users_group_active == true())
2474 if repo_id:
2475 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2476 return q.all()
2477
2478 @classmethod
2479 def get_default_group_perms(cls, user_id, repo_group_id=None):
2480 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2481 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2482 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2483 .filter(UserRepoGroupToPerm.user_id == user_id)
2484 if repo_group_id:
2485 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2486 return q.all()
2487
2488 @classmethod
2489 def get_default_group_perms_from_user_group(
2490 cls, user_id, repo_group_id=None):
2491 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2492 .join(
2493 Permission,
2494 UserGroupRepoGroupToPerm.permission_id ==
2495 Permission.permission_id)\
2496 .join(
2497 RepoGroup,
2498 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2499 .join(
2500 UserGroup,
2501 UserGroupRepoGroupToPerm.users_group_id ==
2502 UserGroup.users_group_id)\
2503 .join(
2504 UserGroupMember,
2505 UserGroupRepoGroupToPerm.users_group_id ==
2506 UserGroupMember.users_group_id)\
2507 .filter(
2508 UserGroupMember.user_id == user_id,
2509 UserGroup.users_group_active == true())
2510 if repo_group_id:
2511 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2512 return q.all()
2513
2514 @classmethod
2515 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2516 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2517 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2518 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2519 .filter(UserUserGroupToPerm.user_id == user_id)
2520 if user_group_id:
2521 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2522 return q.all()
2523
2524 @classmethod
2525 def get_default_user_group_perms_from_user_group(
2526 cls, user_id, user_group_id=None):
2527 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2528 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2529 .join(
2530 Permission,
2531 UserGroupUserGroupToPerm.permission_id ==
2532 Permission.permission_id)\
2533 .join(
2534 TargetUserGroup,
2535 UserGroupUserGroupToPerm.target_user_group_id ==
2536 TargetUserGroup.users_group_id)\
2537 .join(
2538 UserGroup,
2539 UserGroupUserGroupToPerm.user_group_id ==
2540 UserGroup.users_group_id)\
2541 .join(
2542 UserGroupMember,
2543 UserGroupUserGroupToPerm.user_group_id ==
2544 UserGroupMember.users_group_id)\
2545 .filter(
2546 UserGroupMember.user_id == user_id,
2547 UserGroup.users_group_active == true())
2548 if user_group_id:
2549 q = q.filter(
2550 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2551
2552 return q.all()
2553
2554
2555 class UserRepoToPerm(Base, BaseModel):
2556 __tablename__ = 'repo_to_perm'
2557 __table_args__ = (
2558 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2561 )
2562 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2563 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2564 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2565 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2566
2567 user = relationship('User')
2568 repository = relationship('Repository')
2569 permission = relationship('Permission')
2570
2571 @classmethod
2572 def create(cls, user, repository, permission):
2573 n = cls()
2574 n.user = user
2575 n.repository = repository
2576 n.permission = permission
2577 Session().add(n)
2578 return n
2579
2580 def __unicode__(self):
2581 return u'<%s => %s >' % (self.user, self.repository)
2582
2583
2584 class UserUserGroupToPerm(Base, BaseModel):
2585 __tablename__ = 'user_user_group_to_perm'
2586 __table_args__ = (
2587 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2589 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2590 )
2591 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2593 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2594 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2595
2596 user = relationship('User')
2597 user_group = relationship('UserGroup')
2598 permission = relationship('Permission')
2599
2600 @classmethod
2601 def create(cls, user, user_group, permission):
2602 n = cls()
2603 n.user = user
2604 n.user_group = user_group
2605 n.permission = permission
2606 Session().add(n)
2607 return n
2608
2609 def __unicode__(self):
2610 return u'<%s => %s >' % (self.user, self.user_group)
2611
2612
2613 class UserToPerm(Base, BaseModel):
2614 __tablename__ = 'user_to_perm'
2615 __table_args__ = (
2616 UniqueConstraint('user_id', 'permission_id'),
2617 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2618 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2619 )
2620 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2621 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2622 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2623
2624 user = relationship('User')
2625 permission = relationship('Permission', lazy='joined')
2626
2627 def __unicode__(self):
2628 return u'<%s => %s >' % (self.user, self.permission)
2629
2630
2631 class UserGroupRepoToPerm(Base, BaseModel):
2632 __tablename__ = 'users_group_repo_to_perm'
2633 __table_args__ = (
2634 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2636 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2637 )
2638 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2639 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2640 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2641 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2642
2643 users_group = relationship('UserGroup')
2644 permission = relationship('Permission')
2645 repository = relationship('Repository')
2646
2647 @classmethod
2648 def create(cls, users_group, repository, permission):
2649 n = cls()
2650 n.users_group = users_group
2651 n.repository = repository
2652 n.permission = permission
2653 Session().add(n)
2654 return n
2655
2656 def __unicode__(self):
2657 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2658
2659
2660 class UserGroupUserGroupToPerm(Base, BaseModel):
2661 __tablename__ = 'user_group_user_group_to_perm'
2662 __table_args__ = (
2663 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2664 CheckConstraint('target_user_group_id != user_group_id'),
2665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2666 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2667 )
2668 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)
2669 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2670 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2671 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2672
2673 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2674 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2675 permission = relationship('Permission')
2676
2677 @classmethod
2678 def create(cls, target_user_group, user_group, permission):
2679 n = cls()
2680 n.target_user_group = target_user_group
2681 n.user_group = user_group
2682 n.permission = permission
2683 Session().add(n)
2684 return n
2685
2686 def __unicode__(self):
2687 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2688
2689
2690 class UserGroupToPerm(Base, BaseModel):
2691 __tablename__ = 'users_group_to_perm'
2692 __table_args__ = (
2693 UniqueConstraint('users_group_id', 'permission_id',),
2694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2696 )
2697 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2699 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2700
2701 users_group = relationship('UserGroup')
2702 permission = relationship('Permission')
2703
2704
2705 class UserRepoGroupToPerm(Base, BaseModel):
2706 __tablename__ = 'user_repo_group_to_perm'
2707 __table_args__ = (
2708 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2710 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2711 )
2712
2713 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2714 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2715 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2716 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2717
2718 user = relationship('User')
2719 group = relationship('RepoGroup')
2720 permission = relationship('Permission')
2721
2722 @classmethod
2723 def create(cls, user, repository_group, permission):
2724 n = cls()
2725 n.user = user
2726 n.group = repository_group
2727 n.permission = permission
2728 Session().add(n)
2729 return n
2730
2731
2732 class UserGroupRepoGroupToPerm(Base, BaseModel):
2733 __tablename__ = 'users_group_repo_group_to_perm'
2734 __table_args__ = (
2735 UniqueConstraint('users_group_id', 'group_id'),
2736 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2737 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2738 )
2739
2740 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)
2741 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2742 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2743 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2744
2745 users_group = relationship('UserGroup')
2746 permission = relationship('Permission')
2747 group = relationship('RepoGroup')
2748
2749 @classmethod
2750 def create(cls, user_group, repository_group, permission):
2751 n = cls()
2752 n.users_group = user_group
2753 n.group = repository_group
2754 n.permission = permission
2755 Session().add(n)
2756 return n
2757
2758 def __unicode__(self):
2759 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2760
2761
2762 class Statistics(Base, BaseModel):
2763 __tablename__ = 'statistics'
2764 __table_args__ = (
2765 UniqueConstraint('repository_id'),
2766 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2767 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2768 )
2769 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2770 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2771 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2772 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2773 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2774 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2775
2776 repository = relationship('Repository', single_parent=True)
2777
2778
2779 class UserFollowing(Base, BaseModel):
2780 __tablename__ = 'user_followings'
2781 __table_args__ = (
2782 UniqueConstraint('user_id', 'follows_repository_id'),
2783 UniqueConstraint('user_id', 'follows_user_id'),
2784 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2785 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2786 )
2787
2788 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2789 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2790 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2791 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2792 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2793
2794 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2795
2796 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2797 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2798
2799 @classmethod
2800 def get_repo_followers(cls, repo_id):
2801 return cls.query().filter(cls.follows_repo_id == repo_id)
2802
2803
2804 class CacheKey(Base, BaseModel):
2805 __tablename__ = 'cache_invalidation'
2806 __table_args__ = (
2807 UniqueConstraint('cache_key'),
2808 Index('key_idx', 'cache_key'),
2809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2810 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2811 )
2812 CACHE_TYPE_ATOM = 'ATOM'
2813 CACHE_TYPE_RSS = 'RSS'
2814 CACHE_TYPE_README = 'README'
2815
2816 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2817 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2818 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2819 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2820
2821 def __init__(self, cache_key, cache_args=''):
2822 self.cache_key = cache_key
2823 self.cache_args = cache_args
2824 self.cache_active = False
2825
2826 def __unicode__(self):
2827 return u"<%s('%s:%s[%s]')>" % (
2828 self.__class__.__name__,
2829 self.cache_id, self.cache_key, self.cache_active)
2830
2831 def _cache_key_partition(self):
2832 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2833 return prefix, repo_name, suffix
2834
2835 def get_prefix(self):
2836 """
2837 Try to extract prefix from existing cache key. The key could consist
2838 of prefix, repo_name, suffix
2839 """
2840 # this returns prefix, repo_name, suffix
2841 return self._cache_key_partition()[0]
2842
2843 def get_suffix(self):
2844 """
2845 get suffix that might have been used in _get_cache_key to
2846 generate self.cache_key. Only used for informational purposes
2847 in repo_edit.mako.
2848 """
2849 # prefix, repo_name, suffix
2850 return self._cache_key_partition()[2]
2851
2852 @classmethod
2853 def delete_all_cache(cls):
2854 """
2855 Delete all cache keys from database.
2856 Should only be run when all instances are down and all entries
2857 thus stale.
2858 """
2859 cls.query().delete()
2860 Session().commit()
2861
2862 @classmethod
2863 def get_cache_key(cls, repo_name, cache_type):
2864 """
2865
2866 Generate a cache key for this process of RhodeCode instance.
2867 Prefix most likely will be process id or maybe explicitly set
2868 instance_id from .ini file.
2869 """
2870 import rhodecode
2871 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2872
2873 repo_as_unicode = safe_unicode(repo_name)
2874 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2875 if cache_type else repo_as_unicode
2876
2877 return u'{}{}'.format(prefix, key)
2878
2879 @classmethod
2880 def set_invalidate(cls, repo_name, delete=False):
2881 """
2882 Mark all caches of a repo as invalid in the database.
2883 """
2884
2885 try:
2886 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2887 if delete:
2888 log.debug('cache objects deleted for repo %s',
2889 safe_str(repo_name))
2890 qry.delete()
2891 else:
2892 log.debug('cache objects marked as invalid for repo %s',
2893 safe_str(repo_name))
2894 qry.update({"cache_active": False})
2895
2896 Session().commit()
2897 except Exception:
2898 log.exception(
2899 'Cache key invalidation failed for repository %s',
2900 safe_str(repo_name))
2901 Session().rollback()
2902
2903 @classmethod
2904 def get_active_cache(cls, cache_key):
2905 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2906 if inv_obj:
2907 return inv_obj
2908 return None
2909
2910 @classmethod
2911 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2912 thread_scoped=False):
2913 """
2914 @cache_region('long_term')
2915 def _heavy_calculation(cache_key):
2916 return 'result'
2917
2918 cache_context = CacheKey.repo_context_cache(
2919 _heavy_calculation, repo_name, cache_type)
2920
2921 with cache_context as context:
2922 context.invalidate()
2923 computed = context.compute()
2924
2925 assert computed == 'result'
2926 """
2927 from rhodecode.lib import caches
2928 return caches.InvalidationContext(
2929 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2930
2931
2932 class ChangesetComment(Base, BaseModel):
2933 __tablename__ = 'changeset_comments'
2934 __table_args__ = (
2935 Index('cc_revision_idx', 'revision'),
2936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2937 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2938 )
2939
2940 COMMENT_OUTDATED = u'comment_outdated'
2941 COMMENT_TYPE_NOTE = u'note'
2942 COMMENT_TYPE_TODO = u'todo'
2943 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2944
2945 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2946 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2947 revision = Column('revision', String(40), nullable=True)
2948 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2949 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2950 line_no = Column('line_no', Unicode(10), nullable=True)
2951 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2952 f_path = Column('f_path', Unicode(1000), nullable=True)
2953 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2954 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2955 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2956 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2957 renderer = Column('renderer', Unicode(64), nullable=True)
2958 display_state = Column('display_state', Unicode(128), nullable=True)
2959
2960 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2961 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2962 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2963 author = relationship('User', lazy='joined')
2964 repo = relationship('Repository')
2965 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2966 pull_request = relationship('PullRequest', lazy='joined')
2967 pull_request_version = relationship('PullRequestVersion')
2968
2969 @classmethod
2970 def get_users(cls, revision=None, pull_request_id=None):
2971 """
2972 Returns user associated with this ChangesetComment. ie those
2973 who actually commented
2974
2975 :param cls:
2976 :param revision:
2977 """
2978 q = Session().query(User)\
2979 .join(ChangesetComment.author)
2980 if revision:
2981 q = q.filter(cls.revision == revision)
2982 elif pull_request_id:
2983 q = q.filter(cls.pull_request_id == pull_request_id)
2984 return q.all()
2985
2986 @classmethod
2987 def get_index_from_version(cls, pr_version, versions):
2988 num_versions = [x.pull_request_version_id for x in versions]
2989 try:
2990 return num_versions.index(pr_version) +1
2991 except (IndexError, ValueError):
2992 return
2993
2994 @property
2995 def outdated(self):
2996 return self.display_state == self.COMMENT_OUTDATED
2997
2998 def outdated_at_version(self, version):
2999 """
3000 Checks if comment is outdated for given pull request version
3001 """
3002 return self.outdated and self.pull_request_version_id != version
3003
3004 def older_than_version(self, version):
3005 """
3006 Checks if comment is made from previous version than given
3007 """
3008 if version is None:
3009 return self.pull_request_version_id is not None
3010
3011 return self.pull_request_version_id < version
3012
3013 @property
3014 def resolved(self):
3015 return self.resolved_by[0] if self.resolved_by else None
3016
3017 @property
3018 def is_todo(self):
3019 return self.comment_type == self.COMMENT_TYPE_TODO
3020
3021 def get_index_version(self, versions):
3022 return self.get_index_from_version(
3023 self.pull_request_version_id, versions)
3024
3025 def render(self, mentions=False):
3026 from rhodecode.lib import helpers as h
3027 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3028
3029 def __repr__(self):
3030 if self.comment_id:
3031 return '<DB:Comment #%s>' % self.comment_id
3032 else:
3033 return '<DB:Comment at %#x>' % id(self)
3034
3035
3036 class ChangesetStatus(Base, BaseModel):
3037 __tablename__ = 'changeset_statuses'
3038 __table_args__ = (
3039 Index('cs_revision_idx', 'revision'),
3040 Index('cs_version_idx', 'version'),
3041 UniqueConstraint('repo_id', 'revision', 'version'),
3042 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3043 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3044 )
3045 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3046 STATUS_APPROVED = 'approved'
3047 STATUS_REJECTED = 'rejected'
3048 STATUS_UNDER_REVIEW = 'under_review'
3049
3050 STATUSES = [
3051 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3052 (STATUS_APPROVED, _("Approved")),
3053 (STATUS_REJECTED, _("Rejected")),
3054 (STATUS_UNDER_REVIEW, _("Under Review")),
3055 ]
3056
3057 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3058 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3059 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3060 revision = Column('revision', String(40), nullable=False)
3061 status = Column('status', String(128), nullable=False, default=DEFAULT)
3062 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3063 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3064 version = Column('version', Integer(), nullable=False, default=0)
3065 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3066
3067 author = relationship('User', lazy='joined')
3068 repo = relationship('Repository')
3069 comment = relationship('ChangesetComment', lazy='joined')
3070 pull_request = relationship('PullRequest', lazy='joined')
3071
3072 def __unicode__(self):
3073 return u"<%s('%s[v%s]:%s')>" % (
3074 self.__class__.__name__,
3075 self.status, self.version, self.author
3076 )
3077
3078 @classmethod
3079 def get_status_lbl(cls, value):
3080 return dict(cls.STATUSES).get(value)
3081
3082 @property
3083 def status_lbl(self):
3084 return ChangesetStatus.get_status_lbl(self.status)
3085
3086
3087 class _PullRequestBase(BaseModel):
3088 """
3089 Common attributes of pull request and version entries.
3090 """
3091
3092 # .status values
3093 STATUS_NEW = u'new'
3094 STATUS_OPEN = u'open'
3095 STATUS_CLOSED = u'closed'
3096
3097 title = Column('title', Unicode(255), nullable=True)
3098 description = Column(
3099 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3100 nullable=True)
3101 # new/open/closed status of pull request (not approve/reject/etc)
3102 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3103 created_on = Column(
3104 'created_on', DateTime(timezone=False), nullable=False,
3105 default=datetime.datetime.now)
3106 updated_on = Column(
3107 'updated_on', DateTime(timezone=False), nullable=False,
3108 default=datetime.datetime.now)
3109
3110 @declared_attr
3111 def user_id(cls):
3112 return Column(
3113 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3114 unique=None)
3115
3116 # 500 revisions max
3117 _revisions = Column(
3118 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3119
3120 @declared_attr
3121 def source_repo_id(cls):
3122 # TODO: dan: rename column to source_repo_id
3123 return Column(
3124 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3125 nullable=False)
3126
3127 source_ref = Column('org_ref', Unicode(255), nullable=False)
3128
3129 @declared_attr
3130 def target_repo_id(cls):
3131 # TODO: dan: rename column to target_repo_id
3132 return Column(
3133 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3134 nullable=False)
3135
3136 target_ref = Column('other_ref', Unicode(255), nullable=False)
3137 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3138
3139 # TODO: dan: rename column to last_merge_source_rev
3140 _last_merge_source_rev = Column(
3141 'last_merge_org_rev', String(40), nullable=True)
3142 # TODO: dan: rename column to last_merge_target_rev
3143 _last_merge_target_rev = Column(
3144 'last_merge_other_rev', String(40), nullable=True)
3145 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3146 merge_rev = Column('merge_rev', String(40), nullable=True)
3147
3148 @hybrid_property
3149 def revisions(self):
3150 return self._revisions.split(':') if self._revisions else []
3151
3152 @revisions.setter
3153 def revisions(self, val):
3154 self._revisions = ':'.join(val)
3155
3156 @declared_attr
3157 def author(cls):
3158 return relationship('User', lazy='joined')
3159
3160 @declared_attr
3161 def source_repo(cls):
3162 return relationship(
3163 'Repository',
3164 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3165
3166 @property
3167 def source_ref_parts(self):
3168 return self.unicode_to_reference(self.source_ref)
3169
3170 @declared_attr
3171 def target_repo(cls):
3172 return relationship(
3173 'Repository',
3174 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3175
3176 @property
3177 def target_ref_parts(self):
3178 return self.unicode_to_reference(self.target_ref)
3179
3180 @property
3181 def shadow_merge_ref(self):
3182 return self.unicode_to_reference(self._shadow_merge_ref)
3183
3184 @shadow_merge_ref.setter
3185 def shadow_merge_ref(self, ref):
3186 self._shadow_merge_ref = self.reference_to_unicode(ref)
3187
3188 def unicode_to_reference(self, raw):
3189 """
3190 Convert a unicode (or string) to a reference object.
3191 If unicode evaluates to False it returns None.
3192 """
3193 if raw:
3194 refs = raw.split(':')
3195 return Reference(*refs)
3196 else:
3197 return None
3198
3199 def reference_to_unicode(self, ref):
3200 """
3201 Convert a reference object to unicode.
3202 If reference is None it returns None.
3203 """
3204 if ref:
3205 return u':'.join(ref)
3206 else:
3207 return None
3208
3209 def get_api_data(self):
3210 from rhodecode.model.pull_request import PullRequestModel
3211 pull_request = self
3212 merge_status = PullRequestModel().merge_status(pull_request)
3213
3214 pull_request_url = url(
3215 'pullrequest_show', repo_name=self.target_repo.repo_name,
3216 pull_request_id=self.pull_request_id, qualified=True)
3217
3218 merge_data = {
3219 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3220 'reference': (
3221 pull_request.shadow_merge_ref._asdict()
3222 if pull_request.shadow_merge_ref else None),
3223 }
3224
3225 data = {
3226 'pull_request_id': pull_request.pull_request_id,
3227 'url': pull_request_url,
3228 'title': pull_request.title,
3229 'description': pull_request.description,
3230 'status': pull_request.status,
3231 'created_on': pull_request.created_on,
3232 'updated_on': pull_request.updated_on,
3233 'commit_ids': pull_request.revisions,
3234 'review_status': pull_request.calculated_review_status(),
3235 'mergeable': {
3236 'status': merge_status[0],
3237 'message': unicode(merge_status[1]),
3238 },
3239 'source': {
3240 'clone_url': pull_request.source_repo.clone_url(),
3241 'repository': pull_request.source_repo.repo_name,
3242 'reference': {
3243 'name': pull_request.source_ref_parts.name,
3244 'type': pull_request.source_ref_parts.type,
3245 'commit_id': pull_request.source_ref_parts.commit_id,
3246 },
3247 },
3248 'target': {
3249 'clone_url': pull_request.target_repo.clone_url(),
3250 'repository': pull_request.target_repo.repo_name,
3251 'reference': {
3252 'name': pull_request.target_ref_parts.name,
3253 'type': pull_request.target_ref_parts.type,
3254 'commit_id': pull_request.target_ref_parts.commit_id,
3255 },
3256 },
3257 'merge': merge_data,
3258 'author': pull_request.author.get_api_data(include_secrets=False,
3259 details='basic'),
3260 'reviewers': [
3261 {
3262 'user': reviewer.get_api_data(include_secrets=False,
3263 details='basic'),
3264 'reasons': reasons,
3265 'review_status': st[0][1].status if st else 'not_reviewed',
3266 }
3267 for reviewer, reasons, st in pull_request.reviewers_statuses()
3268 ]
3269 }
3270
3271 return data
3272
3273
3274 class PullRequest(Base, _PullRequestBase):
3275 __tablename__ = 'pull_requests'
3276 __table_args__ = (
3277 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3278 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3279 )
3280
3281 pull_request_id = Column(
3282 'pull_request_id', Integer(), nullable=False, primary_key=True)
3283
3284 def __repr__(self):
3285 if self.pull_request_id:
3286 return '<DB:PullRequest #%s>' % self.pull_request_id
3287 else:
3288 return '<DB:PullRequest at %#x>' % id(self)
3289
3290 reviewers = relationship('PullRequestReviewers',
3291 cascade="all, delete, delete-orphan")
3292 statuses = relationship('ChangesetStatus')
3293 comments = relationship('ChangesetComment',
3294 cascade="all, delete, delete-orphan")
3295 versions = relationship('PullRequestVersion',
3296 cascade="all, delete, delete-orphan",
3297 lazy='dynamic')
3298
3299 @classmethod
3300 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3301 internal_methods=None):
3302
3303 class PullRequestDisplay(object):
3304 """
3305 Special object wrapper for showing PullRequest data via Versions
3306 It mimics PR object as close as possible. This is read only object
3307 just for display
3308 """
3309
3310 def __init__(self, attrs, internal=None):
3311 self.attrs = attrs
3312 # internal have priority over the given ones via attrs
3313 self.internal = internal or ['versions']
3314
3315 def __getattr__(self, item):
3316 if item in self.internal:
3317 return getattr(self, item)
3318 try:
3319 return self.attrs[item]
3320 except KeyError:
3321 raise AttributeError(
3322 '%s object has no attribute %s' % (self, item))
3323
3324 def __repr__(self):
3325 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3326
3327 def versions(self):
3328 return pull_request_obj.versions.order_by(
3329 PullRequestVersion.pull_request_version_id).all()
3330
3331 def is_closed(self):
3332 return pull_request_obj.is_closed()
3333
3334 @property
3335 def pull_request_version_id(self):
3336 return getattr(pull_request_obj, 'pull_request_version_id', None)
3337
3338 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3339
3340 attrs.author = StrictAttributeDict(
3341 pull_request_obj.author.get_api_data())
3342 if pull_request_obj.target_repo:
3343 attrs.target_repo = StrictAttributeDict(
3344 pull_request_obj.target_repo.get_api_data())
3345 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3346
3347 if pull_request_obj.source_repo:
3348 attrs.source_repo = StrictAttributeDict(
3349 pull_request_obj.source_repo.get_api_data())
3350 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3351
3352 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3353 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3354 attrs.revisions = pull_request_obj.revisions
3355
3356 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3357
3358 return PullRequestDisplay(attrs, internal=internal_methods)
3359
3360 def is_closed(self):
3361 return self.status == self.STATUS_CLOSED
3362
3363 def __json__(self):
3364 return {
3365 'revisions': self.revisions,
3366 }
3367
3368 def calculated_review_status(self):
3369 from rhodecode.model.changeset_status import ChangesetStatusModel
3370 return ChangesetStatusModel().calculated_review_status(self)
3371
3372 def reviewers_statuses(self):
3373 from rhodecode.model.changeset_status import ChangesetStatusModel
3374 return ChangesetStatusModel().reviewers_statuses(self)
3375
3376 @property
3377 def workspace_id(self):
3378 from rhodecode.model.pull_request import PullRequestModel
3379 return PullRequestModel()._workspace_id(self)
3380
3381 def get_shadow_repo(self):
3382 workspace_id = self.workspace_id
3383 vcs_obj = self.target_repo.scm_instance()
3384 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3385 workspace_id)
3386 return vcs_obj._get_shadow_instance(shadow_repository_path)
3387
3388
3389 class PullRequestVersion(Base, _PullRequestBase):
3390 __tablename__ = 'pull_request_versions'
3391 __table_args__ = (
3392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3394 )
3395
3396 pull_request_version_id = Column(
3397 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3398 pull_request_id = Column(
3399 'pull_request_id', Integer(),
3400 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3401 pull_request = relationship('PullRequest')
3402
3403 def __repr__(self):
3404 if self.pull_request_version_id:
3405 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3406 else:
3407 return '<DB:PullRequestVersion at %#x>' % id(self)
3408
3409 @property
3410 def reviewers(self):
3411 return self.pull_request.reviewers
3412
3413 @property
3414 def versions(self):
3415 return self.pull_request.versions
3416
3417 def is_closed(self):
3418 # calculate from original
3419 return self.pull_request.status == self.STATUS_CLOSED
3420
3421 def calculated_review_status(self):
3422 return self.pull_request.calculated_review_status()
3423
3424 def reviewers_statuses(self):
3425 return self.pull_request.reviewers_statuses()
3426
3427
3428 class PullRequestReviewers(Base, BaseModel):
3429 __tablename__ = 'pull_request_reviewers'
3430 __table_args__ = (
3431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3432 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3433 )
3434
3435 def __init__(self, user=None, pull_request=None, reasons=None):
3436 self.user = user
3437 self.pull_request = pull_request
3438 self.reasons = reasons or []
3439
3440 @hybrid_property
3441 def reasons(self):
3442 if not self._reasons:
3443 return []
3444 return self._reasons
3445
3446 @reasons.setter
3447 def reasons(self, val):
3448 val = val or []
3449 if any(not isinstance(x, basestring) for x in val):
3450 raise Exception('invalid reasons type, must be list of strings')
3451 self._reasons = val
3452
3453 pull_requests_reviewers_id = Column(
3454 'pull_requests_reviewers_id', Integer(), nullable=False,
3455 primary_key=True)
3456 pull_request_id = Column(
3457 "pull_request_id", Integer(),
3458 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3459 user_id = Column(
3460 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3461 _reasons = Column(
3462 'reason', MutationList.as_mutable(
3463 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3464
3465 user = relationship('User')
3466 pull_request = relationship('PullRequest')
3467
3468
3469 class Notification(Base, BaseModel):
3470 __tablename__ = 'notifications'
3471 __table_args__ = (
3472 Index('notification_type_idx', 'type'),
3473 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3474 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3475 )
3476
3477 TYPE_CHANGESET_COMMENT = u'cs_comment'
3478 TYPE_MESSAGE = u'message'
3479 TYPE_MENTION = u'mention'
3480 TYPE_REGISTRATION = u'registration'
3481 TYPE_PULL_REQUEST = u'pull_request'
3482 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3483
3484 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3485 subject = Column('subject', Unicode(512), nullable=True)
3486 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3487 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3488 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3489 type_ = Column('type', Unicode(255))
3490
3491 created_by_user = relationship('User')
3492 notifications_to_users = relationship('UserNotification', lazy='joined',
3493 cascade="all, delete, delete-orphan")
3494
3495 @property
3496 def recipients(self):
3497 return [x.user for x in UserNotification.query()\
3498 .filter(UserNotification.notification == self)\
3499 .order_by(UserNotification.user_id.asc()).all()]
3500
3501 @classmethod
3502 def create(cls, created_by, subject, body, recipients, type_=None):
3503 if type_ is None:
3504 type_ = Notification.TYPE_MESSAGE
3505
3506 notification = cls()
3507 notification.created_by_user = created_by
3508 notification.subject = subject
3509 notification.body = body
3510 notification.type_ = type_
3511 notification.created_on = datetime.datetime.now()
3512
3513 for u in recipients:
3514 assoc = UserNotification()
3515 assoc.notification = notification
3516
3517 # if created_by is inside recipients mark his notification
3518 # as read
3519 if u.user_id == created_by.user_id:
3520 assoc.read = True
3521
3522 u.notifications.append(assoc)
3523 Session().add(notification)
3524
3525 return notification
3526
3527 @property
3528 def description(self):
3529 from rhodecode.model.notification import NotificationModel
3530 return NotificationModel().make_description(self)
3531
3532
3533 class UserNotification(Base, BaseModel):
3534 __tablename__ = 'user_to_notification'
3535 __table_args__ = (
3536 UniqueConstraint('user_id', 'notification_id'),
3537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3538 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3539 )
3540 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3541 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3542 read = Column('read', Boolean, default=False)
3543 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3544
3545 user = relationship('User', lazy="joined")
3546 notification = relationship('Notification', lazy="joined",
3547 order_by=lambda: Notification.created_on.desc(),)
3548
3549 def mark_as_read(self):
3550 self.read = True
3551 Session().add(self)
3552
3553
3554 class Gist(Base, BaseModel):
3555 __tablename__ = 'gists'
3556 __table_args__ = (
3557 Index('g_gist_access_id_idx', 'gist_access_id'),
3558 Index('g_created_on_idx', 'created_on'),
3559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3560 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3561 )
3562 GIST_PUBLIC = u'public'
3563 GIST_PRIVATE = u'private'
3564 DEFAULT_FILENAME = u'gistfile1.txt'
3565
3566 ACL_LEVEL_PUBLIC = u'acl_public'
3567 ACL_LEVEL_PRIVATE = u'acl_private'
3568
3569 gist_id = Column('gist_id', Integer(), primary_key=True)
3570 gist_access_id = Column('gist_access_id', Unicode(250))
3571 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3572 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3573 gist_expires = Column('gist_expires', Float(53), nullable=False)
3574 gist_type = Column('gist_type', Unicode(128), nullable=False)
3575 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3576 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3577 acl_level = Column('acl_level', Unicode(128), nullable=True)
3578
3579 owner = relationship('User')
3580
3581 def __repr__(self):
3582 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3583
3584 @classmethod
3585 def get_or_404(cls, id_):
3586 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3587 if not res:
3588 raise HTTPNotFound
3589 return res
3590
3591 @classmethod
3592 def get_by_access_id(cls, gist_access_id):
3593 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3594
3595 def gist_url(self):
3596 import rhodecode
3597 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3598 if alias_url:
3599 return alias_url.replace('{gistid}', self.gist_access_id)
3600
3601 return url('gist', gist_id=self.gist_access_id, qualified=True)
3602
3603 @classmethod
3604 def base_path(cls):
3605 """
3606 Returns base path when all gists are stored
3607
3608 :param cls:
3609 """
3610 from rhodecode.model.gist import GIST_STORE_LOC
3611 q = Session().query(RhodeCodeUi)\
3612 .filter(RhodeCodeUi.ui_key == URL_SEP)
3613 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3614 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3615
3616 def get_api_data(self):
3617 """
3618 Common function for generating gist related data for API
3619 """
3620 gist = self
3621 data = {
3622 'gist_id': gist.gist_id,
3623 'type': gist.gist_type,
3624 'access_id': gist.gist_access_id,
3625 'description': gist.gist_description,
3626 'url': gist.gist_url(),
3627 'expires': gist.gist_expires,
3628 'created_on': gist.created_on,
3629 'modified_at': gist.modified_at,
3630 'content': None,
3631 'acl_level': gist.acl_level,
3632 }
3633 return data
3634
3635 def __json__(self):
3636 data = dict(
3637 )
3638 data.update(self.get_api_data())
3639 return data
3640 # SCM functions
3641
3642 def scm_instance(self, **kwargs):
3643 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3644 return get_vcs_instance(
3645 repo_path=safe_str(full_repo_path), create=False)
3646
3647
3648 class ExternalIdentity(Base, BaseModel):
3649 __tablename__ = 'external_identities'
3650 __table_args__ = (
3651 Index('local_user_id_idx', 'local_user_id'),
3652 Index('external_id_idx', 'external_id'),
3653 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3654 'mysql_charset': 'utf8'})
3655
3656 external_id = Column('external_id', Unicode(255), default=u'',
3657 primary_key=True)
3658 external_username = Column('external_username', Unicode(1024), default=u'')
3659 local_user_id = Column('local_user_id', Integer(),
3660 ForeignKey('users.user_id'), primary_key=True)
3661 provider_name = Column('provider_name', Unicode(255), default=u'',
3662 primary_key=True)
3663 access_token = Column('access_token', String(1024), default=u'')
3664 alt_token = Column('alt_token', String(1024), default=u'')
3665 token_secret = Column('token_secret', String(1024), default=u'')
3666
3667 @classmethod
3668 def by_external_id_and_provider(cls, external_id, provider_name,
3669 local_user_id=None):
3670 """
3671 Returns ExternalIdentity instance based on search params
3672
3673 :param external_id:
3674 :param provider_name:
3675 :return: ExternalIdentity
3676 """
3677 query = cls.query()
3678 query = query.filter(cls.external_id == external_id)
3679 query = query.filter(cls.provider_name == provider_name)
3680 if local_user_id:
3681 query = query.filter(cls.local_user_id == local_user_id)
3682 return query.first()
3683
3684 @classmethod
3685 def user_by_external_id_and_provider(cls, external_id, provider_name):
3686 """
3687 Returns User instance based on search params
3688
3689 :param external_id:
3690 :param provider_name:
3691 :return: User
3692 """
3693 query = User.query()
3694 query = query.filter(cls.external_id == external_id)
3695 query = query.filter(cls.provider_name == provider_name)
3696 query = query.filter(User.user_id == cls.local_user_id)
3697 return query.first()
3698
3699 @classmethod
3700 def by_local_user_id(cls, local_user_id):
3701 """
3702 Returns all tokens for user
3703
3704 :param local_user_id:
3705 :return: ExternalIdentity
3706 """
3707 query = cls.query()
3708 query = query.filter(cls.local_user_id == local_user_id)
3709 return query
3710
3711
3712 class Integration(Base, BaseModel):
3713 __tablename__ = 'integrations'
3714 __table_args__ = (
3715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3716 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3717 )
3718
3719 integration_id = Column('integration_id', Integer(), primary_key=True)
3720 integration_type = Column('integration_type', String(255))
3721 enabled = Column('enabled', Boolean(), nullable=False)
3722 name = Column('name', String(255), nullable=False)
3723 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3724 default=False)
3725
3726 settings = Column(
3727 'settings_json', MutationObj.as_mutable(
3728 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3729 repo_id = Column(
3730 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3731 nullable=True, unique=None, default=None)
3732 repo = relationship('Repository', lazy='joined')
3733
3734 repo_group_id = Column(
3735 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3736 nullable=True, unique=None, default=None)
3737 repo_group = relationship('RepoGroup', lazy='joined')
3738
3739 @property
3740 def scope(self):
3741 if self.repo:
3742 return repr(self.repo)
3743 if self.repo_group:
3744 if self.child_repos_only:
3745 return repr(self.repo_group) + ' (child repos only)'
3746 else:
3747 return repr(self.repo_group) + ' (recursive)'
3748 if self.child_repos_only:
3749 return 'root_repos'
3750 return 'global'
3751
3752 def __repr__(self):
3753 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3754
3755
3756 class RepoReviewRuleUser(Base, BaseModel):
3757 __tablename__ = 'repo_review_rules_users'
3758 __table_args__ = (
3759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3760 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3761 )
3762 repo_review_rule_user_id = Column(
3763 'repo_review_rule_user_id', Integer(), primary_key=True)
3764 repo_review_rule_id = Column("repo_review_rule_id",
3765 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3766 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3767 nullable=False)
3768 user = relationship('User')
3769
3770
3771 class RepoReviewRuleUserGroup(Base, BaseModel):
3772 __tablename__ = 'repo_review_rules_users_groups'
3773 __table_args__ = (
3774 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3775 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3776 )
3777 repo_review_rule_users_group_id = Column(
3778 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3779 repo_review_rule_id = Column("repo_review_rule_id",
3780 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3781 users_group_id = Column("users_group_id", Integer(),
3782 ForeignKey('users_groups.users_group_id'), nullable=False)
3783 users_group = relationship('UserGroup')
3784
3785
3786 class RepoReviewRule(Base, BaseModel):
3787 __tablename__ = 'repo_review_rules'
3788 __table_args__ = (
3789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3790 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3791 )
3792
3793 repo_review_rule_id = Column(
3794 'repo_review_rule_id', Integer(), primary_key=True)
3795 repo_id = Column(
3796 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3797 repo = relationship('Repository', backref='review_rules')
3798
3799 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3800 default=u'*') # glob
3801 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3802 default=u'*') # glob
3803
3804 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3805 nullable=False, default=False)
3806 rule_users = relationship('RepoReviewRuleUser')
3807 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3808
3809 @hybrid_property
3810 def branch_pattern(self):
3811 return self._branch_pattern or '*'
3812
3813 def _validate_glob(self, value):
3814 re.compile('^' + glob2re(value) + '$')
3815
3816 @branch_pattern.setter
3817 def branch_pattern(self, value):
3818 self._validate_glob(value)
3819 self._branch_pattern = value or '*'
3820
3821 @hybrid_property
3822 def file_pattern(self):
3823 return self._file_pattern or '*'
3824
3825 @file_pattern.setter
3826 def file_pattern(self, value):
3827 self._validate_glob(value)
3828 self._file_pattern = value or '*'
3829
3830 def matches(self, branch, files_changed):
3831 """
3832 Check if this review rule matches a branch/files in a pull request
3833
3834 :param branch: branch name for the commit
3835 :param files_changed: list of file paths changed in the pull request
3836 """
3837
3838 branch = branch or ''
3839 files_changed = files_changed or []
3840
3841 branch_matches = True
3842 if branch:
3843 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3844 branch_matches = bool(branch_regex.search(branch))
3845
3846 files_matches = True
3847 if self.file_pattern != '*':
3848 files_matches = False
3849 file_regex = re.compile(glob2re(self.file_pattern))
3850 for filename in files_changed:
3851 if file_regex.search(filename):
3852 files_matches = True
3853 break
3854
3855 return branch_matches and files_matches
3856
3857 @property
3858 def review_users(self):
3859 """ Returns the users which this rule applies to """
3860
3861 users = set()
3862 users |= set([
3863 rule_user.user for rule_user in self.rule_users
3864 if rule_user.user.active])
3865 users |= set(
3866 member.user
3867 for rule_user_group in self.rule_user_groups
3868 for member in rule_user_group.users_group.members
3869 if member.user.active
3870 )
3871 return users
3872
3873 def __repr__(self):
3874 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3875 self.repo_review_rule_id, self.repo)
3876
3877
3878 class DbMigrateVersion(Base, BaseModel):
3879 __tablename__ = 'db_migrate_version'
3880 __table_args__ = (
3881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3883 )
3884 repository_id = Column('repository_id', String(250), primary_key=True)
3885 repository_path = Column('repository_path', Text)
3886 version = Column('version', Integer)
3887
3888
3889 class DbSession(Base, BaseModel):
3890 __tablename__ = 'db_session'
3891 __table_args__ = (
3892 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3893 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3894 )
3895
3896 def __repr__(self):
3897 return '<DB:DbSession({})>'.format(self.id)
3898
3899 id = Column('id', Integer())
3900 namespace = Column('namespace', String(255), primary_key=True)
3901 accessed = Column('accessed', DateTime, nullable=False)
3902 created = Column('created', DateTime, nullable=False)
3903 data = Column('data', PickleType, nullable=False)
@@ -0,0 +1,27 b''
1 import logging
2
3 from sqlalchemy import Column, MetaData, Integer, Unicode, ForeignKey, DateTime
4
5 from rhodecode.lib.dbmigrate.versions import _reset_base
6
7 log = logging.getLogger(__name__)
8
9
10 def upgrade(migrate_engine):
11 """
12 Upgrade operations go here.
13 Don't create your own engine; bind migrate_engine to your metadata
14 """
15 _reset_base(migrate_engine)
16 from rhodecode.lib.dbmigrate.schema import db_4_7_0_0 as db
17
18 # add last_activity
19 user_table = db.User.__table__
20 col1 = Column(
21 'last_activity', DateTime(timezone=False), nullable=True, unique=None)
22 col1.create(table=user_table)
23
24
25 def downgrade(migrate_engine):
26 meta = MetaData()
27 meta.bind = migrate_engine
@@ -0,0 +1,56 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5
6 from rhodecode.lib.utils2 import safe_str
7 from rhodecode.model import meta
8 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
9
10 log = logging.getLogger(__name__)
11
12
13 def time_to_datetime(tm):
14 if tm:
15 if isinstance(tm, basestring):
16 try:
17 tm = float(tm)
18 except ValueError:
19 return
20 return datetime.datetime.fromtimestamp(tm)
21
22
23 def upgrade(migrate_engine):
24 """
25 Upgrade operations go here.
26 Don't create your own engine; bind migrate_engine to your metadata
27 """
28 _reset_base(migrate_engine)
29 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1
30
31 # fixups
32 fixups(db_4_7_0_1, meta.Session)
33
34
35 def downgrade(migrate_engine):
36 meta = MetaData()
37 meta.bind = migrate_engine
38
39
40 def _migrate_user(db, user):
41 last_activity = time_to_datetime(user.user_data.get('last_activity', 0))
42 user.last_activity = last_activity
43 return user
44
45
46 def fixups(models, _SESSION):
47 # move the builtin token to external tokens
48
49 query = models.User.query().all()
50 for user in query:
51 migrated_user = _migrate_user(models, user)
52 _SESSION.add(migrated_user)
53 log.info(
54 "Migrating last_activity of user '%s'.", safe_str(user.username))
55
56 _SESSION().commit()
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 68 # defines current db version for migrations
54 __dbversion__ = 70 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,3968 +1,3970 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons import url
47 from pylons import url
48 from pylons.i18n.translation import lazy_ugettext as _
48 from pylons.i18n.translation import lazy_ugettext as _
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361
361
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # git part is currently hardcoded.
363 # git part is currently hardcoded.
364
364
365 # SVN PATTERNS
365 # SVN PATTERNS
366 SVN_BRANCH_ID = 'vcs_svn_branch'
366 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_TAG_ID = 'vcs_svn_tag'
367 SVN_TAG_ID = 'vcs_svn_tag'
368
368
369 ui_id = Column(
369 ui_id = Column(
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 primary_key=True)
371 primary_key=True)
372 ui_section = Column(
372 ui_section = Column(
373 "ui_section", String(255), nullable=True, unique=None, default=None)
373 "ui_section", String(255), nullable=True, unique=None, default=None)
374 ui_key = Column(
374 ui_key = Column(
375 "ui_key", String(255), nullable=True, unique=None, default=None)
375 "ui_key", String(255), nullable=True, unique=None, default=None)
376 ui_value = Column(
376 ui_value = Column(
377 "ui_value", String(255), nullable=True, unique=None, default=None)
377 "ui_value", String(255), nullable=True, unique=None, default=None)
378 ui_active = Column(
378 ui_active = Column(
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380
380
381 def __repr__(self):
381 def __repr__(self):
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 self.ui_key, self.ui_value)
383 self.ui_key, self.ui_value)
384
384
385
385
386 class RepoRhodeCodeSetting(Base, BaseModel):
386 class RepoRhodeCodeSetting(Base, BaseModel):
387 __tablename__ = 'repo_rhodecode_settings'
387 __tablename__ = 'repo_rhodecode_settings'
388 __table_args__ = (
388 __table_args__ = (
389 UniqueConstraint(
389 UniqueConstraint(
390 'app_settings_name', 'repository_id',
390 'app_settings_name', 'repository_id',
391 name='uq_repo_rhodecode_setting_name_repo_id'),
391 name='uq_repo_rhodecode_setting_name_repo_id'),
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 )
394 )
395
395
396 repository_id = Column(
396 repository_id = Column(
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 nullable=False)
398 nullable=False)
399 app_settings_id = Column(
399 app_settings_id = Column(
400 "app_settings_id", Integer(), nullable=False, unique=True,
400 "app_settings_id", Integer(), nullable=False, unique=True,
401 default=None, primary_key=True)
401 default=None, primary_key=True)
402 app_settings_name = Column(
402 app_settings_name = Column(
403 "app_settings_name", String(255), nullable=True, unique=None,
403 "app_settings_name", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405 _app_settings_value = Column(
405 _app_settings_value = Column(
406 "app_settings_value", String(4096), nullable=True, unique=None,
406 "app_settings_value", String(4096), nullable=True, unique=None,
407 default=None)
407 default=None)
408 _app_settings_type = Column(
408 _app_settings_type = Column(
409 "app_settings_type", String(255), nullable=True, unique=None,
409 "app_settings_type", String(255), nullable=True, unique=None,
410 default=None)
410 default=None)
411
411
412 repository = relationship('Repository')
412 repository = relationship('Repository')
413
413
414 def __init__(self, repository_id, key='', val='', type='unicode'):
414 def __init__(self, repository_id, key='', val='', type='unicode'):
415 self.repository_id = repository_id
415 self.repository_id = repository_id
416 self.app_settings_name = key
416 self.app_settings_name = key
417 self.app_settings_type = type
417 self.app_settings_type = type
418 self.app_settings_value = val
418 self.app_settings_value = val
419
419
420 @validates('_app_settings_value')
420 @validates('_app_settings_value')
421 def validate_settings_value(self, key, val):
421 def validate_settings_value(self, key, val):
422 assert type(val) == unicode
422 assert type(val) == unicode
423 return val
423 return val
424
424
425 @hybrid_property
425 @hybrid_property
426 def app_settings_value(self):
426 def app_settings_value(self):
427 v = self._app_settings_value
427 v = self._app_settings_value
428 type_ = self.app_settings_type
428 type_ = self.app_settings_type
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 return converter(v)
431 return converter(v)
432
432
433 @app_settings_value.setter
433 @app_settings_value.setter
434 def app_settings_value(self, val):
434 def app_settings_value(self, val):
435 """
435 """
436 Setter that will always make sure we use unicode in app_settings_value
436 Setter that will always make sure we use unicode in app_settings_value
437
437
438 :param val:
438 :param val:
439 """
439 """
440 self._app_settings_value = safe_unicode(val)
440 self._app_settings_value = safe_unicode(val)
441
441
442 @hybrid_property
442 @hybrid_property
443 def app_settings_type(self):
443 def app_settings_type(self):
444 return self._app_settings_type
444 return self._app_settings_type
445
445
446 @app_settings_type.setter
446 @app_settings_type.setter
447 def app_settings_type(self, val):
447 def app_settings_type(self, val):
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 if val not in SETTINGS_TYPES:
449 if val not in SETTINGS_TYPES:
450 raise Exception('type must be one of %s got %s'
450 raise Exception('type must be one of %s got %s'
451 % (SETTINGS_TYPES.keys(), val))
451 % (SETTINGS_TYPES.keys(), val))
452 self._app_settings_type = val
452 self._app_settings_type = val
453
453
454 def __unicode__(self):
454 def __unicode__(self):
455 return u"<%s('%s:%s:%s[%s]')>" % (
455 return u"<%s('%s:%s:%s[%s]')>" % (
456 self.__class__.__name__, self.repository.repo_name,
456 self.__class__.__name__, self.repository.repo_name,
457 self.app_settings_name, self.app_settings_value,
457 self.app_settings_name, self.app_settings_value,
458 self.app_settings_type
458 self.app_settings_type
459 )
459 )
460
460
461
461
462 class RepoRhodeCodeUi(Base, BaseModel):
462 class RepoRhodeCodeUi(Base, BaseModel):
463 __tablename__ = 'repo_rhodecode_ui'
463 __tablename__ = 'repo_rhodecode_ui'
464 __table_args__ = (
464 __table_args__ = (
465 UniqueConstraint(
465 UniqueConstraint(
466 'repository_id', 'ui_section', 'ui_key',
466 'repository_id', 'ui_section', 'ui_key',
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 )
470 )
471
471
472 repository_id = Column(
472 repository_id = Column(
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 nullable=False)
474 nullable=False)
475 ui_id = Column(
475 ui_id = Column(
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 primary_key=True)
477 primary_key=True)
478 ui_section = Column(
478 ui_section = Column(
479 "ui_section", String(255), nullable=True, unique=None, default=None)
479 "ui_section", String(255), nullable=True, unique=None, default=None)
480 ui_key = Column(
480 ui_key = Column(
481 "ui_key", String(255), nullable=True, unique=None, default=None)
481 "ui_key", String(255), nullable=True, unique=None, default=None)
482 ui_value = Column(
482 ui_value = Column(
483 "ui_value", String(255), nullable=True, unique=None, default=None)
483 "ui_value", String(255), nullable=True, unique=None, default=None)
484 ui_active = Column(
484 ui_active = Column(
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486
486
487 repository = relationship('Repository')
487 repository = relationship('Repository')
488
488
489 def __repr__(self):
489 def __repr__(self):
490 return '<%s[%s:%s]%s=>%s]>' % (
490 return '<%s[%s:%s]%s=>%s]>' % (
491 self.__class__.__name__, self.repository.repo_name,
491 self.__class__.__name__, self.repository.repo_name,
492 self.ui_section, self.ui_key, self.ui_value)
492 self.ui_section, self.ui_key, self.ui_value)
493
493
494
494
495 class User(Base, BaseModel):
495 class User(Base, BaseModel):
496 __tablename__ = 'users'
496 __tablename__ = 'users'
497 __table_args__ = (
497 __table_args__ = (
498 UniqueConstraint('username'), UniqueConstraint('email'),
498 UniqueConstraint('username'), UniqueConstraint('email'),
499 Index('u_username_idx', 'username'),
499 Index('u_username_idx', 'username'),
500 Index('u_email_idx', 'email'),
500 Index('u_email_idx', 'email'),
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 )
503 )
504 DEFAULT_USER = 'default'
504 DEFAULT_USER = 'default'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507
507
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
518
517 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
518 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
519 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
520 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
521 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
522 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
523
525
524 user_log = relationship('UserLog')
526 user_log = relationship('UserLog')
525 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
526
528
527 repositories = relationship('Repository')
529 repositories = relationship('Repository')
528 repository_groups = relationship('RepoGroup')
530 repository_groups = relationship('RepoGroup')
529 user_groups = relationship('UserGroup')
531 user_groups = relationship('UserGroup')
530
532
531 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
532 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
533
535
534 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
535 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
536 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
537
539
538 group_member = relationship('UserGroupMember', cascade='all')
540 group_member = relationship('UserGroupMember', cascade='all')
539
541
540 notifications = relationship('UserNotification', cascade='all')
542 notifications = relationship('UserNotification', cascade='all')
541 # notifications assigned to this user
543 # notifications assigned to this user
542 user_created_notifications = relationship('Notification', cascade='all')
544 user_created_notifications = relationship('Notification', cascade='all')
543 # comments created by this user
545 # comments created by this user
544 user_comments = relationship('ChangesetComment', cascade='all')
546 user_comments = relationship('ChangesetComment', cascade='all')
545 # user profile extra info
547 # user profile extra info
546 user_emails = relationship('UserEmailMap', cascade='all')
548 user_emails = relationship('UserEmailMap', cascade='all')
547 user_ip_map = relationship('UserIpMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
548 user_auth_tokens = relationship('UserApiKeys', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
549 # gists
551 # gists
550 user_gists = relationship('Gist', cascade='all')
552 user_gists = relationship('Gist', cascade='all')
551 # user pull requests
553 # user pull requests
552 user_pull_requests = relationship('PullRequest', cascade='all')
554 user_pull_requests = relationship('PullRequest', cascade='all')
553 # external identities
555 # external identities
554 extenal_identities = relationship(
556 extenal_identities = relationship(
555 'ExternalIdentity',
557 'ExternalIdentity',
556 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
557 cascade='all')
559 cascade='all')
558
560
559 def __unicode__(self):
561 def __unicode__(self):
560 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
561 self.user_id, self.username)
563 self.user_id, self.username)
562
564
563 @hybrid_property
565 @hybrid_property
564 def email(self):
566 def email(self):
565 return self._email
567 return self._email
566
568
567 @email.setter
569 @email.setter
568 def email(self, val):
570 def email(self, val):
569 self._email = val.lower() if val else None
571 self._email = val.lower() if val else None
570
572
571 @hybrid_property
573 @hybrid_property
572 def api_key(self):
574 def api_key(self):
573 """
575 """
574 Fetch if exist an auth-token with role ALL connected to this user
576 Fetch if exist an auth-token with role ALL connected to this user
575 """
577 """
576 user_auth_token = UserApiKeys.query()\
578 user_auth_token = UserApiKeys.query()\
577 .filter(UserApiKeys.user_id == self.user_id)\
579 .filter(UserApiKeys.user_id == self.user_id)\
578 .filter(or_(UserApiKeys.expires == -1,
580 .filter(or_(UserApiKeys.expires == -1,
579 UserApiKeys.expires >= time.time()))\
581 UserApiKeys.expires >= time.time()))\
580 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
581 if user_auth_token:
583 if user_auth_token:
582 user_auth_token = user_auth_token.api_key
584 user_auth_token = user_auth_token.api_key
583
585
584 return user_auth_token
586 return user_auth_token
585
587
586 @api_key.setter
588 @api_key.setter
587 def api_key(self, val):
589 def api_key(self, val):
588 # don't allow to set API key this is deprecated for now
590 # don't allow to set API key this is deprecated for now
589 self._api_key = None
591 self._api_key = None
590
592
591 @property
593 @property
592 def firstname(self):
594 def firstname(self):
593 # alias for future
595 # alias for future
594 return self.name
596 return self.name
595
597
596 @property
598 @property
597 def emails(self):
599 def emails(self):
598 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
599 return [self.email] + [x.email for x in other]
601 return [self.email] + [x.email for x in other]
600
602
601 @property
603 @property
602 def auth_tokens(self):
604 def auth_tokens(self):
603 return [x.api_key for x in self.extra_auth_tokens]
605 return [x.api_key for x in self.extra_auth_tokens]
604
606
605 @property
607 @property
606 def extra_auth_tokens(self):
608 def extra_auth_tokens(self):
607 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
608
610
609 @property
611 @property
610 def feed_token(self):
612 def feed_token(self):
611 return self.get_feed_token()
613 return self.get_feed_token()
612
614
613 def get_feed_token(self):
615 def get_feed_token(self):
614 feed_tokens = UserApiKeys.query()\
616 feed_tokens = UserApiKeys.query()\
615 .filter(UserApiKeys.user == self)\
617 .filter(UserApiKeys.user == self)\
616 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
617 .all()
619 .all()
618 if feed_tokens:
620 if feed_tokens:
619 return feed_tokens[0].api_key
621 return feed_tokens[0].api_key
620 return 'NO_FEED_TOKEN_AVAILABLE'
622 return 'NO_FEED_TOKEN_AVAILABLE'
621
623
622 @classmethod
624 @classmethod
623 def extra_valid_auth_tokens(cls, user, role=None):
625 def extra_valid_auth_tokens(cls, user, role=None):
624 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
625 .filter(or_(UserApiKeys.expires == -1,
627 .filter(or_(UserApiKeys.expires == -1,
626 UserApiKeys.expires >= time.time()))
628 UserApiKeys.expires >= time.time()))
627 if role:
629 if role:
628 tokens = tokens.filter(or_(UserApiKeys.role == role,
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
629 UserApiKeys.role == UserApiKeys.ROLE_ALL))
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
630 return tokens.all()
632 return tokens.all()
631
633
632 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
633 from rhodecode.lib import auth
635 from rhodecode.lib import auth
634
636
635 log.debug('Trying to authenticate user: %s via auth-token, '
637 log.debug('Trying to authenticate user: %s via auth-token, '
636 'and roles: %s', self, roles)
638 'and roles: %s', self, roles)
637
639
638 if not auth_token:
640 if not auth_token:
639 return False
641 return False
640
642
641 crypto_backend = auth.crypto_backend()
643 crypto_backend = auth.crypto_backend()
642
644
643 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
644 tokens_q = UserApiKeys.query()\
646 tokens_q = UserApiKeys.query()\
645 .filter(UserApiKeys.user_id == self.user_id)\
647 .filter(UserApiKeys.user_id == self.user_id)\
646 .filter(or_(UserApiKeys.expires == -1,
648 .filter(or_(UserApiKeys.expires == -1,
647 UserApiKeys.expires >= time.time()))
649 UserApiKeys.expires >= time.time()))
648
650
649 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
650
652
651 plain_tokens = []
653 plain_tokens = []
652 hash_tokens = []
654 hash_tokens = []
653
655
654 for token in tokens_q.all():
656 for token in tokens_q.all():
655 # verify scope first
657 # verify scope first
656 if token.repo_id:
658 if token.repo_id:
657 # token has a scope, we need to verify it
659 # token has a scope, we need to verify it
658 if scope_repo_id != token.repo_id:
660 if scope_repo_id != token.repo_id:
659 log.debug(
661 log.debug(
660 'Scope mismatch: token has a set repo scope: %s, '
662 'Scope mismatch: token has a set repo scope: %s, '
661 'and calling scope is:%s, skipping further checks',
663 'and calling scope is:%s, skipping further checks',
662 token.repo, scope_repo_id)
664 token.repo, scope_repo_id)
663 # token has a scope, and it doesn't match, skip token
665 # token has a scope, and it doesn't match, skip token
664 continue
666 continue
665
667
666 if token.api_key.startswith(crypto_backend.ENC_PREF):
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
667 hash_tokens.append(token.api_key)
669 hash_tokens.append(token.api_key)
668 else:
670 else:
669 plain_tokens.append(token.api_key)
671 plain_tokens.append(token.api_key)
670
672
671 is_plain_match = auth_token in plain_tokens
673 is_plain_match = auth_token in plain_tokens
672 if is_plain_match:
674 if is_plain_match:
673 return True
675 return True
674
676
675 for hashed in hash_tokens:
677 for hashed in hash_tokens:
676 # TODO(marcink): this is expensive to calculate, but most secure
678 # TODO(marcink): this is expensive to calculate, but most secure
677 match = crypto_backend.hash_check(auth_token, hashed)
679 match = crypto_backend.hash_check(auth_token, hashed)
678 if match:
680 if match:
679 return True
681 return True
680
682
681 return False
683 return False
682
684
683 @property
685 @property
684 def ip_addresses(self):
686 def ip_addresses(self):
685 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
686 return [x.ip_addr for x in ret]
688 return [x.ip_addr for x in ret]
687
689
688 @property
690 @property
689 def username_and_name(self):
691 def username_and_name(self):
690 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
691
693
692 @property
694 @property
693 def username_or_name_or_email(self):
695 def username_or_name_or_email(self):
694 full_name = self.full_name if self.full_name is not ' ' else None
696 full_name = self.full_name if self.full_name is not ' ' else None
695 return self.username or full_name or self.email
697 return self.username or full_name or self.email
696
698
697 @property
699 @property
698 def full_name(self):
700 def full_name(self):
699 return '%s %s' % (self.firstname, self.lastname)
701 return '%s %s' % (self.firstname, self.lastname)
700
702
701 @property
703 @property
702 def full_name_or_username(self):
704 def full_name_or_username(self):
703 return ('%s %s' % (self.firstname, self.lastname)
705 return ('%s %s' % (self.firstname, self.lastname)
704 if (self.firstname and self.lastname) else self.username)
706 if (self.firstname and self.lastname) else self.username)
705
707
706 @property
708 @property
707 def full_contact(self):
709 def full_contact(self):
708 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
709
711
710 @property
712 @property
711 def short_contact(self):
713 def short_contact(self):
712 return '%s %s' % (self.firstname, self.lastname)
714 return '%s %s' % (self.firstname, self.lastname)
713
715
714 @property
716 @property
715 def is_admin(self):
717 def is_admin(self):
716 return self.admin
718 return self.admin
717
719
718 @property
720 @property
719 def AuthUser(self):
721 def AuthUser(self):
720 """
722 """
721 Returns instance of AuthUser for this user
723 Returns instance of AuthUser for this user
722 """
724 """
723 from rhodecode.lib.auth import AuthUser
725 from rhodecode.lib.auth import AuthUser
724 return AuthUser(user_id=self.user_id, username=self.username)
726 return AuthUser(user_id=self.user_id, username=self.username)
725
727
726 @hybrid_property
728 @hybrid_property
727 def user_data(self):
729 def user_data(self):
728 if not self._user_data:
730 if not self._user_data:
729 return {}
731 return {}
730
732
731 try:
733 try:
732 return json.loads(self._user_data)
734 return json.loads(self._user_data)
733 except TypeError:
735 except TypeError:
734 return {}
736 return {}
735
737
736 @user_data.setter
738 @user_data.setter
737 def user_data(self, val):
739 def user_data(self, val):
738 if not isinstance(val, dict):
740 if not isinstance(val, dict):
739 raise Exception('user_data must be dict, got %s' % type(val))
741 raise Exception('user_data must be dict, got %s' % type(val))
740 try:
742 try:
741 self._user_data = json.dumps(val)
743 self._user_data = json.dumps(val)
742 except Exception:
744 except Exception:
743 log.error(traceback.format_exc())
745 log.error(traceback.format_exc())
744
746
745 @classmethod
747 @classmethod
746 def get_by_username(cls, username, case_insensitive=False,
748 def get_by_username(cls, username, case_insensitive=False,
747 cache=False, identity_cache=False):
749 cache=False, identity_cache=False):
748 session = Session()
750 session = Session()
749
751
750 if case_insensitive:
752 if case_insensitive:
751 q = cls.query().filter(
753 q = cls.query().filter(
752 func.lower(cls.username) == func.lower(username))
754 func.lower(cls.username) == func.lower(username))
753 else:
755 else:
754 q = cls.query().filter(cls.username == username)
756 q = cls.query().filter(cls.username == username)
755
757
756 if cache:
758 if cache:
757 if identity_cache:
759 if identity_cache:
758 val = cls.identity_cache(session, 'username', username)
760 val = cls.identity_cache(session, 'username', username)
759 if val:
761 if val:
760 return val
762 return val
761 else:
763 else:
762 q = q.options(
764 q = q.options(
763 FromCache("sql_cache_short",
765 FromCache("sql_cache_short",
764 "get_user_by_name_%s" % _hash_key(username)))
766 "get_user_by_name_%s" % _hash_key(username)))
765
767
766 return q.scalar()
768 return q.scalar()
767
769
768 @classmethod
770 @classmethod
769 def get_by_auth_token(cls, auth_token, cache=False):
771 def get_by_auth_token(cls, auth_token, cache=False):
770 q = UserApiKeys.query()\
772 q = UserApiKeys.query()\
771 .filter(UserApiKeys.api_key == auth_token)\
773 .filter(UserApiKeys.api_key == auth_token)\
772 .filter(or_(UserApiKeys.expires == -1,
774 .filter(or_(UserApiKeys.expires == -1,
773 UserApiKeys.expires >= time.time()))
775 UserApiKeys.expires >= time.time()))
774 if cache:
776 if cache:
775 q = q.options(FromCache("sql_cache_short",
777 q = q.options(FromCache("sql_cache_short",
776 "get_auth_token_%s" % auth_token))
778 "get_auth_token_%s" % auth_token))
777
779
778 match = q.first()
780 match = q.first()
779 if match:
781 if match:
780 return match.user
782 return match.user
781
783
782 @classmethod
784 @classmethod
783 def get_by_email(cls, email, case_insensitive=False, cache=False):
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
784
786
785 if case_insensitive:
787 if case_insensitive:
786 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
787
789
788 else:
790 else:
789 q = cls.query().filter(cls.email == email)
791 q = cls.query().filter(cls.email == email)
790
792
791 if cache:
793 if cache:
792 q = q.options(FromCache("sql_cache_short",
794 q = q.options(FromCache("sql_cache_short",
793 "get_email_key_%s" % _hash_key(email)))
795 "get_email_key_%s" % _hash_key(email)))
794
796
795 ret = q.scalar()
797 ret = q.scalar()
796 if ret is None:
798 if ret is None:
797 q = UserEmailMap.query()
799 q = UserEmailMap.query()
798 # try fetching in alternate email map
800 # try fetching in alternate email map
799 if case_insensitive:
801 if case_insensitive:
800 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
801 else:
803 else:
802 q = q.filter(UserEmailMap.email == email)
804 q = q.filter(UserEmailMap.email == email)
803 q = q.options(joinedload(UserEmailMap.user))
805 q = q.options(joinedload(UserEmailMap.user))
804 if cache:
806 if cache:
805 q = q.options(FromCache("sql_cache_short",
807 q = q.options(FromCache("sql_cache_short",
806 "get_email_map_key_%s" % email))
808 "get_email_map_key_%s" % email))
807 ret = getattr(q.scalar(), 'user', None)
809 ret = getattr(q.scalar(), 'user', None)
808
810
809 return ret
811 return ret
810
812
811 @classmethod
813 @classmethod
812 def get_from_cs_author(cls, author):
814 def get_from_cs_author(cls, author):
813 """
815 """
814 Tries to get User objects out of commit author string
816 Tries to get User objects out of commit author string
815
817
816 :param author:
818 :param author:
817 """
819 """
818 from rhodecode.lib.helpers import email, author_name
820 from rhodecode.lib.helpers import email, author_name
819 # Valid email in the attribute passed, see if they're in the system
821 # Valid email in the attribute passed, see if they're in the system
820 _email = email(author)
822 _email = email(author)
821 if _email:
823 if _email:
822 user = cls.get_by_email(_email, case_insensitive=True)
824 user = cls.get_by_email(_email, case_insensitive=True)
823 if user:
825 if user:
824 return user
826 return user
825 # Maybe we can match by username?
827 # Maybe we can match by username?
826 _author = author_name(author)
828 _author = author_name(author)
827 user = cls.get_by_username(_author, case_insensitive=True)
829 user = cls.get_by_username(_author, case_insensitive=True)
828 if user:
830 if user:
829 return user
831 return user
830
832
831 def update_userdata(self, **kwargs):
833 def update_userdata(self, **kwargs):
832 usr = self
834 usr = self
833 old = usr.user_data
835 old = usr.user_data
834 old.update(**kwargs)
836 old.update(**kwargs)
835 usr.user_data = old
837 usr.user_data = old
836 Session().add(usr)
838 Session().add(usr)
837 log.debug('updated userdata with ', kwargs)
839 log.debug('updated userdata with ', kwargs)
838
840
839 def update_lastlogin(self):
841 def update_lastlogin(self):
840 """Update user lastlogin"""
842 """Update user lastlogin"""
841 self.last_login = datetime.datetime.now()
843 self.last_login = datetime.datetime.now()
842 Session().add(self)
844 Session().add(self)
843 log.debug('updated user %s lastlogin', self.username)
845 log.debug('updated user %s lastlogin', self.username)
844
846
845 def update_lastactivity(self):
847 def update_lastactivity(self):
846 """Update user lastactivity"""
848 """Update user lastactivity"""
847 usr = self
849 usr = self
848 old = usr.user_data
850 old = usr.user_data
849 old.update({'last_activity': time.time()})
851 old.update({'last_activity': time.time()})
850 usr.user_data = old
852 usr.user_data = old
851 Session().add(usr)
853 Session().add(usr)
852 log.debug('updated user %s lastactivity', usr.username)
854 log.debug('updated user %s lastactivity', usr.username)
853
855
854 def update_password(self, new_password):
856 def update_password(self, new_password):
855 from rhodecode.lib.auth import get_crypt_password
857 from rhodecode.lib.auth import get_crypt_password
856
858
857 self.password = get_crypt_password(new_password)
859 self.password = get_crypt_password(new_password)
858 Session().add(self)
860 Session().add(self)
859
861
860 @classmethod
862 @classmethod
861 def get_first_super_admin(cls):
863 def get_first_super_admin(cls):
862 user = User.query().filter(User.admin == true()).first()
864 user = User.query().filter(User.admin == true()).first()
863 if user is None:
865 if user is None:
864 raise Exception('FATAL: Missing administrative account!')
866 raise Exception('FATAL: Missing administrative account!')
865 return user
867 return user
866
868
867 @classmethod
869 @classmethod
868 def get_all_super_admins(cls):
870 def get_all_super_admins(cls):
869 """
871 """
870 Returns all admin accounts sorted by username
872 Returns all admin accounts sorted by username
871 """
873 """
872 return User.query().filter(User.admin == true())\
874 return User.query().filter(User.admin == true())\
873 .order_by(User.username.asc()).all()
875 .order_by(User.username.asc()).all()
874
876
875 @classmethod
877 @classmethod
876 def get_default_user(cls, cache=False):
878 def get_default_user(cls, cache=False):
877 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
879 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
878 if user is None:
880 if user is None:
879 raise Exception('FATAL: Missing default account!')
881 raise Exception('FATAL: Missing default account!')
880 return user
882 return user
881
883
882 def _get_default_perms(self, user, suffix=''):
884 def _get_default_perms(self, user, suffix=''):
883 from rhodecode.model.permission import PermissionModel
885 from rhodecode.model.permission import PermissionModel
884 return PermissionModel().get_default_perms(user.user_perms, suffix)
886 return PermissionModel().get_default_perms(user.user_perms, suffix)
885
887
886 def get_default_perms(self, suffix=''):
888 def get_default_perms(self, suffix=''):
887 return self._get_default_perms(self, suffix)
889 return self._get_default_perms(self, suffix)
888
890
889 def get_api_data(self, include_secrets=False, details='full'):
891 def get_api_data(self, include_secrets=False, details='full'):
890 """
892 """
891 Common function for generating user related data for API
893 Common function for generating user related data for API
892
894
893 :param include_secrets: By default secrets in the API data will be replaced
895 :param include_secrets: By default secrets in the API data will be replaced
894 by a placeholder value to prevent exposing this data by accident. In case
896 by a placeholder value to prevent exposing this data by accident. In case
895 this data shall be exposed, set this flag to ``True``.
897 this data shall be exposed, set this flag to ``True``.
896
898
897 :param details: details can be 'basic|full' basic gives only a subset of
899 :param details: details can be 'basic|full' basic gives only a subset of
898 the available user information that includes user_id, name and emails.
900 the available user information that includes user_id, name and emails.
899 """
901 """
900 user = self
902 user = self
901 user_data = self.user_data
903 user_data = self.user_data
902 data = {
904 data = {
903 'user_id': user.user_id,
905 'user_id': user.user_id,
904 'username': user.username,
906 'username': user.username,
905 'firstname': user.name,
907 'firstname': user.name,
906 'lastname': user.lastname,
908 'lastname': user.lastname,
907 'email': user.email,
909 'email': user.email,
908 'emails': user.emails,
910 'emails': user.emails,
909 }
911 }
910 if details == 'basic':
912 if details == 'basic':
911 return data
913 return data
912
914
913 api_key_length = 40
915 api_key_length = 40
914 api_key_replacement = '*' * api_key_length
916 api_key_replacement = '*' * api_key_length
915
917
916 extras = {
918 extras = {
917 'api_keys': [api_key_replacement],
919 'api_keys': [api_key_replacement],
918 'auth_tokens': [api_key_replacement],
920 'auth_tokens': [api_key_replacement],
919 'active': user.active,
921 'active': user.active,
920 'admin': user.admin,
922 'admin': user.admin,
921 'extern_type': user.extern_type,
923 'extern_type': user.extern_type,
922 'extern_name': user.extern_name,
924 'extern_name': user.extern_name,
923 'last_login': user.last_login,
925 'last_login': user.last_login,
924 'ip_addresses': user.ip_addresses,
926 'ip_addresses': user.ip_addresses,
925 'language': user_data.get('language')
927 'language': user_data.get('language')
926 }
928 }
927 data.update(extras)
929 data.update(extras)
928
930
929 if include_secrets:
931 if include_secrets:
930 data['api_keys'] = user.auth_tokens
932 data['api_keys'] = user.auth_tokens
931 data['auth_tokens'] = user.extra_auth_tokens
933 data['auth_tokens'] = user.extra_auth_tokens
932 return data
934 return data
933
935
934 def __json__(self):
936 def __json__(self):
935 data = {
937 data = {
936 'full_name': self.full_name,
938 'full_name': self.full_name,
937 'full_name_or_username': self.full_name_or_username,
939 'full_name_or_username': self.full_name_or_username,
938 'short_contact': self.short_contact,
940 'short_contact': self.short_contact,
939 'full_contact': self.full_contact,
941 'full_contact': self.full_contact,
940 }
942 }
941 data.update(self.get_api_data())
943 data.update(self.get_api_data())
942 return data
944 return data
943
945
944
946
945 class UserApiKeys(Base, BaseModel):
947 class UserApiKeys(Base, BaseModel):
946 __tablename__ = 'user_api_keys'
948 __tablename__ = 'user_api_keys'
947 __table_args__ = (
949 __table_args__ = (
948 Index('uak_api_key_idx', 'api_key'),
950 Index('uak_api_key_idx', 'api_key'),
949 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
951 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
950 UniqueConstraint('api_key'),
952 UniqueConstraint('api_key'),
951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
952 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
954 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
953 )
955 )
954 __mapper_args__ = {}
956 __mapper_args__ = {}
955
957
956 # ApiKey role
958 # ApiKey role
957 ROLE_ALL = 'token_role_all'
959 ROLE_ALL = 'token_role_all'
958 ROLE_HTTP = 'token_role_http'
960 ROLE_HTTP = 'token_role_http'
959 ROLE_VCS = 'token_role_vcs'
961 ROLE_VCS = 'token_role_vcs'
960 ROLE_API = 'token_role_api'
962 ROLE_API = 'token_role_api'
961 ROLE_FEED = 'token_role_feed'
963 ROLE_FEED = 'token_role_feed'
962 ROLE_PASSWORD_RESET = 'token_password_reset'
964 ROLE_PASSWORD_RESET = 'token_password_reset'
963
965
964 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
966 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
965
967
966 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
968 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
968 api_key = Column("api_key", String(255), nullable=False, unique=True)
970 api_key = Column("api_key", String(255), nullable=False, unique=True)
969 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
971 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
970 expires = Column('expires', Float(53), nullable=False)
972 expires = Column('expires', Float(53), nullable=False)
971 role = Column('role', String(255), nullable=True)
973 role = Column('role', String(255), nullable=True)
972 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
974 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
973
975
974 # scope columns
976 # scope columns
975 repo_id = Column(
977 repo_id = Column(
976 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
978 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
977 nullable=True, unique=None, default=None)
979 nullable=True, unique=None, default=None)
978 repo = relationship('Repository', lazy='joined')
980 repo = relationship('Repository', lazy='joined')
979
981
980 repo_group_id = Column(
982 repo_group_id = Column(
981 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
983 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
982 nullable=True, unique=None, default=None)
984 nullable=True, unique=None, default=None)
983 repo_group = relationship('RepoGroup', lazy='joined')
985 repo_group = relationship('RepoGroup', lazy='joined')
984
986
985 user = relationship('User', lazy='joined')
987 user = relationship('User', lazy='joined')
986
988
987 def __unicode__(self):
989 def __unicode__(self):
988 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
990 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
989
991
990 def __json__(self):
992 def __json__(self):
991 data = {
993 data = {
992 'auth_token': self.api_key,
994 'auth_token': self.api_key,
993 'role': self.role,
995 'role': self.role,
994 'scope': self.scope_humanized,
996 'scope': self.scope_humanized,
995 'expired': self.expired
997 'expired': self.expired
996 }
998 }
997 return data
999 return data
998
1000
999 @property
1001 @property
1000 def expired(self):
1002 def expired(self):
1001 if self.expires == -1:
1003 if self.expires == -1:
1002 return False
1004 return False
1003 return time.time() > self.expires
1005 return time.time() > self.expires
1004
1006
1005 @classmethod
1007 @classmethod
1006 def _get_role_name(cls, role):
1008 def _get_role_name(cls, role):
1007 return {
1009 return {
1008 cls.ROLE_ALL: _('all'),
1010 cls.ROLE_ALL: _('all'),
1009 cls.ROLE_HTTP: _('http/web interface'),
1011 cls.ROLE_HTTP: _('http/web interface'),
1010 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1012 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1011 cls.ROLE_API: _('api calls'),
1013 cls.ROLE_API: _('api calls'),
1012 cls.ROLE_FEED: _('feed access'),
1014 cls.ROLE_FEED: _('feed access'),
1013 }.get(role, role)
1015 }.get(role, role)
1014
1016
1015 @property
1017 @property
1016 def role_humanized(self):
1018 def role_humanized(self):
1017 return self._get_role_name(self.role)
1019 return self._get_role_name(self.role)
1018
1020
1019 def _get_scope(self):
1021 def _get_scope(self):
1020 if self.repo:
1022 if self.repo:
1021 return repr(self.repo)
1023 return repr(self.repo)
1022 if self.repo_group:
1024 if self.repo_group:
1023 return repr(self.repo_group) + ' (recursive)'
1025 return repr(self.repo_group) + ' (recursive)'
1024 return 'global'
1026 return 'global'
1025
1027
1026 @property
1028 @property
1027 def scope_humanized(self):
1029 def scope_humanized(self):
1028 return self._get_scope()
1030 return self._get_scope()
1029
1031
1030
1032
1031 class UserEmailMap(Base, BaseModel):
1033 class UserEmailMap(Base, BaseModel):
1032 __tablename__ = 'user_email_map'
1034 __tablename__ = 'user_email_map'
1033 __table_args__ = (
1035 __table_args__ = (
1034 Index('uem_email_idx', 'email'),
1036 Index('uem_email_idx', 'email'),
1035 UniqueConstraint('email'),
1037 UniqueConstraint('email'),
1036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1039 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 )
1040 )
1039 __mapper_args__ = {}
1041 __mapper_args__ = {}
1040
1042
1041 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1043 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1043 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1045 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1044 user = relationship('User', lazy='joined')
1046 user = relationship('User', lazy='joined')
1045
1047
1046 @validates('_email')
1048 @validates('_email')
1047 def validate_email(self, key, email):
1049 def validate_email(self, key, email):
1048 # check if this email is not main one
1050 # check if this email is not main one
1049 main_email = Session().query(User).filter(User.email == email).scalar()
1051 main_email = Session().query(User).filter(User.email == email).scalar()
1050 if main_email is not None:
1052 if main_email is not None:
1051 raise AttributeError('email %s is present is user table' % email)
1053 raise AttributeError('email %s is present is user table' % email)
1052 return email
1054 return email
1053
1055
1054 @hybrid_property
1056 @hybrid_property
1055 def email(self):
1057 def email(self):
1056 return self._email
1058 return self._email
1057
1059
1058 @email.setter
1060 @email.setter
1059 def email(self, val):
1061 def email(self, val):
1060 self._email = val.lower() if val else None
1062 self._email = val.lower() if val else None
1061
1063
1062
1064
1063 class UserIpMap(Base, BaseModel):
1065 class UserIpMap(Base, BaseModel):
1064 __tablename__ = 'user_ip_map'
1066 __tablename__ = 'user_ip_map'
1065 __table_args__ = (
1067 __table_args__ = (
1066 UniqueConstraint('user_id', 'ip_addr'),
1068 UniqueConstraint('user_id', 'ip_addr'),
1067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1069 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1070 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1069 )
1071 )
1070 __mapper_args__ = {}
1072 __mapper_args__ = {}
1071
1073
1072 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1074 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1073 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1075 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1074 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1076 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1075 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1077 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1076 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1078 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1077 user = relationship('User', lazy='joined')
1079 user = relationship('User', lazy='joined')
1078
1080
1079 @classmethod
1081 @classmethod
1080 def _get_ip_range(cls, ip_addr):
1082 def _get_ip_range(cls, ip_addr):
1081 net = ipaddress.ip_network(ip_addr, strict=False)
1083 net = ipaddress.ip_network(ip_addr, strict=False)
1082 return [str(net.network_address), str(net.broadcast_address)]
1084 return [str(net.network_address), str(net.broadcast_address)]
1083
1085
1084 def __json__(self):
1086 def __json__(self):
1085 return {
1087 return {
1086 'ip_addr': self.ip_addr,
1088 'ip_addr': self.ip_addr,
1087 'ip_range': self._get_ip_range(self.ip_addr),
1089 'ip_range': self._get_ip_range(self.ip_addr),
1088 }
1090 }
1089
1091
1090 def __unicode__(self):
1092 def __unicode__(self):
1091 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1093 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1092 self.user_id, self.ip_addr)
1094 self.user_id, self.ip_addr)
1093
1095
1094
1096
1095 class UserLog(Base, BaseModel):
1097 class UserLog(Base, BaseModel):
1096 __tablename__ = 'user_logs'
1098 __tablename__ = 'user_logs'
1097 __table_args__ = (
1099 __table_args__ = (
1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1100 )
1102 )
1101 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1103 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1102 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1104 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1103 username = Column("username", String(255), nullable=True, unique=None, default=None)
1105 username = Column("username", String(255), nullable=True, unique=None, default=None)
1104 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1106 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1105 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1107 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1106 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1108 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1107 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1109 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1108 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1110 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1109
1111
1110 def __unicode__(self):
1112 def __unicode__(self):
1111 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1113 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1112 self.repository_name,
1114 self.repository_name,
1113 self.action)
1115 self.action)
1114
1116
1115 @property
1117 @property
1116 def action_as_day(self):
1118 def action_as_day(self):
1117 return datetime.date(*self.action_date.timetuple()[:3])
1119 return datetime.date(*self.action_date.timetuple()[:3])
1118
1120
1119 user = relationship('User')
1121 user = relationship('User')
1120 repository = relationship('Repository', cascade='')
1122 repository = relationship('Repository', cascade='')
1121
1123
1122
1124
1123 class UserGroup(Base, BaseModel):
1125 class UserGroup(Base, BaseModel):
1124 __tablename__ = 'users_groups'
1126 __tablename__ = 'users_groups'
1125 __table_args__ = (
1127 __table_args__ = (
1126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1128 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1127 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1129 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1128 )
1130 )
1129
1131
1130 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1132 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1131 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1133 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1132 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1134 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1133 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1135 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1134 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1136 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1137 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1136 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1138 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1137 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1139 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1138
1140
1139 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1141 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1140 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1142 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1141 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1143 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1142 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1144 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1143 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1145 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1144 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1146 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1145
1147
1146 user = relationship('User')
1148 user = relationship('User')
1147
1149
1148 @hybrid_property
1150 @hybrid_property
1149 def group_data(self):
1151 def group_data(self):
1150 if not self._group_data:
1152 if not self._group_data:
1151 return {}
1153 return {}
1152
1154
1153 try:
1155 try:
1154 return json.loads(self._group_data)
1156 return json.loads(self._group_data)
1155 except TypeError:
1157 except TypeError:
1156 return {}
1158 return {}
1157
1159
1158 @group_data.setter
1160 @group_data.setter
1159 def group_data(self, val):
1161 def group_data(self, val):
1160 try:
1162 try:
1161 self._group_data = json.dumps(val)
1163 self._group_data = json.dumps(val)
1162 except Exception:
1164 except Exception:
1163 log.error(traceback.format_exc())
1165 log.error(traceback.format_exc())
1164
1166
1165 def __unicode__(self):
1167 def __unicode__(self):
1166 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1168 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1167 self.users_group_id,
1169 self.users_group_id,
1168 self.users_group_name)
1170 self.users_group_name)
1169
1171
1170 @classmethod
1172 @classmethod
1171 def get_by_group_name(cls, group_name, cache=False,
1173 def get_by_group_name(cls, group_name, cache=False,
1172 case_insensitive=False):
1174 case_insensitive=False):
1173 if case_insensitive:
1175 if case_insensitive:
1174 q = cls.query().filter(func.lower(cls.users_group_name) ==
1176 q = cls.query().filter(func.lower(cls.users_group_name) ==
1175 func.lower(group_name))
1177 func.lower(group_name))
1176
1178
1177 else:
1179 else:
1178 q = cls.query().filter(cls.users_group_name == group_name)
1180 q = cls.query().filter(cls.users_group_name == group_name)
1179 if cache:
1181 if cache:
1180 q = q.options(FromCache(
1182 q = q.options(FromCache(
1181 "sql_cache_short",
1183 "sql_cache_short",
1182 "get_group_%s" % _hash_key(group_name)))
1184 "get_group_%s" % _hash_key(group_name)))
1183 return q.scalar()
1185 return q.scalar()
1184
1186
1185 @classmethod
1187 @classmethod
1186 def get(cls, user_group_id, cache=False):
1188 def get(cls, user_group_id, cache=False):
1187 user_group = cls.query()
1189 user_group = cls.query()
1188 if cache:
1190 if cache:
1189 user_group = user_group.options(FromCache("sql_cache_short",
1191 user_group = user_group.options(FromCache("sql_cache_short",
1190 "get_users_group_%s" % user_group_id))
1192 "get_users_group_%s" % user_group_id))
1191 return user_group.get(user_group_id)
1193 return user_group.get(user_group_id)
1192
1194
1193 def permissions(self, with_admins=True, with_owner=True):
1195 def permissions(self, with_admins=True, with_owner=True):
1194 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1196 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1195 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1197 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1196 joinedload(UserUserGroupToPerm.user),
1198 joinedload(UserUserGroupToPerm.user),
1197 joinedload(UserUserGroupToPerm.permission),)
1199 joinedload(UserUserGroupToPerm.permission),)
1198
1200
1199 # get owners and admins and permissions. We do a trick of re-writing
1201 # get owners and admins and permissions. We do a trick of re-writing
1200 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1202 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1201 # has a global reference and changing one object propagates to all
1203 # has a global reference and changing one object propagates to all
1202 # others. This means if admin is also an owner admin_row that change
1204 # others. This means if admin is also an owner admin_row that change
1203 # would propagate to both objects
1205 # would propagate to both objects
1204 perm_rows = []
1206 perm_rows = []
1205 for _usr in q.all():
1207 for _usr in q.all():
1206 usr = AttributeDict(_usr.user.get_dict())
1208 usr = AttributeDict(_usr.user.get_dict())
1207 usr.permission = _usr.permission.permission_name
1209 usr.permission = _usr.permission.permission_name
1208 perm_rows.append(usr)
1210 perm_rows.append(usr)
1209
1211
1210 # filter the perm rows by 'default' first and then sort them by
1212 # filter the perm rows by 'default' first and then sort them by
1211 # admin,write,read,none permissions sorted again alphabetically in
1213 # admin,write,read,none permissions sorted again alphabetically in
1212 # each group
1214 # each group
1213 perm_rows = sorted(perm_rows, key=display_sort)
1215 perm_rows = sorted(perm_rows, key=display_sort)
1214
1216
1215 _admin_perm = 'usergroup.admin'
1217 _admin_perm = 'usergroup.admin'
1216 owner_row = []
1218 owner_row = []
1217 if with_owner:
1219 if with_owner:
1218 usr = AttributeDict(self.user.get_dict())
1220 usr = AttributeDict(self.user.get_dict())
1219 usr.owner_row = True
1221 usr.owner_row = True
1220 usr.permission = _admin_perm
1222 usr.permission = _admin_perm
1221 owner_row.append(usr)
1223 owner_row.append(usr)
1222
1224
1223 super_admin_rows = []
1225 super_admin_rows = []
1224 if with_admins:
1226 if with_admins:
1225 for usr in User.get_all_super_admins():
1227 for usr in User.get_all_super_admins():
1226 # if this admin is also owner, don't double the record
1228 # if this admin is also owner, don't double the record
1227 if usr.user_id == owner_row[0].user_id:
1229 if usr.user_id == owner_row[0].user_id:
1228 owner_row[0].admin_row = True
1230 owner_row[0].admin_row = True
1229 else:
1231 else:
1230 usr = AttributeDict(usr.get_dict())
1232 usr = AttributeDict(usr.get_dict())
1231 usr.admin_row = True
1233 usr.admin_row = True
1232 usr.permission = _admin_perm
1234 usr.permission = _admin_perm
1233 super_admin_rows.append(usr)
1235 super_admin_rows.append(usr)
1234
1236
1235 return super_admin_rows + owner_row + perm_rows
1237 return super_admin_rows + owner_row + perm_rows
1236
1238
1237 def permission_user_groups(self):
1239 def permission_user_groups(self):
1238 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1240 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1239 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1241 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1240 joinedload(UserGroupUserGroupToPerm.target_user_group),
1242 joinedload(UserGroupUserGroupToPerm.target_user_group),
1241 joinedload(UserGroupUserGroupToPerm.permission),)
1243 joinedload(UserGroupUserGroupToPerm.permission),)
1242
1244
1243 perm_rows = []
1245 perm_rows = []
1244 for _user_group in q.all():
1246 for _user_group in q.all():
1245 usr = AttributeDict(_user_group.user_group.get_dict())
1247 usr = AttributeDict(_user_group.user_group.get_dict())
1246 usr.permission = _user_group.permission.permission_name
1248 usr.permission = _user_group.permission.permission_name
1247 perm_rows.append(usr)
1249 perm_rows.append(usr)
1248
1250
1249 return perm_rows
1251 return perm_rows
1250
1252
1251 def _get_default_perms(self, user_group, suffix=''):
1253 def _get_default_perms(self, user_group, suffix=''):
1252 from rhodecode.model.permission import PermissionModel
1254 from rhodecode.model.permission import PermissionModel
1253 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1255 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1254
1256
1255 def get_default_perms(self, suffix=''):
1257 def get_default_perms(self, suffix=''):
1256 return self._get_default_perms(self, suffix)
1258 return self._get_default_perms(self, suffix)
1257
1259
1258 def get_api_data(self, with_group_members=True, include_secrets=False):
1260 def get_api_data(self, with_group_members=True, include_secrets=False):
1259 """
1261 """
1260 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1262 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1261 basically forwarded.
1263 basically forwarded.
1262
1264
1263 """
1265 """
1264 user_group = self
1266 user_group = self
1265
1267
1266 data = {
1268 data = {
1267 'users_group_id': user_group.users_group_id,
1269 'users_group_id': user_group.users_group_id,
1268 'group_name': user_group.users_group_name,
1270 'group_name': user_group.users_group_name,
1269 'group_description': user_group.user_group_description,
1271 'group_description': user_group.user_group_description,
1270 'active': user_group.users_group_active,
1272 'active': user_group.users_group_active,
1271 'owner': user_group.user.username,
1273 'owner': user_group.user.username,
1272 }
1274 }
1273 if with_group_members:
1275 if with_group_members:
1274 users = []
1276 users = []
1275 for user in user_group.members:
1277 for user in user_group.members:
1276 user = user.user
1278 user = user.user
1277 users.append(user.get_api_data(include_secrets=include_secrets))
1279 users.append(user.get_api_data(include_secrets=include_secrets))
1278 data['users'] = users
1280 data['users'] = users
1279
1281
1280 return data
1282 return data
1281
1283
1282
1284
1283 class UserGroupMember(Base, BaseModel):
1285 class UserGroupMember(Base, BaseModel):
1284 __tablename__ = 'users_groups_members'
1286 __tablename__ = 'users_groups_members'
1285 __table_args__ = (
1287 __table_args__ = (
1286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1289 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1288 )
1290 )
1289
1291
1290 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1292 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1291 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1293 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1292 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1294 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1293
1295
1294 user = relationship('User', lazy='joined')
1296 user = relationship('User', lazy='joined')
1295 users_group = relationship('UserGroup')
1297 users_group = relationship('UserGroup')
1296
1298
1297 def __init__(self, gr_id='', u_id=''):
1299 def __init__(self, gr_id='', u_id=''):
1298 self.users_group_id = gr_id
1300 self.users_group_id = gr_id
1299 self.user_id = u_id
1301 self.user_id = u_id
1300
1302
1301
1303
1302 class RepositoryField(Base, BaseModel):
1304 class RepositoryField(Base, BaseModel):
1303 __tablename__ = 'repositories_fields'
1305 __tablename__ = 'repositories_fields'
1304 __table_args__ = (
1306 __table_args__ = (
1305 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1307 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1308 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1307 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1309 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1308 )
1310 )
1309 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1311 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1310
1312
1311 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1313 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1312 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1314 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1313 field_key = Column("field_key", String(250))
1315 field_key = Column("field_key", String(250))
1314 field_label = Column("field_label", String(1024), nullable=False)
1316 field_label = Column("field_label", String(1024), nullable=False)
1315 field_value = Column("field_value", String(10000), nullable=False)
1317 field_value = Column("field_value", String(10000), nullable=False)
1316 field_desc = Column("field_desc", String(1024), nullable=False)
1318 field_desc = Column("field_desc", String(1024), nullable=False)
1317 field_type = Column("field_type", String(255), nullable=False, unique=None)
1319 field_type = Column("field_type", String(255), nullable=False, unique=None)
1318 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1320 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1319
1321
1320 repository = relationship('Repository')
1322 repository = relationship('Repository')
1321
1323
1322 @property
1324 @property
1323 def field_key_prefixed(self):
1325 def field_key_prefixed(self):
1324 return 'ex_%s' % self.field_key
1326 return 'ex_%s' % self.field_key
1325
1327
1326 @classmethod
1328 @classmethod
1327 def un_prefix_key(cls, key):
1329 def un_prefix_key(cls, key):
1328 if key.startswith(cls.PREFIX):
1330 if key.startswith(cls.PREFIX):
1329 return key[len(cls.PREFIX):]
1331 return key[len(cls.PREFIX):]
1330 return key
1332 return key
1331
1333
1332 @classmethod
1334 @classmethod
1333 def get_by_key_name(cls, key, repo):
1335 def get_by_key_name(cls, key, repo):
1334 row = cls.query()\
1336 row = cls.query()\
1335 .filter(cls.repository == repo)\
1337 .filter(cls.repository == repo)\
1336 .filter(cls.field_key == key).scalar()
1338 .filter(cls.field_key == key).scalar()
1337 return row
1339 return row
1338
1340
1339
1341
1340 class Repository(Base, BaseModel):
1342 class Repository(Base, BaseModel):
1341 __tablename__ = 'repositories'
1343 __tablename__ = 'repositories'
1342 __table_args__ = (
1344 __table_args__ = (
1343 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1345 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1346 )
1348 )
1347 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1349 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1348 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1350 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1349
1351
1350 STATE_CREATED = 'repo_state_created'
1352 STATE_CREATED = 'repo_state_created'
1351 STATE_PENDING = 'repo_state_pending'
1353 STATE_PENDING = 'repo_state_pending'
1352 STATE_ERROR = 'repo_state_error'
1354 STATE_ERROR = 'repo_state_error'
1353
1355
1354 LOCK_AUTOMATIC = 'lock_auto'
1356 LOCK_AUTOMATIC = 'lock_auto'
1355 LOCK_API = 'lock_api'
1357 LOCK_API = 'lock_api'
1356 LOCK_WEB = 'lock_web'
1358 LOCK_WEB = 'lock_web'
1357 LOCK_PULL = 'lock_pull'
1359 LOCK_PULL = 'lock_pull'
1358
1360
1359 NAME_SEP = URL_SEP
1361 NAME_SEP = URL_SEP
1360
1362
1361 repo_id = Column(
1363 repo_id = Column(
1362 "repo_id", Integer(), nullable=False, unique=True, default=None,
1364 "repo_id", Integer(), nullable=False, unique=True, default=None,
1363 primary_key=True)
1365 primary_key=True)
1364 _repo_name = Column(
1366 _repo_name = Column(
1365 "repo_name", Text(), nullable=False, default=None)
1367 "repo_name", Text(), nullable=False, default=None)
1366 _repo_name_hash = Column(
1368 _repo_name_hash = Column(
1367 "repo_name_hash", String(255), nullable=False, unique=True)
1369 "repo_name_hash", String(255), nullable=False, unique=True)
1368 repo_state = Column("repo_state", String(255), nullable=True)
1370 repo_state = Column("repo_state", String(255), nullable=True)
1369
1371
1370 clone_uri = Column(
1372 clone_uri = Column(
1371 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1373 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1372 default=None)
1374 default=None)
1373 repo_type = Column(
1375 repo_type = Column(
1374 "repo_type", String(255), nullable=False, unique=False, default=None)
1376 "repo_type", String(255), nullable=False, unique=False, default=None)
1375 user_id = Column(
1377 user_id = Column(
1376 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1378 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1377 unique=False, default=None)
1379 unique=False, default=None)
1378 private = Column(
1380 private = Column(
1379 "private", Boolean(), nullable=True, unique=None, default=None)
1381 "private", Boolean(), nullable=True, unique=None, default=None)
1380 enable_statistics = Column(
1382 enable_statistics = Column(
1381 "statistics", Boolean(), nullable=True, unique=None, default=True)
1383 "statistics", Boolean(), nullable=True, unique=None, default=True)
1382 enable_downloads = Column(
1384 enable_downloads = Column(
1383 "downloads", Boolean(), nullable=True, unique=None, default=True)
1385 "downloads", Boolean(), nullable=True, unique=None, default=True)
1384 description = Column(
1386 description = Column(
1385 "description", String(10000), nullable=True, unique=None, default=None)
1387 "description", String(10000), nullable=True, unique=None, default=None)
1386 created_on = Column(
1388 created_on = Column(
1387 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1389 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1388 default=datetime.datetime.now)
1390 default=datetime.datetime.now)
1389 updated_on = Column(
1391 updated_on = Column(
1390 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1392 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1391 default=datetime.datetime.now)
1393 default=datetime.datetime.now)
1392 _landing_revision = Column(
1394 _landing_revision = Column(
1393 "landing_revision", String(255), nullable=False, unique=False,
1395 "landing_revision", String(255), nullable=False, unique=False,
1394 default=None)
1396 default=None)
1395 enable_locking = Column(
1397 enable_locking = Column(
1396 "enable_locking", Boolean(), nullable=False, unique=None,
1398 "enable_locking", Boolean(), nullable=False, unique=None,
1397 default=False)
1399 default=False)
1398 _locked = Column(
1400 _locked = Column(
1399 "locked", String(255), nullable=True, unique=False, default=None)
1401 "locked", String(255), nullable=True, unique=False, default=None)
1400 _changeset_cache = Column(
1402 _changeset_cache = Column(
1401 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1403 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1402
1404
1403 fork_id = Column(
1405 fork_id = Column(
1404 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1406 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1405 nullable=True, unique=False, default=None)
1407 nullable=True, unique=False, default=None)
1406 group_id = Column(
1408 group_id = Column(
1407 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1409 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1408 unique=False, default=None)
1410 unique=False, default=None)
1409
1411
1410 user = relationship('User', lazy='joined')
1412 user = relationship('User', lazy='joined')
1411 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1413 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1412 group = relationship('RepoGroup', lazy='joined')
1414 group = relationship('RepoGroup', lazy='joined')
1413 repo_to_perm = relationship(
1415 repo_to_perm = relationship(
1414 'UserRepoToPerm', cascade='all',
1416 'UserRepoToPerm', cascade='all',
1415 order_by='UserRepoToPerm.repo_to_perm_id')
1417 order_by='UserRepoToPerm.repo_to_perm_id')
1416 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1418 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1417 stats = relationship('Statistics', cascade='all', uselist=False)
1419 stats = relationship('Statistics', cascade='all', uselist=False)
1418
1420
1419 followers = relationship(
1421 followers = relationship(
1420 'UserFollowing',
1422 'UserFollowing',
1421 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1423 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1422 cascade='all')
1424 cascade='all')
1423 extra_fields = relationship(
1425 extra_fields = relationship(
1424 'RepositoryField', cascade="all, delete, delete-orphan")
1426 'RepositoryField', cascade="all, delete, delete-orphan")
1425 logs = relationship('UserLog')
1427 logs = relationship('UserLog')
1426 comments = relationship(
1428 comments = relationship(
1427 'ChangesetComment', cascade="all, delete, delete-orphan")
1429 'ChangesetComment', cascade="all, delete, delete-orphan")
1428 pull_requests_source = relationship(
1430 pull_requests_source = relationship(
1429 'PullRequest',
1431 'PullRequest',
1430 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1432 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1431 cascade="all, delete, delete-orphan")
1433 cascade="all, delete, delete-orphan")
1432 pull_requests_target = relationship(
1434 pull_requests_target = relationship(
1433 'PullRequest',
1435 'PullRequest',
1434 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1436 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1435 cascade="all, delete, delete-orphan")
1437 cascade="all, delete, delete-orphan")
1436 ui = relationship('RepoRhodeCodeUi', cascade="all")
1438 ui = relationship('RepoRhodeCodeUi', cascade="all")
1437 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1439 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1438 integrations = relationship('Integration',
1440 integrations = relationship('Integration',
1439 cascade="all, delete, delete-orphan")
1441 cascade="all, delete, delete-orphan")
1440
1442
1441 def __unicode__(self):
1443 def __unicode__(self):
1442 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1444 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1443 safe_unicode(self.repo_name))
1445 safe_unicode(self.repo_name))
1444
1446
1445 @hybrid_property
1447 @hybrid_property
1446 def landing_rev(self):
1448 def landing_rev(self):
1447 # always should return [rev_type, rev]
1449 # always should return [rev_type, rev]
1448 if self._landing_revision:
1450 if self._landing_revision:
1449 _rev_info = self._landing_revision.split(':')
1451 _rev_info = self._landing_revision.split(':')
1450 if len(_rev_info) < 2:
1452 if len(_rev_info) < 2:
1451 _rev_info.insert(0, 'rev')
1453 _rev_info.insert(0, 'rev')
1452 return [_rev_info[0], _rev_info[1]]
1454 return [_rev_info[0], _rev_info[1]]
1453 return [None, None]
1455 return [None, None]
1454
1456
1455 @landing_rev.setter
1457 @landing_rev.setter
1456 def landing_rev(self, val):
1458 def landing_rev(self, val):
1457 if ':' not in val:
1459 if ':' not in val:
1458 raise ValueError('value must be delimited with `:` and consist '
1460 raise ValueError('value must be delimited with `:` and consist '
1459 'of <rev_type>:<rev>, got %s instead' % val)
1461 'of <rev_type>:<rev>, got %s instead' % val)
1460 self._landing_revision = val
1462 self._landing_revision = val
1461
1463
1462 @hybrid_property
1464 @hybrid_property
1463 def locked(self):
1465 def locked(self):
1464 if self._locked:
1466 if self._locked:
1465 user_id, timelocked, reason = self._locked.split(':')
1467 user_id, timelocked, reason = self._locked.split(':')
1466 lock_values = int(user_id), timelocked, reason
1468 lock_values = int(user_id), timelocked, reason
1467 else:
1469 else:
1468 lock_values = [None, None, None]
1470 lock_values = [None, None, None]
1469 return lock_values
1471 return lock_values
1470
1472
1471 @locked.setter
1473 @locked.setter
1472 def locked(self, val):
1474 def locked(self, val):
1473 if val and isinstance(val, (list, tuple)):
1475 if val and isinstance(val, (list, tuple)):
1474 self._locked = ':'.join(map(str, val))
1476 self._locked = ':'.join(map(str, val))
1475 else:
1477 else:
1476 self._locked = None
1478 self._locked = None
1477
1479
1478 @hybrid_property
1480 @hybrid_property
1479 def changeset_cache(self):
1481 def changeset_cache(self):
1480 from rhodecode.lib.vcs.backends.base import EmptyCommit
1482 from rhodecode.lib.vcs.backends.base import EmptyCommit
1481 dummy = EmptyCommit().__json__()
1483 dummy = EmptyCommit().__json__()
1482 if not self._changeset_cache:
1484 if not self._changeset_cache:
1483 return dummy
1485 return dummy
1484 try:
1486 try:
1485 return json.loads(self._changeset_cache)
1487 return json.loads(self._changeset_cache)
1486 except TypeError:
1488 except TypeError:
1487 return dummy
1489 return dummy
1488 except Exception:
1490 except Exception:
1489 log.error(traceback.format_exc())
1491 log.error(traceback.format_exc())
1490 return dummy
1492 return dummy
1491
1493
1492 @changeset_cache.setter
1494 @changeset_cache.setter
1493 def changeset_cache(self, val):
1495 def changeset_cache(self, val):
1494 try:
1496 try:
1495 self._changeset_cache = json.dumps(val)
1497 self._changeset_cache = json.dumps(val)
1496 except Exception:
1498 except Exception:
1497 log.error(traceback.format_exc())
1499 log.error(traceback.format_exc())
1498
1500
1499 @hybrid_property
1501 @hybrid_property
1500 def repo_name(self):
1502 def repo_name(self):
1501 return self._repo_name
1503 return self._repo_name
1502
1504
1503 @repo_name.setter
1505 @repo_name.setter
1504 def repo_name(self, value):
1506 def repo_name(self, value):
1505 self._repo_name = value
1507 self._repo_name = value
1506 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1508 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1507
1509
1508 @classmethod
1510 @classmethod
1509 def normalize_repo_name(cls, repo_name):
1511 def normalize_repo_name(cls, repo_name):
1510 """
1512 """
1511 Normalizes os specific repo_name to the format internally stored inside
1513 Normalizes os specific repo_name to the format internally stored inside
1512 database using URL_SEP
1514 database using URL_SEP
1513
1515
1514 :param cls:
1516 :param cls:
1515 :param repo_name:
1517 :param repo_name:
1516 """
1518 """
1517 return cls.NAME_SEP.join(repo_name.split(os.sep))
1519 return cls.NAME_SEP.join(repo_name.split(os.sep))
1518
1520
1519 @classmethod
1521 @classmethod
1520 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1522 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1521 session = Session()
1523 session = Session()
1522 q = session.query(cls).filter(cls.repo_name == repo_name)
1524 q = session.query(cls).filter(cls.repo_name == repo_name)
1523
1525
1524 if cache:
1526 if cache:
1525 if identity_cache:
1527 if identity_cache:
1526 val = cls.identity_cache(session, 'repo_name', repo_name)
1528 val = cls.identity_cache(session, 'repo_name', repo_name)
1527 if val:
1529 if val:
1528 return val
1530 return val
1529 else:
1531 else:
1530 q = q.options(
1532 q = q.options(
1531 FromCache("sql_cache_short",
1533 FromCache("sql_cache_short",
1532 "get_repo_by_name_%s" % _hash_key(repo_name)))
1534 "get_repo_by_name_%s" % _hash_key(repo_name)))
1533
1535
1534 return q.scalar()
1536 return q.scalar()
1535
1537
1536 @classmethod
1538 @classmethod
1537 def get_by_full_path(cls, repo_full_path):
1539 def get_by_full_path(cls, repo_full_path):
1538 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1540 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1539 repo_name = cls.normalize_repo_name(repo_name)
1541 repo_name = cls.normalize_repo_name(repo_name)
1540 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1542 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1541
1543
1542 @classmethod
1544 @classmethod
1543 def get_repo_forks(cls, repo_id):
1545 def get_repo_forks(cls, repo_id):
1544 return cls.query().filter(Repository.fork_id == repo_id)
1546 return cls.query().filter(Repository.fork_id == repo_id)
1545
1547
1546 @classmethod
1548 @classmethod
1547 def base_path(cls):
1549 def base_path(cls):
1548 """
1550 """
1549 Returns base path when all repos are stored
1551 Returns base path when all repos are stored
1550
1552
1551 :param cls:
1553 :param cls:
1552 """
1554 """
1553 q = Session().query(RhodeCodeUi)\
1555 q = Session().query(RhodeCodeUi)\
1554 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1556 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1555 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1557 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1556 return q.one().ui_value
1558 return q.one().ui_value
1557
1559
1558 @classmethod
1560 @classmethod
1559 def is_valid(cls, repo_name):
1561 def is_valid(cls, repo_name):
1560 """
1562 """
1561 returns True if given repo name is a valid filesystem repository
1563 returns True if given repo name is a valid filesystem repository
1562
1564
1563 :param cls:
1565 :param cls:
1564 :param repo_name:
1566 :param repo_name:
1565 """
1567 """
1566 from rhodecode.lib.utils import is_valid_repo
1568 from rhodecode.lib.utils import is_valid_repo
1567
1569
1568 return is_valid_repo(repo_name, cls.base_path())
1570 return is_valid_repo(repo_name, cls.base_path())
1569
1571
1570 @classmethod
1572 @classmethod
1571 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1573 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1572 case_insensitive=True):
1574 case_insensitive=True):
1573 q = Repository.query()
1575 q = Repository.query()
1574
1576
1575 if not isinstance(user_id, Optional):
1577 if not isinstance(user_id, Optional):
1576 q = q.filter(Repository.user_id == user_id)
1578 q = q.filter(Repository.user_id == user_id)
1577
1579
1578 if not isinstance(group_id, Optional):
1580 if not isinstance(group_id, Optional):
1579 q = q.filter(Repository.group_id == group_id)
1581 q = q.filter(Repository.group_id == group_id)
1580
1582
1581 if case_insensitive:
1583 if case_insensitive:
1582 q = q.order_by(func.lower(Repository.repo_name))
1584 q = q.order_by(func.lower(Repository.repo_name))
1583 else:
1585 else:
1584 q = q.order_by(Repository.repo_name)
1586 q = q.order_by(Repository.repo_name)
1585 return q.all()
1587 return q.all()
1586
1588
1587 @property
1589 @property
1588 def forks(self):
1590 def forks(self):
1589 """
1591 """
1590 Return forks of this repo
1592 Return forks of this repo
1591 """
1593 """
1592 return Repository.get_repo_forks(self.repo_id)
1594 return Repository.get_repo_forks(self.repo_id)
1593
1595
1594 @property
1596 @property
1595 def parent(self):
1597 def parent(self):
1596 """
1598 """
1597 Returns fork parent
1599 Returns fork parent
1598 """
1600 """
1599 return self.fork
1601 return self.fork
1600
1602
1601 @property
1603 @property
1602 def just_name(self):
1604 def just_name(self):
1603 return self.repo_name.split(self.NAME_SEP)[-1]
1605 return self.repo_name.split(self.NAME_SEP)[-1]
1604
1606
1605 @property
1607 @property
1606 def groups_with_parents(self):
1608 def groups_with_parents(self):
1607 groups = []
1609 groups = []
1608 if self.group is None:
1610 if self.group is None:
1609 return groups
1611 return groups
1610
1612
1611 cur_gr = self.group
1613 cur_gr = self.group
1612 groups.insert(0, cur_gr)
1614 groups.insert(0, cur_gr)
1613 while 1:
1615 while 1:
1614 gr = getattr(cur_gr, 'parent_group', None)
1616 gr = getattr(cur_gr, 'parent_group', None)
1615 cur_gr = cur_gr.parent_group
1617 cur_gr = cur_gr.parent_group
1616 if gr is None:
1618 if gr is None:
1617 break
1619 break
1618 groups.insert(0, gr)
1620 groups.insert(0, gr)
1619
1621
1620 return groups
1622 return groups
1621
1623
1622 @property
1624 @property
1623 def groups_and_repo(self):
1625 def groups_and_repo(self):
1624 return self.groups_with_parents, self
1626 return self.groups_with_parents, self
1625
1627
1626 @LazyProperty
1628 @LazyProperty
1627 def repo_path(self):
1629 def repo_path(self):
1628 """
1630 """
1629 Returns base full path for that repository means where it actually
1631 Returns base full path for that repository means where it actually
1630 exists on a filesystem
1632 exists on a filesystem
1631 """
1633 """
1632 q = Session().query(RhodeCodeUi).filter(
1634 q = Session().query(RhodeCodeUi).filter(
1633 RhodeCodeUi.ui_key == self.NAME_SEP)
1635 RhodeCodeUi.ui_key == self.NAME_SEP)
1634 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1636 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1635 return q.one().ui_value
1637 return q.one().ui_value
1636
1638
1637 @property
1639 @property
1638 def repo_full_path(self):
1640 def repo_full_path(self):
1639 p = [self.repo_path]
1641 p = [self.repo_path]
1640 # we need to split the name by / since this is how we store the
1642 # we need to split the name by / since this is how we store the
1641 # names in the database, but that eventually needs to be converted
1643 # names in the database, but that eventually needs to be converted
1642 # into a valid system path
1644 # into a valid system path
1643 p += self.repo_name.split(self.NAME_SEP)
1645 p += self.repo_name.split(self.NAME_SEP)
1644 return os.path.join(*map(safe_unicode, p))
1646 return os.path.join(*map(safe_unicode, p))
1645
1647
1646 @property
1648 @property
1647 def cache_keys(self):
1649 def cache_keys(self):
1648 """
1650 """
1649 Returns associated cache keys for that repo
1651 Returns associated cache keys for that repo
1650 """
1652 """
1651 return CacheKey.query()\
1653 return CacheKey.query()\
1652 .filter(CacheKey.cache_args == self.repo_name)\
1654 .filter(CacheKey.cache_args == self.repo_name)\
1653 .order_by(CacheKey.cache_key)\
1655 .order_by(CacheKey.cache_key)\
1654 .all()
1656 .all()
1655
1657
1656 def get_new_name(self, repo_name):
1658 def get_new_name(self, repo_name):
1657 """
1659 """
1658 returns new full repository name based on assigned group and new new
1660 returns new full repository name based on assigned group and new new
1659
1661
1660 :param group_name:
1662 :param group_name:
1661 """
1663 """
1662 path_prefix = self.group.full_path_splitted if self.group else []
1664 path_prefix = self.group.full_path_splitted if self.group else []
1663 return self.NAME_SEP.join(path_prefix + [repo_name])
1665 return self.NAME_SEP.join(path_prefix + [repo_name])
1664
1666
1665 @property
1667 @property
1666 def _config(self):
1668 def _config(self):
1667 """
1669 """
1668 Returns db based config object.
1670 Returns db based config object.
1669 """
1671 """
1670 from rhodecode.lib.utils import make_db_config
1672 from rhodecode.lib.utils import make_db_config
1671 return make_db_config(clear_session=False, repo=self)
1673 return make_db_config(clear_session=False, repo=self)
1672
1674
1673 def permissions(self, with_admins=True, with_owner=True):
1675 def permissions(self, with_admins=True, with_owner=True):
1674 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1676 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1675 q = q.options(joinedload(UserRepoToPerm.repository),
1677 q = q.options(joinedload(UserRepoToPerm.repository),
1676 joinedload(UserRepoToPerm.user),
1678 joinedload(UserRepoToPerm.user),
1677 joinedload(UserRepoToPerm.permission),)
1679 joinedload(UserRepoToPerm.permission),)
1678
1680
1679 # get owners and admins and permissions. We do a trick of re-writing
1681 # get owners and admins and permissions. We do a trick of re-writing
1680 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1682 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1681 # has a global reference and changing one object propagates to all
1683 # has a global reference and changing one object propagates to all
1682 # others. This means if admin is also an owner admin_row that change
1684 # others. This means if admin is also an owner admin_row that change
1683 # would propagate to both objects
1685 # would propagate to both objects
1684 perm_rows = []
1686 perm_rows = []
1685 for _usr in q.all():
1687 for _usr in q.all():
1686 usr = AttributeDict(_usr.user.get_dict())
1688 usr = AttributeDict(_usr.user.get_dict())
1687 usr.permission = _usr.permission.permission_name
1689 usr.permission = _usr.permission.permission_name
1688 perm_rows.append(usr)
1690 perm_rows.append(usr)
1689
1691
1690 # filter the perm rows by 'default' first and then sort them by
1692 # filter the perm rows by 'default' first and then sort them by
1691 # admin,write,read,none permissions sorted again alphabetically in
1693 # admin,write,read,none permissions sorted again alphabetically in
1692 # each group
1694 # each group
1693 perm_rows = sorted(perm_rows, key=display_sort)
1695 perm_rows = sorted(perm_rows, key=display_sort)
1694
1696
1695 _admin_perm = 'repository.admin'
1697 _admin_perm = 'repository.admin'
1696 owner_row = []
1698 owner_row = []
1697 if with_owner:
1699 if with_owner:
1698 usr = AttributeDict(self.user.get_dict())
1700 usr = AttributeDict(self.user.get_dict())
1699 usr.owner_row = True
1701 usr.owner_row = True
1700 usr.permission = _admin_perm
1702 usr.permission = _admin_perm
1701 owner_row.append(usr)
1703 owner_row.append(usr)
1702
1704
1703 super_admin_rows = []
1705 super_admin_rows = []
1704 if with_admins:
1706 if with_admins:
1705 for usr in User.get_all_super_admins():
1707 for usr in User.get_all_super_admins():
1706 # if this admin is also owner, don't double the record
1708 # if this admin is also owner, don't double the record
1707 if usr.user_id == owner_row[0].user_id:
1709 if usr.user_id == owner_row[0].user_id:
1708 owner_row[0].admin_row = True
1710 owner_row[0].admin_row = True
1709 else:
1711 else:
1710 usr = AttributeDict(usr.get_dict())
1712 usr = AttributeDict(usr.get_dict())
1711 usr.admin_row = True
1713 usr.admin_row = True
1712 usr.permission = _admin_perm
1714 usr.permission = _admin_perm
1713 super_admin_rows.append(usr)
1715 super_admin_rows.append(usr)
1714
1716
1715 return super_admin_rows + owner_row + perm_rows
1717 return super_admin_rows + owner_row + perm_rows
1716
1718
1717 def permission_user_groups(self):
1719 def permission_user_groups(self):
1718 q = UserGroupRepoToPerm.query().filter(
1720 q = UserGroupRepoToPerm.query().filter(
1719 UserGroupRepoToPerm.repository == self)
1721 UserGroupRepoToPerm.repository == self)
1720 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1722 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1721 joinedload(UserGroupRepoToPerm.users_group),
1723 joinedload(UserGroupRepoToPerm.users_group),
1722 joinedload(UserGroupRepoToPerm.permission),)
1724 joinedload(UserGroupRepoToPerm.permission),)
1723
1725
1724 perm_rows = []
1726 perm_rows = []
1725 for _user_group in q.all():
1727 for _user_group in q.all():
1726 usr = AttributeDict(_user_group.users_group.get_dict())
1728 usr = AttributeDict(_user_group.users_group.get_dict())
1727 usr.permission = _user_group.permission.permission_name
1729 usr.permission = _user_group.permission.permission_name
1728 perm_rows.append(usr)
1730 perm_rows.append(usr)
1729
1731
1730 return perm_rows
1732 return perm_rows
1731
1733
1732 def get_api_data(self, include_secrets=False):
1734 def get_api_data(self, include_secrets=False):
1733 """
1735 """
1734 Common function for generating repo api data
1736 Common function for generating repo api data
1735
1737
1736 :param include_secrets: See :meth:`User.get_api_data`.
1738 :param include_secrets: See :meth:`User.get_api_data`.
1737
1739
1738 """
1740 """
1739 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1741 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1740 # move this methods on models level.
1742 # move this methods on models level.
1741 from rhodecode.model.settings import SettingsModel
1743 from rhodecode.model.settings import SettingsModel
1742
1744
1743 repo = self
1745 repo = self
1744 _user_id, _time, _reason = self.locked
1746 _user_id, _time, _reason = self.locked
1745
1747
1746 data = {
1748 data = {
1747 'repo_id': repo.repo_id,
1749 'repo_id': repo.repo_id,
1748 'repo_name': repo.repo_name,
1750 'repo_name': repo.repo_name,
1749 'repo_type': repo.repo_type,
1751 'repo_type': repo.repo_type,
1750 'clone_uri': repo.clone_uri or '',
1752 'clone_uri': repo.clone_uri or '',
1751 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1753 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1752 'private': repo.private,
1754 'private': repo.private,
1753 'created_on': repo.created_on,
1755 'created_on': repo.created_on,
1754 'description': repo.description,
1756 'description': repo.description,
1755 'landing_rev': repo.landing_rev,
1757 'landing_rev': repo.landing_rev,
1756 'owner': repo.user.username,
1758 'owner': repo.user.username,
1757 'fork_of': repo.fork.repo_name if repo.fork else None,
1759 'fork_of': repo.fork.repo_name if repo.fork else None,
1758 'enable_statistics': repo.enable_statistics,
1760 'enable_statistics': repo.enable_statistics,
1759 'enable_locking': repo.enable_locking,
1761 'enable_locking': repo.enable_locking,
1760 'enable_downloads': repo.enable_downloads,
1762 'enable_downloads': repo.enable_downloads,
1761 'last_changeset': repo.changeset_cache,
1763 'last_changeset': repo.changeset_cache,
1762 'locked_by': User.get(_user_id).get_api_data(
1764 'locked_by': User.get(_user_id).get_api_data(
1763 include_secrets=include_secrets) if _user_id else None,
1765 include_secrets=include_secrets) if _user_id else None,
1764 'locked_date': time_to_datetime(_time) if _time else None,
1766 'locked_date': time_to_datetime(_time) if _time else None,
1765 'lock_reason': _reason if _reason else None,
1767 'lock_reason': _reason if _reason else None,
1766 }
1768 }
1767
1769
1768 # TODO: mikhail: should be per-repo settings here
1770 # TODO: mikhail: should be per-repo settings here
1769 rc_config = SettingsModel().get_all_settings()
1771 rc_config = SettingsModel().get_all_settings()
1770 repository_fields = str2bool(
1772 repository_fields = str2bool(
1771 rc_config.get('rhodecode_repository_fields'))
1773 rc_config.get('rhodecode_repository_fields'))
1772 if repository_fields:
1774 if repository_fields:
1773 for f in self.extra_fields:
1775 for f in self.extra_fields:
1774 data[f.field_key_prefixed] = f.field_value
1776 data[f.field_key_prefixed] = f.field_value
1775
1777
1776 return data
1778 return data
1777
1779
1778 @classmethod
1780 @classmethod
1779 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1781 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1780 if not lock_time:
1782 if not lock_time:
1781 lock_time = time.time()
1783 lock_time = time.time()
1782 if not lock_reason:
1784 if not lock_reason:
1783 lock_reason = cls.LOCK_AUTOMATIC
1785 lock_reason = cls.LOCK_AUTOMATIC
1784 repo.locked = [user_id, lock_time, lock_reason]
1786 repo.locked = [user_id, lock_time, lock_reason]
1785 Session().add(repo)
1787 Session().add(repo)
1786 Session().commit()
1788 Session().commit()
1787
1789
1788 @classmethod
1790 @classmethod
1789 def unlock(cls, repo):
1791 def unlock(cls, repo):
1790 repo.locked = None
1792 repo.locked = None
1791 Session().add(repo)
1793 Session().add(repo)
1792 Session().commit()
1794 Session().commit()
1793
1795
1794 @classmethod
1796 @classmethod
1795 def getlock(cls, repo):
1797 def getlock(cls, repo):
1796 return repo.locked
1798 return repo.locked
1797
1799
1798 def is_user_lock(self, user_id):
1800 def is_user_lock(self, user_id):
1799 if self.lock[0]:
1801 if self.lock[0]:
1800 lock_user_id = safe_int(self.lock[0])
1802 lock_user_id = safe_int(self.lock[0])
1801 user_id = safe_int(user_id)
1803 user_id = safe_int(user_id)
1802 # both are ints, and they are equal
1804 # both are ints, and they are equal
1803 return all([lock_user_id, user_id]) and lock_user_id == user_id
1805 return all([lock_user_id, user_id]) and lock_user_id == user_id
1804
1806
1805 return False
1807 return False
1806
1808
1807 def get_locking_state(self, action, user_id, only_when_enabled=True):
1809 def get_locking_state(self, action, user_id, only_when_enabled=True):
1808 """
1810 """
1809 Checks locking on this repository, if locking is enabled and lock is
1811 Checks locking on this repository, if locking is enabled and lock is
1810 present returns a tuple of make_lock, locked, locked_by.
1812 present returns a tuple of make_lock, locked, locked_by.
1811 make_lock can have 3 states None (do nothing) True, make lock
1813 make_lock can have 3 states None (do nothing) True, make lock
1812 False release lock, This value is later propagated to hooks, which
1814 False release lock, This value is later propagated to hooks, which
1813 do the locking. Think about this as signals passed to hooks what to do.
1815 do the locking. Think about this as signals passed to hooks what to do.
1814
1816
1815 """
1817 """
1816 # TODO: johbo: This is part of the business logic and should be moved
1818 # TODO: johbo: This is part of the business logic and should be moved
1817 # into the RepositoryModel.
1819 # into the RepositoryModel.
1818
1820
1819 if action not in ('push', 'pull'):
1821 if action not in ('push', 'pull'):
1820 raise ValueError("Invalid action value: %s" % repr(action))
1822 raise ValueError("Invalid action value: %s" % repr(action))
1821
1823
1822 # defines if locked error should be thrown to user
1824 # defines if locked error should be thrown to user
1823 currently_locked = False
1825 currently_locked = False
1824 # defines if new lock should be made, tri-state
1826 # defines if new lock should be made, tri-state
1825 make_lock = None
1827 make_lock = None
1826 repo = self
1828 repo = self
1827 user = User.get(user_id)
1829 user = User.get(user_id)
1828
1830
1829 lock_info = repo.locked
1831 lock_info = repo.locked
1830
1832
1831 if repo and (repo.enable_locking or not only_when_enabled):
1833 if repo and (repo.enable_locking or not only_when_enabled):
1832 if action == 'push':
1834 if action == 'push':
1833 # check if it's already locked !, if it is compare users
1835 # check if it's already locked !, if it is compare users
1834 locked_by_user_id = lock_info[0]
1836 locked_by_user_id = lock_info[0]
1835 if user.user_id == locked_by_user_id:
1837 if user.user_id == locked_by_user_id:
1836 log.debug(
1838 log.debug(
1837 'Got `push` action from user %s, now unlocking', user)
1839 'Got `push` action from user %s, now unlocking', user)
1838 # unlock if we have push from user who locked
1840 # unlock if we have push from user who locked
1839 make_lock = False
1841 make_lock = False
1840 else:
1842 else:
1841 # we're not the same user who locked, ban with
1843 # we're not the same user who locked, ban with
1842 # code defined in settings (default is 423 HTTP Locked) !
1844 # code defined in settings (default is 423 HTTP Locked) !
1843 log.debug('Repo %s is currently locked by %s', repo, user)
1845 log.debug('Repo %s is currently locked by %s', repo, user)
1844 currently_locked = True
1846 currently_locked = True
1845 elif action == 'pull':
1847 elif action == 'pull':
1846 # [0] user [1] date
1848 # [0] user [1] date
1847 if lock_info[0] and lock_info[1]:
1849 if lock_info[0] and lock_info[1]:
1848 log.debug('Repo %s is currently locked by %s', repo, user)
1850 log.debug('Repo %s is currently locked by %s', repo, user)
1849 currently_locked = True
1851 currently_locked = True
1850 else:
1852 else:
1851 log.debug('Setting lock on repo %s by %s', repo, user)
1853 log.debug('Setting lock on repo %s by %s', repo, user)
1852 make_lock = True
1854 make_lock = True
1853
1855
1854 else:
1856 else:
1855 log.debug('Repository %s do not have locking enabled', repo)
1857 log.debug('Repository %s do not have locking enabled', repo)
1856
1858
1857 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1859 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1858 make_lock, currently_locked, lock_info)
1860 make_lock, currently_locked, lock_info)
1859
1861
1860 from rhodecode.lib.auth import HasRepoPermissionAny
1862 from rhodecode.lib.auth import HasRepoPermissionAny
1861 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1863 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1862 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1864 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1863 # if we don't have at least write permission we cannot make a lock
1865 # if we don't have at least write permission we cannot make a lock
1864 log.debug('lock state reset back to FALSE due to lack '
1866 log.debug('lock state reset back to FALSE due to lack '
1865 'of at least read permission')
1867 'of at least read permission')
1866 make_lock = False
1868 make_lock = False
1867
1869
1868 return make_lock, currently_locked, lock_info
1870 return make_lock, currently_locked, lock_info
1869
1871
1870 @property
1872 @property
1871 def last_db_change(self):
1873 def last_db_change(self):
1872 return self.updated_on
1874 return self.updated_on
1873
1875
1874 @property
1876 @property
1875 def clone_uri_hidden(self):
1877 def clone_uri_hidden(self):
1876 clone_uri = self.clone_uri
1878 clone_uri = self.clone_uri
1877 if clone_uri:
1879 if clone_uri:
1878 import urlobject
1880 import urlobject
1879 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1881 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1880 if url_obj.password:
1882 if url_obj.password:
1881 clone_uri = url_obj.with_password('*****')
1883 clone_uri = url_obj.with_password('*****')
1882 return clone_uri
1884 return clone_uri
1883
1885
1884 def clone_url(self, **override):
1886 def clone_url(self, **override):
1885 qualified_home_url = url('home', qualified=True)
1887 qualified_home_url = url('home', qualified=True)
1886
1888
1887 uri_tmpl = None
1889 uri_tmpl = None
1888 if 'with_id' in override:
1890 if 'with_id' in override:
1889 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1891 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1890 del override['with_id']
1892 del override['with_id']
1891
1893
1892 if 'uri_tmpl' in override:
1894 if 'uri_tmpl' in override:
1893 uri_tmpl = override['uri_tmpl']
1895 uri_tmpl = override['uri_tmpl']
1894 del override['uri_tmpl']
1896 del override['uri_tmpl']
1895
1897
1896 # we didn't override our tmpl from **overrides
1898 # we didn't override our tmpl from **overrides
1897 if not uri_tmpl:
1899 if not uri_tmpl:
1898 uri_tmpl = self.DEFAULT_CLONE_URI
1900 uri_tmpl = self.DEFAULT_CLONE_URI
1899 try:
1901 try:
1900 from pylons import tmpl_context as c
1902 from pylons import tmpl_context as c
1901 uri_tmpl = c.clone_uri_tmpl
1903 uri_tmpl = c.clone_uri_tmpl
1902 except Exception:
1904 except Exception:
1903 # in any case if we call this outside of request context,
1905 # in any case if we call this outside of request context,
1904 # ie, not having tmpl_context set up
1906 # ie, not having tmpl_context set up
1905 pass
1907 pass
1906
1908
1907 return get_clone_url(uri_tmpl=uri_tmpl,
1909 return get_clone_url(uri_tmpl=uri_tmpl,
1908 qualifed_home_url=qualified_home_url,
1910 qualifed_home_url=qualified_home_url,
1909 repo_name=self.repo_name,
1911 repo_name=self.repo_name,
1910 repo_id=self.repo_id, **override)
1912 repo_id=self.repo_id, **override)
1911
1913
1912 def set_state(self, state):
1914 def set_state(self, state):
1913 self.repo_state = state
1915 self.repo_state = state
1914 Session().add(self)
1916 Session().add(self)
1915 #==========================================================================
1917 #==========================================================================
1916 # SCM PROPERTIES
1918 # SCM PROPERTIES
1917 #==========================================================================
1919 #==========================================================================
1918
1920
1919 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1921 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1920 return get_commit_safe(
1922 return get_commit_safe(
1921 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1923 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1922
1924
1923 def get_changeset(self, rev=None, pre_load=None):
1925 def get_changeset(self, rev=None, pre_load=None):
1924 warnings.warn("Use get_commit", DeprecationWarning)
1926 warnings.warn("Use get_commit", DeprecationWarning)
1925 commit_id = None
1927 commit_id = None
1926 commit_idx = None
1928 commit_idx = None
1927 if isinstance(rev, basestring):
1929 if isinstance(rev, basestring):
1928 commit_id = rev
1930 commit_id = rev
1929 else:
1931 else:
1930 commit_idx = rev
1932 commit_idx = rev
1931 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1933 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1932 pre_load=pre_load)
1934 pre_load=pre_load)
1933
1935
1934 def get_landing_commit(self):
1936 def get_landing_commit(self):
1935 """
1937 """
1936 Returns landing commit, or if that doesn't exist returns the tip
1938 Returns landing commit, or if that doesn't exist returns the tip
1937 """
1939 """
1938 _rev_type, _rev = self.landing_rev
1940 _rev_type, _rev = self.landing_rev
1939 commit = self.get_commit(_rev)
1941 commit = self.get_commit(_rev)
1940 if isinstance(commit, EmptyCommit):
1942 if isinstance(commit, EmptyCommit):
1941 return self.get_commit()
1943 return self.get_commit()
1942 return commit
1944 return commit
1943
1945
1944 def update_commit_cache(self, cs_cache=None, config=None):
1946 def update_commit_cache(self, cs_cache=None, config=None):
1945 """
1947 """
1946 Update cache of last changeset for repository, keys should be::
1948 Update cache of last changeset for repository, keys should be::
1947
1949
1948 short_id
1950 short_id
1949 raw_id
1951 raw_id
1950 revision
1952 revision
1951 parents
1953 parents
1952 message
1954 message
1953 date
1955 date
1954 author
1956 author
1955
1957
1956 :param cs_cache:
1958 :param cs_cache:
1957 """
1959 """
1958 from rhodecode.lib.vcs.backends.base import BaseChangeset
1960 from rhodecode.lib.vcs.backends.base import BaseChangeset
1959 if cs_cache is None:
1961 if cs_cache is None:
1960 # use no-cache version here
1962 # use no-cache version here
1961 scm_repo = self.scm_instance(cache=False, config=config)
1963 scm_repo = self.scm_instance(cache=False, config=config)
1962 if scm_repo:
1964 if scm_repo:
1963 cs_cache = scm_repo.get_commit(
1965 cs_cache = scm_repo.get_commit(
1964 pre_load=["author", "date", "message", "parents"])
1966 pre_load=["author", "date", "message", "parents"])
1965 else:
1967 else:
1966 cs_cache = EmptyCommit()
1968 cs_cache = EmptyCommit()
1967
1969
1968 if isinstance(cs_cache, BaseChangeset):
1970 if isinstance(cs_cache, BaseChangeset):
1969 cs_cache = cs_cache.__json__()
1971 cs_cache = cs_cache.__json__()
1970
1972
1971 def is_outdated(new_cs_cache):
1973 def is_outdated(new_cs_cache):
1972 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1974 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1973 new_cs_cache['revision'] != self.changeset_cache['revision']):
1975 new_cs_cache['revision'] != self.changeset_cache['revision']):
1974 return True
1976 return True
1975 return False
1977 return False
1976
1978
1977 # check if we have maybe already latest cached revision
1979 # check if we have maybe already latest cached revision
1978 if is_outdated(cs_cache) or not self.changeset_cache:
1980 if is_outdated(cs_cache) or not self.changeset_cache:
1979 _default = datetime.datetime.fromtimestamp(0)
1981 _default = datetime.datetime.fromtimestamp(0)
1980 last_change = cs_cache.get('date') or _default
1982 last_change = cs_cache.get('date') or _default
1981 log.debug('updated repo %s with new cs cache %s',
1983 log.debug('updated repo %s with new cs cache %s',
1982 self.repo_name, cs_cache)
1984 self.repo_name, cs_cache)
1983 self.updated_on = last_change
1985 self.updated_on = last_change
1984 self.changeset_cache = cs_cache
1986 self.changeset_cache = cs_cache
1985 Session().add(self)
1987 Session().add(self)
1986 Session().commit()
1988 Session().commit()
1987 else:
1989 else:
1988 log.debug('Skipping update_commit_cache for repo:`%s` '
1990 log.debug('Skipping update_commit_cache for repo:`%s` '
1989 'commit already with latest changes', self.repo_name)
1991 'commit already with latest changes', self.repo_name)
1990
1992
1991 @property
1993 @property
1992 def tip(self):
1994 def tip(self):
1993 return self.get_commit('tip')
1995 return self.get_commit('tip')
1994
1996
1995 @property
1997 @property
1996 def author(self):
1998 def author(self):
1997 return self.tip.author
1999 return self.tip.author
1998
2000
1999 @property
2001 @property
2000 def last_change(self):
2002 def last_change(self):
2001 return self.scm_instance().last_change
2003 return self.scm_instance().last_change
2002
2004
2003 def get_comments(self, revisions=None):
2005 def get_comments(self, revisions=None):
2004 """
2006 """
2005 Returns comments for this repository grouped by revisions
2007 Returns comments for this repository grouped by revisions
2006
2008
2007 :param revisions: filter query by revisions only
2009 :param revisions: filter query by revisions only
2008 """
2010 """
2009 cmts = ChangesetComment.query()\
2011 cmts = ChangesetComment.query()\
2010 .filter(ChangesetComment.repo == self)
2012 .filter(ChangesetComment.repo == self)
2011 if revisions:
2013 if revisions:
2012 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2014 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2013 grouped = collections.defaultdict(list)
2015 grouped = collections.defaultdict(list)
2014 for cmt in cmts.all():
2016 for cmt in cmts.all():
2015 grouped[cmt.revision].append(cmt)
2017 grouped[cmt.revision].append(cmt)
2016 return grouped
2018 return grouped
2017
2019
2018 def statuses(self, revisions=None):
2020 def statuses(self, revisions=None):
2019 """
2021 """
2020 Returns statuses for this repository
2022 Returns statuses for this repository
2021
2023
2022 :param revisions: list of revisions to get statuses for
2024 :param revisions: list of revisions to get statuses for
2023 """
2025 """
2024 statuses = ChangesetStatus.query()\
2026 statuses = ChangesetStatus.query()\
2025 .filter(ChangesetStatus.repo == self)\
2027 .filter(ChangesetStatus.repo == self)\
2026 .filter(ChangesetStatus.version == 0)
2028 .filter(ChangesetStatus.version == 0)
2027
2029
2028 if revisions:
2030 if revisions:
2029 # Try doing the filtering in chunks to avoid hitting limits
2031 # Try doing the filtering in chunks to avoid hitting limits
2030 size = 500
2032 size = 500
2031 status_results = []
2033 status_results = []
2032 for chunk in xrange(0, len(revisions), size):
2034 for chunk in xrange(0, len(revisions), size):
2033 status_results += statuses.filter(
2035 status_results += statuses.filter(
2034 ChangesetStatus.revision.in_(
2036 ChangesetStatus.revision.in_(
2035 revisions[chunk: chunk+size])
2037 revisions[chunk: chunk+size])
2036 ).all()
2038 ).all()
2037 else:
2039 else:
2038 status_results = statuses.all()
2040 status_results = statuses.all()
2039
2041
2040 grouped = {}
2042 grouped = {}
2041
2043
2042 # maybe we have open new pullrequest without a status?
2044 # maybe we have open new pullrequest without a status?
2043 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2045 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2044 status_lbl = ChangesetStatus.get_status_lbl(stat)
2046 status_lbl = ChangesetStatus.get_status_lbl(stat)
2045 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2047 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2046 for rev in pr.revisions:
2048 for rev in pr.revisions:
2047 pr_id = pr.pull_request_id
2049 pr_id = pr.pull_request_id
2048 pr_repo = pr.target_repo.repo_name
2050 pr_repo = pr.target_repo.repo_name
2049 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2051 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2050
2052
2051 for stat in status_results:
2053 for stat in status_results:
2052 pr_id = pr_repo = None
2054 pr_id = pr_repo = None
2053 if stat.pull_request:
2055 if stat.pull_request:
2054 pr_id = stat.pull_request.pull_request_id
2056 pr_id = stat.pull_request.pull_request_id
2055 pr_repo = stat.pull_request.target_repo.repo_name
2057 pr_repo = stat.pull_request.target_repo.repo_name
2056 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2058 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2057 pr_id, pr_repo]
2059 pr_id, pr_repo]
2058 return grouped
2060 return grouped
2059
2061
2060 # ==========================================================================
2062 # ==========================================================================
2061 # SCM CACHE INSTANCE
2063 # SCM CACHE INSTANCE
2062 # ==========================================================================
2064 # ==========================================================================
2063
2065
2064 def scm_instance(self, **kwargs):
2066 def scm_instance(self, **kwargs):
2065 import rhodecode
2067 import rhodecode
2066
2068
2067 # Passing a config will not hit the cache currently only used
2069 # Passing a config will not hit the cache currently only used
2068 # for repo2dbmapper
2070 # for repo2dbmapper
2069 config = kwargs.pop('config', None)
2071 config = kwargs.pop('config', None)
2070 cache = kwargs.pop('cache', None)
2072 cache = kwargs.pop('cache', None)
2071 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2073 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2072 # if cache is NOT defined use default global, else we have a full
2074 # if cache is NOT defined use default global, else we have a full
2073 # control over cache behaviour
2075 # control over cache behaviour
2074 if cache is None and full_cache and not config:
2076 if cache is None and full_cache and not config:
2075 return self._get_instance_cached()
2077 return self._get_instance_cached()
2076 return self._get_instance(cache=bool(cache), config=config)
2078 return self._get_instance(cache=bool(cache), config=config)
2077
2079
2078 def _get_instance_cached(self):
2080 def _get_instance_cached(self):
2079 @cache_region('long_term')
2081 @cache_region('long_term')
2080 def _get_repo(cache_key):
2082 def _get_repo(cache_key):
2081 return self._get_instance()
2083 return self._get_instance()
2082
2084
2083 invalidator_context = CacheKey.repo_context_cache(
2085 invalidator_context = CacheKey.repo_context_cache(
2084 _get_repo, self.repo_name, None, thread_scoped=True)
2086 _get_repo, self.repo_name, None, thread_scoped=True)
2085
2087
2086 with invalidator_context as context:
2088 with invalidator_context as context:
2087 context.invalidate()
2089 context.invalidate()
2088 repo = context.compute()
2090 repo = context.compute()
2089
2091
2090 return repo
2092 return repo
2091
2093
2092 def _get_instance(self, cache=True, config=None):
2094 def _get_instance(self, cache=True, config=None):
2093 config = config or self._config
2095 config = config or self._config
2094 custom_wire = {
2096 custom_wire = {
2095 'cache': cache # controls the vcs.remote cache
2097 'cache': cache # controls the vcs.remote cache
2096 }
2098 }
2097 repo = get_vcs_instance(
2099 repo = get_vcs_instance(
2098 repo_path=safe_str(self.repo_full_path),
2100 repo_path=safe_str(self.repo_full_path),
2099 config=config,
2101 config=config,
2100 with_wire=custom_wire,
2102 with_wire=custom_wire,
2101 create=False,
2103 create=False,
2102 _vcs_alias=self.repo_type)
2104 _vcs_alias=self.repo_type)
2103
2105
2104 return repo
2106 return repo
2105
2107
2106 def __json__(self):
2108 def __json__(self):
2107 return {'landing_rev': self.landing_rev}
2109 return {'landing_rev': self.landing_rev}
2108
2110
2109 def get_dict(self):
2111 def get_dict(self):
2110
2112
2111 # Since we transformed `repo_name` to a hybrid property, we need to
2113 # Since we transformed `repo_name` to a hybrid property, we need to
2112 # keep compatibility with the code which uses `repo_name` field.
2114 # keep compatibility with the code which uses `repo_name` field.
2113
2115
2114 result = super(Repository, self).get_dict()
2116 result = super(Repository, self).get_dict()
2115 result['repo_name'] = result.pop('_repo_name', None)
2117 result['repo_name'] = result.pop('_repo_name', None)
2116 return result
2118 return result
2117
2119
2118
2120
2119 class RepoGroup(Base, BaseModel):
2121 class RepoGroup(Base, BaseModel):
2120 __tablename__ = 'groups'
2122 __tablename__ = 'groups'
2121 __table_args__ = (
2123 __table_args__ = (
2122 UniqueConstraint('group_name', 'group_parent_id'),
2124 UniqueConstraint('group_name', 'group_parent_id'),
2123 CheckConstraint('group_id != group_parent_id'),
2125 CheckConstraint('group_id != group_parent_id'),
2124 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2125 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2127 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2126 )
2128 )
2127 __mapper_args__ = {'order_by': 'group_name'}
2129 __mapper_args__ = {'order_by': 'group_name'}
2128
2130
2129 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2131 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2130
2132
2131 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2133 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2132 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2134 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2133 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2135 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2134 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2136 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2135 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2137 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2137 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2139 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2138 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2140 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2139
2141
2140 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2142 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2141 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2143 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2142 parent_group = relationship('RepoGroup', remote_side=group_id)
2144 parent_group = relationship('RepoGroup', remote_side=group_id)
2143 user = relationship('User')
2145 user = relationship('User')
2144 integrations = relationship('Integration',
2146 integrations = relationship('Integration',
2145 cascade="all, delete, delete-orphan")
2147 cascade="all, delete, delete-orphan")
2146
2148
2147 def __init__(self, group_name='', parent_group=None):
2149 def __init__(self, group_name='', parent_group=None):
2148 self.group_name = group_name
2150 self.group_name = group_name
2149 self.parent_group = parent_group
2151 self.parent_group = parent_group
2150
2152
2151 def __unicode__(self):
2153 def __unicode__(self):
2152 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2154 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2153 self.group_name)
2155 self.group_name)
2154
2156
2155 @classmethod
2157 @classmethod
2156 def _generate_choice(cls, repo_group):
2158 def _generate_choice(cls, repo_group):
2157 from webhelpers.html import literal as _literal
2159 from webhelpers.html import literal as _literal
2158 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2160 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2159 return repo_group.group_id, _name(repo_group.full_path_splitted)
2161 return repo_group.group_id, _name(repo_group.full_path_splitted)
2160
2162
2161 @classmethod
2163 @classmethod
2162 def groups_choices(cls, groups=None, show_empty_group=True):
2164 def groups_choices(cls, groups=None, show_empty_group=True):
2163 if not groups:
2165 if not groups:
2164 groups = cls.query().all()
2166 groups = cls.query().all()
2165
2167
2166 repo_groups = []
2168 repo_groups = []
2167 if show_empty_group:
2169 if show_empty_group:
2168 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2170 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2169
2171
2170 repo_groups.extend([cls._generate_choice(x) for x in groups])
2172 repo_groups.extend([cls._generate_choice(x) for x in groups])
2171
2173
2172 repo_groups = sorted(
2174 repo_groups = sorted(
2173 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2175 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2174 return repo_groups
2176 return repo_groups
2175
2177
2176 @classmethod
2178 @classmethod
2177 def url_sep(cls):
2179 def url_sep(cls):
2178 return URL_SEP
2180 return URL_SEP
2179
2181
2180 @classmethod
2182 @classmethod
2181 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2183 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2182 if case_insensitive:
2184 if case_insensitive:
2183 gr = cls.query().filter(func.lower(cls.group_name)
2185 gr = cls.query().filter(func.lower(cls.group_name)
2184 == func.lower(group_name))
2186 == func.lower(group_name))
2185 else:
2187 else:
2186 gr = cls.query().filter(cls.group_name == group_name)
2188 gr = cls.query().filter(cls.group_name == group_name)
2187 if cache:
2189 if cache:
2188 gr = gr.options(FromCache(
2190 gr = gr.options(FromCache(
2189 "sql_cache_short",
2191 "sql_cache_short",
2190 "get_group_%s" % _hash_key(group_name)))
2192 "get_group_%s" % _hash_key(group_name)))
2191 return gr.scalar()
2193 return gr.scalar()
2192
2194
2193 @classmethod
2195 @classmethod
2194 def get_user_personal_repo_group(cls, user_id):
2196 def get_user_personal_repo_group(cls, user_id):
2195 user = User.get(user_id)
2197 user = User.get(user_id)
2196 return cls.query()\
2198 return cls.query()\
2197 .filter(cls.personal == true())\
2199 .filter(cls.personal == true())\
2198 .filter(cls.user == user).scalar()
2200 .filter(cls.user == user).scalar()
2199
2201
2200 @classmethod
2202 @classmethod
2201 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2203 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2202 case_insensitive=True):
2204 case_insensitive=True):
2203 q = RepoGroup.query()
2205 q = RepoGroup.query()
2204
2206
2205 if not isinstance(user_id, Optional):
2207 if not isinstance(user_id, Optional):
2206 q = q.filter(RepoGroup.user_id == user_id)
2208 q = q.filter(RepoGroup.user_id == user_id)
2207
2209
2208 if not isinstance(group_id, Optional):
2210 if not isinstance(group_id, Optional):
2209 q = q.filter(RepoGroup.group_parent_id == group_id)
2211 q = q.filter(RepoGroup.group_parent_id == group_id)
2210
2212
2211 if case_insensitive:
2213 if case_insensitive:
2212 q = q.order_by(func.lower(RepoGroup.group_name))
2214 q = q.order_by(func.lower(RepoGroup.group_name))
2213 else:
2215 else:
2214 q = q.order_by(RepoGroup.group_name)
2216 q = q.order_by(RepoGroup.group_name)
2215 return q.all()
2217 return q.all()
2216
2218
2217 @property
2219 @property
2218 def parents(self):
2220 def parents(self):
2219 parents_recursion_limit = 10
2221 parents_recursion_limit = 10
2220 groups = []
2222 groups = []
2221 if self.parent_group is None:
2223 if self.parent_group is None:
2222 return groups
2224 return groups
2223 cur_gr = self.parent_group
2225 cur_gr = self.parent_group
2224 groups.insert(0, cur_gr)
2226 groups.insert(0, cur_gr)
2225 cnt = 0
2227 cnt = 0
2226 while 1:
2228 while 1:
2227 cnt += 1
2229 cnt += 1
2228 gr = getattr(cur_gr, 'parent_group', None)
2230 gr = getattr(cur_gr, 'parent_group', None)
2229 cur_gr = cur_gr.parent_group
2231 cur_gr = cur_gr.parent_group
2230 if gr is None:
2232 if gr is None:
2231 break
2233 break
2232 if cnt == parents_recursion_limit:
2234 if cnt == parents_recursion_limit:
2233 # this will prevent accidental infinit loops
2235 # this will prevent accidental infinit loops
2234 log.error(('more than %s parents found for group %s, stopping '
2236 log.error(('more than %s parents found for group %s, stopping '
2235 'recursive parent fetching' % (parents_recursion_limit, self)))
2237 'recursive parent fetching' % (parents_recursion_limit, self)))
2236 break
2238 break
2237
2239
2238 groups.insert(0, gr)
2240 groups.insert(0, gr)
2239 return groups
2241 return groups
2240
2242
2241 @property
2243 @property
2242 def children(self):
2244 def children(self):
2243 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2245 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2244
2246
2245 @property
2247 @property
2246 def name(self):
2248 def name(self):
2247 return self.group_name.split(RepoGroup.url_sep())[-1]
2249 return self.group_name.split(RepoGroup.url_sep())[-1]
2248
2250
2249 @property
2251 @property
2250 def full_path(self):
2252 def full_path(self):
2251 return self.group_name
2253 return self.group_name
2252
2254
2253 @property
2255 @property
2254 def full_path_splitted(self):
2256 def full_path_splitted(self):
2255 return self.group_name.split(RepoGroup.url_sep())
2257 return self.group_name.split(RepoGroup.url_sep())
2256
2258
2257 @property
2259 @property
2258 def repositories(self):
2260 def repositories(self):
2259 return Repository.query()\
2261 return Repository.query()\
2260 .filter(Repository.group == self)\
2262 .filter(Repository.group == self)\
2261 .order_by(Repository.repo_name)
2263 .order_by(Repository.repo_name)
2262
2264
2263 @property
2265 @property
2264 def repositories_recursive_count(self):
2266 def repositories_recursive_count(self):
2265 cnt = self.repositories.count()
2267 cnt = self.repositories.count()
2266
2268
2267 def children_count(group):
2269 def children_count(group):
2268 cnt = 0
2270 cnt = 0
2269 for child in group.children:
2271 for child in group.children:
2270 cnt += child.repositories.count()
2272 cnt += child.repositories.count()
2271 cnt += children_count(child)
2273 cnt += children_count(child)
2272 return cnt
2274 return cnt
2273
2275
2274 return cnt + children_count(self)
2276 return cnt + children_count(self)
2275
2277
2276 def _recursive_objects(self, include_repos=True):
2278 def _recursive_objects(self, include_repos=True):
2277 all_ = []
2279 all_ = []
2278
2280
2279 def _get_members(root_gr):
2281 def _get_members(root_gr):
2280 if include_repos:
2282 if include_repos:
2281 for r in root_gr.repositories:
2283 for r in root_gr.repositories:
2282 all_.append(r)
2284 all_.append(r)
2283 childs = root_gr.children.all()
2285 childs = root_gr.children.all()
2284 if childs:
2286 if childs:
2285 for gr in childs:
2287 for gr in childs:
2286 all_.append(gr)
2288 all_.append(gr)
2287 _get_members(gr)
2289 _get_members(gr)
2288
2290
2289 _get_members(self)
2291 _get_members(self)
2290 return [self] + all_
2292 return [self] + all_
2291
2293
2292 def recursive_groups_and_repos(self):
2294 def recursive_groups_and_repos(self):
2293 """
2295 """
2294 Recursive return all groups, with repositories in those groups
2296 Recursive return all groups, with repositories in those groups
2295 """
2297 """
2296 return self._recursive_objects()
2298 return self._recursive_objects()
2297
2299
2298 def recursive_groups(self):
2300 def recursive_groups(self):
2299 """
2301 """
2300 Returns all children groups for this group including children of children
2302 Returns all children groups for this group including children of children
2301 """
2303 """
2302 return self._recursive_objects(include_repos=False)
2304 return self._recursive_objects(include_repos=False)
2303
2305
2304 def get_new_name(self, group_name):
2306 def get_new_name(self, group_name):
2305 """
2307 """
2306 returns new full group name based on parent and new name
2308 returns new full group name based on parent and new name
2307
2309
2308 :param group_name:
2310 :param group_name:
2309 """
2311 """
2310 path_prefix = (self.parent_group.full_path_splitted if
2312 path_prefix = (self.parent_group.full_path_splitted if
2311 self.parent_group else [])
2313 self.parent_group else [])
2312 return RepoGroup.url_sep().join(path_prefix + [group_name])
2314 return RepoGroup.url_sep().join(path_prefix + [group_name])
2313
2315
2314 def permissions(self, with_admins=True, with_owner=True):
2316 def permissions(self, with_admins=True, with_owner=True):
2315 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2317 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2316 q = q.options(joinedload(UserRepoGroupToPerm.group),
2318 q = q.options(joinedload(UserRepoGroupToPerm.group),
2317 joinedload(UserRepoGroupToPerm.user),
2319 joinedload(UserRepoGroupToPerm.user),
2318 joinedload(UserRepoGroupToPerm.permission),)
2320 joinedload(UserRepoGroupToPerm.permission),)
2319
2321
2320 # get owners and admins and permissions. We do a trick of re-writing
2322 # get owners and admins and permissions. We do a trick of re-writing
2321 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2323 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2322 # has a global reference and changing one object propagates to all
2324 # has a global reference and changing one object propagates to all
2323 # others. This means if admin is also an owner admin_row that change
2325 # others. This means if admin is also an owner admin_row that change
2324 # would propagate to both objects
2326 # would propagate to both objects
2325 perm_rows = []
2327 perm_rows = []
2326 for _usr in q.all():
2328 for _usr in q.all():
2327 usr = AttributeDict(_usr.user.get_dict())
2329 usr = AttributeDict(_usr.user.get_dict())
2328 usr.permission = _usr.permission.permission_name
2330 usr.permission = _usr.permission.permission_name
2329 perm_rows.append(usr)
2331 perm_rows.append(usr)
2330
2332
2331 # filter the perm rows by 'default' first and then sort them by
2333 # filter the perm rows by 'default' first and then sort them by
2332 # admin,write,read,none permissions sorted again alphabetically in
2334 # admin,write,read,none permissions sorted again alphabetically in
2333 # each group
2335 # each group
2334 perm_rows = sorted(perm_rows, key=display_sort)
2336 perm_rows = sorted(perm_rows, key=display_sort)
2335
2337
2336 _admin_perm = 'group.admin'
2338 _admin_perm = 'group.admin'
2337 owner_row = []
2339 owner_row = []
2338 if with_owner:
2340 if with_owner:
2339 usr = AttributeDict(self.user.get_dict())
2341 usr = AttributeDict(self.user.get_dict())
2340 usr.owner_row = True
2342 usr.owner_row = True
2341 usr.permission = _admin_perm
2343 usr.permission = _admin_perm
2342 owner_row.append(usr)
2344 owner_row.append(usr)
2343
2345
2344 super_admin_rows = []
2346 super_admin_rows = []
2345 if with_admins:
2347 if with_admins:
2346 for usr in User.get_all_super_admins():
2348 for usr in User.get_all_super_admins():
2347 # if this admin is also owner, don't double the record
2349 # if this admin is also owner, don't double the record
2348 if usr.user_id == owner_row[0].user_id:
2350 if usr.user_id == owner_row[0].user_id:
2349 owner_row[0].admin_row = True
2351 owner_row[0].admin_row = True
2350 else:
2352 else:
2351 usr = AttributeDict(usr.get_dict())
2353 usr = AttributeDict(usr.get_dict())
2352 usr.admin_row = True
2354 usr.admin_row = True
2353 usr.permission = _admin_perm
2355 usr.permission = _admin_perm
2354 super_admin_rows.append(usr)
2356 super_admin_rows.append(usr)
2355
2357
2356 return super_admin_rows + owner_row + perm_rows
2358 return super_admin_rows + owner_row + perm_rows
2357
2359
2358 def permission_user_groups(self):
2360 def permission_user_groups(self):
2359 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2361 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2360 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2362 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2361 joinedload(UserGroupRepoGroupToPerm.users_group),
2363 joinedload(UserGroupRepoGroupToPerm.users_group),
2362 joinedload(UserGroupRepoGroupToPerm.permission),)
2364 joinedload(UserGroupRepoGroupToPerm.permission),)
2363
2365
2364 perm_rows = []
2366 perm_rows = []
2365 for _user_group in q.all():
2367 for _user_group in q.all():
2366 usr = AttributeDict(_user_group.users_group.get_dict())
2368 usr = AttributeDict(_user_group.users_group.get_dict())
2367 usr.permission = _user_group.permission.permission_name
2369 usr.permission = _user_group.permission.permission_name
2368 perm_rows.append(usr)
2370 perm_rows.append(usr)
2369
2371
2370 return perm_rows
2372 return perm_rows
2371
2373
2372 def get_api_data(self):
2374 def get_api_data(self):
2373 """
2375 """
2374 Common function for generating api data
2376 Common function for generating api data
2375
2377
2376 """
2378 """
2377 group = self
2379 group = self
2378 data = {
2380 data = {
2379 'group_id': group.group_id,
2381 'group_id': group.group_id,
2380 'group_name': group.group_name,
2382 'group_name': group.group_name,
2381 'group_description': group.group_description,
2383 'group_description': group.group_description,
2382 'parent_group': group.parent_group.group_name if group.parent_group else None,
2384 'parent_group': group.parent_group.group_name if group.parent_group else None,
2383 'repositories': [x.repo_name for x in group.repositories],
2385 'repositories': [x.repo_name for x in group.repositories],
2384 'owner': group.user.username,
2386 'owner': group.user.username,
2385 }
2387 }
2386 return data
2388 return data
2387
2389
2388
2390
2389 class Permission(Base, BaseModel):
2391 class Permission(Base, BaseModel):
2390 __tablename__ = 'permissions'
2392 __tablename__ = 'permissions'
2391 __table_args__ = (
2393 __table_args__ = (
2392 Index('p_perm_name_idx', 'permission_name'),
2394 Index('p_perm_name_idx', 'permission_name'),
2393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2395 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2396 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2395 )
2397 )
2396 PERMS = [
2398 PERMS = [
2397 ('hg.admin', _('RhodeCode Super Administrator')),
2399 ('hg.admin', _('RhodeCode Super Administrator')),
2398
2400
2399 ('repository.none', _('Repository no access')),
2401 ('repository.none', _('Repository no access')),
2400 ('repository.read', _('Repository read access')),
2402 ('repository.read', _('Repository read access')),
2401 ('repository.write', _('Repository write access')),
2403 ('repository.write', _('Repository write access')),
2402 ('repository.admin', _('Repository admin access')),
2404 ('repository.admin', _('Repository admin access')),
2403
2405
2404 ('group.none', _('Repository group no access')),
2406 ('group.none', _('Repository group no access')),
2405 ('group.read', _('Repository group read access')),
2407 ('group.read', _('Repository group read access')),
2406 ('group.write', _('Repository group write access')),
2408 ('group.write', _('Repository group write access')),
2407 ('group.admin', _('Repository group admin access')),
2409 ('group.admin', _('Repository group admin access')),
2408
2410
2409 ('usergroup.none', _('User group no access')),
2411 ('usergroup.none', _('User group no access')),
2410 ('usergroup.read', _('User group read access')),
2412 ('usergroup.read', _('User group read access')),
2411 ('usergroup.write', _('User group write access')),
2413 ('usergroup.write', _('User group write access')),
2412 ('usergroup.admin', _('User group admin access')),
2414 ('usergroup.admin', _('User group admin access')),
2413
2415
2414 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2416 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2415 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2417 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2416
2418
2417 ('hg.usergroup.create.false', _('User Group creation disabled')),
2419 ('hg.usergroup.create.false', _('User Group creation disabled')),
2418 ('hg.usergroup.create.true', _('User Group creation enabled')),
2420 ('hg.usergroup.create.true', _('User Group creation enabled')),
2419
2421
2420 ('hg.create.none', _('Repository creation disabled')),
2422 ('hg.create.none', _('Repository creation disabled')),
2421 ('hg.create.repository', _('Repository creation enabled')),
2423 ('hg.create.repository', _('Repository creation enabled')),
2422 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2424 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2423 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2425 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2424
2426
2425 ('hg.fork.none', _('Repository forking disabled')),
2427 ('hg.fork.none', _('Repository forking disabled')),
2426 ('hg.fork.repository', _('Repository forking enabled')),
2428 ('hg.fork.repository', _('Repository forking enabled')),
2427
2429
2428 ('hg.register.none', _('Registration disabled')),
2430 ('hg.register.none', _('Registration disabled')),
2429 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2430 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2431
2433
2432 ('hg.password_reset.enabled', _('Password reset enabled')),
2434 ('hg.password_reset.enabled', _('Password reset enabled')),
2433 ('hg.password_reset.hidden', _('Password reset hidden')),
2435 ('hg.password_reset.hidden', _('Password reset hidden')),
2434 ('hg.password_reset.disabled', _('Password reset disabled')),
2436 ('hg.password_reset.disabled', _('Password reset disabled')),
2435
2437
2436 ('hg.extern_activate.manual', _('Manual activation of external account')),
2438 ('hg.extern_activate.manual', _('Manual activation of external account')),
2437 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2439 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2438
2440
2439 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2441 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2440 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2442 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2441 ]
2443 ]
2442
2444
2443 # definition of system default permissions for DEFAULT user
2445 # definition of system default permissions for DEFAULT user
2444 DEFAULT_USER_PERMISSIONS = [
2446 DEFAULT_USER_PERMISSIONS = [
2445 'repository.read',
2447 'repository.read',
2446 'group.read',
2448 'group.read',
2447 'usergroup.read',
2449 'usergroup.read',
2448 'hg.create.repository',
2450 'hg.create.repository',
2449 'hg.repogroup.create.false',
2451 'hg.repogroup.create.false',
2450 'hg.usergroup.create.false',
2452 'hg.usergroup.create.false',
2451 'hg.create.write_on_repogroup.true',
2453 'hg.create.write_on_repogroup.true',
2452 'hg.fork.repository',
2454 'hg.fork.repository',
2453 'hg.register.manual_activate',
2455 'hg.register.manual_activate',
2454 'hg.password_reset.enabled',
2456 'hg.password_reset.enabled',
2455 'hg.extern_activate.auto',
2457 'hg.extern_activate.auto',
2456 'hg.inherit_default_perms.true',
2458 'hg.inherit_default_perms.true',
2457 ]
2459 ]
2458
2460
2459 # defines which permissions are more important higher the more important
2461 # defines which permissions are more important higher the more important
2460 # Weight defines which permissions are more important.
2462 # Weight defines which permissions are more important.
2461 # The higher number the more important.
2463 # The higher number the more important.
2462 PERM_WEIGHTS = {
2464 PERM_WEIGHTS = {
2463 'repository.none': 0,
2465 'repository.none': 0,
2464 'repository.read': 1,
2466 'repository.read': 1,
2465 'repository.write': 3,
2467 'repository.write': 3,
2466 'repository.admin': 4,
2468 'repository.admin': 4,
2467
2469
2468 'group.none': 0,
2470 'group.none': 0,
2469 'group.read': 1,
2471 'group.read': 1,
2470 'group.write': 3,
2472 'group.write': 3,
2471 'group.admin': 4,
2473 'group.admin': 4,
2472
2474
2473 'usergroup.none': 0,
2475 'usergroup.none': 0,
2474 'usergroup.read': 1,
2476 'usergroup.read': 1,
2475 'usergroup.write': 3,
2477 'usergroup.write': 3,
2476 'usergroup.admin': 4,
2478 'usergroup.admin': 4,
2477
2479
2478 'hg.repogroup.create.false': 0,
2480 'hg.repogroup.create.false': 0,
2479 'hg.repogroup.create.true': 1,
2481 'hg.repogroup.create.true': 1,
2480
2482
2481 'hg.usergroup.create.false': 0,
2483 'hg.usergroup.create.false': 0,
2482 'hg.usergroup.create.true': 1,
2484 'hg.usergroup.create.true': 1,
2483
2485
2484 'hg.fork.none': 0,
2486 'hg.fork.none': 0,
2485 'hg.fork.repository': 1,
2487 'hg.fork.repository': 1,
2486 'hg.create.none': 0,
2488 'hg.create.none': 0,
2487 'hg.create.repository': 1
2489 'hg.create.repository': 1
2488 }
2490 }
2489
2491
2490 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2492 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2491 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2493 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2492 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2494 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2493
2495
2494 def __unicode__(self):
2496 def __unicode__(self):
2495 return u"<%s('%s:%s')>" % (
2497 return u"<%s('%s:%s')>" % (
2496 self.__class__.__name__, self.permission_id, self.permission_name
2498 self.__class__.__name__, self.permission_id, self.permission_name
2497 )
2499 )
2498
2500
2499 @classmethod
2501 @classmethod
2500 def get_by_key(cls, key):
2502 def get_by_key(cls, key):
2501 return cls.query().filter(cls.permission_name == key).scalar()
2503 return cls.query().filter(cls.permission_name == key).scalar()
2502
2504
2503 @classmethod
2505 @classmethod
2504 def get_default_repo_perms(cls, user_id, repo_id=None):
2506 def get_default_repo_perms(cls, user_id, repo_id=None):
2505 q = Session().query(UserRepoToPerm, Repository, Permission)\
2507 q = Session().query(UserRepoToPerm, Repository, Permission)\
2506 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2508 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2507 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2509 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2508 .filter(UserRepoToPerm.user_id == user_id)
2510 .filter(UserRepoToPerm.user_id == user_id)
2509 if repo_id:
2511 if repo_id:
2510 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2512 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2511 return q.all()
2513 return q.all()
2512
2514
2513 @classmethod
2515 @classmethod
2514 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2516 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2515 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2517 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2516 .join(
2518 .join(
2517 Permission,
2519 Permission,
2518 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2520 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2519 .join(
2521 .join(
2520 Repository,
2522 Repository,
2521 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2523 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2522 .join(
2524 .join(
2523 UserGroup,
2525 UserGroup,
2524 UserGroupRepoToPerm.users_group_id ==
2526 UserGroupRepoToPerm.users_group_id ==
2525 UserGroup.users_group_id)\
2527 UserGroup.users_group_id)\
2526 .join(
2528 .join(
2527 UserGroupMember,
2529 UserGroupMember,
2528 UserGroupRepoToPerm.users_group_id ==
2530 UserGroupRepoToPerm.users_group_id ==
2529 UserGroupMember.users_group_id)\
2531 UserGroupMember.users_group_id)\
2530 .filter(
2532 .filter(
2531 UserGroupMember.user_id == user_id,
2533 UserGroupMember.user_id == user_id,
2532 UserGroup.users_group_active == true())
2534 UserGroup.users_group_active == true())
2533 if repo_id:
2535 if repo_id:
2534 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2536 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2535 return q.all()
2537 return q.all()
2536
2538
2537 @classmethod
2539 @classmethod
2538 def get_default_group_perms(cls, user_id, repo_group_id=None):
2540 def get_default_group_perms(cls, user_id, repo_group_id=None):
2539 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2541 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2540 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2542 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2541 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2543 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2542 .filter(UserRepoGroupToPerm.user_id == user_id)
2544 .filter(UserRepoGroupToPerm.user_id == user_id)
2543 if repo_group_id:
2545 if repo_group_id:
2544 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2546 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2545 return q.all()
2547 return q.all()
2546
2548
2547 @classmethod
2549 @classmethod
2548 def get_default_group_perms_from_user_group(
2550 def get_default_group_perms_from_user_group(
2549 cls, user_id, repo_group_id=None):
2551 cls, user_id, repo_group_id=None):
2550 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2552 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2551 .join(
2553 .join(
2552 Permission,
2554 Permission,
2553 UserGroupRepoGroupToPerm.permission_id ==
2555 UserGroupRepoGroupToPerm.permission_id ==
2554 Permission.permission_id)\
2556 Permission.permission_id)\
2555 .join(
2557 .join(
2556 RepoGroup,
2558 RepoGroup,
2557 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2559 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2558 .join(
2560 .join(
2559 UserGroup,
2561 UserGroup,
2560 UserGroupRepoGroupToPerm.users_group_id ==
2562 UserGroupRepoGroupToPerm.users_group_id ==
2561 UserGroup.users_group_id)\
2563 UserGroup.users_group_id)\
2562 .join(
2564 .join(
2563 UserGroupMember,
2565 UserGroupMember,
2564 UserGroupRepoGroupToPerm.users_group_id ==
2566 UserGroupRepoGroupToPerm.users_group_id ==
2565 UserGroupMember.users_group_id)\
2567 UserGroupMember.users_group_id)\
2566 .filter(
2568 .filter(
2567 UserGroupMember.user_id == user_id,
2569 UserGroupMember.user_id == user_id,
2568 UserGroup.users_group_active == true())
2570 UserGroup.users_group_active == true())
2569 if repo_group_id:
2571 if repo_group_id:
2570 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2572 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2571 return q.all()
2573 return q.all()
2572
2574
2573 @classmethod
2575 @classmethod
2574 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2576 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2575 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2577 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2576 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2578 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2577 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2579 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2578 .filter(UserUserGroupToPerm.user_id == user_id)
2580 .filter(UserUserGroupToPerm.user_id == user_id)
2579 if user_group_id:
2581 if user_group_id:
2580 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2582 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2581 return q.all()
2583 return q.all()
2582
2584
2583 @classmethod
2585 @classmethod
2584 def get_default_user_group_perms_from_user_group(
2586 def get_default_user_group_perms_from_user_group(
2585 cls, user_id, user_group_id=None):
2587 cls, user_id, user_group_id=None):
2586 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2588 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2587 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2589 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2588 .join(
2590 .join(
2589 Permission,
2591 Permission,
2590 UserGroupUserGroupToPerm.permission_id ==
2592 UserGroupUserGroupToPerm.permission_id ==
2591 Permission.permission_id)\
2593 Permission.permission_id)\
2592 .join(
2594 .join(
2593 TargetUserGroup,
2595 TargetUserGroup,
2594 UserGroupUserGroupToPerm.target_user_group_id ==
2596 UserGroupUserGroupToPerm.target_user_group_id ==
2595 TargetUserGroup.users_group_id)\
2597 TargetUserGroup.users_group_id)\
2596 .join(
2598 .join(
2597 UserGroup,
2599 UserGroup,
2598 UserGroupUserGroupToPerm.user_group_id ==
2600 UserGroupUserGroupToPerm.user_group_id ==
2599 UserGroup.users_group_id)\
2601 UserGroup.users_group_id)\
2600 .join(
2602 .join(
2601 UserGroupMember,
2603 UserGroupMember,
2602 UserGroupUserGroupToPerm.user_group_id ==
2604 UserGroupUserGroupToPerm.user_group_id ==
2603 UserGroupMember.users_group_id)\
2605 UserGroupMember.users_group_id)\
2604 .filter(
2606 .filter(
2605 UserGroupMember.user_id == user_id,
2607 UserGroupMember.user_id == user_id,
2606 UserGroup.users_group_active == true())
2608 UserGroup.users_group_active == true())
2607 if user_group_id:
2609 if user_group_id:
2608 q = q.filter(
2610 q = q.filter(
2609 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2611 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2610
2612
2611 return q.all()
2613 return q.all()
2612
2614
2613
2615
2614 class UserRepoToPerm(Base, BaseModel):
2616 class UserRepoToPerm(Base, BaseModel):
2615 __tablename__ = 'repo_to_perm'
2617 __tablename__ = 'repo_to_perm'
2616 __table_args__ = (
2618 __table_args__ = (
2617 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2619 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2618 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2619 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2620 )
2622 )
2621 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2623 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2622 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2624 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2623 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2625 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2624 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2626 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2625
2627
2626 user = relationship('User')
2628 user = relationship('User')
2627 repository = relationship('Repository')
2629 repository = relationship('Repository')
2628 permission = relationship('Permission')
2630 permission = relationship('Permission')
2629
2631
2630 @classmethod
2632 @classmethod
2631 def create(cls, user, repository, permission):
2633 def create(cls, user, repository, permission):
2632 n = cls()
2634 n = cls()
2633 n.user = user
2635 n.user = user
2634 n.repository = repository
2636 n.repository = repository
2635 n.permission = permission
2637 n.permission = permission
2636 Session().add(n)
2638 Session().add(n)
2637 return n
2639 return n
2638
2640
2639 def __unicode__(self):
2641 def __unicode__(self):
2640 return u'<%s => %s >' % (self.user, self.repository)
2642 return u'<%s => %s >' % (self.user, self.repository)
2641
2643
2642
2644
2643 class UserUserGroupToPerm(Base, BaseModel):
2645 class UserUserGroupToPerm(Base, BaseModel):
2644 __tablename__ = 'user_user_group_to_perm'
2646 __tablename__ = 'user_user_group_to_perm'
2645 __table_args__ = (
2647 __table_args__ = (
2646 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2648 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2649 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2648 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2650 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2649 )
2651 )
2650 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2652 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2653 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2652 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2654 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2653 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2655 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2654
2656
2655 user = relationship('User')
2657 user = relationship('User')
2656 user_group = relationship('UserGroup')
2658 user_group = relationship('UserGroup')
2657 permission = relationship('Permission')
2659 permission = relationship('Permission')
2658
2660
2659 @classmethod
2661 @classmethod
2660 def create(cls, user, user_group, permission):
2662 def create(cls, user, user_group, permission):
2661 n = cls()
2663 n = cls()
2662 n.user = user
2664 n.user = user
2663 n.user_group = user_group
2665 n.user_group = user_group
2664 n.permission = permission
2666 n.permission = permission
2665 Session().add(n)
2667 Session().add(n)
2666 return n
2668 return n
2667
2669
2668 def __unicode__(self):
2670 def __unicode__(self):
2669 return u'<%s => %s >' % (self.user, self.user_group)
2671 return u'<%s => %s >' % (self.user, self.user_group)
2670
2672
2671
2673
2672 class UserToPerm(Base, BaseModel):
2674 class UserToPerm(Base, BaseModel):
2673 __tablename__ = 'user_to_perm'
2675 __tablename__ = 'user_to_perm'
2674 __table_args__ = (
2676 __table_args__ = (
2675 UniqueConstraint('user_id', 'permission_id'),
2677 UniqueConstraint('user_id', 'permission_id'),
2676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2678 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2677 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2679 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2678 )
2680 )
2679 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2681 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2682 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2681 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2683 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2682
2684
2683 user = relationship('User')
2685 user = relationship('User')
2684 permission = relationship('Permission', lazy='joined')
2686 permission = relationship('Permission', lazy='joined')
2685
2687
2686 def __unicode__(self):
2688 def __unicode__(self):
2687 return u'<%s => %s >' % (self.user, self.permission)
2689 return u'<%s => %s >' % (self.user, self.permission)
2688
2690
2689
2691
2690 class UserGroupRepoToPerm(Base, BaseModel):
2692 class UserGroupRepoToPerm(Base, BaseModel):
2691 __tablename__ = 'users_group_repo_to_perm'
2693 __tablename__ = 'users_group_repo_to_perm'
2692 __table_args__ = (
2694 __table_args__ = (
2693 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2695 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2696 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2697 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2696 )
2698 )
2697 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2699 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2700 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2699 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2700 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2702 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2701
2703
2702 users_group = relationship('UserGroup')
2704 users_group = relationship('UserGroup')
2703 permission = relationship('Permission')
2705 permission = relationship('Permission')
2704 repository = relationship('Repository')
2706 repository = relationship('Repository')
2705
2707
2706 @classmethod
2708 @classmethod
2707 def create(cls, users_group, repository, permission):
2709 def create(cls, users_group, repository, permission):
2708 n = cls()
2710 n = cls()
2709 n.users_group = users_group
2711 n.users_group = users_group
2710 n.repository = repository
2712 n.repository = repository
2711 n.permission = permission
2713 n.permission = permission
2712 Session().add(n)
2714 Session().add(n)
2713 return n
2715 return n
2714
2716
2715 def __unicode__(self):
2717 def __unicode__(self):
2716 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2718 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2717
2719
2718
2720
2719 class UserGroupUserGroupToPerm(Base, BaseModel):
2721 class UserGroupUserGroupToPerm(Base, BaseModel):
2720 __tablename__ = 'user_group_user_group_to_perm'
2722 __tablename__ = 'user_group_user_group_to_perm'
2721 __table_args__ = (
2723 __table_args__ = (
2722 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2724 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2723 CheckConstraint('target_user_group_id != user_group_id'),
2725 CheckConstraint('target_user_group_id != user_group_id'),
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2726 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2727 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2726 )
2728 )
2727 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)
2729 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)
2728 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2730 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2729 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2731 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2730 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2732 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2731
2733
2732 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2734 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2733 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2735 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2734 permission = relationship('Permission')
2736 permission = relationship('Permission')
2735
2737
2736 @classmethod
2738 @classmethod
2737 def create(cls, target_user_group, user_group, permission):
2739 def create(cls, target_user_group, user_group, permission):
2738 n = cls()
2740 n = cls()
2739 n.target_user_group = target_user_group
2741 n.target_user_group = target_user_group
2740 n.user_group = user_group
2742 n.user_group = user_group
2741 n.permission = permission
2743 n.permission = permission
2742 Session().add(n)
2744 Session().add(n)
2743 return n
2745 return n
2744
2746
2745 def __unicode__(self):
2747 def __unicode__(self):
2746 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2748 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2747
2749
2748
2750
2749 class UserGroupToPerm(Base, BaseModel):
2751 class UserGroupToPerm(Base, BaseModel):
2750 __tablename__ = 'users_group_to_perm'
2752 __tablename__ = 'users_group_to_perm'
2751 __table_args__ = (
2753 __table_args__ = (
2752 UniqueConstraint('users_group_id', 'permission_id',),
2754 UniqueConstraint('users_group_id', 'permission_id',),
2753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2754 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2755 )
2757 )
2756 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2758 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2757 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2759 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2758 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2760 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2759
2761
2760 users_group = relationship('UserGroup')
2762 users_group = relationship('UserGroup')
2761 permission = relationship('Permission')
2763 permission = relationship('Permission')
2762
2764
2763
2765
2764 class UserRepoGroupToPerm(Base, BaseModel):
2766 class UserRepoGroupToPerm(Base, BaseModel):
2765 __tablename__ = 'user_repo_group_to_perm'
2767 __tablename__ = 'user_repo_group_to_perm'
2766 __table_args__ = (
2768 __table_args__ = (
2767 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2769 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2771 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2770 )
2772 )
2771
2773
2772 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2774 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2773 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2775 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2774 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2776 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2775 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2776
2778
2777 user = relationship('User')
2779 user = relationship('User')
2778 group = relationship('RepoGroup')
2780 group = relationship('RepoGroup')
2779 permission = relationship('Permission')
2781 permission = relationship('Permission')
2780
2782
2781 @classmethod
2783 @classmethod
2782 def create(cls, user, repository_group, permission):
2784 def create(cls, user, repository_group, permission):
2783 n = cls()
2785 n = cls()
2784 n.user = user
2786 n.user = user
2785 n.group = repository_group
2787 n.group = repository_group
2786 n.permission = permission
2788 n.permission = permission
2787 Session().add(n)
2789 Session().add(n)
2788 return n
2790 return n
2789
2791
2790
2792
2791 class UserGroupRepoGroupToPerm(Base, BaseModel):
2793 class UserGroupRepoGroupToPerm(Base, BaseModel):
2792 __tablename__ = 'users_group_repo_group_to_perm'
2794 __tablename__ = 'users_group_repo_group_to_perm'
2793 __table_args__ = (
2795 __table_args__ = (
2794 UniqueConstraint('users_group_id', 'group_id'),
2796 UniqueConstraint('users_group_id', 'group_id'),
2795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2798 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2797 )
2799 )
2798
2800
2799 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)
2801 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)
2800 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2802 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2801 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2803 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2802 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2804 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2803
2805
2804 users_group = relationship('UserGroup')
2806 users_group = relationship('UserGroup')
2805 permission = relationship('Permission')
2807 permission = relationship('Permission')
2806 group = relationship('RepoGroup')
2808 group = relationship('RepoGroup')
2807
2809
2808 @classmethod
2810 @classmethod
2809 def create(cls, user_group, repository_group, permission):
2811 def create(cls, user_group, repository_group, permission):
2810 n = cls()
2812 n = cls()
2811 n.users_group = user_group
2813 n.users_group = user_group
2812 n.group = repository_group
2814 n.group = repository_group
2813 n.permission = permission
2815 n.permission = permission
2814 Session().add(n)
2816 Session().add(n)
2815 return n
2817 return n
2816
2818
2817 def __unicode__(self):
2819 def __unicode__(self):
2818 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2820 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2819
2821
2820
2822
2821 class Statistics(Base, BaseModel):
2823 class Statistics(Base, BaseModel):
2822 __tablename__ = 'statistics'
2824 __tablename__ = 'statistics'
2823 __table_args__ = (
2825 __table_args__ = (
2824 UniqueConstraint('repository_id'),
2826 UniqueConstraint('repository_id'),
2825 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2827 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2826 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2828 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2827 )
2829 )
2828 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2830 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2829 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2831 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2830 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2832 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2831 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2833 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2832 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2834 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2833 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2835 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2834
2836
2835 repository = relationship('Repository', single_parent=True)
2837 repository = relationship('Repository', single_parent=True)
2836
2838
2837
2839
2838 class UserFollowing(Base, BaseModel):
2840 class UserFollowing(Base, BaseModel):
2839 __tablename__ = 'user_followings'
2841 __tablename__ = 'user_followings'
2840 __table_args__ = (
2842 __table_args__ = (
2841 UniqueConstraint('user_id', 'follows_repository_id'),
2843 UniqueConstraint('user_id', 'follows_repository_id'),
2842 UniqueConstraint('user_id', 'follows_user_id'),
2844 UniqueConstraint('user_id', 'follows_user_id'),
2843 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2844 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2846 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2845 )
2847 )
2846
2848
2847 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2849 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2848 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2850 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2849 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2851 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2850 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2852 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2851 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2853 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2852
2854
2853 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2855 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2854
2856
2855 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2857 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2856 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2858 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2857
2859
2858 @classmethod
2860 @classmethod
2859 def get_repo_followers(cls, repo_id):
2861 def get_repo_followers(cls, repo_id):
2860 return cls.query().filter(cls.follows_repo_id == repo_id)
2862 return cls.query().filter(cls.follows_repo_id == repo_id)
2861
2863
2862
2864
2863 class CacheKey(Base, BaseModel):
2865 class CacheKey(Base, BaseModel):
2864 __tablename__ = 'cache_invalidation'
2866 __tablename__ = 'cache_invalidation'
2865 __table_args__ = (
2867 __table_args__ = (
2866 UniqueConstraint('cache_key'),
2868 UniqueConstraint('cache_key'),
2867 Index('key_idx', 'cache_key'),
2869 Index('key_idx', 'cache_key'),
2868 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2870 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2869 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2871 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2870 )
2872 )
2871 CACHE_TYPE_ATOM = 'ATOM'
2873 CACHE_TYPE_ATOM = 'ATOM'
2872 CACHE_TYPE_RSS = 'RSS'
2874 CACHE_TYPE_RSS = 'RSS'
2873 CACHE_TYPE_README = 'README'
2875 CACHE_TYPE_README = 'README'
2874
2876
2875 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2877 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2876 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2878 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2877 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2879 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2878 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2880 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2879
2881
2880 def __init__(self, cache_key, cache_args=''):
2882 def __init__(self, cache_key, cache_args=''):
2881 self.cache_key = cache_key
2883 self.cache_key = cache_key
2882 self.cache_args = cache_args
2884 self.cache_args = cache_args
2883 self.cache_active = False
2885 self.cache_active = False
2884
2886
2885 def __unicode__(self):
2887 def __unicode__(self):
2886 return u"<%s('%s:%s[%s]')>" % (
2888 return u"<%s('%s:%s[%s]')>" % (
2887 self.__class__.__name__,
2889 self.__class__.__name__,
2888 self.cache_id, self.cache_key, self.cache_active)
2890 self.cache_id, self.cache_key, self.cache_active)
2889
2891
2890 def _cache_key_partition(self):
2892 def _cache_key_partition(self):
2891 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2893 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2892 return prefix, repo_name, suffix
2894 return prefix, repo_name, suffix
2893
2895
2894 def get_prefix(self):
2896 def get_prefix(self):
2895 """
2897 """
2896 Try to extract prefix from existing cache key. The key could consist
2898 Try to extract prefix from existing cache key. The key could consist
2897 of prefix, repo_name, suffix
2899 of prefix, repo_name, suffix
2898 """
2900 """
2899 # this returns prefix, repo_name, suffix
2901 # this returns prefix, repo_name, suffix
2900 return self._cache_key_partition()[0]
2902 return self._cache_key_partition()[0]
2901
2903
2902 def get_suffix(self):
2904 def get_suffix(self):
2903 """
2905 """
2904 get suffix that might have been used in _get_cache_key to
2906 get suffix that might have been used in _get_cache_key to
2905 generate self.cache_key. Only used for informational purposes
2907 generate self.cache_key. Only used for informational purposes
2906 in repo_edit.mako.
2908 in repo_edit.mako.
2907 """
2909 """
2908 # prefix, repo_name, suffix
2910 # prefix, repo_name, suffix
2909 return self._cache_key_partition()[2]
2911 return self._cache_key_partition()[2]
2910
2912
2911 @classmethod
2913 @classmethod
2912 def delete_all_cache(cls):
2914 def delete_all_cache(cls):
2913 """
2915 """
2914 Delete all cache keys from database.
2916 Delete all cache keys from database.
2915 Should only be run when all instances are down and all entries
2917 Should only be run when all instances are down and all entries
2916 thus stale.
2918 thus stale.
2917 """
2919 """
2918 cls.query().delete()
2920 cls.query().delete()
2919 Session().commit()
2921 Session().commit()
2920
2922
2921 @classmethod
2923 @classmethod
2922 def get_cache_key(cls, repo_name, cache_type):
2924 def get_cache_key(cls, repo_name, cache_type):
2923 """
2925 """
2924
2926
2925 Generate a cache key for this process of RhodeCode instance.
2927 Generate a cache key for this process of RhodeCode instance.
2926 Prefix most likely will be process id or maybe explicitly set
2928 Prefix most likely will be process id or maybe explicitly set
2927 instance_id from .ini file.
2929 instance_id from .ini file.
2928 """
2930 """
2929 import rhodecode
2931 import rhodecode
2930 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2932 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2931
2933
2932 repo_as_unicode = safe_unicode(repo_name)
2934 repo_as_unicode = safe_unicode(repo_name)
2933 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2935 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2934 if cache_type else repo_as_unicode
2936 if cache_type else repo_as_unicode
2935
2937
2936 return u'{}{}'.format(prefix, key)
2938 return u'{}{}'.format(prefix, key)
2937
2939
2938 @classmethod
2940 @classmethod
2939 def set_invalidate(cls, repo_name, delete=False):
2941 def set_invalidate(cls, repo_name, delete=False):
2940 """
2942 """
2941 Mark all caches of a repo as invalid in the database.
2943 Mark all caches of a repo as invalid in the database.
2942 """
2944 """
2943
2945
2944 try:
2946 try:
2945 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2947 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2946 if delete:
2948 if delete:
2947 log.debug('cache objects deleted for repo %s',
2949 log.debug('cache objects deleted for repo %s',
2948 safe_str(repo_name))
2950 safe_str(repo_name))
2949 qry.delete()
2951 qry.delete()
2950 else:
2952 else:
2951 log.debug('cache objects marked as invalid for repo %s',
2953 log.debug('cache objects marked as invalid for repo %s',
2952 safe_str(repo_name))
2954 safe_str(repo_name))
2953 qry.update({"cache_active": False})
2955 qry.update({"cache_active": False})
2954
2956
2955 Session().commit()
2957 Session().commit()
2956 except Exception:
2958 except Exception:
2957 log.exception(
2959 log.exception(
2958 'Cache key invalidation failed for repository %s',
2960 'Cache key invalidation failed for repository %s',
2959 safe_str(repo_name))
2961 safe_str(repo_name))
2960 Session().rollback()
2962 Session().rollback()
2961
2963
2962 @classmethod
2964 @classmethod
2963 def get_active_cache(cls, cache_key):
2965 def get_active_cache(cls, cache_key):
2964 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2966 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2965 if inv_obj:
2967 if inv_obj:
2966 return inv_obj
2968 return inv_obj
2967 return None
2969 return None
2968
2970
2969 @classmethod
2971 @classmethod
2970 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2972 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2971 thread_scoped=False):
2973 thread_scoped=False):
2972 """
2974 """
2973 @cache_region('long_term')
2975 @cache_region('long_term')
2974 def _heavy_calculation(cache_key):
2976 def _heavy_calculation(cache_key):
2975 return 'result'
2977 return 'result'
2976
2978
2977 cache_context = CacheKey.repo_context_cache(
2979 cache_context = CacheKey.repo_context_cache(
2978 _heavy_calculation, repo_name, cache_type)
2980 _heavy_calculation, repo_name, cache_type)
2979
2981
2980 with cache_context as context:
2982 with cache_context as context:
2981 context.invalidate()
2983 context.invalidate()
2982 computed = context.compute()
2984 computed = context.compute()
2983
2985
2984 assert computed == 'result'
2986 assert computed == 'result'
2985 """
2987 """
2986 from rhodecode.lib import caches
2988 from rhodecode.lib import caches
2987 return caches.InvalidationContext(
2989 return caches.InvalidationContext(
2988 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2990 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2989
2991
2990
2992
2991 class ChangesetComment(Base, BaseModel):
2993 class ChangesetComment(Base, BaseModel):
2992 __tablename__ = 'changeset_comments'
2994 __tablename__ = 'changeset_comments'
2993 __table_args__ = (
2995 __table_args__ = (
2994 Index('cc_revision_idx', 'revision'),
2996 Index('cc_revision_idx', 'revision'),
2995 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2996 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2997 )
2999 )
2998
3000
2999 COMMENT_OUTDATED = u'comment_outdated'
3001 COMMENT_OUTDATED = u'comment_outdated'
3000 COMMENT_TYPE_NOTE = u'note'
3002 COMMENT_TYPE_NOTE = u'note'
3001 COMMENT_TYPE_TODO = u'todo'
3003 COMMENT_TYPE_TODO = u'todo'
3002 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3004 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3003
3005
3004 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3006 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3005 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3007 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3006 revision = Column('revision', String(40), nullable=True)
3008 revision = Column('revision', String(40), nullable=True)
3007 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3009 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3008 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3010 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3009 line_no = Column('line_no', Unicode(10), nullable=True)
3011 line_no = Column('line_no', Unicode(10), nullable=True)
3010 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3012 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3011 f_path = Column('f_path', Unicode(1000), nullable=True)
3013 f_path = Column('f_path', Unicode(1000), nullable=True)
3012 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3014 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3013 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3015 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3014 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3016 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3015 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3017 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3016 renderer = Column('renderer', Unicode(64), nullable=True)
3018 renderer = Column('renderer', Unicode(64), nullable=True)
3017 display_state = Column('display_state', Unicode(128), nullable=True)
3019 display_state = Column('display_state', Unicode(128), nullable=True)
3018
3020
3019 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3021 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3020 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3022 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3021 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3023 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3022 author = relationship('User', lazy='joined')
3024 author = relationship('User', lazy='joined')
3023 repo = relationship('Repository')
3025 repo = relationship('Repository')
3024 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3026 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3025 pull_request = relationship('PullRequest', lazy='joined')
3027 pull_request = relationship('PullRequest', lazy='joined')
3026 pull_request_version = relationship('PullRequestVersion')
3028 pull_request_version = relationship('PullRequestVersion')
3027
3029
3028 @classmethod
3030 @classmethod
3029 def get_users(cls, revision=None, pull_request_id=None):
3031 def get_users(cls, revision=None, pull_request_id=None):
3030 """
3032 """
3031 Returns user associated with this ChangesetComment. ie those
3033 Returns user associated with this ChangesetComment. ie those
3032 who actually commented
3034 who actually commented
3033
3035
3034 :param cls:
3036 :param cls:
3035 :param revision:
3037 :param revision:
3036 """
3038 """
3037 q = Session().query(User)\
3039 q = Session().query(User)\
3038 .join(ChangesetComment.author)
3040 .join(ChangesetComment.author)
3039 if revision:
3041 if revision:
3040 q = q.filter(cls.revision == revision)
3042 q = q.filter(cls.revision == revision)
3041 elif pull_request_id:
3043 elif pull_request_id:
3042 q = q.filter(cls.pull_request_id == pull_request_id)
3044 q = q.filter(cls.pull_request_id == pull_request_id)
3043 return q.all()
3045 return q.all()
3044
3046
3045 @classmethod
3047 @classmethod
3046 def get_index_from_version(cls, pr_version, versions):
3048 def get_index_from_version(cls, pr_version, versions):
3047 num_versions = [x.pull_request_version_id for x in versions]
3049 num_versions = [x.pull_request_version_id for x in versions]
3048 try:
3050 try:
3049 return num_versions.index(pr_version) +1
3051 return num_versions.index(pr_version) +1
3050 except (IndexError, ValueError):
3052 except (IndexError, ValueError):
3051 return
3053 return
3052
3054
3053 @property
3055 @property
3054 def outdated(self):
3056 def outdated(self):
3055 return self.display_state == self.COMMENT_OUTDATED
3057 return self.display_state == self.COMMENT_OUTDATED
3056
3058
3057 def outdated_at_version(self, version):
3059 def outdated_at_version(self, version):
3058 """
3060 """
3059 Checks if comment is outdated for given pull request version
3061 Checks if comment is outdated for given pull request version
3060 """
3062 """
3061 return self.outdated and self.pull_request_version_id != version
3063 return self.outdated and self.pull_request_version_id != version
3062
3064
3063 def older_than_version(self, version):
3065 def older_than_version(self, version):
3064 """
3066 """
3065 Checks if comment is made from previous version than given
3067 Checks if comment is made from previous version than given
3066 """
3068 """
3067 if version is None:
3069 if version is None:
3068 return self.pull_request_version_id is not None
3070 return self.pull_request_version_id is not None
3069
3071
3070 return self.pull_request_version_id < version
3072 return self.pull_request_version_id < version
3071
3073
3072 @property
3074 @property
3073 def resolved(self):
3075 def resolved(self):
3074 return self.resolved_by[0] if self.resolved_by else None
3076 return self.resolved_by[0] if self.resolved_by else None
3075
3077
3076 @property
3078 @property
3077 def is_todo(self):
3079 def is_todo(self):
3078 return self.comment_type == self.COMMENT_TYPE_TODO
3080 return self.comment_type == self.COMMENT_TYPE_TODO
3079
3081
3080 def get_index_version(self, versions):
3082 def get_index_version(self, versions):
3081 return self.get_index_from_version(
3083 return self.get_index_from_version(
3082 self.pull_request_version_id, versions)
3084 self.pull_request_version_id, versions)
3083
3085
3084 def render(self, mentions=False):
3086 def render(self, mentions=False):
3085 from rhodecode.lib import helpers as h
3087 from rhodecode.lib import helpers as h
3086 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3088 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3087
3089
3088 def __repr__(self):
3090 def __repr__(self):
3089 if self.comment_id:
3091 if self.comment_id:
3090 return '<DB:Comment #%s>' % self.comment_id
3092 return '<DB:Comment #%s>' % self.comment_id
3091 else:
3093 else:
3092 return '<DB:Comment at %#x>' % id(self)
3094 return '<DB:Comment at %#x>' % id(self)
3093
3095
3094
3096
3095 class ChangesetStatus(Base, BaseModel):
3097 class ChangesetStatus(Base, BaseModel):
3096 __tablename__ = 'changeset_statuses'
3098 __tablename__ = 'changeset_statuses'
3097 __table_args__ = (
3099 __table_args__ = (
3098 Index('cs_revision_idx', 'revision'),
3100 Index('cs_revision_idx', 'revision'),
3099 Index('cs_version_idx', 'version'),
3101 Index('cs_version_idx', 'version'),
3100 UniqueConstraint('repo_id', 'revision', 'version'),
3102 UniqueConstraint('repo_id', 'revision', 'version'),
3101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3103 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3104 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3103 )
3105 )
3104 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3106 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3105 STATUS_APPROVED = 'approved'
3107 STATUS_APPROVED = 'approved'
3106 STATUS_REJECTED = 'rejected'
3108 STATUS_REJECTED = 'rejected'
3107 STATUS_UNDER_REVIEW = 'under_review'
3109 STATUS_UNDER_REVIEW = 'under_review'
3108
3110
3109 STATUSES = [
3111 STATUSES = [
3110 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3112 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3111 (STATUS_APPROVED, _("Approved")),
3113 (STATUS_APPROVED, _("Approved")),
3112 (STATUS_REJECTED, _("Rejected")),
3114 (STATUS_REJECTED, _("Rejected")),
3113 (STATUS_UNDER_REVIEW, _("Under Review")),
3115 (STATUS_UNDER_REVIEW, _("Under Review")),
3114 ]
3116 ]
3115
3117
3116 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3118 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3117 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3119 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3118 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3120 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3119 revision = Column('revision', String(40), nullable=False)
3121 revision = Column('revision', String(40), nullable=False)
3120 status = Column('status', String(128), nullable=False, default=DEFAULT)
3122 status = Column('status', String(128), nullable=False, default=DEFAULT)
3121 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3123 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3122 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3124 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3123 version = Column('version', Integer(), nullable=False, default=0)
3125 version = Column('version', Integer(), nullable=False, default=0)
3124 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3126 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3125
3127
3126 author = relationship('User', lazy='joined')
3128 author = relationship('User', lazy='joined')
3127 repo = relationship('Repository')
3129 repo = relationship('Repository')
3128 comment = relationship('ChangesetComment', lazy='joined')
3130 comment = relationship('ChangesetComment', lazy='joined')
3129 pull_request = relationship('PullRequest', lazy='joined')
3131 pull_request = relationship('PullRequest', lazy='joined')
3130
3132
3131 def __unicode__(self):
3133 def __unicode__(self):
3132 return u"<%s('%s[v%s]:%s')>" % (
3134 return u"<%s('%s[v%s]:%s')>" % (
3133 self.__class__.__name__,
3135 self.__class__.__name__,
3134 self.status, self.version, self.author
3136 self.status, self.version, self.author
3135 )
3137 )
3136
3138
3137 @classmethod
3139 @classmethod
3138 def get_status_lbl(cls, value):
3140 def get_status_lbl(cls, value):
3139 return dict(cls.STATUSES).get(value)
3141 return dict(cls.STATUSES).get(value)
3140
3142
3141 @property
3143 @property
3142 def status_lbl(self):
3144 def status_lbl(self):
3143 return ChangesetStatus.get_status_lbl(self.status)
3145 return ChangesetStatus.get_status_lbl(self.status)
3144
3146
3145
3147
3146 class _PullRequestBase(BaseModel):
3148 class _PullRequestBase(BaseModel):
3147 """
3149 """
3148 Common attributes of pull request and version entries.
3150 Common attributes of pull request and version entries.
3149 """
3151 """
3150
3152
3151 # .status values
3153 # .status values
3152 STATUS_NEW = u'new'
3154 STATUS_NEW = u'new'
3153 STATUS_OPEN = u'open'
3155 STATUS_OPEN = u'open'
3154 STATUS_CLOSED = u'closed'
3156 STATUS_CLOSED = u'closed'
3155
3157
3156 title = Column('title', Unicode(255), nullable=True)
3158 title = Column('title', Unicode(255), nullable=True)
3157 description = Column(
3159 description = Column(
3158 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3160 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3159 nullable=True)
3161 nullable=True)
3160 # new/open/closed status of pull request (not approve/reject/etc)
3162 # new/open/closed status of pull request (not approve/reject/etc)
3161 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3163 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3162 created_on = Column(
3164 created_on = Column(
3163 'created_on', DateTime(timezone=False), nullable=False,
3165 'created_on', DateTime(timezone=False), nullable=False,
3164 default=datetime.datetime.now)
3166 default=datetime.datetime.now)
3165 updated_on = Column(
3167 updated_on = Column(
3166 'updated_on', DateTime(timezone=False), nullable=False,
3168 'updated_on', DateTime(timezone=False), nullable=False,
3167 default=datetime.datetime.now)
3169 default=datetime.datetime.now)
3168
3170
3169 @declared_attr
3171 @declared_attr
3170 def user_id(cls):
3172 def user_id(cls):
3171 return Column(
3173 return Column(
3172 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3174 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3173 unique=None)
3175 unique=None)
3174
3176
3175 # 500 revisions max
3177 # 500 revisions max
3176 _revisions = Column(
3178 _revisions = Column(
3177 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3179 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3178
3180
3179 @declared_attr
3181 @declared_attr
3180 def source_repo_id(cls):
3182 def source_repo_id(cls):
3181 # TODO: dan: rename column to source_repo_id
3183 # TODO: dan: rename column to source_repo_id
3182 return Column(
3184 return Column(
3183 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3185 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3184 nullable=False)
3186 nullable=False)
3185
3187
3186 source_ref = Column('org_ref', Unicode(255), nullable=False)
3188 source_ref = Column('org_ref', Unicode(255), nullable=False)
3187
3189
3188 @declared_attr
3190 @declared_attr
3189 def target_repo_id(cls):
3191 def target_repo_id(cls):
3190 # TODO: dan: rename column to target_repo_id
3192 # TODO: dan: rename column to target_repo_id
3191 return Column(
3193 return Column(
3192 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3194 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3193 nullable=False)
3195 nullable=False)
3194
3196
3195 target_ref = Column('other_ref', Unicode(255), nullable=False)
3197 target_ref = Column('other_ref', Unicode(255), nullable=False)
3196 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3198 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3197
3199
3198 # TODO: dan: rename column to last_merge_source_rev
3200 # TODO: dan: rename column to last_merge_source_rev
3199 _last_merge_source_rev = Column(
3201 _last_merge_source_rev = Column(
3200 'last_merge_org_rev', String(40), nullable=True)
3202 'last_merge_org_rev', String(40), nullable=True)
3201 # TODO: dan: rename column to last_merge_target_rev
3203 # TODO: dan: rename column to last_merge_target_rev
3202 _last_merge_target_rev = Column(
3204 _last_merge_target_rev = Column(
3203 'last_merge_other_rev', String(40), nullable=True)
3205 'last_merge_other_rev', String(40), nullable=True)
3204 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3206 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3205 merge_rev = Column('merge_rev', String(40), nullable=True)
3207 merge_rev = Column('merge_rev', String(40), nullable=True)
3206
3208
3207 @hybrid_property
3209 @hybrid_property
3208 def revisions(self):
3210 def revisions(self):
3209 return self._revisions.split(':') if self._revisions else []
3211 return self._revisions.split(':') if self._revisions else []
3210
3212
3211 @revisions.setter
3213 @revisions.setter
3212 def revisions(self, val):
3214 def revisions(self, val):
3213 self._revisions = ':'.join(val)
3215 self._revisions = ':'.join(val)
3214
3216
3215 @declared_attr
3217 @declared_attr
3216 def author(cls):
3218 def author(cls):
3217 return relationship('User', lazy='joined')
3219 return relationship('User', lazy='joined')
3218
3220
3219 @declared_attr
3221 @declared_attr
3220 def source_repo(cls):
3222 def source_repo(cls):
3221 return relationship(
3223 return relationship(
3222 'Repository',
3224 'Repository',
3223 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3225 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3224
3226
3225 @property
3227 @property
3226 def source_ref_parts(self):
3228 def source_ref_parts(self):
3227 return self.unicode_to_reference(self.source_ref)
3229 return self.unicode_to_reference(self.source_ref)
3228
3230
3229 @declared_attr
3231 @declared_attr
3230 def target_repo(cls):
3232 def target_repo(cls):
3231 return relationship(
3233 return relationship(
3232 'Repository',
3234 'Repository',
3233 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3235 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3234
3236
3235 @property
3237 @property
3236 def target_ref_parts(self):
3238 def target_ref_parts(self):
3237 return self.unicode_to_reference(self.target_ref)
3239 return self.unicode_to_reference(self.target_ref)
3238
3240
3239 @property
3241 @property
3240 def shadow_merge_ref(self):
3242 def shadow_merge_ref(self):
3241 return self.unicode_to_reference(self._shadow_merge_ref)
3243 return self.unicode_to_reference(self._shadow_merge_ref)
3242
3244
3243 @shadow_merge_ref.setter
3245 @shadow_merge_ref.setter
3244 def shadow_merge_ref(self, ref):
3246 def shadow_merge_ref(self, ref):
3245 self._shadow_merge_ref = self.reference_to_unicode(ref)
3247 self._shadow_merge_ref = self.reference_to_unicode(ref)
3246
3248
3247 def unicode_to_reference(self, raw):
3249 def unicode_to_reference(self, raw):
3248 """
3250 """
3249 Convert a unicode (or string) to a reference object.
3251 Convert a unicode (or string) to a reference object.
3250 If unicode evaluates to False it returns None.
3252 If unicode evaluates to False it returns None.
3251 """
3253 """
3252 if raw:
3254 if raw:
3253 refs = raw.split(':')
3255 refs = raw.split(':')
3254 return Reference(*refs)
3256 return Reference(*refs)
3255 else:
3257 else:
3256 return None
3258 return None
3257
3259
3258 def reference_to_unicode(self, ref):
3260 def reference_to_unicode(self, ref):
3259 """
3261 """
3260 Convert a reference object to unicode.
3262 Convert a reference object to unicode.
3261 If reference is None it returns None.
3263 If reference is None it returns None.
3262 """
3264 """
3263 if ref:
3265 if ref:
3264 return u':'.join(ref)
3266 return u':'.join(ref)
3265 else:
3267 else:
3266 return None
3268 return None
3267
3269
3268 def get_api_data(self):
3270 def get_api_data(self):
3269 from rhodecode.model.pull_request import PullRequestModel
3271 from rhodecode.model.pull_request import PullRequestModel
3270 pull_request = self
3272 pull_request = self
3271 merge_status = PullRequestModel().merge_status(pull_request)
3273 merge_status = PullRequestModel().merge_status(pull_request)
3272
3274
3273 pull_request_url = url(
3275 pull_request_url = url(
3274 'pullrequest_show', repo_name=self.target_repo.repo_name,
3276 'pullrequest_show', repo_name=self.target_repo.repo_name,
3275 pull_request_id=self.pull_request_id, qualified=True)
3277 pull_request_id=self.pull_request_id, qualified=True)
3276
3278
3277 merge_data = {
3279 merge_data = {
3278 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3280 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3279 'reference': (
3281 'reference': (
3280 pull_request.shadow_merge_ref._asdict()
3282 pull_request.shadow_merge_ref._asdict()
3281 if pull_request.shadow_merge_ref else None),
3283 if pull_request.shadow_merge_ref else None),
3282 }
3284 }
3283
3285
3284 data = {
3286 data = {
3285 'pull_request_id': pull_request.pull_request_id,
3287 'pull_request_id': pull_request.pull_request_id,
3286 'url': pull_request_url,
3288 'url': pull_request_url,
3287 'title': pull_request.title,
3289 'title': pull_request.title,
3288 'description': pull_request.description,
3290 'description': pull_request.description,
3289 'status': pull_request.status,
3291 'status': pull_request.status,
3290 'created_on': pull_request.created_on,
3292 'created_on': pull_request.created_on,
3291 'updated_on': pull_request.updated_on,
3293 'updated_on': pull_request.updated_on,
3292 'commit_ids': pull_request.revisions,
3294 'commit_ids': pull_request.revisions,
3293 'review_status': pull_request.calculated_review_status(),
3295 'review_status': pull_request.calculated_review_status(),
3294 'mergeable': {
3296 'mergeable': {
3295 'status': merge_status[0],
3297 'status': merge_status[0],
3296 'message': unicode(merge_status[1]),
3298 'message': unicode(merge_status[1]),
3297 },
3299 },
3298 'source': {
3300 'source': {
3299 'clone_url': pull_request.source_repo.clone_url(),
3301 'clone_url': pull_request.source_repo.clone_url(),
3300 'repository': pull_request.source_repo.repo_name,
3302 'repository': pull_request.source_repo.repo_name,
3301 'reference': {
3303 'reference': {
3302 'name': pull_request.source_ref_parts.name,
3304 'name': pull_request.source_ref_parts.name,
3303 'type': pull_request.source_ref_parts.type,
3305 'type': pull_request.source_ref_parts.type,
3304 'commit_id': pull_request.source_ref_parts.commit_id,
3306 'commit_id': pull_request.source_ref_parts.commit_id,
3305 },
3307 },
3306 },
3308 },
3307 'target': {
3309 'target': {
3308 'clone_url': pull_request.target_repo.clone_url(),
3310 'clone_url': pull_request.target_repo.clone_url(),
3309 'repository': pull_request.target_repo.repo_name,
3311 'repository': pull_request.target_repo.repo_name,
3310 'reference': {
3312 'reference': {
3311 'name': pull_request.target_ref_parts.name,
3313 'name': pull_request.target_ref_parts.name,
3312 'type': pull_request.target_ref_parts.type,
3314 'type': pull_request.target_ref_parts.type,
3313 'commit_id': pull_request.target_ref_parts.commit_id,
3315 'commit_id': pull_request.target_ref_parts.commit_id,
3314 },
3316 },
3315 },
3317 },
3316 'merge': merge_data,
3318 'merge': merge_data,
3317 'author': pull_request.author.get_api_data(include_secrets=False,
3319 'author': pull_request.author.get_api_data(include_secrets=False,
3318 details='basic'),
3320 details='basic'),
3319 'reviewers': [
3321 'reviewers': [
3320 {
3322 {
3321 'user': reviewer.get_api_data(include_secrets=False,
3323 'user': reviewer.get_api_data(include_secrets=False,
3322 details='basic'),
3324 details='basic'),
3323 'reasons': reasons,
3325 'reasons': reasons,
3324 'review_status': st[0][1].status if st else 'not_reviewed',
3326 'review_status': st[0][1].status if st else 'not_reviewed',
3325 }
3327 }
3326 for reviewer, reasons, st in pull_request.reviewers_statuses()
3328 for reviewer, reasons, st in pull_request.reviewers_statuses()
3327 ]
3329 ]
3328 }
3330 }
3329
3331
3330 return data
3332 return data
3331
3333
3332
3334
3333 class PullRequest(Base, _PullRequestBase):
3335 class PullRequest(Base, _PullRequestBase):
3334 __tablename__ = 'pull_requests'
3336 __tablename__ = 'pull_requests'
3335 __table_args__ = (
3337 __table_args__ = (
3336 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3338 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3337 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3339 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3338 )
3340 )
3339
3341
3340 pull_request_id = Column(
3342 pull_request_id = Column(
3341 'pull_request_id', Integer(), nullable=False, primary_key=True)
3343 'pull_request_id', Integer(), nullable=False, primary_key=True)
3342
3344
3343 def __repr__(self):
3345 def __repr__(self):
3344 if self.pull_request_id:
3346 if self.pull_request_id:
3345 return '<DB:PullRequest #%s>' % self.pull_request_id
3347 return '<DB:PullRequest #%s>' % self.pull_request_id
3346 else:
3348 else:
3347 return '<DB:PullRequest at %#x>' % id(self)
3349 return '<DB:PullRequest at %#x>' % id(self)
3348
3350
3349 reviewers = relationship('PullRequestReviewers',
3351 reviewers = relationship('PullRequestReviewers',
3350 cascade="all, delete, delete-orphan")
3352 cascade="all, delete, delete-orphan")
3351 statuses = relationship('ChangesetStatus')
3353 statuses = relationship('ChangesetStatus')
3352 comments = relationship('ChangesetComment',
3354 comments = relationship('ChangesetComment',
3353 cascade="all, delete, delete-orphan")
3355 cascade="all, delete, delete-orphan")
3354 versions = relationship('PullRequestVersion',
3356 versions = relationship('PullRequestVersion',
3355 cascade="all, delete, delete-orphan",
3357 cascade="all, delete, delete-orphan",
3356 lazy='dynamic')
3358 lazy='dynamic')
3357
3359
3358 @classmethod
3360 @classmethod
3359 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3361 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3360 internal_methods=None):
3362 internal_methods=None):
3361
3363
3362 class PullRequestDisplay(object):
3364 class PullRequestDisplay(object):
3363 """
3365 """
3364 Special object wrapper for showing PullRequest data via Versions
3366 Special object wrapper for showing PullRequest data via Versions
3365 It mimics PR object as close as possible. This is read only object
3367 It mimics PR object as close as possible. This is read only object
3366 just for display
3368 just for display
3367 """
3369 """
3368
3370
3369 def __init__(self, attrs, internal=None):
3371 def __init__(self, attrs, internal=None):
3370 self.attrs = attrs
3372 self.attrs = attrs
3371 # internal have priority over the given ones via attrs
3373 # internal have priority over the given ones via attrs
3372 self.internal = internal or ['versions']
3374 self.internal = internal or ['versions']
3373
3375
3374 def __getattr__(self, item):
3376 def __getattr__(self, item):
3375 if item in self.internal:
3377 if item in self.internal:
3376 return getattr(self, item)
3378 return getattr(self, item)
3377 try:
3379 try:
3378 return self.attrs[item]
3380 return self.attrs[item]
3379 except KeyError:
3381 except KeyError:
3380 raise AttributeError(
3382 raise AttributeError(
3381 '%s object has no attribute %s' % (self, item))
3383 '%s object has no attribute %s' % (self, item))
3382
3384
3383 def __repr__(self):
3385 def __repr__(self):
3384 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3386 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3385
3387
3386 def versions(self):
3388 def versions(self):
3387 return pull_request_obj.versions.order_by(
3389 return pull_request_obj.versions.order_by(
3388 PullRequestVersion.pull_request_version_id).all()
3390 PullRequestVersion.pull_request_version_id).all()
3389
3391
3390 def is_closed(self):
3392 def is_closed(self):
3391 return pull_request_obj.is_closed()
3393 return pull_request_obj.is_closed()
3392
3394
3393 @property
3395 @property
3394 def pull_request_version_id(self):
3396 def pull_request_version_id(self):
3395 return getattr(pull_request_obj, 'pull_request_version_id', None)
3397 return getattr(pull_request_obj, 'pull_request_version_id', None)
3396
3398
3397 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3399 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3398
3400
3399 attrs.author = StrictAttributeDict(
3401 attrs.author = StrictAttributeDict(
3400 pull_request_obj.author.get_api_data())
3402 pull_request_obj.author.get_api_data())
3401 if pull_request_obj.target_repo:
3403 if pull_request_obj.target_repo:
3402 attrs.target_repo = StrictAttributeDict(
3404 attrs.target_repo = StrictAttributeDict(
3403 pull_request_obj.target_repo.get_api_data())
3405 pull_request_obj.target_repo.get_api_data())
3404 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3406 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3405
3407
3406 if pull_request_obj.source_repo:
3408 if pull_request_obj.source_repo:
3407 attrs.source_repo = StrictAttributeDict(
3409 attrs.source_repo = StrictAttributeDict(
3408 pull_request_obj.source_repo.get_api_data())
3410 pull_request_obj.source_repo.get_api_data())
3409 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3411 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3410
3412
3411 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3413 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3412 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3414 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3413 attrs.revisions = pull_request_obj.revisions
3415 attrs.revisions = pull_request_obj.revisions
3414
3416
3415 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3417 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3416
3418
3417 return PullRequestDisplay(attrs, internal=internal_methods)
3419 return PullRequestDisplay(attrs, internal=internal_methods)
3418
3420
3419 def is_closed(self):
3421 def is_closed(self):
3420 return self.status == self.STATUS_CLOSED
3422 return self.status == self.STATUS_CLOSED
3421
3423
3422 def __json__(self):
3424 def __json__(self):
3423 return {
3425 return {
3424 'revisions': self.revisions,
3426 'revisions': self.revisions,
3425 }
3427 }
3426
3428
3427 def calculated_review_status(self):
3429 def calculated_review_status(self):
3428 from rhodecode.model.changeset_status import ChangesetStatusModel
3430 from rhodecode.model.changeset_status import ChangesetStatusModel
3429 return ChangesetStatusModel().calculated_review_status(self)
3431 return ChangesetStatusModel().calculated_review_status(self)
3430
3432
3431 def reviewers_statuses(self):
3433 def reviewers_statuses(self):
3432 from rhodecode.model.changeset_status import ChangesetStatusModel
3434 from rhodecode.model.changeset_status import ChangesetStatusModel
3433 return ChangesetStatusModel().reviewers_statuses(self)
3435 return ChangesetStatusModel().reviewers_statuses(self)
3434
3436
3435 @property
3437 @property
3436 def workspace_id(self):
3438 def workspace_id(self):
3437 from rhodecode.model.pull_request import PullRequestModel
3439 from rhodecode.model.pull_request import PullRequestModel
3438 return PullRequestModel()._workspace_id(self)
3440 return PullRequestModel()._workspace_id(self)
3439
3441
3440 def get_shadow_repo(self):
3442 def get_shadow_repo(self):
3441 workspace_id = self.workspace_id
3443 workspace_id = self.workspace_id
3442 vcs_obj = self.target_repo.scm_instance()
3444 vcs_obj = self.target_repo.scm_instance()
3443 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3445 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3444 workspace_id)
3446 workspace_id)
3445 return vcs_obj._get_shadow_instance(shadow_repository_path)
3447 return vcs_obj._get_shadow_instance(shadow_repository_path)
3446
3448
3447
3449
3448 class PullRequestVersion(Base, _PullRequestBase):
3450 class PullRequestVersion(Base, _PullRequestBase):
3449 __tablename__ = 'pull_request_versions'
3451 __tablename__ = 'pull_request_versions'
3450 __table_args__ = (
3452 __table_args__ = (
3451 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3453 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3452 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3454 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3453 )
3455 )
3454
3456
3455 pull_request_version_id = Column(
3457 pull_request_version_id = Column(
3456 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3458 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3457 pull_request_id = Column(
3459 pull_request_id = Column(
3458 'pull_request_id', Integer(),
3460 'pull_request_id', Integer(),
3459 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3461 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3460 pull_request = relationship('PullRequest')
3462 pull_request = relationship('PullRequest')
3461
3463
3462 def __repr__(self):
3464 def __repr__(self):
3463 if self.pull_request_version_id:
3465 if self.pull_request_version_id:
3464 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3466 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3465 else:
3467 else:
3466 return '<DB:PullRequestVersion at %#x>' % id(self)
3468 return '<DB:PullRequestVersion at %#x>' % id(self)
3467
3469
3468 @property
3470 @property
3469 def reviewers(self):
3471 def reviewers(self):
3470 return self.pull_request.reviewers
3472 return self.pull_request.reviewers
3471
3473
3472 @property
3474 @property
3473 def versions(self):
3475 def versions(self):
3474 return self.pull_request.versions
3476 return self.pull_request.versions
3475
3477
3476 def is_closed(self):
3478 def is_closed(self):
3477 # calculate from original
3479 # calculate from original
3478 return self.pull_request.status == self.STATUS_CLOSED
3480 return self.pull_request.status == self.STATUS_CLOSED
3479
3481
3480 def calculated_review_status(self):
3482 def calculated_review_status(self):
3481 return self.pull_request.calculated_review_status()
3483 return self.pull_request.calculated_review_status()
3482
3484
3483 def reviewers_statuses(self):
3485 def reviewers_statuses(self):
3484 return self.pull_request.reviewers_statuses()
3486 return self.pull_request.reviewers_statuses()
3485
3487
3486
3488
3487 class PullRequestReviewers(Base, BaseModel):
3489 class PullRequestReviewers(Base, BaseModel):
3488 __tablename__ = 'pull_request_reviewers'
3490 __tablename__ = 'pull_request_reviewers'
3489 __table_args__ = (
3491 __table_args__ = (
3490 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3491 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3493 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3492 )
3494 )
3493
3495
3494 def __init__(self, user=None, pull_request=None, reasons=None):
3496 def __init__(self, user=None, pull_request=None, reasons=None):
3495 self.user = user
3497 self.user = user
3496 self.pull_request = pull_request
3498 self.pull_request = pull_request
3497 self.reasons = reasons or []
3499 self.reasons = reasons or []
3498
3500
3499 @hybrid_property
3501 @hybrid_property
3500 def reasons(self):
3502 def reasons(self):
3501 if not self._reasons:
3503 if not self._reasons:
3502 return []
3504 return []
3503 return self._reasons
3505 return self._reasons
3504
3506
3505 @reasons.setter
3507 @reasons.setter
3506 def reasons(self, val):
3508 def reasons(self, val):
3507 val = val or []
3509 val = val or []
3508 if any(not isinstance(x, basestring) for x in val):
3510 if any(not isinstance(x, basestring) for x in val):
3509 raise Exception('invalid reasons type, must be list of strings')
3511 raise Exception('invalid reasons type, must be list of strings')
3510 self._reasons = val
3512 self._reasons = val
3511
3513
3512 pull_requests_reviewers_id = Column(
3514 pull_requests_reviewers_id = Column(
3513 'pull_requests_reviewers_id', Integer(), nullable=False,
3515 'pull_requests_reviewers_id', Integer(), nullable=False,
3514 primary_key=True)
3516 primary_key=True)
3515 pull_request_id = Column(
3517 pull_request_id = Column(
3516 "pull_request_id", Integer(),
3518 "pull_request_id", Integer(),
3517 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3519 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3518 user_id = Column(
3520 user_id = Column(
3519 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3521 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3520 _reasons = Column(
3522 _reasons = Column(
3521 'reason', MutationList.as_mutable(
3523 'reason', MutationList.as_mutable(
3522 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3524 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3523
3525
3524 user = relationship('User')
3526 user = relationship('User')
3525 pull_request = relationship('PullRequest')
3527 pull_request = relationship('PullRequest')
3526
3528
3527
3529
3528 class Notification(Base, BaseModel):
3530 class Notification(Base, BaseModel):
3529 __tablename__ = 'notifications'
3531 __tablename__ = 'notifications'
3530 __table_args__ = (
3532 __table_args__ = (
3531 Index('notification_type_idx', 'type'),
3533 Index('notification_type_idx', 'type'),
3532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3533 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3534 )
3536 )
3535
3537
3536 TYPE_CHANGESET_COMMENT = u'cs_comment'
3538 TYPE_CHANGESET_COMMENT = u'cs_comment'
3537 TYPE_MESSAGE = u'message'
3539 TYPE_MESSAGE = u'message'
3538 TYPE_MENTION = u'mention'
3540 TYPE_MENTION = u'mention'
3539 TYPE_REGISTRATION = u'registration'
3541 TYPE_REGISTRATION = u'registration'
3540 TYPE_PULL_REQUEST = u'pull_request'
3542 TYPE_PULL_REQUEST = u'pull_request'
3541 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3543 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3542
3544
3543 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3545 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3544 subject = Column('subject', Unicode(512), nullable=True)
3546 subject = Column('subject', Unicode(512), nullable=True)
3545 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3547 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3546 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3548 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3547 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3549 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3548 type_ = Column('type', Unicode(255))
3550 type_ = Column('type', Unicode(255))
3549
3551
3550 created_by_user = relationship('User')
3552 created_by_user = relationship('User')
3551 notifications_to_users = relationship('UserNotification', lazy='joined',
3553 notifications_to_users = relationship('UserNotification', lazy='joined',
3552 cascade="all, delete, delete-orphan")
3554 cascade="all, delete, delete-orphan")
3553
3555
3554 @property
3556 @property
3555 def recipients(self):
3557 def recipients(self):
3556 return [x.user for x in UserNotification.query()\
3558 return [x.user for x in UserNotification.query()\
3557 .filter(UserNotification.notification == self)\
3559 .filter(UserNotification.notification == self)\
3558 .order_by(UserNotification.user_id.asc()).all()]
3560 .order_by(UserNotification.user_id.asc()).all()]
3559
3561
3560 @classmethod
3562 @classmethod
3561 def create(cls, created_by, subject, body, recipients, type_=None):
3563 def create(cls, created_by, subject, body, recipients, type_=None):
3562 if type_ is None:
3564 if type_ is None:
3563 type_ = Notification.TYPE_MESSAGE
3565 type_ = Notification.TYPE_MESSAGE
3564
3566
3565 notification = cls()
3567 notification = cls()
3566 notification.created_by_user = created_by
3568 notification.created_by_user = created_by
3567 notification.subject = subject
3569 notification.subject = subject
3568 notification.body = body
3570 notification.body = body
3569 notification.type_ = type_
3571 notification.type_ = type_
3570 notification.created_on = datetime.datetime.now()
3572 notification.created_on = datetime.datetime.now()
3571
3573
3572 for u in recipients:
3574 for u in recipients:
3573 assoc = UserNotification()
3575 assoc = UserNotification()
3574 assoc.notification = notification
3576 assoc.notification = notification
3575
3577
3576 # if created_by is inside recipients mark his notification
3578 # if created_by is inside recipients mark his notification
3577 # as read
3579 # as read
3578 if u.user_id == created_by.user_id:
3580 if u.user_id == created_by.user_id:
3579 assoc.read = True
3581 assoc.read = True
3580
3582
3581 u.notifications.append(assoc)
3583 u.notifications.append(assoc)
3582 Session().add(notification)
3584 Session().add(notification)
3583
3585
3584 return notification
3586 return notification
3585
3587
3586 @property
3588 @property
3587 def description(self):
3589 def description(self):
3588 from rhodecode.model.notification import NotificationModel
3590 from rhodecode.model.notification import NotificationModel
3589 return NotificationModel().make_description(self)
3591 return NotificationModel().make_description(self)
3590
3592
3591
3593
3592 class UserNotification(Base, BaseModel):
3594 class UserNotification(Base, BaseModel):
3593 __tablename__ = 'user_to_notification'
3595 __tablename__ = 'user_to_notification'
3594 __table_args__ = (
3596 __table_args__ = (
3595 UniqueConstraint('user_id', 'notification_id'),
3597 UniqueConstraint('user_id', 'notification_id'),
3596 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3598 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3597 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3599 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3598 )
3600 )
3599 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3601 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3600 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3602 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3601 read = Column('read', Boolean, default=False)
3603 read = Column('read', Boolean, default=False)
3602 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3604 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3603
3605
3604 user = relationship('User', lazy="joined")
3606 user = relationship('User', lazy="joined")
3605 notification = relationship('Notification', lazy="joined",
3607 notification = relationship('Notification', lazy="joined",
3606 order_by=lambda: Notification.created_on.desc(),)
3608 order_by=lambda: Notification.created_on.desc(),)
3607
3609
3608 def mark_as_read(self):
3610 def mark_as_read(self):
3609 self.read = True
3611 self.read = True
3610 Session().add(self)
3612 Session().add(self)
3611
3613
3612
3614
3613 class Gist(Base, BaseModel):
3615 class Gist(Base, BaseModel):
3614 __tablename__ = 'gists'
3616 __tablename__ = 'gists'
3615 __table_args__ = (
3617 __table_args__ = (
3616 Index('g_gist_access_id_idx', 'gist_access_id'),
3618 Index('g_gist_access_id_idx', 'gist_access_id'),
3617 Index('g_created_on_idx', 'created_on'),
3619 Index('g_created_on_idx', 'created_on'),
3618 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3619 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3620 )
3622 )
3621 GIST_PUBLIC = u'public'
3623 GIST_PUBLIC = u'public'
3622 GIST_PRIVATE = u'private'
3624 GIST_PRIVATE = u'private'
3623 DEFAULT_FILENAME = u'gistfile1.txt'
3625 DEFAULT_FILENAME = u'gistfile1.txt'
3624
3626
3625 ACL_LEVEL_PUBLIC = u'acl_public'
3627 ACL_LEVEL_PUBLIC = u'acl_public'
3626 ACL_LEVEL_PRIVATE = u'acl_private'
3628 ACL_LEVEL_PRIVATE = u'acl_private'
3627
3629
3628 gist_id = Column('gist_id', Integer(), primary_key=True)
3630 gist_id = Column('gist_id', Integer(), primary_key=True)
3629 gist_access_id = Column('gist_access_id', Unicode(250))
3631 gist_access_id = Column('gist_access_id', Unicode(250))
3630 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3632 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3631 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3633 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3632 gist_expires = Column('gist_expires', Float(53), nullable=False)
3634 gist_expires = Column('gist_expires', Float(53), nullable=False)
3633 gist_type = Column('gist_type', Unicode(128), nullable=False)
3635 gist_type = Column('gist_type', Unicode(128), nullable=False)
3634 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3636 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3635 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3637 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3636 acl_level = Column('acl_level', Unicode(128), nullable=True)
3638 acl_level = Column('acl_level', Unicode(128), nullable=True)
3637
3639
3638 owner = relationship('User')
3640 owner = relationship('User')
3639
3641
3640 def __repr__(self):
3642 def __repr__(self):
3641 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3643 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3642
3644
3643 @classmethod
3645 @classmethod
3644 def get_or_404(cls, id_, pyramid_exc=False):
3646 def get_or_404(cls, id_, pyramid_exc=False):
3645
3647
3646 if pyramid_exc:
3648 if pyramid_exc:
3647 from pyramid.httpexceptions import HTTPNotFound
3649 from pyramid.httpexceptions import HTTPNotFound
3648 else:
3650 else:
3649 from webob.exc import HTTPNotFound
3651 from webob.exc import HTTPNotFound
3650
3652
3651 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3653 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3652 if not res:
3654 if not res:
3653 raise HTTPNotFound
3655 raise HTTPNotFound
3654 return res
3656 return res
3655
3657
3656 @classmethod
3658 @classmethod
3657 def get_by_access_id(cls, gist_access_id):
3659 def get_by_access_id(cls, gist_access_id):
3658 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3660 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3659
3661
3660 def gist_url(self):
3662 def gist_url(self):
3661 import rhodecode
3663 import rhodecode
3662 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3664 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3663 if alias_url:
3665 if alias_url:
3664 return alias_url.replace('{gistid}', self.gist_access_id)
3666 return alias_url.replace('{gistid}', self.gist_access_id)
3665
3667
3666 return url('gist', gist_id=self.gist_access_id, qualified=True)
3668 return url('gist', gist_id=self.gist_access_id, qualified=True)
3667
3669
3668 @classmethod
3670 @classmethod
3669 def base_path(cls):
3671 def base_path(cls):
3670 """
3672 """
3671 Returns base path when all gists are stored
3673 Returns base path when all gists are stored
3672
3674
3673 :param cls:
3675 :param cls:
3674 """
3676 """
3675 from rhodecode.model.gist import GIST_STORE_LOC
3677 from rhodecode.model.gist import GIST_STORE_LOC
3676 q = Session().query(RhodeCodeUi)\
3678 q = Session().query(RhodeCodeUi)\
3677 .filter(RhodeCodeUi.ui_key == URL_SEP)
3679 .filter(RhodeCodeUi.ui_key == URL_SEP)
3678 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3680 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3679 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3681 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3680
3682
3681 def get_api_data(self):
3683 def get_api_data(self):
3682 """
3684 """
3683 Common function for generating gist related data for API
3685 Common function for generating gist related data for API
3684 """
3686 """
3685 gist = self
3687 gist = self
3686 data = {
3688 data = {
3687 'gist_id': gist.gist_id,
3689 'gist_id': gist.gist_id,
3688 'type': gist.gist_type,
3690 'type': gist.gist_type,
3689 'access_id': gist.gist_access_id,
3691 'access_id': gist.gist_access_id,
3690 'description': gist.gist_description,
3692 'description': gist.gist_description,
3691 'url': gist.gist_url(),
3693 'url': gist.gist_url(),
3692 'expires': gist.gist_expires,
3694 'expires': gist.gist_expires,
3693 'created_on': gist.created_on,
3695 'created_on': gist.created_on,
3694 'modified_at': gist.modified_at,
3696 'modified_at': gist.modified_at,
3695 'content': None,
3697 'content': None,
3696 'acl_level': gist.acl_level,
3698 'acl_level': gist.acl_level,
3697 }
3699 }
3698 return data
3700 return data
3699
3701
3700 def __json__(self):
3702 def __json__(self):
3701 data = dict(
3703 data = dict(
3702 )
3704 )
3703 data.update(self.get_api_data())
3705 data.update(self.get_api_data())
3704 return data
3706 return data
3705 # SCM functions
3707 # SCM functions
3706
3708
3707 def scm_instance(self, **kwargs):
3709 def scm_instance(self, **kwargs):
3708 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3710 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3709 return get_vcs_instance(
3711 return get_vcs_instance(
3710 repo_path=safe_str(full_repo_path), create=False)
3712 repo_path=safe_str(full_repo_path), create=False)
3711
3713
3712
3714
3713 class ExternalIdentity(Base, BaseModel):
3715 class ExternalIdentity(Base, BaseModel):
3714 __tablename__ = 'external_identities'
3716 __tablename__ = 'external_identities'
3715 __table_args__ = (
3717 __table_args__ = (
3716 Index('local_user_id_idx', 'local_user_id'),
3718 Index('local_user_id_idx', 'local_user_id'),
3717 Index('external_id_idx', 'external_id'),
3719 Index('external_id_idx', 'external_id'),
3718 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3719 'mysql_charset': 'utf8'})
3721 'mysql_charset': 'utf8'})
3720
3722
3721 external_id = Column('external_id', Unicode(255), default=u'',
3723 external_id = Column('external_id', Unicode(255), default=u'',
3722 primary_key=True)
3724 primary_key=True)
3723 external_username = Column('external_username', Unicode(1024), default=u'')
3725 external_username = Column('external_username', Unicode(1024), default=u'')
3724 local_user_id = Column('local_user_id', Integer(),
3726 local_user_id = Column('local_user_id', Integer(),
3725 ForeignKey('users.user_id'), primary_key=True)
3727 ForeignKey('users.user_id'), primary_key=True)
3726 provider_name = Column('provider_name', Unicode(255), default=u'',
3728 provider_name = Column('provider_name', Unicode(255), default=u'',
3727 primary_key=True)
3729 primary_key=True)
3728 access_token = Column('access_token', String(1024), default=u'')
3730 access_token = Column('access_token', String(1024), default=u'')
3729 alt_token = Column('alt_token', String(1024), default=u'')
3731 alt_token = Column('alt_token', String(1024), default=u'')
3730 token_secret = Column('token_secret', String(1024), default=u'')
3732 token_secret = Column('token_secret', String(1024), default=u'')
3731
3733
3732 @classmethod
3734 @classmethod
3733 def by_external_id_and_provider(cls, external_id, provider_name,
3735 def by_external_id_and_provider(cls, external_id, provider_name,
3734 local_user_id=None):
3736 local_user_id=None):
3735 """
3737 """
3736 Returns ExternalIdentity instance based on search params
3738 Returns ExternalIdentity instance based on search params
3737
3739
3738 :param external_id:
3740 :param external_id:
3739 :param provider_name:
3741 :param provider_name:
3740 :return: ExternalIdentity
3742 :return: ExternalIdentity
3741 """
3743 """
3742 query = cls.query()
3744 query = cls.query()
3743 query = query.filter(cls.external_id == external_id)
3745 query = query.filter(cls.external_id == external_id)
3744 query = query.filter(cls.provider_name == provider_name)
3746 query = query.filter(cls.provider_name == provider_name)
3745 if local_user_id:
3747 if local_user_id:
3746 query = query.filter(cls.local_user_id == local_user_id)
3748 query = query.filter(cls.local_user_id == local_user_id)
3747 return query.first()
3749 return query.first()
3748
3750
3749 @classmethod
3751 @classmethod
3750 def user_by_external_id_and_provider(cls, external_id, provider_name):
3752 def user_by_external_id_and_provider(cls, external_id, provider_name):
3751 """
3753 """
3752 Returns User instance based on search params
3754 Returns User instance based on search params
3753
3755
3754 :param external_id:
3756 :param external_id:
3755 :param provider_name:
3757 :param provider_name:
3756 :return: User
3758 :return: User
3757 """
3759 """
3758 query = User.query()
3760 query = User.query()
3759 query = query.filter(cls.external_id == external_id)
3761 query = query.filter(cls.external_id == external_id)
3760 query = query.filter(cls.provider_name == provider_name)
3762 query = query.filter(cls.provider_name == provider_name)
3761 query = query.filter(User.user_id == cls.local_user_id)
3763 query = query.filter(User.user_id == cls.local_user_id)
3762 return query.first()
3764 return query.first()
3763
3765
3764 @classmethod
3766 @classmethod
3765 def by_local_user_id(cls, local_user_id):
3767 def by_local_user_id(cls, local_user_id):
3766 """
3768 """
3767 Returns all tokens for user
3769 Returns all tokens for user
3768
3770
3769 :param local_user_id:
3771 :param local_user_id:
3770 :return: ExternalIdentity
3772 :return: ExternalIdentity
3771 """
3773 """
3772 query = cls.query()
3774 query = cls.query()
3773 query = query.filter(cls.local_user_id == local_user_id)
3775 query = query.filter(cls.local_user_id == local_user_id)
3774 return query
3776 return query
3775
3777
3776
3778
3777 class Integration(Base, BaseModel):
3779 class Integration(Base, BaseModel):
3778 __tablename__ = 'integrations'
3780 __tablename__ = 'integrations'
3779 __table_args__ = (
3781 __table_args__ = (
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3782 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3783 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3782 )
3784 )
3783
3785
3784 integration_id = Column('integration_id', Integer(), primary_key=True)
3786 integration_id = Column('integration_id', Integer(), primary_key=True)
3785 integration_type = Column('integration_type', String(255))
3787 integration_type = Column('integration_type', String(255))
3786 enabled = Column('enabled', Boolean(), nullable=False)
3788 enabled = Column('enabled', Boolean(), nullable=False)
3787 name = Column('name', String(255), nullable=False)
3789 name = Column('name', String(255), nullable=False)
3788 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3790 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3789 default=False)
3791 default=False)
3790
3792
3791 settings = Column(
3793 settings = Column(
3792 'settings_json', MutationObj.as_mutable(
3794 'settings_json', MutationObj.as_mutable(
3793 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3795 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3794 repo_id = Column(
3796 repo_id = Column(
3795 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3797 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3796 nullable=True, unique=None, default=None)
3798 nullable=True, unique=None, default=None)
3797 repo = relationship('Repository', lazy='joined')
3799 repo = relationship('Repository', lazy='joined')
3798
3800
3799 repo_group_id = Column(
3801 repo_group_id = Column(
3800 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3802 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3801 nullable=True, unique=None, default=None)
3803 nullable=True, unique=None, default=None)
3802 repo_group = relationship('RepoGroup', lazy='joined')
3804 repo_group = relationship('RepoGroup', lazy='joined')
3803
3805
3804 @property
3806 @property
3805 def scope(self):
3807 def scope(self):
3806 if self.repo:
3808 if self.repo:
3807 return repr(self.repo)
3809 return repr(self.repo)
3808 if self.repo_group:
3810 if self.repo_group:
3809 if self.child_repos_only:
3811 if self.child_repos_only:
3810 return repr(self.repo_group) + ' (child repos only)'
3812 return repr(self.repo_group) + ' (child repos only)'
3811 else:
3813 else:
3812 return repr(self.repo_group) + ' (recursive)'
3814 return repr(self.repo_group) + ' (recursive)'
3813 if self.child_repos_only:
3815 if self.child_repos_only:
3814 return 'root_repos'
3816 return 'root_repos'
3815 return 'global'
3817 return 'global'
3816
3818
3817 def __repr__(self):
3819 def __repr__(self):
3818 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3820 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3819
3821
3820
3822
3821 class RepoReviewRuleUser(Base, BaseModel):
3823 class RepoReviewRuleUser(Base, BaseModel):
3822 __tablename__ = 'repo_review_rules_users'
3824 __tablename__ = 'repo_review_rules_users'
3823 __table_args__ = (
3825 __table_args__ = (
3824 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3825 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3827 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3826 )
3828 )
3827 repo_review_rule_user_id = Column(
3829 repo_review_rule_user_id = Column(
3828 'repo_review_rule_user_id', Integer(), primary_key=True)
3830 'repo_review_rule_user_id', Integer(), primary_key=True)
3829 repo_review_rule_id = Column("repo_review_rule_id",
3831 repo_review_rule_id = Column("repo_review_rule_id",
3830 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3832 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3831 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3833 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3832 nullable=False)
3834 nullable=False)
3833 user = relationship('User')
3835 user = relationship('User')
3834
3836
3835
3837
3836 class RepoReviewRuleUserGroup(Base, BaseModel):
3838 class RepoReviewRuleUserGroup(Base, BaseModel):
3837 __tablename__ = 'repo_review_rules_users_groups'
3839 __tablename__ = 'repo_review_rules_users_groups'
3838 __table_args__ = (
3840 __table_args__ = (
3839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3841 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3842 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3841 )
3843 )
3842 repo_review_rule_users_group_id = Column(
3844 repo_review_rule_users_group_id = Column(
3843 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3845 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3844 repo_review_rule_id = Column("repo_review_rule_id",
3846 repo_review_rule_id = Column("repo_review_rule_id",
3845 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3847 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3846 users_group_id = Column("users_group_id", Integer(),
3848 users_group_id = Column("users_group_id", Integer(),
3847 ForeignKey('users_groups.users_group_id'), nullable=False)
3849 ForeignKey('users_groups.users_group_id'), nullable=False)
3848 users_group = relationship('UserGroup')
3850 users_group = relationship('UserGroup')
3849
3851
3850
3852
3851 class RepoReviewRule(Base, BaseModel):
3853 class RepoReviewRule(Base, BaseModel):
3852 __tablename__ = 'repo_review_rules'
3854 __tablename__ = 'repo_review_rules'
3853 __table_args__ = (
3855 __table_args__ = (
3854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3856 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3857 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3856 )
3858 )
3857
3859
3858 repo_review_rule_id = Column(
3860 repo_review_rule_id = Column(
3859 'repo_review_rule_id', Integer(), primary_key=True)
3861 'repo_review_rule_id', Integer(), primary_key=True)
3860 repo_id = Column(
3862 repo_id = Column(
3861 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3863 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3862 repo = relationship('Repository', backref='review_rules')
3864 repo = relationship('Repository', backref='review_rules')
3863
3865
3864 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3866 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3865 default=u'*') # glob
3867 default=u'*') # glob
3866 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3868 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3867 default=u'*') # glob
3869 default=u'*') # glob
3868
3870
3869 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3871 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3870 nullable=False, default=False)
3872 nullable=False, default=False)
3871 rule_users = relationship('RepoReviewRuleUser')
3873 rule_users = relationship('RepoReviewRuleUser')
3872 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3874 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3873
3875
3874 @hybrid_property
3876 @hybrid_property
3875 def branch_pattern(self):
3877 def branch_pattern(self):
3876 return self._branch_pattern or '*'
3878 return self._branch_pattern or '*'
3877
3879
3878 def _validate_glob(self, value):
3880 def _validate_glob(self, value):
3879 re.compile('^' + glob2re(value) + '$')
3881 re.compile('^' + glob2re(value) + '$')
3880
3882
3881 @branch_pattern.setter
3883 @branch_pattern.setter
3882 def branch_pattern(self, value):
3884 def branch_pattern(self, value):
3883 self._validate_glob(value)
3885 self._validate_glob(value)
3884 self._branch_pattern = value or '*'
3886 self._branch_pattern = value or '*'
3885
3887
3886 @hybrid_property
3888 @hybrid_property
3887 def file_pattern(self):
3889 def file_pattern(self):
3888 return self._file_pattern or '*'
3890 return self._file_pattern or '*'
3889
3891
3890 @file_pattern.setter
3892 @file_pattern.setter
3891 def file_pattern(self, value):
3893 def file_pattern(self, value):
3892 self._validate_glob(value)
3894 self._validate_glob(value)
3893 self._file_pattern = value or '*'
3895 self._file_pattern = value or '*'
3894
3896
3895 def matches(self, branch, files_changed):
3897 def matches(self, branch, files_changed):
3896 """
3898 """
3897 Check if this review rule matches a branch/files in a pull request
3899 Check if this review rule matches a branch/files in a pull request
3898
3900
3899 :param branch: branch name for the commit
3901 :param branch: branch name for the commit
3900 :param files_changed: list of file paths changed in the pull request
3902 :param files_changed: list of file paths changed in the pull request
3901 """
3903 """
3902
3904
3903 branch = branch or ''
3905 branch = branch or ''
3904 files_changed = files_changed or []
3906 files_changed = files_changed or []
3905
3907
3906 branch_matches = True
3908 branch_matches = True
3907 if branch:
3909 if branch:
3908 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3910 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3909 branch_matches = bool(branch_regex.search(branch))
3911 branch_matches = bool(branch_regex.search(branch))
3910
3912
3911 files_matches = True
3913 files_matches = True
3912 if self.file_pattern != '*':
3914 if self.file_pattern != '*':
3913 files_matches = False
3915 files_matches = False
3914 file_regex = re.compile(glob2re(self.file_pattern))
3916 file_regex = re.compile(glob2re(self.file_pattern))
3915 for filename in files_changed:
3917 for filename in files_changed:
3916 if file_regex.search(filename):
3918 if file_regex.search(filename):
3917 files_matches = True
3919 files_matches = True
3918 break
3920 break
3919
3921
3920 return branch_matches and files_matches
3922 return branch_matches and files_matches
3921
3923
3922 @property
3924 @property
3923 def review_users(self):
3925 def review_users(self):
3924 """ Returns the users which this rule applies to """
3926 """ Returns the users which this rule applies to """
3925
3927
3926 users = set()
3928 users = set()
3927 users |= set([
3929 users |= set([
3928 rule_user.user for rule_user in self.rule_users
3930 rule_user.user for rule_user in self.rule_users
3929 if rule_user.user.active])
3931 if rule_user.user.active])
3930 users |= set(
3932 users |= set(
3931 member.user
3933 member.user
3932 for rule_user_group in self.rule_user_groups
3934 for rule_user_group in self.rule_user_groups
3933 for member in rule_user_group.users_group.members
3935 for member in rule_user_group.users_group.members
3934 if member.user.active
3936 if member.user.active
3935 )
3937 )
3936 return users
3938 return users
3937
3939
3938 def __repr__(self):
3940 def __repr__(self):
3939 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3941 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3940 self.repo_review_rule_id, self.repo)
3942 self.repo_review_rule_id, self.repo)
3941
3943
3942
3944
3943 class DbMigrateVersion(Base, BaseModel):
3945 class DbMigrateVersion(Base, BaseModel):
3944 __tablename__ = 'db_migrate_version'
3946 __tablename__ = 'db_migrate_version'
3945 __table_args__ = (
3947 __table_args__ = (
3946 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3947 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3948 )
3950 )
3949 repository_id = Column('repository_id', String(250), primary_key=True)
3951 repository_id = Column('repository_id', String(250), primary_key=True)
3950 repository_path = Column('repository_path', Text)
3952 repository_path = Column('repository_path', Text)
3951 version = Column('version', Integer)
3953 version = Column('version', Integer)
3952
3954
3953
3955
3954 class DbSession(Base, BaseModel):
3956 class DbSession(Base, BaseModel):
3955 __tablename__ = 'db_session'
3957 __tablename__ = 'db_session'
3956 __table_args__ = (
3958 __table_args__ = (
3957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3959 )
3961 )
3960
3962
3961 def __repr__(self):
3963 def __repr__(self):
3962 return '<DB:DbSession({})>'.format(self.id)
3964 return '<DB:DbSession({})>'.format(self.id)
3963
3965
3964 id = Column('id', Integer())
3966 id = Column('id', Integer())
3965 namespace = Column('namespace', String(255), primary_key=True)
3967 namespace = Column('namespace', String(255), primary_key=True)
3966 accessed = Column('accessed', DateTime, nullable=False)
3968 accessed = Column('accessed', DateTime, nullable=False)
3967 created = Column('created', DateTime, nullable=False)
3969 created = Column('created', DateTime, nullable=False)
3968 data = Column('data', PickleType, nullable=False)
3970 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now