##// END OF EJS Templates
auth-tokens: added scope into auth tokens (ApiKeys before refactoring)...
marcink -
r1475:9985d7e7 default
parent child Browse files
Show More
@@ -0,0 +1,65 b''
1 import logging
2
3 from sqlalchemy import *
4 from sqlalchemy.engine import reflection
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8
9 from rhodecode.model import meta
10 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
11
12 log = logging.getLogger(__name__)
13
14
15 def get_by_key(cls, key):
16 return cls.query().filter(cls.ui_key == key).scalar()
17
18
19 def upgrade(migrate_engine):
20 """
21 Upgrade operations go here.
22 Don't create your own engine; bind migrate_engine to your metadata
23 """
24 _reset_base(migrate_engine)
25 from rhodecode.lib.dbmigrate.schema import db_4_7_0_0
26
27 # make sure we re-create api-keys indexes
28
29 context = MigrationContext.configure(migrate_engine.connect())
30 op = Operations(context)
31
32 existing_indexes = _get_indexes_list(
33 migrate_engine, db_4_7_0_0.UserApiKeys.__tablename__)
34
35 names = [idx['name'] for idx in existing_indexes]
36
37 with op.batch_alter_table(db_4_7_0_0.UserApiKeys.__tablename__) as batch_op:
38 if 'uak_api_key_idx' not in names:
39 batch_op.create_index(
40 'uak_api_key_idx', ['api_key'])
41 if 'uak_api_key_expires_idx' not in names:
42 batch_op.create_index(
43 'uak_api_key_expires_idx', ['api_key', 'expires'])
44
45 # issue fixups
46 fixups(db_4_7_0_0, meta.Session)
47
48
49 def downgrade(migrate_engine):
50 meta = MetaData()
51 meta.bind = migrate_engine
52
53
54 def fixups(models, _SESSION):
55 pass
56
57
58 def _get_unique_constraint_list(migrate_engine, table_name):
59 inspector = reflection.Inspector.from_engine(migrate_engine)
60 return inspector.get_unique_constraints(table_name)
61
62
63 def _get_indexes_list(migrate_engine, table_name):
64 inspector = reflection.Inspector.from_engine(migrate_engine)
65 return inspector.get_indexes(table_name)
@@ -0,0 +1,44 b''
1 import logging
2
3 from sqlalchemy import *
4 from rhodecode.model import meta
5 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
6
7 log = logging.getLogger(__name__)
8
9
10 def get_by_key(cls, key):
11 return cls.query().filter(cls.ui_key == key).scalar()
12
13
14 def upgrade(migrate_engine):
15 """
16 Upgrade operations go here.
17 Don't create your own engine; bind migrate_engine to your metadata
18 """
19 _reset_base(migrate_engine)
20 from rhodecode.lib.dbmigrate.schema import db_4_7_0_0
21
22 auth_token_table = db_4_7_0_0.UserApiKeys.__table__
23
24 repo_id = Column(
25 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
26 nullable=True, unique=None, default=None)
27 repo_id.create(table=auth_token_table)
28
29 repo_group_id = Column(
30 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
31 nullable=True, unique=None, default=None)
32 repo_group_id.create(table=auth_token_table)
33
34 # issue fixups
35 fixups(db_4_7_0_0, meta.Session)
36
37
38 def downgrade(migrate_engine):
39 meta = MetaData()
40 meta.bind = migrate_engine
41
42
43 def fixups(models, _SESSION):
44 pass
@@ -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__ = 65 # defines current db version for migrations
54 __dbversion__ = 67 # 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,3911 +1,3922 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 webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
56 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
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.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
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)
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)
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)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
353 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
354 HOOK_PUSH = 'changegroup.push_logger'
354 HOOK_PUSH = 'changegroup.push_logger'
355
355
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # git part is currently hardcoded.
357 # git part is currently hardcoded.
358
358
359 # SVN PATTERNS
359 # SVN PATTERNS
360 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_TAG_ID = 'vcs_svn_tag'
361 SVN_TAG_ID = 'vcs_svn_tag'
362
362
363 ui_id = Column(
363 ui_id = Column(
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 primary_key=True)
365 primary_key=True)
366 ui_section = Column(
366 ui_section = Column(
367 "ui_section", String(255), nullable=True, unique=None, default=None)
367 "ui_section", String(255), nullable=True, unique=None, default=None)
368 ui_key = Column(
368 ui_key = Column(
369 "ui_key", String(255), nullable=True, unique=None, default=None)
369 "ui_key", String(255), nullable=True, unique=None, default=None)
370 ui_value = Column(
370 ui_value = Column(
371 "ui_value", String(255), nullable=True, unique=None, default=None)
371 "ui_value", String(255), nullable=True, unique=None, default=None)
372 ui_active = Column(
372 ui_active = Column(
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374
374
375 def __repr__(self):
375 def __repr__(self):
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 self.ui_key, self.ui_value)
377 self.ui_key, self.ui_value)
378
378
379
379
380 class RepoRhodeCodeSetting(Base, BaseModel):
380 class RepoRhodeCodeSetting(Base, BaseModel):
381 __tablename__ = 'repo_rhodecode_settings'
381 __tablename__ = 'repo_rhodecode_settings'
382 __table_args__ = (
382 __table_args__ = (
383 UniqueConstraint(
383 UniqueConstraint(
384 'app_settings_name', 'repository_id',
384 'app_settings_name', 'repository_id',
385 name='uq_repo_rhodecode_setting_name_repo_id'),
385 name='uq_repo_rhodecode_setting_name_repo_id'),
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 )
388 )
389
389
390 repository_id = Column(
390 repository_id = Column(
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 nullable=False)
392 nullable=False)
393 app_settings_id = Column(
393 app_settings_id = Column(
394 "app_settings_id", Integer(), nullable=False, unique=True,
394 "app_settings_id", Integer(), nullable=False, unique=True,
395 default=None, primary_key=True)
395 default=None, primary_key=True)
396 app_settings_name = Column(
396 app_settings_name = Column(
397 "app_settings_name", String(255), nullable=True, unique=None,
397 "app_settings_name", String(255), nullable=True, unique=None,
398 default=None)
398 default=None)
399 _app_settings_value = Column(
399 _app_settings_value = Column(
400 "app_settings_value", String(4096), nullable=True, unique=None,
400 "app_settings_value", String(4096), nullable=True, unique=None,
401 default=None)
401 default=None)
402 _app_settings_type = Column(
402 _app_settings_type = Column(
403 "app_settings_type", String(255), nullable=True, unique=None,
403 "app_settings_type", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405
405
406 repository = relationship('Repository')
406 repository = relationship('Repository')
407
407
408 def __init__(self, repository_id, key='', val='', type='unicode'):
408 def __init__(self, repository_id, key='', val='', type='unicode'):
409 self.repository_id = repository_id
409 self.repository_id = repository_id
410 self.app_settings_name = key
410 self.app_settings_name = key
411 self.app_settings_type = type
411 self.app_settings_type = type
412 self.app_settings_value = val
412 self.app_settings_value = val
413
413
414 @validates('_app_settings_value')
414 @validates('_app_settings_value')
415 def validate_settings_value(self, key, val):
415 def validate_settings_value(self, key, val):
416 assert type(val) == unicode
416 assert type(val) == unicode
417 return val
417 return val
418
418
419 @hybrid_property
419 @hybrid_property
420 def app_settings_value(self):
420 def app_settings_value(self):
421 v = self._app_settings_value
421 v = self._app_settings_value
422 type_ = self.app_settings_type
422 type_ = self.app_settings_type
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 return converter(v)
425 return converter(v)
426
426
427 @app_settings_value.setter
427 @app_settings_value.setter
428 def app_settings_value(self, val):
428 def app_settings_value(self, val):
429 """
429 """
430 Setter that will always make sure we use unicode in app_settings_value
430 Setter that will always make sure we use unicode in app_settings_value
431
431
432 :param val:
432 :param val:
433 """
433 """
434 self._app_settings_value = safe_unicode(val)
434 self._app_settings_value = safe_unicode(val)
435
435
436 @hybrid_property
436 @hybrid_property
437 def app_settings_type(self):
437 def app_settings_type(self):
438 return self._app_settings_type
438 return self._app_settings_type
439
439
440 @app_settings_type.setter
440 @app_settings_type.setter
441 def app_settings_type(self, val):
441 def app_settings_type(self, val):
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 if val not in SETTINGS_TYPES:
443 if val not in SETTINGS_TYPES:
444 raise Exception('type must be one of %s got %s'
444 raise Exception('type must be one of %s got %s'
445 % (SETTINGS_TYPES.keys(), val))
445 % (SETTINGS_TYPES.keys(), val))
446 self._app_settings_type = val
446 self._app_settings_type = val
447
447
448 def __unicode__(self):
448 def __unicode__(self):
449 return u"<%s('%s:%s:%s[%s]')>" % (
449 return u"<%s('%s:%s:%s[%s]')>" % (
450 self.__class__.__name__, self.repository.repo_name,
450 self.__class__.__name__, self.repository.repo_name,
451 self.app_settings_name, self.app_settings_value,
451 self.app_settings_name, self.app_settings_value,
452 self.app_settings_type
452 self.app_settings_type
453 )
453 )
454
454
455
455
456 class RepoRhodeCodeUi(Base, BaseModel):
456 class RepoRhodeCodeUi(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_ui'
457 __tablename__ = 'repo_rhodecode_ui'
458 __table_args__ = (
458 __table_args__ = (
459 UniqueConstraint(
459 UniqueConstraint(
460 'repository_id', 'ui_section', 'ui_key',
460 'repository_id', 'ui_section', 'ui_key',
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 )
464 )
465
465
466 repository_id = Column(
466 repository_id = Column(
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 nullable=False)
468 nullable=False)
469 ui_id = Column(
469 ui_id = Column(
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 primary_key=True)
471 primary_key=True)
472 ui_section = Column(
472 ui_section = Column(
473 "ui_section", String(255), nullable=True, unique=None, default=None)
473 "ui_section", String(255), nullable=True, unique=None, default=None)
474 ui_key = Column(
474 ui_key = Column(
475 "ui_key", String(255), nullable=True, unique=None, default=None)
475 "ui_key", String(255), nullable=True, unique=None, default=None)
476 ui_value = Column(
476 ui_value = Column(
477 "ui_value", String(255), nullable=True, unique=None, default=None)
477 "ui_value", String(255), nullable=True, unique=None, default=None)
478 ui_active = Column(
478 ui_active = Column(
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480
480
481 repository = relationship('Repository')
481 repository = relationship('Repository')
482
482
483 def __repr__(self):
483 def __repr__(self):
484 return '<%s[%s:%s]%s=>%s]>' % (
484 return '<%s[%s:%s]%s=>%s]>' % (
485 self.__class__.__name__, self.repository.repo_name,
485 self.__class__.__name__, self.repository.repo_name,
486 self.ui_section, self.ui_key, self.ui_value)
486 self.ui_section, self.ui_key, self.ui_value)
487
487
488
488
489 class User(Base, BaseModel):
489 class User(Base, BaseModel):
490 __tablename__ = 'users'
490 __tablename__ = 'users'
491 __table_args__ = (
491 __table_args__ = (
492 UniqueConstraint('username'), UniqueConstraint('email'),
492 UniqueConstraint('username'), UniqueConstraint('email'),
493 Index('u_username_idx', 'username'),
493 Index('u_username_idx', 'username'),
494 Index('u_email_idx', 'email'),
494 Index('u_email_idx', 'email'),
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 )
497 )
498 DEFAULT_USER = 'default'
498 DEFAULT_USER = 'default'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501
501
502 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
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)
503 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 password = Column("password", 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)
505 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 lastname = Column("lastname", 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)
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)
510 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517
517
518 user_log = relationship('UserLog')
518 user_log = relationship('UserLog')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520
520
521 repositories = relationship('Repository')
521 repositories = relationship('Repository')
522 repository_groups = relationship('RepoGroup')
522 repository_groups = relationship('RepoGroup')
523 user_groups = relationship('UserGroup')
523 user_groups = relationship('UserGroup')
524
524
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527
527
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531
531
532 group_member = relationship('UserGroupMember', cascade='all')
532 group_member = relationship('UserGroupMember', cascade='all')
533
533
534 notifications = relationship('UserNotification', cascade='all')
534 notifications = relationship('UserNotification', cascade='all')
535 # notifications assigned to this user
535 # notifications assigned to this user
536 user_created_notifications = relationship('Notification', cascade='all')
536 user_created_notifications = relationship('Notification', cascade='all')
537 # comments created by this user
537 # comments created by this user
538 user_comments = relationship('ChangesetComment', cascade='all')
538 user_comments = relationship('ChangesetComment', cascade='all')
539 # user profile extra info
539 # user profile extra info
540 user_emails = relationship('UserEmailMap', cascade='all')
540 user_emails = relationship('UserEmailMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 # gists
543 # gists
544 user_gists = relationship('Gist', cascade='all')
544 user_gists = relationship('Gist', cascade='all')
545 # user pull requests
545 # user pull requests
546 user_pull_requests = relationship('PullRequest', cascade='all')
546 user_pull_requests = relationship('PullRequest', cascade='all')
547 # external identities
547 # external identities
548 extenal_identities = relationship(
548 extenal_identities = relationship(
549 'ExternalIdentity',
549 'ExternalIdentity',
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 cascade='all')
551 cascade='all')
552
552
553 def __unicode__(self):
553 def __unicode__(self):
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 self.user_id, self.username)
555 self.user_id, self.username)
556
556
557 @hybrid_property
557 @hybrid_property
558 def email(self):
558 def email(self):
559 return self._email
559 return self._email
560
560
561 @email.setter
561 @email.setter
562 def email(self, val):
562 def email(self, val):
563 self._email = val.lower() if val else None
563 self._email = val.lower() if val else None
564
564
565 @property
565 @property
566 def firstname(self):
566 def firstname(self):
567 # alias for future
567 # alias for future
568 return self.name
568 return self.name
569
569
570 @property
570 @property
571 def emails(self):
571 def emails(self):
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 return [self.email] + [x.email for x in other]
573 return [self.email] + [x.email for x in other]
574
574
575 @property
575 @property
576 def auth_tokens(self):
576 def auth_tokens(self):
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578
578
579 @property
579 @property
580 def extra_auth_tokens(self):
580 def extra_auth_tokens(self):
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582
582
583 @property
583 @property
584 def feed_token(self):
584 def feed_token(self):
585 return self.get_feed_token()
585 return self.get_feed_token()
586
586
587 def get_feed_token(self):
587 def get_feed_token(self):
588 feed_tokens = UserApiKeys.query()\
588 feed_tokens = UserApiKeys.query()\
589 .filter(UserApiKeys.user == self)\
589 .filter(UserApiKeys.user == self)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
590 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
591 .all()
591 .all()
592 if feed_tokens:
592 if feed_tokens:
593 return feed_tokens[0].api_key
593 return feed_tokens[0].api_key
594 return 'NO_FEED_TOKEN_AVAILABLE'
594 return 'NO_FEED_TOKEN_AVAILABLE'
595
595
596 @classmethod
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
601 if role:
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
604 return tokens.all()
605
605
606 def authenticate_by_token(self, auth_token, roles=None,
606 def authenticate_by_token(self, auth_token, roles=None,
607 include_builtin_token=False):
607 include_builtin_token=False):
608 from rhodecode.lib import auth
608 from rhodecode.lib import auth
609
609
610 log.debug('Trying to authenticate user: %s via auth-token, '
610 log.debug('Trying to authenticate user: %s via auth-token, '
611 'and roles: %s', self, roles)
611 'and roles: %s', self, roles)
612
612
613 if not auth_token:
613 if not auth_token:
614 return False
614 return False
615
615
616 crypto_backend = auth.crypto_backend()
616 crypto_backend = auth.crypto_backend()
617
617
618 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
618 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
619 tokens_q = UserApiKeys.query()\
619 tokens_q = UserApiKeys.query()\
620 .filter(UserApiKeys.user_id == self.user_id)\
620 .filter(UserApiKeys.user_id == self.user_id)\
621 .filter(or_(UserApiKeys.expires == -1,
621 .filter(or_(UserApiKeys.expires == -1,
622 UserApiKeys.expires >= time.time()))
622 UserApiKeys.expires >= time.time()))
623
623
624 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
624 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
625
625
626 maybe_builtin = []
626 maybe_builtin = []
627 if include_builtin_token:
627 if include_builtin_token:
628 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
628 maybe_builtin = [AttributeDict({'api_key': self.api_key})]
629
629
630 plain_tokens = []
630 plain_tokens = []
631 hash_tokens = []
631 hash_tokens = []
632
632
633 for token in tokens_q.all() + maybe_builtin:
633 for token in tokens_q.all() + maybe_builtin:
634 if token.api_key.startswith(crypto_backend.ENC_PREF):
634 if token.api_key.startswith(crypto_backend.ENC_PREF):
635 hash_tokens.append(token.api_key)
635 hash_tokens.append(token.api_key)
636 else:
636 else:
637 plain_tokens.append(token.api_key)
637 plain_tokens.append(token.api_key)
638
638
639 is_plain_match = auth_token in plain_tokens
639 is_plain_match = auth_token in plain_tokens
640 if is_plain_match:
640 if is_plain_match:
641 return True
641 return True
642
642
643 for hashed in hash_tokens:
643 for hashed in hash_tokens:
644 # marcink: this is expensive to calculate, but the most secure
644 # marcink: this is expensive to calculate, but the most secure
645 match = crypto_backend.hash_check(auth_token, hashed)
645 match = crypto_backend.hash_check(auth_token, hashed)
646 if match:
646 if match:
647 return True
647 return True
648
648
649 return False
649 return False
650
650
651 @property
651 @property
652 def builtin_token_roles(self):
652 def builtin_token_roles(self):
653 roles = [
653 roles = [
654 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
654 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
655 ]
655 ]
656 return map(UserApiKeys._get_role_name, roles)
656 return map(UserApiKeys._get_role_name, roles)
657
657
658 @property
658 @property
659 def ip_addresses(self):
659 def ip_addresses(self):
660 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
660 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
661 return [x.ip_addr for x in ret]
661 return [x.ip_addr for x in ret]
662
662
663 @property
663 @property
664 def username_and_name(self):
664 def username_and_name(self):
665 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
665 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
666
666
667 @property
667 @property
668 def username_or_name_or_email(self):
668 def username_or_name_or_email(self):
669 full_name = self.full_name if self.full_name is not ' ' else None
669 full_name = self.full_name if self.full_name is not ' ' else None
670 return self.username or full_name or self.email
670 return self.username or full_name or self.email
671
671
672 @property
672 @property
673 def full_name(self):
673 def full_name(self):
674 return '%s %s' % (self.firstname, self.lastname)
674 return '%s %s' % (self.firstname, self.lastname)
675
675
676 @property
676 @property
677 def full_name_or_username(self):
677 def full_name_or_username(self):
678 return ('%s %s' % (self.firstname, self.lastname)
678 return ('%s %s' % (self.firstname, self.lastname)
679 if (self.firstname and self.lastname) else self.username)
679 if (self.firstname and self.lastname) else self.username)
680
680
681 @property
681 @property
682 def full_contact(self):
682 def full_contact(self):
683 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
683 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
684
684
685 @property
685 @property
686 def short_contact(self):
686 def short_contact(self):
687 return '%s %s' % (self.firstname, self.lastname)
687 return '%s %s' % (self.firstname, self.lastname)
688
688
689 @property
689 @property
690 def is_admin(self):
690 def is_admin(self):
691 return self.admin
691 return self.admin
692
692
693 @property
693 @property
694 def AuthUser(self):
694 def AuthUser(self):
695 """
695 """
696 Returns instance of AuthUser for this user
696 Returns instance of AuthUser for this user
697 """
697 """
698 from rhodecode.lib.auth import AuthUser
698 from rhodecode.lib.auth import AuthUser
699 return AuthUser(user_id=self.user_id, api_key=self.api_key,
699 return AuthUser(user_id=self.user_id, api_key=self.api_key,
700 username=self.username)
700 username=self.username)
701
701
702 @hybrid_property
702 @hybrid_property
703 def user_data(self):
703 def user_data(self):
704 if not self._user_data:
704 if not self._user_data:
705 return {}
705 return {}
706
706
707 try:
707 try:
708 return json.loads(self._user_data)
708 return json.loads(self._user_data)
709 except TypeError:
709 except TypeError:
710 return {}
710 return {}
711
711
712 @user_data.setter
712 @user_data.setter
713 def user_data(self, val):
713 def user_data(self, val):
714 if not isinstance(val, dict):
714 if not isinstance(val, dict):
715 raise Exception('user_data must be dict, got %s' % type(val))
715 raise Exception('user_data must be dict, got %s' % type(val))
716 try:
716 try:
717 self._user_data = json.dumps(val)
717 self._user_data = json.dumps(val)
718 except Exception:
718 except Exception:
719 log.error(traceback.format_exc())
719 log.error(traceback.format_exc())
720
720
721 @classmethod
721 @classmethod
722 def get_by_username(cls, username, case_insensitive=False,
722 def get_by_username(cls, username, case_insensitive=False,
723 cache=False, identity_cache=False):
723 cache=False, identity_cache=False):
724 session = Session()
724 session = Session()
725
725
726 if case_insensitive:
726 if case_insensitive:
727 q = cls.query().filter(
727 q = cls.query().filter(
728 func.lower(cls.username) == func.lower(username))
728 func.lower(cls.username) == func.lower(username))
729 else:
729 else:
730 q = cls.query().filter(cls.username == username)
730 q = cls.query().filter(cls.username == username)
731
731
732 if cache:
732 if cache:
733 if identity_cache:
733 if identity_cache:
734 val = cls.identity_cache(session, 'username', username)
734 val = cls.identity_cache(session, 'username', username)
735 if val:
735 if val:
736 return val
736 return val
737 else:
737 else:
738 q = q.options(
738 q = q.options(
739 FromCache("sql_cache_short",
739 FromCache("sql_cache_short",
740 "get_user_by_name_%s" % _hash_key(username)))
740 "get_user_by_name_%s" % _hash_key(username)))
741
741
742 return q.scalar()
742 return q.scalar()
743
743
744 @classmethod
744 @classmethod
745 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
745 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
746 q = cls.query().filter(cls.api_key == auth_token)
746 q = cls.query().filter(cls.api_key == auth_token)
747
747
748 if cache:
748 if cache:
749 q = q.options(FromCache("sql_cache_short",
749 q = q.options(FromCache("sql_cache_short",
750 "get_auth_token_%s" % auth_token))
750 "get_auth_token_%s" % auth_token))
751 res = q.scalar()
751 res = q.scalar()
752
752
753 if fallback and not res:
753 if fallback and not res:
754 #fallback to additional keys
754 #fallback to additional keys
755 _res = UserApiKeys.query()\
755 _res = UserApiKeys.query()\
756 .filter(UserApiKeys.api_key == auth_token)\
756 .filter(UserApiKeys.api_key == auth_token)\
757 .filter(or_(UserApiKeys.expires == -1,
757 .filter(or_(UserApiKeys.expires == -1,
758 UserApiKeys.expires >= time.time()))\
758 UserApiKeys.expires >= time.time()))\
759 .first()
759 .first()
760 if _res:
760 if _res:
761 res = _res.user
761 res = _res.user
762 return res
762 return res
763
763
764 @classmethod
764 @classmethod
765 def get_by_email(cls, email, case_insensitive=False, cache=False):
765 def get_by_email(cls, email, case_insensitive=False, cache=False):
766
766
767 if case_insensitive:
767 if case_insensitive:
768 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
768 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
769
769
770 else:
770 else:
771 q = cls.query().filter(cls.email == email)
771 q = cls.query().filter(cls.email == email)
772
772
773 if cache:
773 if cache:
774 q = q.options(FromCache("sql_cache_short",
774 q = q.options(FromCache("sql_cache_short",
775 "get_email_key_%s" % _hash_key(email)))
775 "get_email_key_%s" % _hash_key(email)))
776
776
777 ret = q.scalar()
777 ret = q.scalar()
778 if ret is None:
778 if ret is None:
779 q = UserEmailMap.query()
779 q = UserEmailMap.query()
780 # try fetching in alternate email map
780 # try fetching in alternate email map
781 if case_insensitive:
781 if case_insensitive:
782 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
782 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
783 else:
783 else:
784 q = q.filter(UserEmailMap.email == email)
784 q = q.filter(UserEmailMap.email == email)
785 q = q.options(joinedload(UserEmailMap.user))
785 q = q.options(joinedload(UserEmailMap.user))
786 if cache:
786 if cache:
787 q = q.options(FromCache("sql_cache_short",
787 q = q.options(FromCache("sql_cache_short",
788 "get_email_map_key_%s" % email))
788 "get_email_map_key_%s" % email))
789 ret = getattr(q.scalar(), 'user', None)
789 ret = getattr(q.scalar(), 'user', None)
790
790
791 return ret
791 return ret
792
792
793 @classmethod
793 @classmethod
794 def get_from_cs_author(cls, author):
794 def get_from_cs_author(cls, author):
795 """
795 """
796 Tries to get User objects out of commit author string
796 Tries to get User objects out of commit author string
797
797
798 :param author:
798 :param author:
799 """
799 """
800 from rhodecode.lib.helpers import email, author_name
800 from rhodecode.lib.helpers import email, author_name
801 # Valid email in the attribute passed, see if they're in the system
801 # Valid email in the attribute passed, see if they're in the system
802 _email = email(author)
802 _email = email(author)
803 if _email:
803 if _email:
804 user = cls.get_by_email(_email, case_insensitive=True)
804 user = cls.get_by_email(_email, case_insensitive=True)
805 if user:
805 if user:
806 return user
806 return user
807 # Maybe we can match by username?
807 # Maybe we can match by username?
808 _author = author_name(author)
808 _author = author_name(author)
809 user = cls.get_by_username(_author, case_insensitive=True)
809 user = cls.get_by_username(_author, case_insensitive=True)
810 if user:
810 if user:
811 return user
811 return user
812
812
813 def update_userdata(self, **kwargs):
813 def update_userdata(self, **kwargs):
814 usr = self
814 usr = self
815 old = usr.user_data
815 old = usr.user_data
816 old.update(**kwargs)
816 old.update(**kwargs)
817 usr.user_data = old
817 usr.user_data = old
818 Session().add(usr)
818 Session().add(usr)
819 log.debug('updated userdata with ', kwargs)
819 log.debug('updated userdata with ', kwargs)
820
820
821 def update_lastlogin(self):
821 def update_lastlogin(self):
822 """Update user lastlogin"""
822 """Update user lastlogin"""
823 self.last_login = datetime.datetime.now()
823 self.last_login = datetime.datetime.now()
824 Session().add(self)
824 Session().add(self)
825 log.debug('updated user %s lastlogin', self.username)
825 log.debug('updated user %s lastlogin', self.username)
826
826
827 def update_lastactivity(self):
827 def update_lastactivity(self):
828 """Update user lastactivity"""
828 """Update user lastactivity"""
829 usr = self
829 usr = self
830 old = usr.user_data
830 old = usr.user_data
831 old.update({'last_activity': time.time()})
831 old.update({'last_activity': time.time()})
832 usr.user_data = old
832 usr.user_data = old
833 Session().add(usr)
833 Session().add(usr)
834 log.debug('updated user %s lastactivity', usr.username)
834 log.debug('updated user %s lastactivity', usr.username)
835
835
836 def update_password(self, new_password, change_api_key=False):
836 def update_password(self, new_password, change_api_key=False):
837 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
837 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
838
838
839 self.password = get_crypt_password(new_password)
839 self.password = get_crypt_password(new_password)
840 if change_api_key:
840 if change_api_key:
841 self.api_key = generate_auth_token(self.username)
841 self.api_key = generate_auth_token(self.username)
842 Session().add(self)
842 Session().add(self)
843
843
844 @classmethod
844 @classmethod
845 def get_first_super_admin(cls):
845 def get_first_super_admin(cls):
846 user = User.query().filter(User.admin == true()).first()
846 user = User.query().filter(User.admin == true()).first()
847 if user is None:
847 if user is None:
848 raise Exception('FATAL: Missing administrative account!')
848 raise Exception('FATAL: Missing administrative account!')
849 return user
849 return user
850
850
851 @classmethod
851 @classmethod
852 def get_all_super_admins(cls):
852 def get_all_super_admins(cls):
853 """
853 """
854 Returns all admin accounts sorted by username
854 Returns all admin accounts sorted by username
855 """
855 """
856 return User.query().filter(User.admin == true())\
856 return User.query().filter(User.admin == true())\
857 .order_by(User.username.asc()).all()
857 .order_by(User.username.asc()).all()
858
858
859 @classmethod
859 @classmethod
860 def get_default_user(cls, cache=False):
860 def get_default_user(cls, cache=False):
861 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
861 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
862 if user is None:
862 if user is None:
863 raise Exception('FATAL: Missing default account!')
863 raise Exception('FATAL: Missing default account!')
864 return user
864 return user
865
865
866 def _get_default_perms(self, user, suffix=''):
866 def _get_default_perms(self, user, suffix=''):
867 from rhodecode.model.permission import PermissionModel
867 from rhodecode.model.permission import PermissionModel
868 return PermissionModel().get_default_perms(user.user_perms, suffix)
868 return PermissionModel().get_default_perms(user.user_perms, suffix)
869
869
870 def get_default_perms(self, suffix=''):
870 def get_default_perms(self, suffix=''):
871 return self._get_default_perms(self, suffix)
871 return self._get_default_perms(self, suffix)
872
872
873 def get_api_data(self, include_secrets=False, details='full'):
873 def get_api_data(self, include_secrets=False, details='full'):
874 """
874 """
875 Common function for generating user related data for API
875 Common function for generating user related data for API
876
876
877 :param include_secrets: By default secrets in the API data will be replaced
877 :param include_secrets: By default secrets in the API data will be replaced
878 by a placeholder value to prevent exposing this data by accident. In case
878 by a placeholder value to prevent exposing this data by accident. In case
879 this data shall be exposed, set this flag to ``True``.
879 this data shall be exposed, set this flag to ``True``.
880
880
881 :param details: details can be 'basic|full' basic gives only a subset of
881 :param details: details can be 'basic|full' basic gives only a subset of
882 the available user information that includes user_id, name and emails.
882 the available user information that includes user_id, name and emails.
883 """
883 """
884 user = self
884 user = self
885 user_data = self.user_data
885 user_data = self.user_data
886 data = {
886 data = {
887 'user_id': user.user_id,
887 'user_id': user.user_id,
888 'username': user.username,
888 'username': user.username,
889 'firstname': user.name,
889 'firstname': user.name,
890 'lastname': user.lastname,
890 'lastname': user.lastname,
891 'email': user.email,
891 'email': user.email,
892 'emails': user.emails,
892 'emails': user.emails,
893 }
893 }
894 if details == 'basic':
894 if details == 'basic':
895 return data
895 return data
896
896
897 api_key_length = 40
897 api_key_length = 40
898 api_key_replacement = '*' * api_key_length
898 api_key_replacement = '*' * api_key_length
899
899
900 extras = {
900 extras = {
901 'api_key': api_key_replacement,
901 'api_key': api_key_replacement,
902 'api_keys': [api_key_replacement],
902 'api_keys': [api_key_replacement],
903 'active': user.active,
903 'active': user.active,
904 'admin': user.admin,
904 'admin': user.admin,
905 'extern_type': user.extern_type,
905 'extern_type': user.extern_type,
906 'extern_name': user.extern_name,
906 'extern_name': user.extern_name,
907 'last_login': user.last_login,
907 'last_login': user.last_login,
908 'ip_addresses': user.ip_addresses,
908 'ip_addresses': user.ip_addresses,
909 'language': user_data.get('language')
909 'language': user_data.get('language')
910 }
910 }
911 data.update(extras)
911 data.update(extras)
912
912
913 if include_secrets:
913 if include_secrets:
914 data['api_key'] = user.api_key
914 data['api_key'] = user.api_key
915 data['api_keys'] = user.auth_tokens
915 data['api_keys'] = user.auth_tokens
916 return data
916 return data
917
917
918 def __json__(self):
918 def __json__(self):
919 data = {
919 data = {
920 'full_name': self.full_name,
920 'full_name': self.full_name,
921 'full_name_or_username': self.full_name_or_username,
921 'full_name_or_username': self.full_name_or_username,
922 'short_contact': self.short_contact,
922 'short_contact': self.short_contact,
923 'full_contact': self.full_contact,
923 'full_contact': self.full_contact,
924 }
924 }
925 data.update(self.get_api_data())
925 data.update(self.get_api_data())
926 return data
926 return data
927
927
928
928
929 class UserApiKeys(Base, BaseModel):
929 class UserApiKeys(Base, BaseModel):
930 __tablename__ = 'user_api_keys'
930 __tablename__ = 'user_api_keys'
931 __table_args__ = (
931 __table_args__ = (
932 Index('uak_api_key_idx', 'api_key'),
932 Index('uak_api_key_idx', 'api_key'),
933 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
933 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
934 UniqueConstraint('api_key'),
934 UniqueConstraint('api_key'),
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
935 {'extend_existing': True, 'mysql_engine': 'InnoDB',
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
936 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
937 )
937 )
938 __mapper_args__ = {}
938 __mapper_args__ = {}
939
939
940 # ApiKey role
940 # ApiKey role
941 ROLE_ALL = 'token_role_all'
941 ROLE_ALL = 'token_role_all'
942 ROLE_HTTP = 'token_role_http'
942 ROLE_HTTP = 'token_role_http'
943 ROLE_VCS = 'token_role_vcs'
943 ROLE_VCS = 'token_role_vcs'
944 ROLE_API = 'token_role_api'
944 ROLE_API = 'token_role_api'
945 ROLE_FEED = 'token_role_feed'
945 ROLE_FEED = 'token_role_feed'
946 ROLE_PASSWORD_RESET = 'token_password_reset'
946 ROLE_PASSWORD_RESET = 'token_password_reset'
947
947
948 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
948 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
949
949
950 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
951 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
951 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
952 api_key = Column("api_key", String(255), nullable=False, unique=True)
952 api_key = Column("api_key", String(255), nullable=False, unique=True)
953 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
953 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
954 expires = Column('expires', Float(53), nullable=False)
954 expires = Column('expires', Float(53), nullable=False)
955 role = Column('role', String(255), nullable=True)
955 role = Column('role', String(255), nullable=True)
956 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
956 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
957
957
958 # scope columns
959 repo_id = Column(
960 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
961 nullable=True, unique=None, default=None)
962 repo = relationship('Repository', lazy='joined')
963
964 repo_group_id = Column(
965 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
966 nullable=True, unique=None, default=None)
967 repo_group = relationship('RepoGroup', lazy='joined')
968
958 user = relationship('User', lazy='joined')
969 user = relationship('User', lazy='joined')
959
970
960 @classmethod
971 @classmethod
961 def _get_role_name(cls, role):
972 def _get_role_name(cls, role):
962 return {
973 return {
963 cls.ROLE_ALL: _('all'),
974 cls.ROLE_ALL: _('all'),
964 cls.ROLE_HTTP: _('http/web interface'),
975 cls.ROLE_HTTP: _('http/web interface'),
965 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
976 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
966 cls.ROLE_API: _('api calls'),
977 cls.ROLE_API: _('api calls'),
967 cls.ROLE_FEED: _('feed access'),
978 cls.ROLE_FEED: _('feed access'),
968 }.get(role, role)
979 }.get(role, role)
969
980
970 @property
981 @property
971 def expired(self):
982 def expired(self):
972 if self.expires == -1:
983 if self.expires == -1:
973 return False
984 return False
974 return time.time() > self.expires
985 return time.time() > self.expires
975
986
976 @property
987 @property
977 def role_humanized(self):
988 def role_humanized(self):
978 return self._get_role_name(self.role)
989 return self._get_role_name(self.role)
979
990
980
991
981 class UserEmailMap(Base, BaseModel):
992 class UserEmailMap(Base, BaseModel):
982 __tablename__ = 'user_email_map'
993 __tablename__ = 'user_email_map'
983 __table_args__ = (
994 __table_args__ = (
984 Index('uem_email_idx', 'email'),
995 Index('uem_email_idx', 'email'),
985 UniqueConstraint('email'),
996 UniqueConstraint('email'),
986 {'extend_existing': True, 'mysql_engine': 'InnoDB',
997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
987 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
988 )
999 )
989 __mapper_args__ = {}
1000 __mapper_args__ = {}
990
1001
991 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1002 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
992 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1003 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
993 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1004 _email = Column("email", String(255), nullable=True, unique=False, default=None)
994 user = relationship('User', lazy='joined')
1005 user = relationship('User', lazy='joined')
995
1006
996 @validates('_email')
1007 @validates('_email')
997 def validate_email(self, key, email):
1008 def validate_email(self, key, email):
998 # check if this email is not main one
1009 # check if this email is not main one
999 main_email = Session().query(User).filter(User.email == email).scalar()
1010 main_email = Session().query(User).filter(User.email == email).scalar()
1000 if main_email is not None:
1011 if main_email is not None:
1001 raise AttributeError('email %s is present is user table' % email)
1012 raise AttributeError('email %s is present is user table' % email)
1002 return email
1013 return email
1003
1014
1004 @hybrid_property
1015 @hybrid_property
1005 def email(self):
1016 def email(self):
1006 return self._email
1017 return self._email
1007
1018
1008 @email.setter
1019 @email.setter
1009 def email(self, val):
1020 def email(self, val):
1010 self._email = val.lower() if val else None
1021 self._email = val.lower() if val else None
1011
1022
1012
1023
1013 class UserIpMap(Base, BaseModel):
1024 class UserIpMap(Base, BaseModel):
1014 __tablename__ = 'user_ip_map'
1025 __tablename__ = 'user_ip_map'
1015 __table_args__ = (
1026 __table_args__ = (
1016 UniqueConstraint('user_id', 'ip_addr'),
1027 UniqueConstraint('user_id', 'ip_addr'),
1017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1018 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1029 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1019 )
1030 )
1020 __mapper_args__ = {}
1031 __mapper_args__ = {}
1021
1032
1022 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1033 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1023 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1024 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1035 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1025 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1036 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1026 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1037 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1027 user = relationship('User', lazy='joined')
1038 user = relationship('User', lazy='joined')
1028
1039
1029 @classmethod
1040 @classmethod
1030 def _get_ip_range(cls, ip_addr):
1041 def _get_ip_range(cls, ip_addr):
1031 net = ipaddress.ip_network(ip_addr, strict=False)
1042 net = ipaddress.ip_network(ip_addr, strict=False)
1032 return [str(net.network_address), str(net.broadcast_address)]
1043 return [str(net.network_address), str(net.broadcast_address)]
1033
1044
1034 def __json__(self):
1045 def __json__(self):
1035 return {
1046 return {
1036 'ip_addr': self.ip_addr,
1047 'ip_addr': self.ip_addr,
1037 'ip_range': self._get_ip_range(self.ip_addr),
1048 'ip_range': self._get_ip_range(self.ip_addr),
1038 }
1049 }
1039
1050
1040 def __unicode__(self):
1051 def __unicode__(self):
1041 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1052 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1042 self.user_id, self.ip_addr)
1053 self.user_id, self.ip_addr)
1043
1054
1044 class UserLog(Base, BaseModel):
1055 class UserLog(Base, BaseModel):
1045 __tablename__ = 'user_logs'
1056 __tablename__ = 'user_logs'
1046 __table_args__ = (
1057 __table_args__ = (
1047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1058 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1048 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1059 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1049 )
1060 )
1050 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1061 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1051 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1062 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1052 username = Column("username", String(255), nullable=True, unique=None, default=None)
1063 username = Column("username", String(255), nullable=True, unique=None, default=None)
1053 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1064 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1054 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1065 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1055 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1066 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1056 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1067 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1057 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1068 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1058
1069
1059 def __unicode__(self):
1070 def __unicode__(self):
1060 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1071 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1061 self.repository_name,
1072 self.repository_name,
1062 self.action)
1073 self.action)
1063
1074
1064 @property
1075 @property
1065 def action_as_day(self):
1076 def action_as_day(self):
1066 return datetime.date(*self.action_date.timetuple()[:3])
1077 return datetime.date(*self.action_date.timetuple()[:3])
1067
1078
1068 user = relationship('User')
1079 user = relationship('User')
1069 repository = relationship('Repository', cascade='')
1080 repository = relationship('Repository', cascade='')
1070
1081
1071
1082
1072 class UserGroup(Base, BaseModel):
1083 class UserGroup(Base, BaseModel):
1073 __tablename__ = 'users_groups'
1084 __tablename__ = 'users_groups'
1074 __table_args__ = (
1085 __table_args__ = (
1075 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1086 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1076 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1087 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1077 )
1088 )
1078
1089
1079 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1090 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1091 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1081 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1092 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1082 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1093 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1083 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1094 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1084 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1085 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1096 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1097 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1087
1098
1088 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1099 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1089 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1100 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1090 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1101 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1091 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1102 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1092 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1103 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1093 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1104 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1094
1105
1095 user = relationship('User')
1106 user = relationship('User')
1096
1107
1097 @hybrid_property
1108 @hybrid_property
1098 def group_data(self):
1109 def group_data(self):
1099 if not self._group_data:
1110 if not self._group_data:
1100 return {}
1111 return {}
1101
1112
1102 try:
1113 try:
1103 return json.loads(self._group_data)
1114 return json.loads(self._group_data)
1104 except TypeError:
1115 except TypeError:
1105 return {}
1116 return {}
1106
1117
1107 @group_data.setter
1118 @group_data.setter
1108 def group_data(self, val):
1119 def group_data(self, val):
1109 try:
1120 try:
1110 self._group_data = json.dumps(val)
1121 self._group_data = json.dumps(val)
1111 except Exception:
1122 except Exception:
1112 log.error(traceback.format_exc())
1123 log.error(traceback.format_exc())
1113
1124
1114 def __unicode__(self):
1125 def __unicode__(self):
1115 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1126 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1116 self.users_group_id,
1127 self.users_group_id,
1117 self.users_group_name)
1128 self.users_group_name)
1118
1129
1119 @classmethod
1130 @classmethod
1120 def get_by_group_name(cls, group_name, cache=False,
1131 def get_by_group_name(cls, group_name, cache=False,
1121 case_insensitive=False):
1132 case_insensitive=False):
1122 if case_insensitive:
1133 if case_insensitive:
1123 q = cls.query().filter(func.lower(cls.users_group_name) ==
1134 q = cls.query().filter(func.lower(cls.users_group_name) ==
1124 func.lower(group_name))
1135 func.lower(group_name))
1125
1136
1126 else:
1137 else:
1127 q = cls.query().filter(cls.users_group_name == group_name)
1138 q = cls.query().filter(cls.users_group_name == group_name)
1128 if cache:
1139 if cache:
1129 q = q.options(FromCache(
1140 q = q.options(FromCache(
1130 "sql_cache_short",
1141 "sql_cache_short",
1131 "get_group_%s" % _hash_key(group_name)))
1142 "get_group_%s" % _hash_key(group_name)))
1132 return q.scalar()
1143 return q.scalar()
1133
1144
1134 @classmethod
1145 @classmethod
1135 def get(cls, user_group_id, cache=False):
1146 def get(cls, user_group_id, cache=False):
1136 user_group = cls.query()
1147 user_group = cls.query()
1137 if cache:
1148 if cache:
1138 user_group = user_group.options(FromCache("sql_cache_short",
1149 user_group = user_group.options(FromCache("sql_cache_short",
1139 "get_users_group_%s" % user_group_id))
1150 "get_users_group_%s" % user_group_id))
1140 return user_group.get(user_group_id)
1151 return user_group.get(user_group_id)
1141
1152
1142 def permissions(self, with_admins=True, with_owner=True):
1153 def permissions(self, with_admins=True, with_owner=True):
1143 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1154 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1144 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1155 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1145 joinedload(UserUserGroupToPerm.user),
1156 joinedload(UserUserGroupToPerm.user),
1146 joinedload(UserUserGroupToPerm.permission),)
1157 joinedload(UserUserGroupToPerm.permission),)
1147
1158
1148 # get owners and admins and permissions. We do a trick of re-writing
1159 # get owners and admins and permissions. We do a trick of re-writing
1149 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1160 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1150 # has a global reference and changing one object propagates to all
1161 # has a global reference and changing one object propagates to all
1151 # others. This means if admin is also an owner admin_row that change
1162 # others. This means if admin is also an owner admin_row that change
1152 # would propagate to both objects
1163 # would propagate to both objects
1153 perm_rows = []
1164 perm_rows = []
1154 for _usr in q.all():
1165 for _usr in q.all():
1155 usr = AttributeDict(_usr.user.get_dict())
1166 usr = AttributeDict(_usr.user.get_dict())
1156 usr.permission = _usr.permission.permission_name
1167 usr.permission = _usr.permission.permission_name
1157 perm_rows.append(usr)
1168 perm_rows.append(usr)
1158
1169
1159 # filter the perm rows by 'default' first and then sort them by
1170 # filter the perm rows by 'default' first and then sort them by
1160 # admin,write,read,none permissions sorted again alphabetically in
1171 # admin,write,read,none permissions sorted again alphabetically in
1161 # each group
1172 # each group
1162 perm_rows = sorted(perm_rows, key=display_sort)
1173 perm_rows = sorted(perm_rows, key=display_sort)
1163
1174
1164 _admin_perm = 'usergroup.admin'
1175 _admin_perm = 'usergroup.admin'
1165 owner_row = []
1176 owner_row = []
1166 if with_owner:
1177 if with_owner:
1167 usr = AttributeDict(self.user.get_dict())
1178 usr = AttributeDict(self.user.get_dict())
1168 usr.owner_row = True
1179 usr.owner_row = True
1169 usr.permission = _admin_perm
1180 usr.permission = _admin_perm
1170 owner_row.append(usr)
1181 owner_row.append(usr)
1171
1182
1172 super_admin_rows = []
1183 super_admin_rows = []
1173 if with_admins:
1184 if with_admins:
1174 for usr in User.get_all_super_admins():
1185 for usr in User.get_all_super_admins():
1175 # if this admin is also owner, don't double the record
1186 # if this admin is also owner, don't double the record
1176 if usr.user_id == owner_row[0].user_id:
1187 if usr.user_id == owner_row[0].user_id:
1177 owner_row[0].admin_row = True
1188 owner_row[0].admin_row = True
1178 else:
1189 else:
1179 usr = AttributeDict(usr.get_dict())
1190 usr = AttributeDict(usr.get_dict())
1180 usr.admin_row = True
1191 usr.admin_row = True
1181 usr.permission = _admin_perm
1192 usr.permission = _admin_perm
1182 super_admin_rows.append(usr)
1193 super_admin_rows.append(usr)
1183
1194
1184 return super_admin_rows + owner_row + perm_rows
1195 return super_admin_rows + owner_row + perm_rows
1185
1196
1186 def permission_user_groups(self):
1197 def permission_user_groups(self):
1187 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1198 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1188 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1199 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1189 joinedload(UserGroupUserGroupToPerm.target_user_group),
1200 joinedload(UserGroupUserGroupToPerm.target_user_group),
1190 joinedload(UserGroupUserGroupToPerm.permission),)
1201 joinedload(UserGroupUserGroupToPerm.permission),)
1191
1202
1192 perm_rows = []
1203 perm_rows = []
1193 for _user_group in q.all():
1204 for _user_group in q.all():
1194 usr = AttributeDict(_user_group.user_group.get_dict())
1205 usr = AttributeDict(_user_group.user_group.get_dict())
1195 usr.permission = _user_group.permission.permission_name
1206 usr.permission = _user_group.permission.permission_name
1196 perm_rows.append(usr)
1207 perm_rows.append(usr)
1197
1208
1198 return perm_rows
1209 return perm_rows
1199
1210
1200 def _get_default_perms(self, user_group, suffix=''):
1211 def _get_default_perms(self, user_group, suffix=''):
1201 from rhodecode.model.permission import PermissionModel
1212 from rhodecode.model.permission import PermissionModel
1202 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1213 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1203
1214
1204 def get_default_perms(self, suffix=''):
1215 def get_default_perms(self, suffix=''):
1205 return self._get_default_perms(self, suffix)
1216 return self._get_default_perms(self, suffix)
1206
1217
1207 def get_api_data(self, with_group_members=True, include_secrets=False):
1218 def get_api_data(self, with_group_members=True, include_secrets=False):
1208 """
1219 """
1209 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1220 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1210 basically forwarded.
1221 basically forwarded.
1211
1222
1212 """
1223 """
1213 user_group = self
1224 user_group = self
1214
1225
1215 data = {
1226 data = {
1216 'users_group_id': user_group.users_group_id,
1227 'users_group_id': user_group.users_group_id,
1217 'group_name': user_group.users_group_name,
1228 'group_name': user_group.users_group_name,
1218 'group_description': user_group.user_group_description,
1229 'group_description': user_group.user_group_description,
1219 'active': user_group.users_group_active,
1230 'active': user_group.users_group_active,
1220 'owner': user_group.user.username,
1231 'owner': user_group.user.username,
1221 }
1232 }
1222 if with_group_members:
1233 if with_group_members:
1223 users = []
1234 users = []
1224 for user in user_group.members:
1235 for user in user_group.members:
1225 user = user.user
1236 user = user.user
1226 users.append(user.get_api_data(include_secrets=include_secrets))
1237 users.append(user.get_api_data(include_secrets=include_secrets))
1227 data['users'] = users
1238 data['users'] = users
1228
1239
1229 return data
1240 return data
1230
1241
1231
1242
1232 class UserGroupMember(Base, BaseModel):
1243 class UserGroupMember(Base, BaseModel):
1233 __tablename__ = 'users_groups_members'
1244 __tablename__ = 'users_groups_members'
1234 __table_args__ = (
1245 __table_args__ = (
1235 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1246 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1236 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1247 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1237 )
1248 )
1238
1249
1239 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1250 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1240 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1251 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1241 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1252 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1242
1253
1243 user = relationship('User', lazy='joined')
1254 user = relationship('User', lazy='joined')
1244 users_group = relationship('UserGroup')
1255 users_group = relationship('UserGroup')
1245
1256
1246 def __init__(self, gr_id='', u_id=''):
1257 def __init__(self, gr_id='', u_id=''):
1247 self.users_group_id = gr_id
1258 self.users_group_id = gr_id
1248 self.user_id = u_id
1259 self.user_id = u_id
1249
1260
1250
1261
1251 class RepositoryField(Base, BaseModel):
1262 class RepositoryField(Base, BaseModel):
1252 __tablename__ = 'repositories_fields'
1263 __tablename__ = 'repositories_fields'
1253 __table_args__ = (
1264 __table_args__ = (
1254 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1265 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1255 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1266 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1256 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1267 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1257 )
1268 )
1258 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1269 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1259
1270
1260 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1271 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1261 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1272 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1262 field_key = Column("field_key", String(250))
1273 field_key = Column("field_key", String(250))
1263 field_label = Column("field_label", String(1024), nullable=False)
1274 field_label = Column("field_label", String(1024), nullable=False)
1264 field_value = Column("field_value", String(10000), nullable=False)
1275 field_value = Column("field_value", String(10000), nullable=False)
1265 field_desc = Column("field_desc", String(1024), nullable=False)
1276 field_desc = Column("field_desc", String(1024), nullable=False)
1266 field_type = Column("field_type", String(255), nullable=False, unique=None)
1277 field_type = Column("field_type", String(255), nullable=False, unique=None)
1267 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1278 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1268
1279
1269 repository = relationship('Repository')
1280 repository = relationship('Repository')
1270
1281
1271 @property
1282 @property
1272 def field_key_prefixed(self):
1283 def field_key_prefixed(self):
1273 return 'ex_%s' % self.field_key
1284 return 'ex_%s' % self.field_key
1274
1285
1275 @classmethod
1286 @classmethod
1276 def un_prefix_key(cls, key):
1287 def un_prefix_key(cls, key):
1277 if key.startswith(cls.PREFIX):
1288 if key.startswith(cls.PREFIX):
1278 return key[len(cls.PREFIX):]
1289 return key[len(cls.PREFIX):]
1279 return key
1290 return key
1280
1291
1281 @classmethod
1292 @classmethod
1282 def get_by_key_name(cls, key, repo):
1293 def get_by_key_name(cls, key, repo):
1283 row = cls.query()\
1294 row = cls.query()\
1284 .filter(cls.repository == repo)\
1295 .filter(cls.repository == repo)\
1285 .filter(cls.field_key == key).scalar()
1296 .filter(cls.field_key == key).scalar()
1286 return row
1297 return row
1287
1298
1288
1299
1289 class Repository(Base, BaseModel):
1300 class Repository(Base, BaseModel):
1290 __tablename__ = 'repositories'
1301 __tablename__ = 'repositories'
1291 __table_args__ = (
1302 __table_args__ = (
1292 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1303 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1293 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1304 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1305 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1295 )
1306 )
1296 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1307 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1297 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1308 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1298
1309
1299 STATE_CREATED = 'repo_state_created'
1310 STATE_CREATED = 'repo_state_created'
1300 STATE_PENDING = 'repo_state_pending'
1311 STATE_PENDING = 'repo_state_pending'
1301 STATE_ERROR = 'repo_state_error'
1312 STATE_ERROR = 'repo_state_error'
1302
1313
1303 LOCK_AUTOMATIC = 'lock_auto'
1314 LOCK_AUTOMATIC = 'lock_auto'
1304 LOCK_API = 'lock_api'
1315 LOCK_API = 'lock_api'
1305 LOCK_WEB = 'lock_web'
1316 LOCK_WEB = 'lock_web'
1306 LOCK_PULL = 'lock_pull'
1317 LOCK_PULL = 'lock_pull'
1307
1318
1308 NAME_SEP = URL_SEP
1319 NAME_SEP = URL_SEP
1309
1320
1310 repo_id = Column(
1321 repo_id = Column(
1311 "repo_id", Integer(), nullable=False, unique=True, default=None,
1322 "repo_id", Integer(), nullable=False, unique=True, default=None,
1312 primary_key=True)
1323 primary_key=True)
1313 _repo_name = Column(
1324 _repo_name = Column(
1314 "repo_name", Text(), nullable=False, default=None)
1325 "repo_name", Text(), nullable=False, default=None)
1315 _repo_name_hash = Column(
1326 _repo_name_hash = Column(
1316 "repo_name_hash", String(255), nullable=False, unique=True)
1327 "repo_name_hash", String(255), nullable=False, unique=True)
1317 repo_state = Column("repo_state", String(255), nullable=True)
1328 repo_state = Column("repo_state", String(255), nullable=True)
1318
1329
1319 clone_uri = Column(
1330 clone_uri = Column(
1320 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1331 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1321 default=None)
1332 default=None)
1322 repo_type = Column(
1333 repo_type = Column(
1323 "repo_type", String(255), nullable=False, unique=False, default=None)
1334 "repo_type", String(255), nullable=False, unique=False, default=None)
1324 user_id = Column(
1335 user_id = Column(
1325 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1336 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1326 unique=False, default=None)
1337 unique=False, default=None)
1327 private = Column(
1338 private = Column(
1328 "private", Boolean(), nullable=True, unique=None, default=None)
1339 "private", Boolean(), nullable=True, unique=None, default=None)
1329 enable_statistics = Column(
1340 enable_statistics = Column(
1330 "statistics", Boolean(), nullable=True, unique=None, default=True)
1341 "statistics", Boolean(), nullable=True, unique=None, default=True)
1331 enable_downloads = Column(
1342 enable_downloads = Column(
1332 "downloads", Boolean(), nullable=True, unique=None, default=True)
1343 "downloads", Boolean(), nullable=True, unique=None, default=True)
1333 description = Column(
1344 description = Column(
1334 "description", String(10000), nullable=True, unique=None, default=None)
1345 "description", String(10000), nullable=True, unique=None, default=None)
1335 created_on = Column(
1346 created_on = Column(
1336 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1347 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1337 default=datetime.datetime.now)
1348 default=datetime.datetime.now)
1338 updated_on = Column(
1349 updated_on = Column(
1339 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1350 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1340 default=datetime.datetime.now)
1351 default=datetime.datetime.now)
1341 _landing_revision = Column(
1352 _landing_revision = Column(
1342 "landing_revision", String(255), nullable=False, unique=False,
1353 "landing_revision", String(255), nullable=False, unique=False,
1343 default=None)
1354 default=None)
1344 enable_locking = Column(
1355 enable_locking = Column(
1345 "enable_locking", Boolean(), nullable=False, unique=None,
1356 "enable_locking", Boolean(), nullable=False, unique=None,
1346 default=False)
1357 default=False)
1347 _locked = Column(
1358 _locked = Column(
1348 "locked", String(255), nullable=True, unique=False, default=None)
1359 "locked", String(255), nullable=True, unique=False, default=None)
1349 _changeset_cache = Column(
1360 _changeset_cache = Column(
1350 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1361 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1351
1362
1352 fork_id = Column(
1363 fork_id = Column(
1353 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1364 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1354 nullable=True, unique=False, default=None)
1365 nullable=True, unique=False, default=None)
1355 group_id = Column(
1366 group_id = Column(
1356 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1367 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1357 unique=False, default=None)
1368 unique=False, default=None)
1358
1369
1359 user = relationship('User', lazy='joined')
1370 user = relationship('User', lazy='joined')
1360 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1371 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1361 group = relationship('RepoGroup', lazy='joined')
1372 group = relationship('RepoGroup', lazy='joined')
1362 repo_to_perm = relationship(
1373 repo_to_perm = relationship(
1363 'UserRepoToPerm', cascade='all',
1374 'UserRepoToPerm', cascade='all',
1364 order_by='UserRepoToPerm.repo_to_perm_id')
1375 order_by='UserRepoToPerm.repo_to_perm_id')
1365 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1376 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1366 stats = relationship('Statistics', cascade='all', uselist=False)
1377 stats = relationship('Statistics', cascade='all', uselist=False)
1367
1378
1368 followers = relationship(
1379 followers = relationship(
1369 'UserFollowing',
1380 'UserFollowing',
1370 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1381 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1371 cascade='all')
1382 cascade='all')
1372 extra_fields = relationship(
1383 extra_fields = relationship(
1373 'RepositoryField', cascade="all, delete, delete-orphan")
1384 'RepositoryField', cascade="all, delete, delete-orphan")
1374 logs = relationship('UserLog')
1385 logs = relationship('UserLog')
1375 comments = relationship(
1386 comments = relationship(
1376 'ChangesetComment', cascade="all, delete, delete-orphan")
1387 'ChangesetComment', cascade="all, delete, delete-orphan")
1377 pull_requests_source = relationship(
1388 pull_requests_source = relationship(
1378 'PullRequest',
1389 'PullRequest',
1379 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1390 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1380 cascade="all, delete, delete-orphan")
1391 cascade="all, delete, delete-orphan")
1381 pull_requests_target = relationship(
1392 pull_requests_target = relationship(
1382 'PullRequest',
1393 'PullRequest',
1383 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1394 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1384 cascade="all, delete, delete-orphan")
1395 cascade="all, delete, delete-orphan")
1385 ui = relationship('RepoRhodeCodeUi', cascade="all")
1396 ui = relationship('RepoRhodeCodeUi', cascade="all")
1386 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1397 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1387 integrations = relationship('Integration',
1398 integrations = relationship('Integration',
1388 cascade="all, delete, delete-orphan")
1399 cascade="all, delete, delete-orphan")
1389
1400
1390 def __unicode__(self):
1401 def __unicode__(self):
1391 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1402 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1392 safe_unicode(self.repo_name))
1403 safe_unicode(self.repo_name))
1393
1404
1394 @hybrid_property
1405 @hybrid_property
1395 def landing_rev(self):
1406 def landing_rev(self):
1396 # always should return [rev_type, rev]
1407 # always should return [rev_type, rev]
1397 if self._landing_revision:
1408 if self._landing_revision:
1398 _rev_info = self._landing_revision.split(':')
1409 _rev_info = self._landing_revision.split(':')
1399 if len(_rev_info) < 2:
1410 if len(_rev_info) < 2:
1400 _rev_info.insert(0, 'rev')
1411 _rev_info.insert(0, 'rev')
1401 return [_rev_info[0], _rev_info[1]]
1412 return [_rev_info[0], _rev_info[1]]
1402 return [None, None]
1413 return [None, None]
1403
1414
1404 @landing_rev.setter
1415 @landing_rev.setter
1405 def landing_rev(self, val):
1416 def landing_rev(self, val):
1406 if ':' not in val:
1417 if ':' not in val:
1407 raise ValueError('value must be delimited with `:` and consist '
1418 raise ValueError('value must be delimited with `:` and consist '
1408 'of <rev_type>:<rev>, got %s instead' % val)
1419 'of <rev_type>:<rev>, got %s instead' % val)
1409 self._landing_revision = val
1420 self._landing_revision = val
1410
1421
1411 @hybrid_property
1422 @hybrid_property
1412 def locked(self):
1423 def locked(self):
1413 if self._locked:
1424 if self._locked:
1414 user_id, timelocked, reason = self._locked.split(':')
1425 user_id, timelocked, reason = self._locked.split(':')
1415 lock_values = int(user_id), timelocked, reason
1426 lock_values = int(user_id), timelocked, reason
1416 else:
1427 else:
1417 lock_values = [None, None, None]
1428 lock_values = [None, None, None]
1418 return lock_values
1429 return lock_values
1419
1430
1420 @locked.setter
1431 @locked.setter
1421 def locked(self, val):
1432 def locked(self, val):
1422 if val and isinstance(val, (list, tuple)):
1433 if val and isinstance(val, (list, tuple)):
1423 self._locked = ':'.join(map(str, val))
1434 self._locked = ':'.join(map(str, val))
1424 else:
1435 else:
1425 self._locked = None
1436 self._locked = None
1426
1437
1427 @hybrid_property
1438 @hybrid_property
1428 def changeset_cache(self):
1439 def changeset_cache(self):
1429 from rhodecode.lib.vcs.backends.base import EmptyCommit
1440 from rhodecode.lib.vcs.backends.base import EmptyCommit
1430 dummy = EmptyCommit().__json__()
1441 dummy = EmptyCommit().__json__()
1431 if not self._changeset_cache:
1442 if not self._changeset_cache:
1432 return dummy
1443 return dummy
1433 try:
1444 try:
1434 return json.loads(self._changeset_cache)
1445 return json.loads(self._changeset_cache)
1435 except TypeError:
1446 except TypeError:
1436 return dummy
1447 return dummy
1437 except Exception:
1448 except Exception:
1438 log.error(traceback.format_exc())
1449 log.error(traceback.format_exc())
1439 return dummy
1450 return dummy
1440
1451
1441 @changeset_cache.setter
1452 @changeset_cache.setter
1442 def changeset_cache(self, val):
1453 def changeset_cache(self, val):
1443 try:
1454 try:
1444 self._changeset_cache = json.dumps(val)
1455 self._changeset_cache = json.dumps(val)
1445 except Exception:
1456 except Exception:
1446 log.error(traceback.format_exc())
1457 log.error(traceback.format_exc())
1447
1458
1448 @hybrid_property
1459 @hybrid_property
1449 def repo_name(self):
1460 def repo_name(self):
1450 return self._repo_name
1461 return self._repo_name
1451
1462
1452 @repo_name.setter
1463 @repo_name.setter
1453 def repo_name(self, value):
1464 def repo_name(self, value):
1454 self._repo_name = value
1465 self._repo_name = value
1455 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1466 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1456
1467
1457 @classmethod
1468 @classmethod
1458 def normalize_repo_name(cls, repo_name):
1469 def normalize_repo_name(cls, repo_name):
1459 """
1470 """
1460 Normalizes os specific repo_name to the format internally stored inside
1471 Normalizes os specific repo_name to the format internally stored inside
1461 database using URL_SEP
1472 database using URL_SEP
1462
1473
1463 :param cls:
1474 :param cls:
1464 :param repo_name:
1475 :param repo_name:
1465 """
1476 """
1466 return cls.NAME_SEP.join(repo_name.split(os.sep))
1477 return cls.NAME_SEP.join(repo_name.split(os.sep))
1467
1478
1468 @classmethod
1479 @classmethod
1469 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1480 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1470 session = Session()
1481 session = Session()
1471 q = session.query(cls).filter(cls.repo_name == repo_name)
1482 q = session.query(cls).filter(cls.repo_name == repo_name)
1472
1483
1473 if cache:
1484 if cache:
1474 if identity_cache:
1485 if identity_cache:
1475 val = cls.identity_cache(session, 'repo_name', repo_name)
1486 val = cls.identity_cache(session, 'repo_name', repo_name)
1476 if val:
1487 if val:
1477 return val
1488 return val
1478 else:
1489 else:
1479 q = q.options(
1490 q = q.options(
1480 FromCache("sql_cache_short",
1491 FromCache("sql_cache_short",
1481 "get_repo_by_name_%s" % _hash_key(repo_name)))
1492 "get_repo_by_name_%s" % _hash_key(repo_name)))
1482
1493
1483 return q.scalar()
1494 return q.scalar()
1484
1495
1485 @classmethod
1496 @classmethod
1486 def get_by_full_path(cls, repo_full_path):
1497 def get_by_full_path(cls, repo_full_path):
1487 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1498 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1488 repo_name = cls.normalize_repo_name(repo_name)
1499 repo_name = cls.normalize_repo_name(repo_name)
1489 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1500 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1490
1501
1491 @classmethod
1502 @classmethod
1492 def get_repo_forks(cls, repo_id):
1503 def get_repo_forks(cls, repo_id):
1493 return cls.query().filter(Repository.fork_id == repo_id)
1504 return cls.query().filter(Repository.fork_id == repo_id)
1494
1505
1495 @classmethod
1506 @classmethod
1496 def base_path(cls):
1507 def base_path(cls):
1497 """
1508 """
1498 Returns base path when all repos are stored
1509 Returns base path when all repos are stored
1499
1510
1500 :param cls:
1511 :param cls:
1501 """
1512 """
1502 q = Session().query(RhodeCodeUi)\
1513 q = Session().query(RhodeCodeUi)\
1503 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1514 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1504 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1515 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1505 return q.one().ui_value
1516 return q.one().ui_value
1506
1517
1507 @classmethod
1518 @classmethod
1508 def is_valid(cls, repo_name):
1519 def is_valid(cls, repo_name):
1509 """
1520 """
1510 returns True if given repo name is a valid filesystem repository
1521 returns True if given repo name is a valid filesystem repository
1511
1522
1512 :param cls:
1523 :param cls:
1513 :param repo_name:
1524 :param repo_name:
1514 """
1525 """
1515 from rhodecode.lib.utils import is_valid_repo
1526 from rhodecode.lib.utils import is_valid_repo
1516
1527
1517 return is_valid_repo(repo_name, cls.base_path())
1528 return is_valid_repo(repo_name, cls.base_path())
1518
1529
1519 @classmethod
1530 @classmethod
1520 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1531 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1521 case_insensitive=True):
1532 case_insensitive=True):
1522 q = Repository.query()
1533 q = Repository.query()
1523
1534
1524 if not isinstance(user_id, Optional):
1535 if not isinstance(user_id, Optional):
1525 q = q.filter(Repository.user_id == user_id)
1536 q = q.filter(Repository.user_id == user_id)
1526
1537
1527 if not isinstance(group_id, Optional):
1538 if not isinstance(group_id, Optional):
1528 q = q.filter(Repository.group_id == group_id)
1539 q = q.filter(Repository.group_id == group_id)
1529
1540
1530 if case_insensitive:
1541 if case_insensitive:
1531 q = q.order_by(func.lower(Repository.repo_name))
1542 q = q.order_by(func.lower(Repository.repo_name))
1532 else:
1543 else:
1533 q = q.order_by(Repository.repo_name)
1544 q = q.order_by(Repository.repo_name)
1534 return q.all()
1545 return q.all()
1535
1546
1536 @property
1547 @property
1537 def forks(self):
1548 def forks(self):
1538 """
1549 """
1539 Return forks of this repo
1550 Return forks of this repo
1540 """
1551 """
1541 return Repository.get_repo_forks(self.repo_id)
1552 return Repository.get_repo_forks(self.repo_id)
1542
1553
1543 @property
1554 @property
1544 def parent(self):
1555 def parent(self):
1545 """
1556 """
1546 Returns fork parent
1557 Returns fork parent
1547 """
1558 """
1548 return self.fork
1559 return self.fork
1549
1560
1550 @property
1561 @property
1551 def just_name(self):
1562 def just_name(self):
1552 return self.repo_name.split(self.NAME_SEP)[-1]
1563 return self.repo_name.split(self.NAME_SEP)[-1]
1553
1564
1554 @property
1565 @property
1555 def groups_with_parents(self):
1566 def groups_with_parents(self):
1556 groups = []
1567 groups = []
1557 if self.group is None:
1568 if self.group is None:
1558 return groups
1569 return groups
1559
1570
1560 cur_gr = self.group
1571 cur_gr = self.group
1561 groups.insert(0, cur_gr)
1572 groups.insert(0, cur_gr)
1562 while 1:
1573 while 1:
1563 gr = getattr(cur_gr, 'parent_group', None)
1574 gr = getattr(cur_gr, 'parent_group', None)
1564 cur_gr = cur_gr.parent_group
1575 cur_gr = cur_gr.parent_group
1565 if gr is None:
1576 if gr is None:
1566 break
1577 break
1567 groups.insert(0, gr)
1578 groups.insert(0, gr)
1568
1579
1569 return groups
1580 return groups
1570
1581
1571 @property
1582 @property
1572 def groups_and_repo(self):
1583 def groups_and_repo(self):
1573 return self.groups_with_parents, self
1584 return self.groups_with_parents, self
1574
1585
1575 @LazyProperty
1586 @LazyProperty
1576 def repo_path(self):
1587 def repo_path(self):
1577 """
1588 """
1578 Returns base full path for that repository means where it actually
1589 Returns base full path for that repository means where it actually
1579 exists on a filesystem
1590 exists on a filesystem
1580 """
1591 """
1581 q = Session().query(RhodeCodeUi).filter(
1592 q = Session().query(RhodeCodeUi).filter(
1582 RhodeCodeUi.ui_key == self.NAME_SEP)
1593 RhodeCodeUi.ui_key == self.NAME_SEP)
1583 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1594 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1584 return q.one().ui_value
1595 return q.one().ui_value
1585
1596
1586 @property
1597 @property
1587 def repo_full_path(self):
1598 def repo_full_path(self):
1588 p = [self.repo_path]
1599 p = [self.repo_path]
1589 # we need to split the name by / since this is how we store the
1600 # we need to split the name by / since this is how we store the
1590 # names in the database, but that eventually needs to be converted
1601 # names in the database, but that eventually needs to be converted
1591 # into a valid system path
1602 # into a valid system path
1592 p += self.repo_name.split(self.NAME_SEP)
1603 p += self.repo_name.split(self.NAME_SEP)
1593 return os.path.join(*map(safe_unicode, p))
1604 return os.path.join(*map(safe_unicode, p))
1594
1605
1595 @property
1606 @property
1596 def cache_keys(self):
1607 def cache_keys(self):
1597 """
1608 """
1598 Returns associated cache keys for that repo
1609 Returns associated cache keys for that repo
1599 """
1610 """
1600 return CacheKey.query()\
1611 return CacheKey.query()\
1601 .filter(CacheKey.cache_args == self.repo_name)\
1612 .filter(CacheKey.cache_args == self.repo_name)\
1602 .order_by(CacheKey.cache_key)\
1613 .order_by(CacheKey.cache_key)\
1603 .all()
1614 .all()
1604
1615
1605 def get_new_name(self, repo_name):
1616 def get_new_name(self, repo_name):
1606 """
1617 """
1607 returns new full repository name based on assigned group and new new
1618 returns new full repository name based on assigned group and new new
1608
1619
1609 :param group_name:
1620 :param group_name:
1610 """
1621 """
1611 path_prefix = self.group.full_path_splitted if self.group else []
1622 path_prefix = self.group.full_path_splitted if self.group else []
1612 return self.NAME_SEP.join(path_prefix + [repo_name])
1623 return self.NAME_SEP.join(path_prefix + [repo_name])
1613
1624
1614 @property
1625 @property
1615 def _config(self):
1626 def _config(self):
1616 """
1627 """
1617 Returns db based config object.
1628 Returns db based config object.
1618 """
1629 """
1619 from rhodecode.lib.utils import make_db_config
1630 from rhodecode.lib.utils import make_db_config
1620 return make_db_config(clear_session=False, repo=self)
1631 return make_db_config(clear_session=False, repo=self)
1621
1632
1622 def permissions(self, with_admins=True, with_owner=True):
1633 def permissions(self, with_admins=True, with_owner=True):
1623 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1634 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1624 q = q.options(joinedload(UserRepoToPerm.repository),
1635 q = q.options(joinedload(UserRepoToPerm.repository),
1625 joinedload(UserRepoToPerm.user),
1636 joinedload(UserRepoToPerm.user),
1626 joinedload(UserRepoToPerm.permission),)
1637 joinedload(UserRepoToPerm.permission),)
1627
1638
1628 # get owners and admins and permissions. We do a trick of re-writing
1639 # get owners and admins and permissions. We do a trick of re-writing
1629 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1640 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1630 # has a global reference and changing one object propagates to all
1641 # has a global reference and changing one object propagates to all
1631 # others. This means if admin is also an owner admin_row that change
1642 # others. This means if admin is also an owner admin_row that change
1632 # would propagate to both objects
1643 # would propagate to both objects
1633 perm_rows = []
1644 perm_rows = []
1634 for _usr in q.all():
1645 for _usr in q.all():
1635 usr = AttributeDict(_usr.user.get_dict())
1646 usr = AttributeDict(_usr.user.get_dict())
1636 usr.permission = _usr.permission.permission_name
1647 usr.permission = _usr.permission.permission_name
1637 perm_rows.append(usr)
1648 perm_rows.append(usr)
1638
1649
1639 # filter the perm rows by 'default' first and then sort them by
1650 # filter the perm rows by 'default' first and then sort them by
1640 # admin,write,read,none permissions sorted again alphabetically in
1651 # admin,write,read,none permissions sorted again alphabetically in
1641 # each group
1652 # each group
1642 perm_rows = sorted(perm_rows, key=display_sort)
1653 perm_rows = sorted(perm_rows, key=display_sort)
1643
1654
1644 _admin_perm = 'repository.admin'
1655 _admin_perm = 'repository.admin'
1645 owner_row = []
1656 owner_row = []
1646 if with_owner:
1657 if with_owner:
1647 usr = AttributeDict(self.user.get_dict())
1658 usr = AttributeDict(self.user.get_dict())
1648 usr.owner_row = True
1659 usr.owner_row = True
1649 usr.permission = _admin_perm
1660 usr.permission = _admin_perm
1650 owner_row.append(usr)
1661 owner_row.append(usr)
1651
1662
1652 super_admin_rows = []
1663 super_admin_rows = []
1653 if with_admins:
1664 if with_admins:
1654 for usr in User.get_all_super_admins():
1665 for usr in User.get_all_super_admins():
1655 # if this admin is also owner, don't double the record
1666 # if this admin is also owner, don't double the record
1656 if usr.user_id == owner_row[0].user_id:
1667 if usr.user_id == owner_row[0].user_id:
1657 owner_row[0].admin_row = True
1668 owner_row[0].admin_row = True
1658 else:
1669 else:
1659 usr = AttributeDict(usr.get_dict())
1670 usr = AttributeDict(usr.get_dict())
1660 usr.admin_row = True
1671 usr.admin_row = True
1661 usr.permission = _admin_perm
1672 usr.permission = _admin_perm
1662 super_admin_rows.append(usr)
1673 super_admin_rows.append(usr)
1663
1674
1664 return super_admin_rows + owner_row + perm_rows
1675 return super_admin_rows + owner_row + perm_rows
1665
1676
1666 def permission_user_groups(self):
1677 def permission_user_groups(self):
1667 q = UserGroupRepoToPerm.query().filter(
1678 q = UserGroupRepoToPerm.query().filter(
1668 UserGroupRepoToPerm.repository == self)
1679 UserGroupRepoToPerm.repository == self)
1669 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1680 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1670 joinedload(UserGroupRepoToPerm.users_group),
1681 joinedload(UserGroupRepoToPerm.users_group),
1671 joinedload(UserGroupRepoToPerm.permission),)
1682 joinedload(UserGroupRepoToPerm.permission),)
1672
1683
1673 perm_rows = []
1684 perm_rows = []
1674 for _user_group in q.all():
1685 for _user_group in q.all():
1675 usr = AttributeDict(_user_group.users_group.get_dict())
1686 usr = AttributeDict(_user_group.users_group.get_dict())
1676 usr.permission = _user_group.permission.permission_name
1687 usr.permission = _user_group.permission.permission_name
1677 perm_rows.append(usr)
1688 perm_rows.append(usr)
1678
1689
1679 return perm_rows
1690 return perm_rows
1680
1691
1681 def get_api_data(self, include_secrets=False):
1692 def get_api_data(self, include_secrets=False):
1682 """
1693 """
1683 Common function for generating repo api data
1694 Common function for generating repo api data
1684
1695
1685 :param include_secrets: See :meth:`User.get_api_data`.
1696 :param include_secrets: See :meth:`User.get_api_data`.
1686
1697
1687 """
1698 """
1688 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1699 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1689 # move this methods on models level.
1700 # move this methods on models level.
1690 from rhodecode.model.settings import SettingsModel
1701 from rhodecode.model.settings import SettingsModel
1691
1702
1692 repo = self
1703 repo = self
1693 _user_id, _time, _reason = self.locked
1704 _user_id, _time, _reason = self.locked
1694
1705
1695 data = {
1706 data = {
1696 'repo_id': repo.repo_id,
1707 'repo_id': repo.repo_id,
1697 'repo_name': repo.repo_name,
1708 'repo_name': repo.repo_name,
1698 'repo_type': repo.repo_type,
1709 'repo_type': repo.repo_type,
1699 'clone_uri': repo.clone_uri or '',
1710 'clone_uri': repo.clone_uri or '',
1700 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1711 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1701 'private': repo.private,
1712 'private': repo.private,
1702 'created_on': repo.created_on,
1713 'created_on': repo.created_on,
1703 'description': repo.description,
1714 'description': repo.description,
1704 'landing_rev': repo.landing_rev,
1715 'landing_rev': repo.landing_rev,
1705 'owner': repo.user.username,
1716 'owner': repo.user.username,
1706 'fork_of': repo.fork.repo_name if repo.fork else None,
1717 'fork_of': repo.fork.repo_name if repo.fork else None,
1707 'enable_statistics': repo.enable_statistics,
1718 'enable_statistics': repo.enable_statistics,
1708 'enable_locking': repo.enable_locking,
1719 'enable_locking': repo.enable_locking,
1709 'enable_downloads': repo.enable_downloads,
1720 'enable_downloads': repo.enable_downloads,
1710 'last_changeset': repo.changeset_cache,
1721 'last_changeset': repo.changeset_cache,
1711 'locked_by': User.get(_user_id).get_api_data(
1722 'locked_by': User.get(_user_id).get_api_data(
1712 include_secrets=include_secrets) if _user_id else None,
1723 include_secrets=include_secrets) if _user_id else None,
1713 'locked_date': time_to_datetime(_time) if _time else None,
1724 'locked_date': time_to_datetime(_time) if _time else None,
1714 'lock_reason': _reason if _reason else None,
1725 'lock_reason': _reason if _reason else None,
1715 }
1726 }
1716
1727
1717 # TODO: mikhail: should be per-repo settings here
1728 # TODO: mikhail: should be per-repo settings here
1718 rc_config = SettingsModel().get_all_settings()
1729 rc_config = SettingsModel().get_all_settings()
1719 repository_fields = str2bool(
1730 repository_fields = str2bool(
1720 rc_config.get('rhodecode_repository_fields'))
1731 rc_config.get('rhodecode_repository_fields'))
1721 if repository_fields:
1732 if repository_fields:
1722 for f in self.extra_fields:
1733 for f in self.extra_fields:
1723 data[f.field_key_prefixed] = f.field_value
1734 data[f.field_key_prefixed] = f.field_value
1724
1735
1725 return data
1736 return data
1726
1737
1727 @classmethod
1738 @classmethod
1728 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1739 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1729 if not lock_time:
1740 if not lock_time:
1730 lock_time = time.time()
1741 lock_time = time.time()
1731 if not lock_reason:
1742 if not lock_reason:
1732 lock_reason = cls.LOCK_AUTOMATIC
1743 lock_reason = cls.LOCK_AUTOMATIC
1733 repo.locked = [user_id, lock_time, lock_reason]
1744 repo.locked = [user_id, lock_time, lock_reason]
1734 Session().add(repo)
1745 Session().add(repo)
1735 Session().commit()
1746 Session().commit()
1736
1747
1737 @classmethod
1748 @classmethod
1738 def unlock(cls, repo):
1749 def unlock(cls, repo):
1739 repo.locked = None
1750 repo.locked = None
1740 Session().add(repo)
1751 Session().add(repo)
1741 Session().commit()
1752 Session().commit()
1742
1753
1743 @classmethod
1754 @classmethod
1744 def getlock(cls, repo):
1755 def getlock(cls, repo):
1745 return repo.locked
1756 return repo.locked
1746
1757
1747 def is_user_lock(self, user_id):
1758 def is_user_lock(self, user_id):
1748 if self.lock[0]:
1759 if self.lock[0]:
1749 lock_user_id = safe_int(self.lock[0])
1760 lock_user_id = safe_int(self.lock[0])
1750 user_id = safe_int(user_id)
1761 user_id = safe_int(user_id)
1751 # both are ints, and they are equal
1762 # both are ints, and they are equal
1752 return all([lock_user_id, user_id]) and lock_user_id == user_id
1763 return all([lock_user_id, user_id]) and lock_user_id == user_id
1753
1764
1754 return False
1765 return False
1755
1766
1756 def get_locking_state(self, action, user_id, only_when_enabled=True):
1767 def get_locking_state(self, action, user_id, only_when_enabled=True):
1757 """
1768 """
1758 Checks locking on this repository, if locking is enabled and lock is
1769 Checks locking on this repository, if locking is enabled and lock is
1759 present returns a tuple of make_lock, locked, locked_by.
1770 present returns a tuple of make_lock, locked, locked_by.
1760 make_lock can have 3 states None (do nothing) True, make lock
1771 make_lock can have 3 states None (do nothing) True, make lock
1761 False release lock, This value is later propagated to hooks, which
1772 False release lock, This value is later propagated to hooks, which
1762 do the locking. Think about this as signals passed to hooks what to do.
1773 do the locking. Think about this as signals passed to hooks what to do.
1763
1774
1764 """
1775 """
1765 # TODO: johbo: This is part of the business logic and should be moved
1776 # TODO: johbo: This is part of the business logic and should be moved
1766 # into the RepositoryModel.
1777 # into the RepositoryModel.
1767
1778
1768 if action not in ('push', 'pull'):
1779 if action not in ('push', 'pull'):
1769 raise ValueError("Invalid action value: %s" % repr(action))
1780 raise ValueError("Invalid action value: %s" % repr(action))
1770
1781
1771 # defines if locked error should be thrown to user
1782 # defines if locked error should be thrown to user
1772 currently_locked = False
1783 currently_locked = False
1773 # defines if new lock should be made, tri-state
1784 # defines if new lock should be made, tri-state
1774 make_lock = None
1785 make_lock = None
1775 repo = self
1786 repo = self
1776 user = User.get(user_id)
1787 user = User.get(user_id)
1777
1788
1778 lock_info = repo.locked
1789 lock_info = repo.locked
1779
1790
1780 if repo and (repo.enable_locking or not only_when_enabled):
1791 if repo and (repo.enable_locking or not only_when_enabled):
1781 if action == 'push':
1792 if action == 'push':
1782 # check if it's already locked !, if it is compare users
1793 # check if it's already locked !, if it is compare users
1783 locked_by_user_id = lock_info[0]
1794 locked_by_user_id = lock_info[0]
1784 if user.user_id == locked_by_user_id:
1795 if user.user_id == locked_by_user_id:
1785 log.debug(
1796 log.debug(
1786 'Got `push` action from user %s, now unlocking', user)
1797 'Got `push` action from user %s, now unlocking', user)
1787 # unlock if we have push from user who locked
1798 # unlock if we have push from user who locked
1788 make_lock = False
1799 make_lock = False
1789 else:
1800 else:
1790 # we're not the same user who locked, ban with
1801 # we're not the same user who locked, ban with
1791 # code defined in settings (default is 423 HTTP Locked) !
1802 # code defined in settings (default is 423 HTTP Locked) !
1792 log.debug('Repo %s is currently locked by %s', repo, user)
1803 log.debug('Repo %s is currently locked by %s', repo, user)
1793 currently_locked = True
1804 currently_locked = True
1794 elif action == 'pull':
1805 elif action == 'pull':
1795 # [0] user [1] date
1806 # [0] user [1] date
1796 if lock_info[0] and lock_info[1]:
1807 if lock_info[0] and lock_info[1]:
1797 log.debug('Repo %s is currently locked by %s', repo, user)
1808 log.debug('Repo %s is currently locked by %s', repo, user)
1798 currently_locked = True
1809 currently_locked = True
1799 else:
1810 else:
1800 log.debug('Setting lock on repo %s by %s', repo, user)
1811 log.debug('Setting lock on repo %s by %s', repo, user)
1801 make_lock = True
1812 make_lock = True
1802
1813
1803 else:
1814 else:
1804 log.debug('Repository %s do not have locking enabled', repo)
1815 log.debug('Repository %s do not have locking enabled', repo)
1805
1816
1806 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1817 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1807 make_lock, currently_locked, lock_info)
1818 make_lock, currently_locked, lock_info)
1808
1819
1809 from rhodecode.lib.auth import HasRepoPermissionAny
1820 from rhodecode.lib.auth import HasRepoPermissionAny
1810 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1821 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1811 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1822 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1812 # if we don't have at least write permission we cannot make a lock
1823 # if we don't have at least write permission we cannot make a lock
1813 log.debug('lock state reset back to FALSE due to lack '
1824 log.debug('lock state reset back to FALSE due to lack '
1814 'of at least read permission')
1825 'of at least read permission')
1815 make_lock = False
1826 make_lock = False
1816
1827
1817 return make_lock, currently_locked, lock_info
1828 return make_lock, currently_locked, lock_info
1818
1829
1819 @property
1830 @property
1820 def last_db_change(self):
1831 def last_db_change(self):
1821 return self.updated_on
1832 return self.updated_on
1822
1833
1823 @property
1834 @property
1824 def clone_uri_hidden(self):
1835 def clone_uri_hidden(self):
1825 clone_uri = self.clone_uri
1836 clone_uri = self.clone_uri
1826 if clone_uri:
1837 if clone_uri:
1827 import urlobject
1838 import urlobject
1828 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1839 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1829 if url_obj.password:
1840 if url_obj.password:
1830 clone_uri = url_obj.with_password('*****')
1841 clone_uri = url_obj.with_password('*****')
1831 return clone_uri
1842 return clone_uri
1832
1843
1833 def clone_url(self, **override):
1844 def clone_url(self, **override):
1834 qualified_home_url = url('home', qualified=True)
1845 qualified_home_url = url('home', qualified=True)
1835
1846
1836 uri_tmpl = None
1847 uri_tmpl = None
1837 if 'with_id' in override:
1848 if 'with_id' in override:
1838 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1849 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1839 del override['with_id']
1850 del override['with_id']
1840
1851
1841 if 'uri_tmpl' in override:
1852 if 'uri_tmpl' in override:
1842 uri_tmpl = override['uri_tmpl']
1853 uri_tmpl = override['uri_tmpl']
1843 del override['uri_tmpl']
1854 del override['uri_tmpl']
1844
1855
1845 # we didn't override our tmpl from **overrides
1856 # we didn't override our tmpl from **overrides
1846 if not uri_tmpl:
1857 if not uri_tmpl:
1847 uri_tmpl = self.DEFAULT_CLONE_URI
1858 uri_tmpl = self.DEFAULT_CLONE_URI
1848 try:
1859 try:
1849 from pylons import tmpl_context as c
1860 from pylons import tmpl_context as c
1850 uri_tmpl = c.clone_uri_tmpl
1861 uri_tmpl = c.clone_uri_tmpl
1851 except Exception:
1862 except Exception:
1852 # in any case if we call this outside of request context,
1863 # in any case if we call this outside of request context,
1853 # ie, not having tmpl_context set up
1864 # ie, not having tmpl_context set up
1854 pass
1865 pass
1855
1866
1856 return get_clone_url(uri_tmpl=uri_tmpl,
1867 return get_clone_url(uri_tmpl=uri_tmpl,
1857 qualifed_home_url=qualified_home_url,
1868 qualifed_home_url=qualified_home_url,
1858 repo_name=self.repo_name,
1869 repo_name=self.repo_name,
1859 repo_id=self.repo_id, **override)
1870 repo_id=self.repo_id, **override)
1860
1871
1861 def set_state(self, state):
1872 def set_state(self, state):
1862 self.repo_state = state
1873 self.repo_state = state
1863 Session().add(self)
1874 Session().add(self)
1864 #==========================================================================
1875 #==========================================================================
1865 # SCM PROPERTIES
1876 # SCM PROPERTIES
1866 #==========================================================================
1877 #==========================================================================
1867
1878
1868 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1879 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1869 return get_commit_safe(
1880 return get_commit_safe(
1870 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1881 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1871
1882
1872 def get_changeset(self, rev=None, pre_load=None):
1883 def get_changeset(self, rev=None, pre_load=None):
1873 warnings.warn("Use get_commit", DeprecationWarning)
1884 warnings.warn("Use get_commit", DeprecationWarning)
1874 commit_id = None
1885 commit_id = None
1875 commit_idx = None
1886 commit_idx = None
1876 if isinstance(rev, basestring):
1887 if isinstance(rev, basestring):
1877 commit_id = rev
1888 commit_id = rev
1878 else:
1889 else:
1879 commit_idx = rev
1890 commit_idx = rev
1880 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1891 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1881 pre_load=pre_load)
1892 pre_load=pre_load)
1882
1893
1883 def get_landing_commit(self):
1894 def get_landing_commit(self):
1884 """
1895 """
1885 Returns landing commit, or if that doesn't exist returns the tip
1896 Returns landing commit, or if that doesn't exist returns the tip
1886 """
1897 """
1887 _rev_type, _rev = self.landing_rev
1898 _rev_type, _rev = self.landing_rev
1888 commit = self.get_commit(_rev)
1899 commit = self.get_commit(_rev)
1889 if isinstance(commit, EmptyCommit):
1900 if isinstance(commit, EmptyCommit):
1890 return self.get_commit()
1901 return self.get_commit()
1891 return commit
1902 return commit
1892
1903
1893 def update_commit_cache(self, cs_cache=None, config=None):
1904 def update_commit_cache(self, cs_cache=None, config=None):
1894 """
1905 """
1895 Update cache of last changeset for repository, keys should be::
1906 Update cache of last changeset for repository, keys should be::
1896
1907
1897 short_id
1908 short_id
1898 raw_id
1909 raw_id
1899 revision
1910 revision
1900 parents
1911 parents
1901 message
1912 message
1902 date
1913 date
1903 author
1914 author
1904
1915
1905 :param cs_cache:
1916 :param cs_cache:
1906 """
1917 """
1907 from rhodecode.lib.vcs.backends.base import BaseChangeset
1918 from rhodecode.lib.vcs.backends.base import BaseChangeset
1908 if cs_cache is None:
1919 if cs_cache is None:
1909 # use no-cache version here
1920 # use no-cache version here
1910 scm_repo = self.scm_instance(cache=False, config=config)
1921 scm_repo = self.scm_instance(cache=False, config=config)
1911 if scm_repo:
1922 if scm_repo:
1912 cs_cache = scm_repo.get_commit(
1923 cs_cache = scm_repo.get_commit(
1913 pre_load=["author", "date", "message", "parents"])
1924 pre_load=["author", "date", "message", "parents"])
1914 else:
1925 else:
1915 cs_cache = EmptyCommit()
1926 cs_cache = EmptyCommit()
1916
1927
1917 if isinstance(cs_cache, BaseChangeset):
1928 if isinstance(cs_cache, BaseChangeset):
1918 cs_cache = cs_cache.__json__()
1929 cs_cache = cs_cache.__json__()
1919
1930
1920 def is_outdated(new_cs_cache):
1931 def is_outdated(new_cs_cache):
1921 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1932 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1922 new_cs_cache['revision'] != self.changeset_cache['revision']):
1933 new_cs_cache['revision'] != self.changeset_cache['revision']):
1923 return True
1934 return True
1924 return False
1935 return False
1925
1936
1926 # check if we have maybe already latest cached revision
1937 # check if we have maybe already latest cached revision
1927 if is_outdated(cs_cache) or not self.changeset_cache:
1938 if is_outdated(cs_cache) or not self.changeset_cache:
1928 _default = datetime.datetime.fromtimestamp(0)
1939 _default = datetime.datetime.fromtimestamp(0)
1929 last_change = cs_cache.get('date') or _default
1940 last_change = cs_cache.get('date') or _default
1930 log.debug('updated repo %s with new cs cache %s',
1941 log.debug('updated repo %s with new cs cache %s',
1931 self.repo_name, cs_cache)
1942 self.repo_name, cs_cache)
1932 self.updated_on = last_change
1943 self.updated_on = last_change
1933 self.changeset_cache = cs_cache
1944 self.changeset_cache = cs_cache
1934 Session().add(self)
1945 Session().add(self)
1935 Session().commit()
1946 Session().commit()
1936 else:
1947 else:
1937 log.debug('Skipping update_commit_cache for repo:`%s` '
1948 log.debug('Skipping update_commit_cache for repo:`%s` '
1938 'commit already with latest changes', self.repo_name)
1949 'commit already with latest changes', self.repo_name)
1939
1950
1940 @property
1951 @property
1941 def tip(self):
1952 def tip(self):
1942 return self.get_commit('tip')
1953 return self.get_commit('tip')
1943
1954
1944 @property
1955 @property
1945 def author(self):
1956 def author(self):
1946 return self.tip.author
1957 return self.tip.author
1947
1958
1948 @property
1959 @property
1949 def last_change(self):
1960 def last_change(self):
1950 return self.scm_instance().last_change
1961 return self.scm_instance().last_change
1951
1962
1952 def get_comments(self, revisions=None):
1963 def get_comments(self, revisions=None):
1953 """
1964 """
1954 Returns comments for this repository grouped by revisions
1965 Returns comments for this repository grouped by revisions
1955
1966
1956 :param revisions: filter query by revisions only
1967 :param revisions: filter query by revisions only
1957 """
1968 """
1958 cmts = ChangesetComment.query()\
1969 cmts = ChangesetComment.query()\
1959 .filter(ChangesetComment.repo == self)
1970 .filter(ChangesetComment.repo == self)
1960 if revisions:
1971 if revisions:
1961 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1972 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1962 grouped = collections.defaultdict(list)
1973 grouped = collections.defaultdict(list)
1963 for cmt in cmts.all():
1974 for cmt in cmts.all():
1964 grouped[cmt.revision].append(cmt)
1975 grouped[cmt.revision].append(cmt)
1965 return grouped
1976 return grouped
1966
1977
1967 def statuses(self, revisions=None):
1978 def statuses(self, revisions=None):
1968 """
1979 """
1969 Returns statuses for this repository
1980 Returns statuses for this repository
1970
1981
1971 :param revisions: list of revisions to get statuses for
1982 :param revisions: list of revisions to get statuses for
1972 """
1983 """
1973 statuses = ChangesetStatus.query()\
1984 statuses = ChangesetStatus.query()\
1974 .filter(ChangesetStatus.repo == self)\
1985 .filter(ChangesetStatus.repo == self)\
1975 .filter(ChangesetStatus.version == 0)
1986 .filter(ChangesetStatus.version == 0)
1976
1987
1977 if revisions:
1988 if revisions:
1978 # Try doing the filtering in chunks to avoid hitting limits
1989 # Try doing the filtering in chunks to avoid hitting limits
1979 size = 500
1990 size = 500
1980 status_results = []
1991 status_results = []
1981 for chunk in xrange(0, len(revisions), size):
1992 for chunk in xrange(0, len(revisions), size):
1982 status_results += statuses.filter(
1993 status_results += statuses.filter(
1983 ChangesetStatus.revision.in_(
1994 ChangesetStatus.revision.in_(
1984 revisions[chunk: chunk+size])
1995 revisions[chunk: chunk+size])
1985 ).all()
1996 ).all()
1986 else:
1997 else:
1987 status_results = statuses.all()
1998 status_results = statuses.all()
1988
1999
1989 grouped = {}
2000 grouped = {}
1990
2001
1991 # maybe we have open new pullrequest without a status?
2002 # maybe we have open new pullrequest without a status?
1992 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2003 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1993 status_lbl = ChangesetStatus.get_status_lbl(stat)
2004 status_lbl = ChangesetStatus.get_status_lbl(stat)
1994 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2005 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1995 for rev in pr.revisions:
2006 for rev in pr.revisions:
1996 pr_id = pr.pull_request_id
2007 pr_id = pr.pull_request_id
1997 pr_repo = pr.target_repo.repo_name
2008 pr_repo = pr.target_repo.repo_name
1998 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2009 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1999
2010
2000 for stat in status_results:
2011 for stat in status_results:
2001 pr_id = pr_repo = None
2012 pr_id = pr_repo = None
2002 if stat.pull_request:
2013 if stat.pull_request:
2003 pr_id = stat.pull_request.pull_request_id
2014 pr_id = stat.pull_request.pull_request_id
2004 pr_repo = stat.pull_request.target_repo.repo_name
2015 pr_repo = stat.pull_request.target_repo.repo_name
2005 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2016 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2006 pr_id, pr_repo]
2017 pr_id, pr_repo]
2007 return grouped
2018 return grouped
2008
2019
2009 # ==========================================================================
2020 # ==========================================================================
2010 # SCM CACHE INSTANCE
2021 # SCM CACHE INSTANCE
2011 # ==========================================================================
2022 # ==========================================================================
2012
2023
2013 def scm_instance(self, **kwargs):
2024 def scm_instance(self, **kwargs):
2014 import rhodecode
2025 import rhodecode
2015
2026
2016 # Passing a config will not hit the cache currently only used
2027 # Passing a config will not hit the cache currently only used
2017 # for repo2dbmapper
2028 # for repo2dbmapper
2018 config = kwargs.pop('config', None)
2029 config = kwargs.pop('config', None)
2019 cache = kwargs.pop('cache', None)
2030 cache = kwargs.pop('cache', None)
2020 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2031 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2021 # if cache is NOT defined use default global, else we have a full
2032 # if cache is NOT defined use default global, else we have a full
2022 # control over cache behaviour
2033 # control over cache behaviour
2023 if cache is None and full_cache and not config:
2034 if cache is None and full_cache and not config:
2024 return self._get_instance_cached()
2035 return self._get_instance_cached()
2025 return self._get_instance(cache=bool(cache), config=config)
2036 return self._get_instance(cache=bool(cache), config=config)
2026
2037
2027 def _get_instance_cached(self):
2038 def _get_instance_cached(self):
2028 @cache_region('long_term')
2039 @cache_region('long_term')
2029 def _get_repo(cache_key):
2040 def _get_repo(cache_key):
2030 return self._get_instance()
2041 return self._get_instance()
2031
2042
2032 invalidator_context = CacheKey.repo_context_cache(
2043 invalidator_context = CacheKey.repo_context_cache(
2033 _get_repo, self.repo_name, None, thread_scoped=True)
2044 _get_repo, self.repo_name, None, thread_scoped=True)
2034
2045
2035 with invalidator_context as context:
2046 with invalidator_context as context:
2036 context.invalidate()
2047 context.invalidate()
2037 repo = context.compute()
2048 repo = context.compute()
2038
2049
2039 return repo
2050 return repo
2040
2051
2041 def _get_instance(self, cache=True, config=None):
2052 def _get_instance(self, cache=True, config=None):
2042 config = config or self._config
2053 config = config or self._config
2043 custom_wire = {
2054 custom_wire = {
2044 'cache': cache # controls the vcs.remote cache
2055 'cache': cache # controls the vcs.remote cache
2045 }
2056 }
2046 repo = get_vcs_instance(
2057 repo = get_vcs_instance(
2047 repo_path=safe_str(self.repo_full_path),
2058 repo_path=safe_str(self.repo_full_path),
2048 config=config,
2059 config=config,
2049 with_wire=custom_wire,
2060 with_wire=custom_wire,
2050 create=False,
2061 create=False,
2051 _vcs_alias=self.repo_type)
2062 _vcs_alias=self.repo_type)
2052
2063
2053 return repo
2064 return repo
2054
2065
2055 def __json__(self):
2066 def __json__(self):
2056 return {'landing_rev': self.landing_rev}
2067 return {'landing_rev': self.landing_rev}
2057
2068
2058 def get_dict(self):
2069 def get_dict(self):
2059
2070
2060 # Since we transformed `repo_name` to a hybrid property, we need to
2071 # Since we transformed `repo_name` to a hybrid property, we need to
2061 # keep compatibility with the code which uses `repo_name` field.
2072 # keep compatibility with the code which uses `repo_name` field.
2062
2073
2063 result = super(Repository, self).get_dict()
2074 result = super(Repository, self).get_dict()
2064 result['repo_name'] = result.pop('_repo_name', None)
2075 result['repo_name'] = result.pop('_repo_name', None)
2065 return result
2076 return result
2066
2077
2067
2078
2068 class RepoGroup(Base, BaseModel):
2079 class RepoGroup(Base, BaseModel):
2069 __tablename__ = 'groups'
2080 __tablename__ = 'groups'
2070 __table_args__ = (
2081 __table_args__ = (
2071 UniqueConstraint('group_name', 'group_parent_id'),
2082 UniqueConstraint('group_name', 'group_parent_id'),
2072 CheckConstraint('group_id != group_parent_id'),
2083 CheckConstraint('group_id != group_parent_id'),
2073 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2074 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2075 )
2086 )
2076 __mapper_args__ = {'order_by': 'group_name'}
2087 __mapper_args__ = {'order_by': 'group_name'}
2077
2088
2078 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2089 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2079
2090
2080 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2091 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2081 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2092 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2082 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2093 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2083 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2094 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2084 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2095 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2085 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2096 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2097 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2087 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2098 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2088
2099
2089 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2100 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2090 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2101 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2091 parent_group = relationship('RepoGroup', remote_side=group_id)
2102 parent_group = relationship('RepoGroup', remote_side=group_id)
2092 user = relationship('User')
2103 user = relationship('User')
2093 integrations = relationship('Integration',
2104 integrations = relationship('Integration',
2094 cascade="all, delete, delete-orphan")
2105 cascade="all, delete, delete-orphan")
2095
2106
2096 def __init__(self, group_name='', parent_group=None):
2107 def __init__(self, group_name='', parent_group=None):
2097 self.group_name = group_name
2108 self.group_name = group_name
2098 self.parent_group = parent_group
2109 self.parent_group = parent_group
2099
2110
2100 def __unicode__(self):
2111 def __unicode__(self):
2101 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2112 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2102 self.group_name)
2113 self.group_name)
2103
2114
2104 @classmethod
2115 @classmethod
2105 def _generate_choice(cls, repo_group):
2116 def _generate_choice(cls, repo_group):
2106 from webhelpers.html import literal as _literal
2117 from webhelpers.html import literal as _literal
2107 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2118 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2108 return repo_group.group_id, _name(repo_group.full_path_splitted)
2119 return repo_group.group_id, _name(repo_group.full_path_splitted)
2109
2120
2110 @classmethod
2121 @classmethod
2111 def groups_choices(cls, groups=None, show_empty_group=True):
2122 def groups_choices(cls, groups=None, show_empty_group=True):
2112 if not groups:
2123 if not groups:
2113 groups = cls.query().all()
2124 groups = cls.query().all()
2114
2125
2115 repo_groups = []
2126 repo_groups = []
2116 if show_empty_group:
2127 if show_empty_group:
2117 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2128 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2118
2129
2119 repo_groups.extend([cls._generate_choice(x) for x in groups])
2130 repo_groups.extend([cls._generate_choice(x) for x in groups])
2120
2131
2121 repo_groups = sorted(
2132 repo_groups = sorted(
2122 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2133 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2123 return repo_groups
2134 return repo_groups
2124
2135
2125 @classmethod
2136 @classmethod
2126 def url_sep(cls):
2137 def url_sep(cls):
2127 return URL_SEP
2138 return URL_SEP
2128
2139
2129 @classmethod
2140 @classmethod
2130 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2141 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2131 if case_insensitive:
2142 if case_insensitive:
2132 gr = cls.query().filter(func.lower(cls.group_name)
2143 gr = cls.query().filter(func.lower(cls.group_name)
2133 == func.lower(group_name))
2144 == func.lower(group_name))
2134 else:
2145 else:
2135 gr = cls.query().filter(cls.group_name == group_name)
2146 gr = cls.query().filter(cls.group_name == group_name)
2136 if cache:
2147 if cache:
2137 gr = gr.options(FromCache(
2148 gr = gr.options(FromCache(
2138 "sql_cache_short",
2149 "sql_cache_short",
2139 "get_group_%s" % _hash_key(group_name)))
2150 "get_group_%s" % _hash_key(group_name)))
2140 return gr.scalar()
2151 return gr.scalar()
2141
2152
2142 @classmethod
2153 @classmethod
2143 def get_user_personal_repo_group(cls, user_id):
2154 def get_user_personal_repo_group(cls, user_id):
2144 user = User.get(user_id)
2155 user = User.get(user_id)
2145 return cls.query()\
2156 return cls.query()\
2146 .filter(cls.personal == true())\
2157 .filter(cls.personal == true())\
2147 .filter(cls.user == user).scalar()
2158 .filter(cls.user == user).scalar()
2148
2159
2149 @classmethod
2160 @classmethod
2150 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2161 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2151 case_insensitive=True):
2162 case_insensitive=True):
2152 q = RepoGroup.query()
2163 q = RepoGroup.query()
2153
2164
2154 if not isinstance(user_id, Optional):
2165 if not isinstance(user_id, Optional):
2155 q = q.filter(RepoGroup.user_id == user_id)
2166 q = q.filter(RepoGroup.user_id == user_id)
2156
2167
2157 if not isinstance(group_id, Optional):
2168 if not isinstance(group_id, Optional):
2158 q = q.filter(RepoGroup.group_parent_id == group_id)
2169 q = q.filter(RepoGroup.group_parent_id == group_id)
2159
2170
2160 if case_insensitive:
2171 if case_insensitive:
2161 q = q.order_by(func.lower(RepoGroup.group_name))
2172 q = q.order_by(func.lower(RepoGroup.group_name))
2162 else:
2173 else:
2163 q = q.order_by(RepoGroup.group_name)
2174 q = q.order_by(RepoGroup.group_name)
2164 return q.all()
2175 return q.all()
2165
2176
2166 @property
2177 @property
2167 def parents(self):
2178 def parents(self):
2168 parents_recursion_limit = 10
2179 parents_recursion_limit = 10
2169 groups = []
2180 groups = []
2170 if self.parent_group is None:
2181 if self.parent_group is None:
2171 return groups
2182 return groups
2172 cur_gr = self.parent_group
2183 cur_gr = self.parent_group
2173 groups.insert(0, cur_gr)
2184 groups.insert(0, cur_gr)
2174 cnt = 0
2185 cnt = 0
2175 while 1:
2186 while 1:
2176 cnt += 1
2187 cnt += 1
2177 gr = getattr(cur_gr, 'parent_group', None)
2188 gr = getattr(cur_gr, 'parent_group', None)
2178 cur_gr = cur_gr.parent_group
2189 cur_gr = cur_gr.parent_group
2179 if gr is None:
2190 if gr is None:
2180 break
2191 break
2181 if cnt == parents_recursion_limit:
2192 if cnt == parents_recursion_limit:
2182 # this will prevent accidental infinit loops
2193 # this will prevent accidental infinit loops
2183 log.error(('more than %s parents found for group %s, stopping '
2194 log.error(('more than %s parents found for group %s, stopping '
2184 'recursive parent fetching' % (parents_recursion_limit, self)))
2195 'recursive parent fetching' % (parents_recursion_limit, self)))
2185 break
2196 break
2186
2197
2187 groups.insert(0, gr)
2198 groups.insert(0, gr)
2188 return groups
2199 return groups
2189
2200
2190 @property
2201 @property
2191 def children(self):
2202 def children(self):
2192 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2203 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2193
2204
2194 @property
2205 @property
2195 def name(self):
2206 def name(self):
2196 return self.group_name.split(RepoGroup.url_sep())[-1]
2207 return self.group_name.split(RepoGroup.url_sep())[-1]
2197
2208
2198 @property
2209 @property
2199 def full_path(self):
2210 def full_path(self):
2200 return self.group_name
2211 return self.group_name
2201
2212
2202 @property
2213 @property
2203 def full_path_splitted(self):
2214 def full_path_splitted(self):
2204 return self.group_name.split(RepoGroup.url_sep())
2215 return self.group_name.split(RepoGroup.url_sep())
2205
2216
2206 @property
2217 @property
2207 def repositories(self):
2218 def repositories(self):
2208 return Repository.query()\
2219 return Repository.query()\
2209 .filter(Repository.group == self)\
2220 .filter(Repository.group == self)\
2210 .order_by(Repository.repo_name)
2221 .order_by(Repository.repo_name)
2211
2222
2212 @property
2223 @property
2213 def repositories_recursive_count(self):
2224 def repositories_recursive_count(self):
2214 cnt = self.repositories.count()
2225 cnt = self.repositories.count()
2215
2226
2216 def children_count(group):
2227 def children_count(group):
2217 cnt = 0
2228 cnt = 0
2218 for child in group.children:
2229 for child in group.children:
2219 cnt += child.repositories.count()
2230 cnt += child.repositories.count()
2220 cnt += children_count(child)
2231 cnt += children_count(child)
2221 return cnt
2232 return cnt
2222
2233
2223 return cnt + children_count(self)
2234 return cnt + children_count(self)
2224
2235
2225 def _recursive_objects(self, include_repos=True):
2236 def _recursive_objects(self, include_repos=True):
2226 all_ = []
2237 all_ = []
2227
2238
2228 def _get_members(root_gr):
2239 def _get_members(root_gr):
2229 if include_repos:
2240 if include_repos:
2230 for r in root_gr.repositories:
2241 for r in root_gr.repositories:
2231 all_.append(r)
2242 all_.append(r)
2232 childs = root_gr.children.all()
2243 childs = root_gr.children.all()
2233 if childs:
2244 if childs:
2234 for gr in childs:
2245 for gr in childs:
2235 all_.append(gr)
2246 all_.append(gr)
2236 _get_members(gr)
2247 _get_members(gr)
2237
2248
2238 _get_members(self)
2249 _get_members(self)
2239 return [self] + all_
2250 return [self] + all_
2240
2251
2241 def recursive_groups_and_repos(self):
2252 def recursive_groups_and_repos(self):
2242 """
2253 """
2243 Recursive return all groups, with repositories in those groups
2254 Recursive return all groups, with repositories in those groups
2244 """
2255 """
2245 return self._recursive_objects()
2256 return self._recursive_objects()
2246
2257
2247 def recursive_groups(self):
2258 def recursive_groups(self):
2248 """
2259 """
2249 Returns all children groups for this group including children of children
2260 Returns all children groups for this group including children of children
2250 """
2261 """
2251 return self._recursive_objects(include_repos=False)
2262 return self._recursive_objects(include_repos=False)
2252
2263
2253 def get_new_name(self, group_name):
2264 def get_new_name(self, group_name):
2254 """
2265 """
2255 returns new full group name based on parent and new name
2266 returns new full group name based on parent and new name
2256
2267
2257 :param group_name:
2268 :param group_name:
2258 """
2269 """
2259 path_prefix = (self.parent_group.full_path_splitted if
2270 path_prefix = (self.parent_group.full_path_splitted if
2260 self.parent_group else [])
2271 self.parent_group else [])
2261 return RepoGroup.url_sep().join(path_prefix + [group_name])
2272 return RepoGroup.url_sep().join(path_prefix + [group_name])
2262
2273
2263 def permissions(self, with_admins=True, with_owner=True):
2274 def permissions(self, with_admins=True, with_owner=True):
2264 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2275 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2265 q = q.options(joinedload(UserRepoGroupToPerm.group),
2276 q = q.options(joinedload(UserRepoGroupToPerm.group),
2266 joinedload(UserRepoGroupToPerm.user),
2277 joinedload(UserRepoGroupToPerm.user),
2267 joinedload(UserRepoGroupToPerm.permission),)
2278 joinedload(UserRepoGroupToPerm.permission),)
2268
2279
2269 # get owners and admins and permissions. We do a trick of re-writing
2280 # get owners and admins and permissions. We do a trick of re-writing
2270 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2281 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2271 # has a global reference and changing one object propagates to all
2282 # has a global reference and changing one object propagates to all
2272 # others. This means if admin is also an owner admin_row that change
2283 # others. This means if admin is also an owner admin_row that change
2273 # would propagate to both objects
2284 # would propagate to both objects
2274 perm_rows = []
2285 perm_rows = []
2275 for _usr in q.all():
2286 for _usr in q.all():
2276 usr = AttributeDict(_usr.user.get_dict())
2287 usr = AttributeDict(_usr.user.get_dict())
2277 usr.permission = _usr.permission.permission_name
2288 usr.permission = _usr.permission.permission_name
2278 perm_rows.append(usr)
2289 perm_rows.append(usr)
2279
2290
2280 # filter the perm rows by 'default' first and then sort them by
2291 # filter the perm rows by 'default' first and then sort them by
2281 # admin,write,read,none permissions sorted again alphabetically in
2292 # admin,write,read,none permissions sorted again alphabetically in
2282 # each group
2293 # each group
2283 perm_rows = sorted(perm_rows, key=display_sort)
2294 perm_rows = sorted(perm_rows, key=display_sort)
2284
2295
2285 _admin_perm = 'group.admin'
2296 _admin_perm = 'group.admin'
2286 owner_row = []
2297 owner_row = []
2287 if with_owner:
2298 if with_owner:
2288 usr = AttributeDict(self.user.get_dict())
2299 usr = AttributeDict(self.user.get_dict())
2289 usr.owner_row = True
2300 usr.owner_row = True
2290 usr.permission = _admin_perm
2301 usr.permission = _admin_perm
2291 owner_row.append(usr)
2302 owner_row.append(usr)
2292
2303
2293 super_admin_rows = []
2304 super_admin_rows = []
2294 if with_admins:
2305 if with_admins:
2295 for usr in User.get_all_super_admins():
2306 for usr in User.get_all_super_admins():
2296 # if this admin is also owner, don't double the record
2307 # if this admin is also owner, don't double the record
2297 if usr.user_id == owner_row[0].user_id:
2308 if usr.user_id == owner_row[0].user_id:
2298 owner_row[0].admin_row = True
2309 owner_row[0].admin_row = True
2299 else:
2310 else:
2300 usr = AttributeDict(usr.get_dict())
2311 usr = AttributeDict(usr.get_dict())
2301 usr.admin_row = True
2312 usr.admin_row = True
2302 usr.permission = _admin_perm
2313 usr.permission = _admin_perm
2303 super_admin_rows.append(usr)
2314 super_admin_rows.append(usr)
2304
2315
2305 return super_admin_rows + owner_row + perm_rows
2316 return super_admin_rows + owner_row + perm_rows
2306
2317
2307 def permission_user_groups(self):
2318 def permission_user_groups(self):
2308 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2319 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2309 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2320 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2310 joinedload(UserGroupRepoGroupToPerm.users_group),
2321 joinedload(UserGroupRepoGroupToPerm.users_group),
2311 joinedload(UserGroupRepoGroupToPerm.permission),)
2322 joinedload(UserGroupRepoGroupToPerm.permission),)
2312
2323
2313 perm_rows = []
2324 perm_rows = []
2314 for _user_group in q.all():
2325 for _user_group in q.all():
2315 usr = AttributeDict(_user_group.users_group.get_dict())
2326 usr = AttributeDict(_user_group.users_group.get_dict())
2316 usr.permission = _user_group.permission.permission_name
2327 usr.permission = _user_group.permission.permission_name
2317 perm_rows.append(usr)
2328 perm_rows.append(usr)
2318
2329
2319 return perm_rows
2330 return perm_rows
2320
2331
2321 def get_api_data(self):
2332 def get_api_data(self):
2322 """
2333 """
2323 Common function for generating api data
2334 Common function for generating api data
2324
2335
2325 """
2336 """
2326 group = self
2337 group = self
2327 data = {
2338 data = {
2328 'group_id': group.group_id,
2339 'group_id': group.group_id,
2329 'group_name': group.group_name,
2340 'group_name': group.group_name,
2330 'group_description': group.group_description,
2341 'group_description': group.group_description,
2331 'parent_group': group.parent_group.group_name if group.parent_group else None,
2342 'parent_group': group.parent_group.group_name if group.parent_group else None,
2332 'repositories': [x.repo_name for x in group.repositories],
2343 'repositories': [x.repo_name for x in group.repositories],
2333 'owner': group.user.username,
2344 'owner': group.user.username,
2334 }
2345 }
2335 return data
2346 return data
2336
2347
2337
2348
2338 class Permission(Base, BaseModel):
2349 class Permission(Base, BaseModel):
2339 __tablename__ = 'permissions'
2350 __tablename__ = 'permissions'
2340 __table_args__ = (
2351 __table_args__ = (
2341 Index('p_perm_name_idx', 'permission_name'),
2352 Index('p_perm_name_idx', 'permission_name'),
2342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2353 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2343 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2354 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2344 )
2355 )
2345 PERMS = [
2356 PERMS = [
2346 ('hg.admin', _('RhodeCode Super Administrator')),
2357 ('hg.admin', _('RhodeCode Super Administrator')),
2347
2358
2348 ('repository.none', _('Repository no access')),
2359 ('repository.none', _('Repository no access')),
2349 ('repository.read', _('Repository read access')),
2360 ('repository.read', _('Repository read access')),
2350 ('repository.write', _('Repository write access')),
2361 ('repository.write', _('Repository write access')),
2351 ('repository.admin', _('Repository admin access')),
2362 ('repository.admin', _('Repository admin access')),
2352
2363
2353 ('group.none', _('Repository group no access')),
2364 ('group.none', _('Repository group no access')),
2354 ('group.read', _('Repository group read access')),
2365 ('group.read', _('Repository group read access')),
2355 ('group.write', _('Repository group write access')),
2366 ('group.write', _('Repository group write access')),
2356 ('group.admin', _('Repository group admin access')),
2367 ('group.admin', _('Repository group admin access')),
2357
2368
2358 ('usergroup.none', _('User group no access')),
2369 ('usergroup.none', _('User group no access')),
2359 ('usergroup.read', _('User group read access')),
2370 ('usergroup.read', _('User group read access')),
2360 ('usergroup.write', _('User group write access')),
2371 ('usergroup.write', _('User group write access')),
2361 ('usergroup.admin', _('User group admin access')),
2372 ('usergroup.admin', _('User group admin access')),
2362
2373
2363 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2374 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2364 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2375 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2365
2376
2366 ('hg.usergroup.create.false', _('User Group creation disabled')),
2377 ('hg.usergroup.create.false', _('User Group creation disabled')),
2367 ('hg.usergroup.create.true', _('User Group creation enabled')),
2378 ('hg.usergroup.create.true', _('User Group creation enabled')),
2368
2379
2369 ('hg.create.none', _('Repository creation disabled')),
2380 ('hg.create.none', _('Repository creation disabled')),
2370 ('hg.create.repository', _('Repository creation enabled')),
2381 ('hg.create.repository', _('Repository creation enabled')),
2371 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2382 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2372 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2383 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2373
2384
2374 ('hg.fork.none', _('Repository forking disabled')),
2385 ('hg.fork.none', _('Repository forking disabled')),
2375 ('hg.fork.repository', _('Repository forking enabled')),
2386 ('hg.fork.repository', _('Repository forking enabled')),
2376
2387
2377 ('hg.register.none', _('Registration disabled')),
2388 ('hg.register.none', _('Registration disabled')),
2378 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2389 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2379 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2390 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2380
2391
2381 ('hg.password_reset.enabled', _('Password reset enabled')),
2392 ('hg.password_reset.enabled', _('Password reset enabled')),
2382 ('hg.password_reset.hidden', _('Password reset hidden')),
2393 ('hg.password_reset.hidden', _('Password reset hidden')),
2383 ('hg.password_reset.disabled', _('Password reset disabled')),
2394 ('hg.password_reset.disabled', _('Password reset disabled')),
2384
2395
2385 ('hg.extern_activate.manual', _('Manual activation of external account')),
2396 ('hg.extern_activate.manual', _('Manual activation of external account')),
2386 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2397 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2387
2398
2388 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2399 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2389 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2400 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2390 ]
2401 ]
2391
2402
2392 # definition of system default permissions for DEFAULT user
2403 # definition of system default permissions for DEFAULT user
2393 DEFAULT_USER_PERMISSIONS = [
2404 DEFAULT_USER_PERMISSIONS = [
2394 'repository.read',
2405 'repository.read',
2395 'group.read',
2406 'group.read',
2396 'usergroup.read',
2407 'usergroup.read',
2397 'hg.create.repository',
2408 'hg.create.repository',
2398 'hg.repogroup.create.false',
2409 'hg.repogroup.create.false',
2399 'hg.usergroup.create.false',
2410 'hg.usergroup.create.false',
2400 'hg.create.write_on_repogroup.true',
2411 'hg.create.write_on_repogroup.true',
2401 'hg.fork.repository',
2412 'hg.fork.repository',
2402 'hg.register.manual_activate',
2413 'hg.register.manual_activate',
2403 'hg.password_reset.enabled',
2414 'hg.password_reset.enabled',
2404 'hg.extern_activate.auto',
2415 'hg.extern_activate.auto',
2405 'hg.inherit_default_perms.true',
2416 'hg.inherit_default_perms.true',
2406 ]
2417 ]
2407
2418
2408 # defines which permissions are more important higher the more important
2419 # defines which permissions are more important higher the more important
2409 # Weight defines which permissions are more important.
2420 # Weight defines which permissions are more important.
2410 # The higher number the more important.
2421 # The higher number the more important.
2411 PERM_WEIGHTS = {
2422 PERM_WEIGHTS = {
2412 'repository.none': 0,
2423 'repository.none': 0,
2413 'repository.read': 1,
2424 'repository.read': 1,
2414 'repository.write': 3,
2425 'repository.write': 3,
2415 'repository.admin': 4,
2426 'repository.admin': 4,
2416
2427
2417 'group.none': 0,
2428 'group.none': 0,
2418 'group.read': 1,
2429 'group.read': 1,
2419 'group.write': 3,
2430 'group.write': 3,
2420 'group.admin': 4,
2431 'group.admin': 4,
2421
2432
2422 'usergroup.none': 0,
2433 'usergroup.none': 0,
2423 'usergroup.read': 1,
2434 'usergroup.read': 1,
2424 'usergroup.write': 3,
2435 'usergroup.write': 3,
2425 'usergroup.admin': 4,
2436 'usergroup.admin': 4,
2426
2437
2427 'hg.repogroup.create.false': 0,
2438 'hg.repogroup.create.false': 0,
2428 'hg.repogroup.create.true': 1,
2439 'hg.repogroup.create.true': 1,
2429
2440
2430 'hg.usergroup.create.false': 0,
2441 'hg.usergroup.create.false': 0,
2431 'hg.usergroup.create.true': 1,
2442 'hg.usergroup.create.true': 1,
2432
2443
2433 'hg.fork.none': 0,
2444 'hg.fork.none': 0,
2434 'hg.fork.repository': 1,
2445 'hg.fork.repository': 1,
2435 'hg.create.none': 0,
2446 'hg.create.none': 0,
2436 'hg.create.repository': 1
2447 'hg.create.repository': 1
2437 }
2448 }
2438
2449
2439 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2450 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2440 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2451 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2441 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2452 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2442
2453
2443 def __unicode__(self):
2454 def __unicode__(self):
2444 return u"<%s('%s:%s')>" % (
2455 return u"<%s('%s:%s')>" % (
2445 self.__class__.__name__, self.permission_id, self.permission_name
2456 self.__class__.__name__, self.permission_id, self.permission_name
2446 )
2457 )
2447
2458
2448 @classmethod
2459 @classmethod
2449 def get_by_key(cls, key):
2460 def get_by_key(cls, key):
2450 return cls.query().filter(cls.permission_name == key).scalar()
2461 return cls.query().filter(cls.permission_name == key).scalar()
2451
2462
2452 @classmethod
2463 @classmethod
2453 def get_default_repo_perms(cls, user_id, repo_id=None):
2464 def get_default_repo_perms(cls, user_id, repo_id=None):
2454 q = Session().query(UserRepoToPerm, Repository, Permission)\
2465 q = Session().query(UserRepoToPerm, Repository, Permission)\
2455 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2466 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2456 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2467 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2457 .filter(UserRepoToPerm.user_id == user_id)
2468 .filter(UserRepoToPerm.user_id == user_id)
2458 if repo_id:
2469 if repo_id:
2459 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2470 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2460 return q.all()
2471 return q.all()
2461
2472
2462 @classmethod
2473 @classmethod
2463 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2474 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2464 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2475 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2465 .join(
2476 .join(
2466 Permission,
2477 Permission,
2467 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2478 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2468 .join(
2479 .join(
2469 Repository,
2480 Repository,
2470 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2481 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2471 .join(
2482 .join(
2472 UserGroup,
2483 UserGroup,
2473 UserGroupRepoToPerm.users_group_id ==
2484 UserGroupRepoToPerm.users_group_id ==
2474 UserGroup.users_group_id)\
2485 UserGroup.users_group_id)\
2475 .join(
2486 .join(
2476 UserGroupMember,
2487 UserGroupMember,
2477 UserGroupRepoToPerm.users_group_id ==
2488 UserGroupRepoToPerm.users_group_id ==
2478 UserGroupMember.users_group_id)\
2489 UserGroupMember.users_group_id)\
2479 .filter(
2490 .filter(
2480 UserGroupMember.user_id == user_id,
2491 UserGroupMember.user_id == user_id,
2481 UserGroup.users_group_active == true())
2492 UserGroup.users_group_active == true())
2482 if repo_id:
2493 if repo_id:
2483 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2494 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2484 return q.all()
2495 return q.all()
2485
2496
2486 @classmethod
2497 @classmethod
2487 def get_default_group_perms(cls, user_id, repo_group_id=None):
2498 def get_default_group_perms(cls, user_id, repo_group_id=None):
2488 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2499 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2489 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2500 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2490 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2501 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2491 .filter(UserRepoGroupToPerm.user_id == user_id)
2502 .filter(UserRepoGroupToPerm.user_id == user_id)
2492 if repo_group_id:
2503 if repo_group_id:
2493 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2504 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2494 return q.all()
2505 return q.all()
2495
2506
2496 @classmethod
2507 @classmethod
2497 def get_default_group_perms_from_user_group(
2508 def get_default_group_perms_from_user_group(
2498 cls, user_id, repo_group_id=None):
2509 cls, user_id, repo_group_id=None):
2499 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2510 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2500 .join(
2511 .join(
2501 Permission,
2512 Permission,
2502 UserGroupRepoGroupToPerm.permission_id ==
2513 UserGroupRepoGroupToPerm.permission_id ==
2503 Permission.permission_id)\
2514 Permission.permission_id)\
2504 .join(
2515 .join(
2505 RepoGroup,
2516 RepoGroup,
2506 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2517 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2507 .join(
2518 .join(
2508 UserGroup,
2519 UserGroup,
2509 UserGroupRepoGroupToPerm.users_group_id ==
2520 UserGroupRepoGroupToPerm.users_group_id ==
2510 UserGroup.users_group_id)\
2521 UserGroup.users_group_id)\
2511 .join(
2522 .join(
2512 UserGroupMember,
2523 UserGroupMember,
2513 UserGroupRepoGroupToPerm.users_group_id ==
2524 UserGroupRepoGroupToPerm.users_group_id ==
2514 UserGroupMember.users_group_id)\
2525 UserGroupMember.users_group_id)\
2515 .filter(
2526 .filter(
2516 UserGroupMember.user_id == user_id,
2527 UserGroupMember.user_id == user_id,
2517 UserGroup.users_group_active == true())
2528 UserGroup.users_group_active == true())
2518 if repo_group_id:
2529 if repo_group_id:
2519 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2530 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2520 return q.all()
2531 return q.all()
2521
2532
2522 @classmethod
2533 @classmethod
2523 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2534 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2524 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2535 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2525 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2536 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2526 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2537 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2527 .filter(UserUserGroupToPerm.user_id == user_id)
2538 .filter(UserUserGroupToPerm.user_id == user_id)
2528 if user_group_id:
2539 if user_group_id:
2529 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2540 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2530 return q.all()
2541 return q.all()
2531
2542
2532 @classmethod
2543 @classmethod
2533 def get_default_user_group_perms_from_user_group(
2544 def get_default_user_group_perms_from_user_group(
2534 cls, user_id, user_group_id=None):
2545 cls, user_id, user_group_id=None):
2535 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2546 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2536 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2547 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2537 .join(
2548 .join(
2538 Permission,
2549 Permission,
2539 UserGroupUserGroupToPerm.permission_id ==
2550 UserGroupUserGroupToPerm.permission_id ==
2540 Permission.permission_id)\
2551 Permission.permission_id)\
2541 .join(
2552 .join(
2542 TargetUserGroup,
2553 TargetUserGroup,
2543 UserGroupUserGroupToPerm.target_user_group_id ==
2554 UserGroupUserGroupToPerm.target_user_group_id ==
2544 TargetUserGroup.users_group_id)\
2555 TargetUserGroup.users_group_id)\
2545 .join(
2556 .join(
2546 UserGroup,
2557 UserGroup,
2547 UserGroupUserGroupToPerm.user_group_id ==
2558 UserGroupUserGroupToPerm.user_group_id ==
2548 UserGroup.users_group_id)\
2559 UserGroup.users_group_id)\
2549 .join(
2560 .join(
2550 UserGroupMember,
2561 UserGroupMember,
2551 UserGroupUserGroupToPerm.user_group_id ==
2562 UserGroupUserGroupToPerm.user_group_id ==
2552 UserGroupMember.users_group_id)\
2563 UserGroupMember.users_group_id)\
2553 .filter(
2564 .filter(
2554 UserGroupMember.user_id == user_id,
2565 UserGroupMember.user_id == user_id,
2555 UserGroup.users_group_active == true())
2566 UserGroup.users_group_active == true())
2556 if user_group_id:
2567 if user_group_id:
2557 q = q.filter(
2568 q = q.filter(
2558 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2569 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2559
2570
2560 return q.all()
2571 return q.all()
2561
2572
2562
2573
2563 class UserRepoToPerm(Base, BaseModel):
2574 class UserRepoToPerm(Base, BaseModel):
2564 __tablename__ = 'repo_to_perm'
2575 __tablename__ = 'repo_to_perm'
2565 __table_args__ = (
2576 __table_args__ = (
2566 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2577 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2579 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2569 )
2580 )
2570 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2581 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2571 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2582 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2583 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2573 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2584 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2574
2585
2575 user = relationship('User')
2586 user = relationship('User')
2576 repository = relationship('Repository')
2587 repository = relationship('Repository')
2577 permission = relationship('Permission')
2588 permission = relationship('Permission')
2578
2589
2579 @classmethod
2590 @classmethod
2580 def create(cls, user, repository, permission):
2591 def create(cls, user, repository, permission):
2581 n = cls()
2592 n = cls()
2582 n.user = user
2593 n.user = user
2583 n.repository = repository
2594 n.repository = repository
2584 n.permission = permission
2595 n.permission = permission
2585 Session().add(n)
2596 Session().add(n)
2586 return n
2597 return n
2587
2598
2588 def __unicode__(self):
2599 def __unicode__(self):
2589 return u'<%s => %s >' % (self.user, self.repository)
2600 return u'<%s => %s >' % (self.user, self.repository)
2590
2601
2591
2602
2592 class UserUserGroupToPerm(Base, BaseModel):
2603 class UserUserGroupToPerm(Base, BaseModel):
2593 __tablename__ = 'user_user_group_to_perm'
2604 __tablename__ = 'user_user_group_to_perm'
2594 __table_args__ = (
2605 __table_args__ = (
2595 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2606 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2596 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2607 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2597 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2608 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2598 )
2609 )
2599 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2610 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2600 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2601 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2612 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2602 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2613 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2603
2614
2604 user = relationship('User')
2615 user = relationship('User')
2605 user_group = relationship('UserGroup')
2616 user_group = relationship('UserGroup')
2606 permission = relationship('Permission')
2617 permission = relationship('Permission')
2607
2618
2608 @classmethod
2619 @classmethod
2609 def create(cls, user, user_group, permission):
2620 def create(cls, user, user_group, permission):
2610 n = cls()
2621 n = cls()
2611 n.user = user
2622 n.user = user
2612 n.user_group = user_group
2623 n.user_group = user_group
2613 n.permission = permission
2624 n.permission = permission
2614 Session().add(n)
2625 Session().add(n)
2615 return n
2626 return n
2616
2627
2617 def __unicode__(self):
2628 def __unicode__(self):
2618 return u'<%s => %s >' % (self.user, self.user_group)
2629 return u'<%s => %s >' % (self.user, self.user_group)
2619
2630
2620
2631
2621 class UserToPerm(Base, BaseModel):
2632 class UserToPerm(Base, BaseModel):
2622 __tablename__ = 'user_to_perm'
2633 __tablename__ = 'user_to_perm'
2623 __table_args__ = (
2634 __table_args__ = (
2624 UniqueConstraint('user_id', 'permission_id'),
2635 UniqueConstraint('user_id', 'permission_id'),
2625 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2626 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2637 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2627 )
2638 )
2628 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2639 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2629 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2640 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2630 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2641 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2631
2642
2632 user = relationship('User')
2643 user = relationship('User')
2633 permission = relationship('Permission', lazy='joined')
2644 permission = relationship('Permission', lazy='joined')
2634
2645
2635 def __unicode__(self):
2646 def __unicode__(self):
2636 return u'<%s => %s >' % (self.user, self.permission)
2647 return u'<%s => %s >' % (self.user, self.permission)
2637
2648
2638
2649
2639 class UserGroupRepoToPerm(Base, BaseModel):
2650 class UserGroupRepoToPerm(Base, BaseModel):
2640 __tablename__ = 'users_group_repo_to_perm'
2651 __tablename__ = 'users_group_repo_to_perm'
2641 __table_args__ = (
2652 __table_args__ = (
2642 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2653 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2643 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2654 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2644 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2655 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2645 )
2656 )
2646 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2657 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2647 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2658 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2648 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2659 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2649 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2660 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2650
2661
2651 users_group = relationship('UserGroup')
2662 users_group = relationship('UserGroup')
2652 permission = relationship('Permission')
2663 permission = relationship('Permission')
2653 repository = relationship('Repository')
2664 repository = relationship('Repository')
2654
2665
2655 @classmethod
2666 @classmethod
2656 def create(cls, users_group, repository, permission):
2667 def create(cls, users_group, repository, permission):
2657 n = cls()
2668 n = cls()
2658 n.users_group = users_group
2669 n.users_group = users_group
2659 n.repository = repository
2670 n.repository = repository
2660 n.permission = permission
2671 n.permission = permission
2661 Session().add(n)
2672 Session().add(n)
2662 return n
2673 return n
2663
2674
2664 def __unicode__(self):
2675 def __unicode__(self):
2665 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2676 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2666
2677
2667
2678
2668 class UserGroupUserGroupToPerm(Base, BaseModel):
2679 class UserGroupUserGroupToPerm(Base, BaseModel):
2669 __tablename__ = 'user_group_user_group_to_perm'
2680 __tablename__ = 'user_group_user_group_to_perm'
2670 __table_args__ = (
2681 __table_args__ = (
2671 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2682 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2672 CheckConstraint('target_user_group_id != user_group_id'),
2683 CheckConstraint('target_user_group_id != user_group_id'),
2673 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2674 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2685 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2675 )
2686 )
2676 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)
2687 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)
2677 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2688 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2678 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2689 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2679 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2690 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2680
2691
2681 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2692 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2682 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2693 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2683 permission = relationship('Permission')
2694 permission = relationship('Permission')
2684
2695
2685 @classmethod
2696 @classmethod
2686 def create(cls, target_user_group, user_group, permission):
2697 def create(cls, target_user_group, user_group, permission):
2687 n = cls()
2698 n = cls()
2688 n.target_user_group = target_user_group
2699 n.target_user_group = target_user_group
2689 n.user_group = user_group
2700 n.user_group = user_group
2690 n.permission = permission
2701 n.permission = permission
2691 Session().add(n)
2702 Session().add(n)
2692 return n
2703 return n
2693
2704
2694 def __unicode__(self):
2705 def __unicode__(self):
2695 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2706 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2696
2707
2697
2708
2698 class UserGroupToPerm(Base, BaseModel):
2709 class UserGroupToPerm(Base, BaseModel):
2699 __tablename__ = 'users_group_to_perm'
2710 __tablename__ = 'users_group_to_perm'
2700 __table_args__ = (
2711 __table_args__ = (
2701 UniqueConstraint('users_group_id', 'permission_id',),
2712 UniqueConstraint('users_group_id', 'permission_id',),
2702 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2713 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2703 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2714 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2704 )
2715 )
2705 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2716 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2706 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2717 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2707 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2718 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2708
2719
2709 users_group = relationship('UserGroup')
2720 users_group = relationship('UserGroup')
2710 permission = relationship('Permission')
2721 permission = relationship('Permission')
2711
2722
2712
2723
2713 class UserRepoGroupToPerm(Base, BaseModel):
2724 class UserRepoGroupToPerm(Base, BaseModel):
2714 __tablename__ = 'user_repo_group_to_perm'
2725 __tablename__ = 'user_repo_group_to_perm'
2715 __table_args__ = (
2726 __table_args__ = (
2716 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2727 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2717 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2718 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2719 )
2730 )
2720
2731
2721 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2732 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2722 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2723 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2734 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2724 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2735 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2725
2736
2726 user = relationship('User')
2737 user = relationship('User')
2727 group = relationship('RepoGroup')
2738 group = relationship('RepoGroup')
2728 permission = relationship('Permission')
2739 permission = relationship('Permission')
2729
2740
2730 @classmethod
2741 @classmethod
2731 def create(cls, user, repository_group, permission):
2742 def create(cls, user, repository_group, permission):
2732 n = cls()
2743 n = cls()
2733 n.user = user
2744 n.user = user
2734 n.group = repository_group
2745 n.group = repository_group
2735 n.permission = permission
2746 n.permission = permission
2736 Session().add(n)
2747 Session().add(n)
2737 return n
2748 return n
2738
2749
2739
2750
2740 class UserGroupRepoGroupToPerm(Base, BaseModel):
2751 class UserGroupRepoGroupToPerm(Base, BaseModel):
2741 __tablename__ = 'users_group_repo_group_to_perm'
2752 __tablename__ = 'users_group_repo_group_to_perm'
2742 __table_args__ = (
2753 __table_args__ = (
2743 UniqueConstraint('users_group_id', 'group_id'),
2754 UniqueConstraint('users_group_id', 'group_id'),
2744 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2745 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2746 )
2757 )
2747
2758
2748 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)
2759 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)
2749 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2760 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2750 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2761 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2751 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2762 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2752
2763
2753 users_group = relationship('UserGroup')
2764 users_group = relationship('UserGroup')
2754 permission = relationship('Permission')
2765 permission = relationship('Permission')
2755 group = relationship('RepoGroup')
2766 group = relationship('RepoGroup')
2756
2767
2757 @classmethod
2768 @classmethod
2758 def create(cls, user_group, repository_group, permission):
2769 def create(cls, user_group, repository_group, permission):
2759 n = cls()
2770 n = cls()
2760 n.users_group = user_group
2771 n.users_group = user_group
2761 n.group = repository_group
2772 n.group = repository_group
2762 n.permission = permission
2773 n.permission = permission
2763 Session().add(n)
2774 Session().add(n)
2764 return n
2775 return n
2765
2776
2766 def __unicode__(self):
2777 def __unicode__(self):
2767 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2778 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2768
2779
2769
2780
2770 class Statistics(Base, BaseModel):
2781 class Statistics(Base, BaseModel):
2771 __tablename__ = 'statistics'
2782 __tablename__ = 'statistics'
2772 __table_args__ = (
2783 __table_args__ = (
2773 UniqueConstraint('repository_id'),
2784 UniqueConstraint('repository_id'),
2774 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2785 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2775 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2786 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2776 )
2787 )
2777 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2788 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2778 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2789 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2779 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2790 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2780 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2791 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2781 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2792 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2782 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2793 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2783
2794
2784 repository = relationship('Repository', single_parent=True)
2795 repository = relationship('Repository', single_parent=True)
2785
2796
2786
2797
2787 class UserFollowing(Base, BaseModel):
2798 class UserFollowing(Base, BaseModel):
2788 __tablename__ = 'user_followings'
2799 __tablename__ = 'user_followings'
2789 __table_args__ = (
2800 __table_args__ = (
2790 UniqueConstraint('user_id', 'follows_repository_id'),
2801 UniqueConstraint('user_id', 'follows_repository_id'),
2791 UniqueConstraint('user_id', 'follows_user_id'),
2802 UniqueConstraint('user_id', 'follows_user_id'),
2792 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2803 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2793 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2804 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2794 )
2805 )
2795
2806
2796 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2807 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2797 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2808 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2798 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2809 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2799 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2810 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2800 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2811 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2801
2812
2802 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2813 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2803
2814
2804 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2815 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2805 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2816 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2806
2817
2807 @classmethod
2818 @classmethod
2808 def get_repo_followers(cls, repo_id):
2819 def get_repo_followers(cls, repo_id):
2809 return cls.query().filter(cls.follows_repo_id == repo_id)
2820 return cls.query().filter(cls.follows_repo_id == repo_id)
2810
2821
2811
2822
2812 class CacheKey(Base, BaseModel):
2823 class CacheKey(Base, BaseModel):
2813 __tablename__ = 'cache_invalidation'
2824 __tablename__ = 'cache_invalidation'
2814 __table_args__ = (
2825 __table_args__ = (
2815 UniqueConstraint('cache_key'),
2826 UniqueConstraint('cache_key'),
2816 Index('key_idx', 'cache_key'),
2827 Index('key_idx', 'cache_key'),
2817 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2828 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2818 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2829 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2819 )
2830 )
2820 CACHE_TYPE_ATOM = 'ATOM'
2831 CACHE_TYPE_ATOM = 'ATOM'
2821 CACHE_TYPE_RSS = 'RSS'
2832 CACHE_TYPE_RSS = 'RSS'
2822 CACHE_TYPE_README = 'README'
2833 CACHE_TYPE_README = 'README'
2823
2834
2824 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2835 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2825 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2836 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2826 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2837 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2827 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2838 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2828
2839
2829 def __init__(self, cache_key, cache_args=''):
2840 def __init__(self, cache_key, cache_args=''):
2830 self.cache_key = cache_key
2841 self.cache_key = cache_key
2831 self.cache_args = cache_args
2842 self.cache_args = cache_args
2832 self.cache_active = False
2843 self.cache_active = False
2833
2844
2834 def __unicode__(self):
2845 def __unicode__(self):
2835 return u"<%s('%s:%s[%s]')>" % (
2846 return u"<%s('%s:%s[%s]')>" % (
2836 self.__class__.__name__,
2847 self.__class__.__name__,
2837 self.cache_id, self.cache_key, self.cache_active)
2848 self.cache_id, self.cache_key, self.cache_active)
2838
2849
2839 def _cache_key_partition(self):
2850 def _cache_key_partition(self):
2840 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2851 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2841 return prefix, repo_name, suffix
2852 return prefix, repo_name, suffix
2842
2853
2843 def get_prefix(self):
2854 def get_prefix(self):
2844 """
2855 """
2845 Try to extract prefix from existing cache key. The key could consist
2856 Try to extract prefix from existing cache key. The key could consist
2846 of prefix, repo_name, suffix
2857 of prefix, repo_name, suffix
2847 """
2858 """
2848 # this returns prefix, repo_name, suffix
2859 # this returns prefix, repo_name, suffix
2849 return self._cache_key_partition()[0]
2860 return self._cache_key_partition()[0]
2850
2861
2851 def get_suffix(self):
2862 def get_suffix(self):
2852 """
2863 """
2853 get suffix that might have been used in _get_cache_key to
2864 get suffix that might have been used in _get_cache_key to
2854 generate self.cache_key. Only used for informational purposes
2865 generate self.cache_key. Only used for informational purposes
2855 in repo_edit.mako.
2866 in repo_edit.mako.
2856 """
2867 """
2857 # prefix, repo_name, suffix
2868 # prefix, repo_name, suffix
2858 return self._cache_key_partition()[2]
2869 return self._cache_key_partition()[2]
2859
2870
2860 @classmethod
2871 @classmethod
2861 def delete_all_cache(cls):
2872 def delete_all_cache(cls):
2862 """
2873 """
2863 Delete all cache keys from database.
2874 Delete all cache keys from database.
2864 Should only be run when all instances are down and all entries
2875 Should only be run when all instances are down and all entries
2865 thus stale.
2876 thus stale.
2866 """
2877 """
2867 cls.query().delete()
2878 cls.query().delete()
2868 Session().commit()
2879 Session().commit()
2869
2880
2870 @classmethod
2881 @classmethod
2871 def get_cache_key(cls, repo_name, cache_type):
2882 def get_cache_key(cls, repo_name, cache_type):
2872 """
2883 """
2873
2884
2874 Generate a cache key for this process of RhodeCode instance.
2885 Generate a cache key for this process of RhodeCode instance.
2875 Prefix most likely will be process id or maybe explicitly set
2886 Prefix most likely will be process id or maybe explicitly set
2876 instance_id from .ini file.
2887 instance_id from .ini file.
2877 """
2888 """
2878 import rhodecode
2889 import rhodecode
2879 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2890 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2880
2891
2881 repo_as_unicode = safe_unicode(repo_name)
2892 repo_as_unicode = safe_unicode(repo_name)
2882 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2893 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2883 if cache_type else repo_as_unicode
2894 if cache_type else repo_as_unicode
2884
2895
2885 return u'{}{}'.format(prefix, key)
2896 return u'{}{}'.format(prefix, key)
2886
2897
2887 @classmethod
2898 @classmethod
2888 def set_invalidate(cls, repo_name, delete=False):
2899 def set_invalidate(cls, repo_name, delete=False):
2889 """
2900 """
2890 Mark all caches of a repo as invalid in the database.
2901 Mark all caches of a repo as invalid in the database.
2891 """
2902 """
2892
2903
2893 try:
2904 try:
2894 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2905 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2895 if delete:
2906 if delete:
2896 log.debug('cache objects deleted for repo %s',
2907 log.debug('cache objects deleted for repo %s',
2897 safe_str(repo_name))
2908 safe_str(repo_name))
2898 qry.delete()
2909 qry.delete()
2899 else:
2910 else:
2900 log.debug('cache objects marked as invalid for repo %s',
2911 log.debug('cache objects marked as invalid for repo %s',
2901 safe_str(repo_name))
2912 safe_str(repo_name))
2902 qry.update({"cache_active": False})
2913 qry.update({"cache_active": False})
2903
2914
2904 Session().commit()
2915 Session().commit()
2905 except Exception:
2916 except Exception:
2906 log.exception(
2917 log.exception(
2907 'Cache key invalidation failed for repository %s',
2918 'Cache key invalidation failed for repository %s',
2908 safe_str(repo_name))
2919 safe_str(repo_name))
2909 Session().rollback()
2920 Session().rollback()
2910
2921
2911 @classmethod
2922 @classmethod
2912 def get_active_cache(cls, cache_key):
2923 def get_active_cache(cls, cache_key):
2913 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2924 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2914 if inv_obj:
2925 if inv_obj:
2915 return inv_obj
2926 return inv_obj
2916 return None
2927 return None
2917
2928
2918 @classmethod
2929 @classmethod
2919 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2930 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2920 thread_scoped=False):
2931 thread_scoped=False):
2921 """
2932 """
2922 @cache_region('long_term')
2933 @cache_region('long_term')
2923 def _heavy_calculation(cache_key):
2934 def _heavy_calculation(cache_key):
2924 return 'result'
2935 return 'result'
2925
2936
2926 cache_context = CacheKey.repo_context_cache(
2937 cache_context = CacheKey.repo_context_cache(
2927 _heavy_calculation, repo_name, cache_type)
2938 _heavy_calculation, repo_name, cache_type)
2928
2939
2929 with cache_context as context:
2940 with cache_context as context:
2930 context.invalidate()
2941 context.invalidate()
2931 computed = context.compute()
2942 computed = context.compute()
2932
2943
2933 assert computed == 'result'
2944 assert computed == 'result'
2934 """
2945 """
2935 from rhodecode.lib import caches
2946 from rhodecode.lib import caches
2936 return caches.InvalidationContext(
2947 return caches.InvalidationContext(
2937 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2948 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2938
2949
2939
2950
2940 class ChangesetComment(Base, BaseModel):
2951 class ChangesetComment(Base, BaseModel):
2941 __tablename__ = 'changeset_comments'
2952 __tablename__ = 'changeset_comments'
2942 __table_args__ = (
2953 __table_args__ = (
2943 Index('cc_revision_idx', 'revision'),
2954 Index('cc_revision_idx', 'revision'),
2944 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2955 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2945 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2956 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2946 )
2957 )
2947
2958
2948 COMMENT_OUTDATED = u'comment_outdated'
2959 COMMENT_OUTDATED = u'comment_outdated'
2949 COMMENT_TYPE_NOTE = u'note'
2960 COMMENT_TYPE_NOTE = u'note'
2950 COMMENT_TYPE_TODO = u'todo'
2961 COMMENT_TYPE_TODO = u'todo'
2951 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2962 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
2952
2963
2953 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2964 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2954 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2965 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2955 revision = Column('revision', String(40), nullable=True)
2966 revision = Column('revision', String(40), nullable=True)
2956 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2967 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2957 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2968 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2958 line_no = Column('line_no', Unicode(10), nullable=True)
2969 line_no = Column('line_no', Unicode(10), nullable=True)
2959 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2970 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2960 f_path = Column('f_path', Unicode(1000), nullable=True)
2971 f_path = Column('f_path', Unicode(1000), nullable=True)
2961 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2972 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2962 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2973 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2963 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2974 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2964 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2975 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2965 renderer = Column('renderer', Unicode(64), nullable=True)
2976 renderer = Column('renderer', Unicode(64), nullable=True)
2966 display_state = Column('display_state', Unicode(128), nullable=True)
2977 display_state = Column('display_state', Unicode(128), nullable=True)
2967
2978
2968 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2979 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
2969 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2980 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
2970 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2981 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
2971 author = relationship('User', lazy='joined')
2982 author = relationship('User', lazy='joined')
2972 repo = relationship('Repository')
2983 repo = relationship('Repository')
2973 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2984 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
2974 pull_request = relationship('PullRequest', lazy='joined')
2985 pull_request = relationship('PullRequest', lazy='joined')
2975 pull_request_version = relationship('PullRequestVersion')
2986 pull_request_version = relationship('PullRequestVersion')
2976
2987
2977 @classmethod
2988 @classmethod
2978 def get_users(cls, revision=None, pull_request_id=None):
2989 def get_users(cls, revision=None, pull_request_id=None):
2979 """
2990 """
2980 Returns user associated with this ChangesetComment. ie those
2991 Returns user associated with this ChangesetComment. ie those
2981 who actually commented
2992 who actually commented
2982
2993
2983 :param cls:
2994 :param cls:
2984 :param revision:
2995 :param revision:
2985 """
2996 """
2986 q = Session().query(User)\
2997 q = Session().query(User)\
2987 .join(ChangesetComment.author)
2998 .join(ChangesetComment.author)
2988 if revision:
2999 if revision:
2989 q = q.filter(cls.revision == revision)
3000 q = q.filter(cls.revision == revision)
2990 elif pull_request_id:
3001 elif pull_request_id:
2991 q = q.filter(cls.pull_request_id == pull_request_id)
3002 q = q.filter(cls.pull_request_id == pull_request_id)
2992 return q.all()
3003 return q.all()
2993
3004
2994 @classmethod
3005 @classmethod
2995 def get_index_from_version(cls, pr_version, versions):
3006 def get_index_from_version(cls, pr_version, versions):
2996 num_versions = [x.pull_request_version_id for x in versions]
3007 num_versions = [x.pull_request_version_id for x in versions]
2997 try:
3008 try:
2998 return num_versions.index(pr_version) +1
3009 return num_versions.index(pr_version) +1
2999 except (IndexError, ValueError):
3010 except (IndexError, ValueError):
3000 return
3011 return
3001
3012
3002 @property
3013 @property
3003 def outdated(self):
3014 def outdated(self):
3004 return self.display_state == self.COMMENT_OUTDATED
3015 return self.display_state == self.COMMENT_OUTDATED
3005
3016
3006 def outdated_at_version(self, version):
3017 def outdated_at_version(self, version):
3007 """
3018 """
3008 Checks if comment is outdated for given pull request version
3019 Checks if comment is outdated for given pull request version
3009 """
3020 """
3010 return self.outdated and self.pull_request_version_id != version
3021 return self.outdated and self.pull_request_version_id != version
3011
3022
3012 def older_than_version(self, version):
3023 def older_than_version(self, version):
3013 """
3024 """
3014 Checks if comment is made from previous version than given
3025 Checks if comment is made from previous version than given
3015 """
3026 """
3016 if version is None:
3027 if version is None:
3017 return self.pull_request_version_id is not None
3028 return self.pull_request_version_id is not None
3018
3029
3019 return self.pull_request_version_id < version
3030 return self.pull_request_version_id < version
3020
3031
3021 @property
3032 @property
3022 def resolved(self):
3033 def resolved(self):
3023 return self.resolved_by[0] if self.resolved_by else None
3034 return self.resolved_by[0] if self.resolved_by else None
3024
3035
3025 @property
3036 @property
3026 def is_todo(self):
3037 def is_todo(self):
3027 return self.comment_type == self.COMMENT_TYPE_TODO
3038 return self.comment_type == self.COMMENT_TYPE_TODO
3028
3039
3029 def get_index_version(self, versions):
3040 def get_index_version(self, versions):
3030 return self.get_index_from_version(
3041 return self.get_index_from_version(
3031 self.pull_request_version_id, versions)
3042 self.pull_request_version_id, versions)
3032
3043
3033 def render(self, mentions=False):
3044 def render(self, mentions=False):
3034 from rhodecode.lib import helpers as h
3045 from rhodecode.lib import helpers as h
3035 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3046 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3036
3047
3037 def __repr__(self):
3048 def __repr__(self):
3038 if self.comment_id:
3049 if self.comment_id:
3039 return '<DB:Comment #%s>' % self.comment_id
3050 return '<DB:Comment #%s>' % self.comment_id
3040 else:
3051 else:
3041 return '<DB:Comment at %#x>' % id(self)
3052 return '<DB:Comment at %#x>' % id(self)
3042
3053
3043
3054
3044 class ChangesetStatus(Base, BaseModel):
3055 class ChangesetStatus(Base, BaseModel):
3045 __tablename__ = 'changeset_statuses'
3056 __tablename__ = 'changeset_statuses'
3046 __table_args__ = (
3057 __table_args__ = (
3047 Index('cs_revision_idx', 'revision'),
3058 Index('cs_revision_idx', 'revision'),
3048 Index('cs_version_idx', 'version'),
3059 Index('cs_version_idx', 'version'),
3049 UniqueConstraint('repo_id', 'revision', 'version'),
3060 UniqueConstraint('repo_id', 'revision', 'version'),
3050 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3061 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3051 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3062 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3052 )
3063 )
3053 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3064 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3054 STATUS_APPROVED = 'approved'
3065 STATUS_APPROVED = 'approved'
3055 STATUS_REJECTED = 'rejected'
3066 STATUS_REJECTED = 'rejected'
3056 STATUS_UNDER_REVIEW = 'under_review'
3067 STATUS_UNDER_REVIEW = 'under_review'
3057
3068
3058 STATUSES = [
3069 STATUSES = [
3059 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3070 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3060 (STATUS_APPROVED, _("Approved")),
3071 (STATUS_APPROVED, _("Approved")),
3061 (STATUS_REJECTED, _("Rejected")),
3072 (STATUS_REJECTED, _("Rejected")),
3062 (STATUS_UNDER_REVIEW, _("Under Review")),
3073 (STATUS_UNDER_REVIEW, _("Under Review")),
3063 ]
3074 ]
3064
3075
3065 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3076 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3066 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3077 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3067 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3078 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3068 revision = Column('revision', String(40), nullable=False)
3079 revision = Column('revision', String(40), nullable=False)
3069 status = Column('status', String(128), nullable=False, default=DEFAULT)
3080 status = Column('status', String(128), nullable=False, default=DEFAULT)
3070 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3081 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3071 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3082 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3072 version = Column('version', Integer(), nullable=False, default=0)
3083 version = Column('version', Integer(), nullable=False, default=0)
3073 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3084 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3074
3085
3075 author = relationship('User', lazy='joined')
3086 author = relationship('User', lazy='joined')
3076 repo = relationship('Repository')
3087 repo = relationship('Repository')
3077 comment = relationship('ChangesetComment', lazy='joined')
3088 comment = relationship('ChangesetComment', lazy='joined')
3078 pull_request = relationship('PullRequest', lazy='joined')
3089 pull_request = relationship('PullRequest', lazy='joined')
3079
3090
3080 def __unicode__(self):
3091 def __unicode__(self):
3081 return u"<%s('%s[v%s]:%s')>" % (
3092 return u"<%s('%s[v%s]:%s')>" % (
3082 self.__class__.__name__,
3093 self.__class__.__name__,
3083 self.status, self.version, self.author
3094 self.status, self.version, self.author
3084 )
3095 )
3085
3096
3086 @classmethod
3097 @classmethod
3087 def get_status_lbl(cls, value):
3098 def get_status_lbl(cls, value):
3088 return dict(cls.STATUSES).get(value)
3099 return dict(cls.STATUSES).get(value)
3089
3100
3090 @property
3101 @property
3091 def status_lbl(self):
3102 def status_lbl(self):
3092 return ChangesetStatus.get_status_lbl(self.status)
3103 return ChangesetStatus.get_status_lbl(self.status)
3093
3104
3094
3105
3095 class _PullRequestBase(BaseModel):
3106 class _PullRequestBase(BaseModel):
3096 """
3107 """
3097 Common attributes of pull request and version entries.
3108 Common attributes of pull request and version entries.
3098 """
3109 """
3099
3110
3100 # .status values
3111 # .status values
3101 STATUS_NEW = u'new'
3112 STATUS_NEW = u'new'
3102 STATUS_OPEN = u'open'
3113 STATUS_OPEN = u'open'
3103 STATUS_CLOSED = u'closed'
3114 STATUS_CLOSED = u'closed'
3104
3115
3105 title = Column('title', Unicode(255), nullable=True)
3116 title = Column('title', Unicode(255), nullable=True)
3106 description = Column(
3117 description = Column(
3107 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3118 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3108 nullable=True)
3119 nullable=True)
3109 # new/open/closed status of pull request (not approve/reject/etc)
3120 # new/open/closed status of pull request (not approve/reject/etc)
3110 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3121 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3111 created_on = Column(
3122 created_on = Column(
3112 'created_on', DateTime(timezone=False), nullable=False,
3123 'created_on', DateTime(timezone=False), nullable=False,
3113 default=datetime.datetime.now)
3124 default=datetime.datetime.now)
3114 updated_on = Column(
3125 updated_on = Column(
3115 'updated_on', DateTime(timezone=False), nullable=False,
3126 'updated_on', DateTime(timezone=False), nullable=False,
3116 default=datetime.datetime.now)
3127 default=datetime.datetime.now)
3117
3128
3118 @declared_attr
3129 @declared_attr
3119 def user_id(cls):
3130 def user_id(cls):
3120 return Column(
3131 return Column(
3121 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3132 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3122 unique=None)
3133 unique=None)
3123
3134
3124 # 500 revisions max
3135 # 500 revisions max
3125 _revisions = Column(
3136 _revisions = Column(
3126 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3137 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3127
3138
3128 @declared_attr
3139 @declared_attr
3129 def source_repo_id(cls):
3140 def source_repo_id(cls):
3130 # TODO: dan: rename column to source_repo_id
3141 # TODO: dan: rename column to source_repo_id
3131 return Column(
3142 return Column(
3132 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3143 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3133 nullable=False)
3144 nullable=False)
3134
3145
3135 source_ref = Column('org_ref', Unicode(255), nullable=False)
3146 source_ref = Column('org_ref', Unicode(255), nullable=False)
3136
3147
3137 @declared_attr
3148 @declared_attr
3138 def target_repo_id(cls):
3149 def target_repo_id(cls):
3139 # TODO: dan: rename column to target_repo_id
3150 # TODO: dan: rename column to target_repo_id
3140 return Column(
3151 return Column(
3141 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3152 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3142 nullable=False)
3153 nullable=False)
3143
3154
3144 target_ref = Column('other_ref', Unicode(255), nullable=False)
3155 target_ref = Column('other_ref', Unicode(255), nullable=False)
3145 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3156 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3146
3157
3147 # TODO: dan: rename column to last_merge_source_rev
3158 # TODO: dan: rename column to last_merge_source_rev
3148 _last_merge_source_rev = Column(
3159 _last_merge_source_rev = Column(
3149 'last_merge_org_rev', String(40), nullable=True)
3160 'last_merge_org_rev', String(40), nullable=True)
3150 # TODO: dan: rename column to last_merge_target_rev
3161 # TODO: dan: rename column to last_merge_target_rev
3151 _last_merge_target_rev = Column(
3162 _last_merge_target_rev = Column(
3152 'last_merge_other_rev', String(40), nullable=True)
3163 'last_merge_other_rev', String(40), nullable=True)
3153 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3164 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3154 merge_rev = Column('merge_rev', String(40), nullable=True)
3165 merge_rev = Column('merge_rev', String(40), nullable=True)
3155
3166
3156 @hybrid_property
3167 @hybrid_property
3157 def revisions(self):
3168 def revisions(self):
3158 return self._revisions.split(':') if self._revisions else []
3169 return self._revisions.split(':') if self._revisions else []
3159
3170
3160 @revisions.setter
3171 @revisions.setter
3161 def revisions(self, val):
3172 def revisions(self, val):
3162 self._revisions = ':'.join(val)
3173 self._revisions = ':'.join(val)
3163
3174
3164 @declared_attr
3175 @declared_attr
3165 def author(cls):
3176 def author(cls):
3166 return relationship('User', lazy='joined')
3177 return relationship('User', lazy='joined')
3167
3178
3168 @declared_attr
3179 @declared_attr
3169 def source_repo(cls):
3180 def source_repo(cls):
3170 return relationship(
3181 return relationship(
3171 'Repository',
3182 'Repository',
3172 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3183 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3173
3184
3174 @property
3185 @property
3175 def source_ref_parts(self):
3186 def source_ref_parts(self):
3176 return self.unicode_to_reference(self.source_ref)
3187 return self.unicode_to_reference(self.source_ref)
3177
3188
3178 @declared_attr
3189 @declared_attr
3179 def target_repo(cls):
3190 def target_repo(cls):
3180 return relationship(
3191 return relationship(
3181 'Repository',
3192 'Repository',
3182 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3193 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3183
3194
3184 @property
3195 @property
3185 def target_ref_parts(self):
3196 def target_ref_parts(self):
3186 return self.unicode_to_reference(self.target_ref)
3197 return self.unicode_to_reference(self.target_ref)
3187
3198
3188 @property
3199 @property
3189 def shadow_merge_ref(self):
3200 def shadow_merge_ref(self):
3190 return self.unicode_to_reference(self._shadow_merge_ref)
3201 return self.unicode_to_reference(self._shadow_merge_ref)
3191
3202
3192 @shadow_merge_ref.setter
3203 @shadow_merge_ref.setter
3193 def shadow_merge_ref(self, ref):
3204 def shadow_merge_ref(self, ref):
3194 self._shadow_merge_ref = self.reference_to_unicode(ref)
3205 self._shadow_merge_ref = self.reference_to_unicode(ref)
3195
3206
3196 def unicode_to_reference(self, raw):
3207 def unicode_to_reference(self, raw):
3197 """
3208 """
3198 Convert a unicode (or string) to a reference object.
3209 Convert a unicode (or string) to a reference object.
3199 If unicode evaluates to False it returns None.
3210 If unicode evaluates to False it returns None.
3200 """
3211 """
3201 if raw:
3212 if raw:
3202 refs = raw.split(':')
3213 refs = raw.split(':')
3203 return Reference(*refs)
3214 return Reference(*refs)
3204 else:
3215 else:
3205 return None
3216 return None
3206
3217
3207 def reference_to_unicode(self, ref):
3218 def reference_to_unicode(self, ref):
3208 """
3219 """
3209 Convert a reference object to unicode.
3220 Convert a reference object to unicode.
3210 If reference is None it returns None.
3221 If reference is None it returns None.
3211 """
3222 """
3212 if ref:
3223 if ref:
3213 return u':'.join(ref)
3224 return u':'.join(ref)
3214 else:
3225 else:
3215 return None
3226 return None
3216
3227
3217 def get_api_data(self):
3228 def get_api_data(self):
3218 from rhodecode.model.pull_request import PullRequestModel
3229 from rhodecode.model.pull_request import PullRequestModel
3219 pull_request = self
3230 pull_request = self
3220 merge_status = PullRequestModel().merge_status(pull_request)
3231 merge_status = PullRequestModel().merge_status(pull_request)
3221
3232
3222 pull_request_url = url(
3233 pull_request_url = url(
3223 'pullrequest_show', repo_name=self.target_repo.repo_name,
3234 'pullrequest_show', repo_name=self.target_repo.repo_name,
3224 pull_request_id=self.pull_request_id, qualified=True)
3235 pull_request_id=self.pull_request_id, qualified=True)
3225
3236
3226 merge_data = {
3237 merge_data = {
3227 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3238 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3228 'reference': (
3239 'reference': (
3229 pull_request.shadow_merge_ref._asdict()
3240 pull_request.shadow_merge_ref._asdict()
3230 if pull_request.shadow_merge_ref else None),
3241 if pull_request.shadow_merge_ref else None),
3231 }
3242 }
3232
3243
3233 data = {
3244 data = {
3234 'pull_request_id': pull_request.pull_request_id,
3245 'pull_request_id': pull_request.pull_request_id,
3235 'url': pull_request_url,
3246 'url': pull_request_url,
3236 'title': pull_request.title,
3247 'title': pull_request.title,
3237 'description': pull_request.description,
3248 'description': pull_request.description,
3238 'status': pull_request.status,
3249 'status': pull_request.status,
3239 'created_on': pull_request.created_on,
3250 'created_on': pull_request.created_on,
3240 'updated_on': pull_request.updated_on,
3251 'updated_on': pull_request.updated_on,
3241 'commit_ids': pull_request.revisions,
3252 'commit_ids': pull_request.revisions,
3242 'review_status': pull_request.calculated_review_status(),
3253 'review_status': pull_request.calculated_review_status(),
3243 'mergeable': {
3254 'mergeable': {
3244 'status': merge_status[0],
3255 'status': merge_status[0],
3245 'message': unicode(merge_status[1]),
3256 'message': unicode(merge_status[1]),
3246 },
3257 },
3247 'source': {
3258 'source': {
3248 'clone_url': pull_request.source_repo.clone_url(),
3259 'clone_url': pull_request.source_repo.clone_url(),
3249 'repository': pull_request.source_repo.repo_name,
3260 'repository': pull_request.source_repo.repo_name,
3250 'reference': {
3261 'reference': {
3251 'name': pull_request.source_ref_parts.name,
3262 'name': pull_request.source_ref_parts.name,
3252 'type': pull_request.source_ref_parts.type,
3263 'type': pull_request.source_ref_parts.type,
3253 'commit_id': pull_request.source_ref_parts.commit_id,
3264 'commit_id': pull_request.source_ref_parts.commit_id,
3254 },
3265 },
3255 },
3266 },
3256 'target': {
3267 'target': {
3257 'clone_url': pull_request.target_repo.clone_url(),
3268 'clone_url': pull_request.target_repo.clone_url(),
3258 'repository': pull_request.target_repo.repo_name,
3269 'repository': pull_request.target_repo.repo_name,
3259 'reference': {
3270 'reference': {
3260 'name': pull_request.target_ref_parts.name,
3271 'name': pull_request.target_ref_parts.name,
3261 'type': pull_request.target_ref_parts.type,
3272 'type': pull_request.target_ref_parts.type,
3262 'commit_id': pull_request.target_ref_parts.commit_id,
3273 'commit_id': pull_request.target_ref_parts.commit_id,
3263 },
3274 },
3264 },
3275 },
3265 'merge': merge_data,
3276 'merge': merge_data,
3266 'author': pull_request.author.get_api_data(include_secrets=False,
3277 'author': pull_request.author.get_api_data(include_secrets=False,
3267 details='basic'),
3278 details='basic'),
3268 'reviewers': [
3279 'reviewers': [
3269 {
3280 {
3270 'user': reviewer.get_api_data(include_secrets=False,
3281 'user': reviewer.get_api_data(include_secrets=False,
3271 details='basic'),
3282 details='basic'),
3272 'reasons': reasons,
3283 'reasons': reasons,
3273 'review_status': st[0][1].status if st else 'not_reviewed',
3284 'review_status': st[0][1].status if st else 'not_reviewed',
3274 }
3285 }
3275 for reviewer, reasons, st in pull_request.reviewers_statuses()
3286 for reviewer, reasons, st in pull_request.reviewers_statuses()
3276 ]
3287 ]
3277 }
3288 }
3278
3289
3279 return data
3290 return data
3280
3291
3281
3292
3282 class PullRequest(Base, _PullRequestBase):
3293 class PullRequest(Base, _PullRequestBase):
3283 __tablename__ = 'pull_requests'
3294 __tablename__ = 'pull_requests'
3284 __table_args__ = (
3295 __table_args__ = (
3285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3297 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3287 )
3298 )
3288
3299
3289 pull_request_id = Column(
3300 pull_request_id = Column(
3290 'pull_request_id', Integer(), nullable=False, primary_key=True)
3301 'pull_request_id', Integer(), nullable=False, primary_key=True)
3291
3302
3292 def __repr__(self):
3303 def __repr__(self):
3293 if self.pull_request_id:
3304 if self.pull_request_id:
3294 return '<DB:PullRequest #%s>' % self.pull_request_id
3305 return '<DB:PullRequest #%s>' % self.pull_request_id
3295 else:
3306 else:
3296 return '<DB:PullRequest at %#x>' % id(self)
3307 return '<DB:PullRequest at %#x>' % id(self)
3297
3308
3298 reviewers = relationship('PullRequestReviewers',
3309 reviewers = relationship('PullRequestReviewers',
3299 cascade="all, delete, delete-orphan")
3310 cascade="all, delete, delete-orphan")
3300 statuses = relationship('ChangesetStatus')
3311 statuses = relationship('ChangesetStatus')
3301 comments = relationship('ChangesetComment',
3312 comments = relationship('ChangesetComment',
3302 cascade="all, delete, delete-orphan")
3313 cascade="all, delete, delete-orphan")
3303 versions = relationship('PullRequestVersion',
3314 versions = relationship('PullRequestVersion',
3304 cascade="all, delete, delete-orphan",
3315 cascade="all, delete, delete-orphan",
3305 lazy='dynamic')
3316 lazy='dynamic')
3306
3317
3307 @classmethod
3318 @classmethod
3308 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3319 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3309 internal_methods=None):
3320 internal_methods=None):
3310
3321
3311 class PullRequestDisplay(object):
3322 class PullRequestDisplay(object):
3312 """
3323 """
3313 Special object wrapper for showing PullRequest data via Versions
3324 Special object wrapper for showing PullRequest data via Versions
3314 It mimics PR object as close as possible. This is read only object
3325 It mimics PR object as close as possible. This is read only object
3315 just for display
3326 just for display
3316 """
3327 """
3317
3328
3318 def __init__(self, attrs, internal=None):
3329 def __init__(self, attrs, internal=None):
3319 self.attrs = attrs
3330 self.attrs = attrs
3320 # internal have priority over the given ones via attrs
3331 # internal have priority over the given ones via attrs
3321 self.internal = internal or ['versions']
3332 self.internal = internal or ['versions']
3322
3333
3323 def __getattr__(self, item):
3334 def __getattr__(self, item):
3324 if item in self.internal:
3335 if item in self.internal:
3325 return getattr(self, item)
3336 return getattr(self, item)
3326 try:
3337 try:
3327 return self.attrs[item]
3338 return self.attrs[item]
3328 except KeyError:
3339 except KeyError:
3329 raise AttributeError(
3340 raise AttributeError(
3330 '%s object has no attribute %s' % (self, item))
3341 '%s object has no attribute %s' % (self, item))
3331
3342
3332 def __repr__(self):
3343 def __repr__(self):
3333 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3344 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3334
3345
3335 def versions(self):
3346 def versions(self):
3336 return pull_request_obj.versions.order_by(
3347 return pull_request_obj.versions.order_by(
3337 PullRequestVersion.pull_request_version_id).all()
3348 PullRequestVersion.pull_request_version_id).all()
3338
3349
3339 def is_closed(self):
3350 def is_closed(self):
3340 return pull_request_obj.is_closed()
3351 return pull_request_obj.is_closed()
3341
3352
3342 @property
3353 @property
3343 def pull_request_version_id(self):
3354 def pull_request_version_id(self):
3344 return getattr(pull_request_obj, 'pull_request_version_id', None)
3355 return getattr(pull_request_obj, 'pull_request_version_id', None)
3345
3356
3346 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3357 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3347
3358
3348 attrs.author = StrictAttributeDict(
3359 attrs.author = StrictAttributeDict(
3349 pull_request_obj.author.get_api_data())
3360 pull_request_obj.author.get_api_data())
3350 if pull_request_obj.target_repo:
3361 if pull_request_obj.target_repo:
3351 attrs.target_repo = StrictAttributeDict(
3362 attrs.target_repo = StrictAttributeDict(
3352 pull_request_obj.target_repo.get_api_data())
3363 pull_request_obj.target_repo.get_api_data())
3353 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3364 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3354
3365
3355 if pull_request_obj.source_repo:
3366 if pull_request_obj.source_repo:
3356 attrs.source_repo = StrictAttributeDict(
3367 attrs.source_repo = StrictAttributeDict(
3357 pull_request_obj.source_repo.get_api_data())
3368 pull_request_obj.source_repo.get_api_data())
3358 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3369 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3359
3370
3360 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3371 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3361 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3372 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3362 attrs.revisions = pull_request_obj.revisions
3373 attrs.revisions = pull_request_obj.revisions
3363
3374
3364 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3375 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3365
3376
3366 return PullRequestDisplay(attrs, internal=internal_methods)
3377 return PullRequestDisplay(attrs, internal=internal_methods)
3367
3378
3368 def is_closed(self):
3379 def is_closed(self):
3369 return self.status == self.STATUS_CLOSED
3380 return self.status == self.STATUS_CLOSED
3370
3381
3371 def __json__(self):
3382 def __json__(self):
3372 return {
3383 return {
3373 'revisions': self.revisions,
3384 'revisions': self.revisions,
3374 }
3385 }
3375
3386
3376 def calculated_review_status(self):
3387 def calculated_review_status(self):
3377 from rhodecode.model.changeset_status import ChangesetStatusModel
3388 from rhodecode.model.changeset_status import ChangesetStatusModel
3378 return ChangesetStatusModel().calculated_review_status(self)
3389 return ChangesetStatusModel().calculated_review_status(self)
3379
3390
3380 def reviewers_statuses(self):
3391 def reviewers_statuses(self):
3381 from rhodecode.model.changeset_status import ChangesetStatusModel
3392 from rhodecode.model.changeset_status import ChangesetStatusModel
3382 return ChangesetStatusModel().reviewers_statuses(self)
3393 return ChangesetStatusModel().reviewers_statuses(self)
3383
3394
3384 @property
3395 @property
3385 def workspace_id(self):
3396 def workspace_id(self):
3386 from rhodecode.model.pull_request import PullRequestModel
3397 from rhodecode.model.pull_request import PullRequestModel
3387 return PullRequestModel()._workspace_id(self)
3398 return PullRequestModel()._workspace_id(self)
3388
3399
3389 def get_shadow_repo(self):
3400 def get_shadow_repo(self):
3390 workspace_id = self.workspace_id
3401 workspace_id = self.workspace_id
3391 vcs_obj = self.target_repo.scm_instance()
3402 vcs_obj = self.target_repo.scm_instance()
3392 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3403 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3393 workspace_id)
3404 workspace_id)
3394 return vcs_obj._get_shadow_instance(shadow_repository_path)
3405 return vcs_obj._get_shadow_instance(shadow_repository_path)
3395
3406
3396
3407
3397 class PullRequestVersion(Base, _PullRequestBase):
3408 class PullRequestVersion(Base, _PullRequestBase):
3398 __tablename__ = 'pull_request_versions'
3409 __tablename__ = 'pull_request_versions'
3399 __table_args__ = (
3410 __table_args__ = (
3400 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3411 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3401 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3412 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3402 )
3413 )
3403
3414
3404 pull_request_version_id = Column(
3415 pull_request_version_id = Column(
3405 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3416 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3406 pull_request_id = Column(
3417 pull_request_id = Column(
3407 'pull_request_id', Integer(),
3418 'pull_request_id', Integer(),
3408 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3419 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3409 pull_request = relationship('PullRequest')
3420 pull_request = relationship('PullRequest')
3410
3421
3411 def __repr__(self):
3422 def __repr__(self):
3412 if self.pull_request_version_id:
3423 if self.pull_request_version_id:
3413 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3424 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3414 else:
3425 else:
3415 return '<DB:PullRequestVersion at %#x>' % id(self)
3426 return '<DB:PullRequestVersion at %#x>' % id(self)
3416
3427
3417 @property
3428 @property
3418 def reviewers(self):
3429 def reviewers(self):
3419 return self.pull_request.reviewers
3430 return self.pull_request.reviewers
3420
3431
3421 @property
3432 @property
3422 def versions(self):
3433 def versions(self):
3423 return self.pull_request.versions
3434 return self.pull_request.versions
3424
3435
3425 def is_closed(self):
3436 def is_closed(self):
3426 # calculate from original
3437 # calculate from original
3427 return self.pull_request.status == self.STATUS_CLOSED
3438 return self.pull_request.status == self.STATUS_CLOSED
3428
3439
3429 def calculated_review_status(self):
3440 def calculated_review_status(self):
3430 return self.pull_request.calculated_review_status()
3441 return self.pull_request.calculated_review_status()
3431
3442
3432 def reviewers_statuses(self):
3443 def reviewers_statuses(self):
3433 return self.pull_request.reviewers_statuses()
3444 return self.pull_request.reviewers_statuses()
3434
3445
3435
3446
3436 class PullRequestReviewers(Base, BaseModel):
3447 class PullRequestReviewers(Base, BaseModel):
3437 __tablename__ = 'pull_request_reviewers'
3448 __tablename__ = 'pull_request_reviewers'
3438 __table_args__ = (
3449 __table_args__ = (
3439 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3450 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3440 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3451 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3441 )
3452 )
3442
3453
3443 def __init__(self, user=None, pull_request=None, reasons=None):
3454 def __init__(self, user=None, pull_request=None, reasons=None):
3444 self.user = user
3455 self.user = user
3445 self.pull_request = pull_request
3456 self.pull_request = pull_request
3446 self.reasons = reasons or []
3457 self.reasons = reasons or []
3447
3458
3448 @hybrid_property
3459 @hybrid_property
3449 def reasons(self):
3460 def reasons(self):
3450 if not self._reasons:
3461 if not self._reasons:
3451 return []
3462 return []
3452 return self._reasons
3463 return self._reasons
3453
3464
3454 @reasons.setter
3465 @reasons.setter
3455 def reasons(self, val):
3466 def reasons(self, val):
3456 val = val or []
3467 val = val or []
3457 if any(not isinstance(x, basestring) for x in val):
3468 if any(not isinstance(x, basestring) for x in val):
3458 raise Exception('invalid reasons type, must be list of strings')
3469 raise Exception('invalid reasons type, must be list of strings')
3459 self._reasons = val
3470 self._reasons = val
3460
3471
3461 pull_requests_reviewers_id = Column(
3472 pull_requests_reviewers_id = Column(
3462 'pull_requests_reviewers_id', Integer(), nullable=False,
3473 'pull_requests_reviewers_id', Integer(), nullable=False,
3463 primary_key=True)
3474 primary_key=True)
3464 pull_request_id = Column(
3475 pull_request_id = Column(
3465 "pull_request_id", Integer(),
3476 "pull_request_id", Integer(),
3466 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3477 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3467 user_id = Column(
3478 user_id = Column(
3468 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3479 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3469 _reasons = Column(
3480 _reasons = Column(
3470 'reason', MutationList.as_mutable(
3481 'reason', MutationList.as_mutable(
3471 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3482 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3472
3483
3473 user = relationship('User')
3484 user = relationship('User')
3474 pull_request = relationship('PullRequest')
3485 pull_request = relationship('PullRequest')
3475
3486
3476
3487
3477 class Notification(Base, BaseModel):
3488 class Notification(Base, BaseModel):
3478 __tablename__ = 'notifications'
3489 __tablename__ = 'notifications'
3479 __table_args__ = (
3490 __table_args__ = (
3480 Index('notification_type_idx', 'type'),
3491 Index('notification_type_idx', 'type'),
3481 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3482 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3493 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3483 )
3494 )
3484
3495
3485 TYPE_CHANGESET_COMMENT = u'cs_comment'
3496 TYPE_CHANGESET_COMMENT = u'cs_comment'
3486 TYPE_MESSAGE = u'message'
3497 TYPE_MESSAGE = u'message'
3487 TYPE_MENTION = u'mention'
3498 TYPE_MENTION = u'mention'
3488 TYPE_REGISTRATION = u'registration'
3499 TYPE_REGISTRATION = u'registration'
3489 TYPE_PULL_REQUEST = u'pull_request'
3500 TYPE_PULL_REQUEST = u'pull_request'
3490 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3501 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3491
3502
3492 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3503 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3493 subject = Column('subject', Unicode(512), nullable=True)
3504 subject = Column('subject', Unicode(512), nullable=True)
3494 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3505 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3495 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3506 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3496 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3507 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3497 type_ = Column('type', Unicode(255))
3508 type_ = Column('type', Unicode(255))
3498
3509
3499 created_by_user = relationship('User')
3510 created_by_user = relationship('User')
3500 notifications_to_users = relationship('UserNotification', lazy='joined',
3511 notifications_to_users = relationship('UserNotification', lazy='joined',
3501 cascade="all, delete, delete-orphan")
3512 cascade="all, delete, delete-orphan")
3502
3513
3503 @property
3514 @property
3504 def recipients(self):
3515 def recipients(self):
3505 return [x.user for x in UserNotification.query()\
3516 return [x.user for x in UserNotification.query()\
3506 .filter(UserNotification.notification == self)\
3517 .filter(UserNotification.notification == self)\
3507 .order_by(UserNotification.user_id.asc()).all()]
3518 .order_by(UserNotification.user_id.asc()).all()]
3508
3519
3509 @classmethod
3520 @classmethod
3510 def create(cls, created_by, subject, body, recipients, type_=None):
3521 def create(cls, created_by, subject, body, recipients, type_=None):
3511 if type_ is None:
3522 if type_ is None:
3512 type_ = Notification.TYPE_MESSAGE
3523 type_ = Notification.TYPE_MESSAGE
3513
3524
3514 notification = cls()
3525 notification = cls()
3515 notification.created_by_user = created_by
3526 notification.created_by_user = created_by
3516 notification.subject = subject
3527 notification.subject = subject
3517 notification.body = body
3528 notification.body = body
3518 notification.type_ = type_
3529 notification.type_ = type_
3519 notification.created_on = datetime.datetime.now()
3530 notification.created_on = datetime.datetime.now()
3520
3531
3521 for u in recipients:
3532 for u in recipients:
3522 assoc = UserNotification()
3533 assoc = UserNotification()
3523 assoc.notification = notification
3534 assoc.notification = notification
3524
3535
3525 # if created_by is inside recipients mark his notification
3536 # if created_by is inside recipients mark his notification
3526 # as read
3537 # as read
3527 if u.user_id == created_by.user_id:
3538 if u.user_id == created_by.user_id:
3528 assoc.read = True
3539 assoc.read = True
3529
3540
3530 u.notifications.append(assoc)
3541 u.notifications.append(assoc)
3531 Session().add(notification)
3542 Session().add(notification)
3532
3543
3533 return notification
3544 return notification
3534
3545
3535 @property
3546 @property
3536 def description(self):
3547 def description(self):
3537 from rhodecode.model.notification import NotificationModel
3548 from rhodecode.model.notification import NotificationModel
3538 return NotificationModel().make_description(self)
3549 return NotificationModel().make_description(self)
3539
3550
3540
3551
3541 class UserNotification(Base, BaseModel):
3552 class UserNotification(Base, BaseModel):
3542 __tablename__ = 'user_to_notification'
3553 __tablename__ = 'user_to_notification'
3543 __table_args__ = (
3554 __table_args__ = (
3544 UniqueConstraint('user_id', 'notification_id'),
3555 UniqueConstraint('user_id', 'notification_id'),
3545 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3556 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3546 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3557 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3547 )
3558 )
3548 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3559 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3549 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3560 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3550 read = Column('read', Boolean, default=False)
3561 read = Column('read', Boolean, default=False)
3551 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3562 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3552
3563
3553 user = relationship('User', lazy="joined")
3564 user = relationship('User', lazy="joined")
3554 notification = relationship('Notification', lazy="joined",
3565 notification = relationship('Notification', lazy="joined",
3555 order_by=lambda: Notification.created_on.desc(),)
3566 order_by=lambda: Notification.created_on.desc(),)
3556
3567
3557 def mark_as_read(self):
3568 def mark_as_read(self):
3558 self.read = True
3569 self.read = True
3559 Session().add(self)
3570 Session().add(self)
3560
3571
3561
3572
3562 class Gist(Base, BaseModel):
3573 class Gist(Base, BaseModel):
3563 __tablename__ = 'gists'
3574 __tablename__ = 'gists'
3564 __table_args__ = (
3575 __table_args__ = (
3565 Index('g_gist_access_id_idx', 'gist_access_id'),
3576 Index('g_gist_access_id_idx', 'gist_access_id'),
3566 Index('g_created_on_idx', 'created_on'),
3577 Index('g_created_on_idx', 'created_on'),
3567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3579 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3569 )
3580 )
3570 GIST_PUBLIC = u'public'
3581 GIST_PUBLIC = u'public'
3571 GIST_PRIVATE = u'private'
3582 GIST_PRIVATE = u'private'
3572 DEFAULT_FILENAME = u'gistfile1.txt'
3583 DEFAULT_FILENAME = u'gistfile1.txt'
3573
3584
3574 ACL_LEVEL_PUBLIC = u'acl_public'
3585 ACL_LEVEL_PUBLIC = u'acl_public'
3575 ACL_LEVEL_PRIVATE = u'acl_private'
3586 ACL_LEVEL_PRIVATE = u'acl_private'
3576
3587
3577 gist_id = Column('gist_id', Integer(), primary_key=True)
3588 gist_id = Column('gist_id', Integer(), primary_key=True)
3578 gist_access_id = Column('gist_access_id', Unicode(250))
3589 gist_access_id = Column('gist_access_id', Unicode(250))
3579 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3590 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3580 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3591 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3581 gist_expires = Column('gist_expires', Float(53), nullable=False)
3592 gist_expires = Column('gist_expires', Float(53), nullable=False)
3582 gist_type = Column('gist_type', Unicode(128), nullable=False)
3593 gist_type = Column('gist_type', Unicode(128), nullable=False)
3583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3594 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3584 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3595 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3585 acl_level = Column('acl_level', Unicode(128), nullable=True)
3596 acl_level = Column('acl_level', Unicode(128), nullable=True)
3586
3597
3587 owner = relationship('User')
3598 owner = relationship('User')
3588
3599
3589 def __repr__(self):
3600 def __repr__(self):
3590 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3601 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3591
3602
3592 @classmethod
3603 @classmethod
3593 def get_or_404(cls, id_):
3604 def get_or_404(cls, id_):
3594 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3605 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3595 if not res:
3606 if not res:
3596 raise HTTPNotFound
3607 raise HTTPNotFound
3597 return res
3608 return res
3598
3609
3599 @classmethod
3610 @classmethod
3600 def get_by_access_id(cls, gist_access_id):
3611 def get_by_access_id(cls, gist_access_id):
3601 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3612 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3602
3613
3603 def gist_url(self):
3614 def gist_url(self):
3604 import rhodecode
3615 import rhodecode
3605 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3616 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3606 if alias_url:
3617 if alias_url:
3607 return alias_url.replace('{gistid}', self.gist_access_id)
3618 return alias_url.replace('{gistid}', self.gist_access_id)
3608
3619
3609 return url('gist', gist_id=self.gist_access_id, qualified=True)
3620 return url('gist', gist_id=self.gist_access_id, qualified=True)
3610
3621
3611 @classmethod
3622 @classmethod
3612 def base_path(cls):
3623 def base_path(cls):
3613 """
3624 """
3614 Returns base path when all gists are stored
3625 Returns base path when all gists are stored
3615
3626
3616 :param cls:
3627 :param cls:
3617 """
3628 """
3618 from rhodecode.model.gist import GIST_STORE_LOC
3629 from rhodecode.model.gist import GIST_STORE_LOC
3619 q = Session().query(RhodeCodeUi)\
3630 q = Session().query(RhodeCodeUi)\
3620 .filter(RhodeCodeUi.ui_key == URL_SEP)
3631 .filter(RhodeCodeUi.ui_key == URL_SEP)
3621 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3632 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3622 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3633 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3623
3634
3624 def get_api_data(self):
3635 def get_api_data(self):
3625 """
3636 """
3626 Common function for generating gist related data for API
3637 Common function for generating gist related data for API
3627 """
3638 """
3628 gist = self
3639 gist = self
3629 data = {
3640 data = {
3630 'gist_id': gist.gist_id,
3641 'gist_id': gist.gist_id,
3631 'type': gist.gist_type,
3642 'type': gist.gist_type,
3632 'access_id': gist.gist_access_id,
3643 'access_id': gist.gist_access_id,
3633 'description': gist.gist_description,
3644 'description': gist.gist_description,
3634 'url': gist.gist_url(),
3645 'url': gist.gist_url(),
3635 'expires': gist.gist_expires,
3646 'expires': gist.gist_expires,
3636 'created_on': gist.created_on,
3647 'created_on': gist.created_on,
3637 'modified_at': gist.modified_at,
3648 'modified_at': gist.modified_at,
3638 'content': None,
3649 'content': None,
3639 'acl_level': gist.acl_level,
3650 'acl_level': gist.acl_level,
3640 }
3651 }
3641 return data
3652 return data
3642
3653
3643 def __json__(self):
3654 def __json__(self):
3644 data = dict(
3655 data = dict(
3645 )
3656 )
3646 data.update(self.get_api_data())
3657 data.update(self.get_api_data())
3647 return data
3658 return data
3648 # SCM functions
3659 # SCM functions
3649
3660
3650 def scm_instance(self, **kwargs):
3661 def scm_instance(self, **kwargs):
3651 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3662 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3652 return get_vcs_instance(
3663 return get_vcs_instance(
3653 repo_path=safe_str(full_repo_path), create=False)
3664 repo_path=safe_str(full_repo_path), create=False)
3654
3665
3655
3666
3656 class ExternalIdentity(Base, BaseModel):
3667 class ExternalIdentity(Base, BaseModel):
3657 __tablename__ = 'external_identities'
3668 __tablename__ = 'external_identities'
3658 __table_args__ = (
3669 __table_args__ = (
3659 Index('local_user_id_idx', 'local_user_id'),
3670 Index('local_user_id_idx', 'local_user_id'),
3660 Index('external_id_idx', 'external_id'),
3671 Index('external_id_idx', 'external_id'),
3661 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3672 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3662 'mysql_charset': 'utf8'})
3673 'mysql_charset': 'utf8'})
3663
3674
3664 external_id = Column('external_id', Unicode(255), default=u'',
3675 external_id = Column('external_id', Unicode(255), default=u'',
3665 primary_key=True)
3676 primary_key=True)
3666 external_username = Column('external_username', Unicode(1024), default=u'')
3677 external_username = Column('external_username', Unicode(1024), default=u'')
3667 local_user_id = Column('local_user_id', Integer(),
3678 local_user_id = Column('local_user_id', Integer(),
3668 ForeignKey('users.user_id'), primary_key=True)
3679 ForeignKey('users.user_id'), primary_key=True)
3669 provider_name = Column('provider_name', Unicode(255), default=u'',
3680 provider_name = Column('provider_name', Unicode(255), default=u'',
3670 primary_key=True)
3681 primary_key=True)
3671 access_token = Column('access_token', String(1024), default=u'')
3682 access_token = Column('access_token', String(1024), default=u'')
3672 alt_token = Column('alt_token', String(1024), default=u'')
3683 alt_token = Column('alt_token', String(1024), default=u'')
3673 token_secret = Column('token_secret', String(1024), default=u'')
3684 token_secret = Column('token_secret', String(1024), default=u'')
3674
3685
3675 @classmethod
3686 @classmethod
3676 def by_external_id_and_provider(cls, external_id, provider_name,
3687 def by_external_id_and_provider(cls, external_id, provider_name,
3677 local_user_id=None):
3688 local_user_id=None):
3678 """
3689 """
3679 Returns ExternalIdentity instance based on search params
3690 Returns ExternalIdentity instance based on search params
3680
3691
3681 :param external_id:
3692 :param external_id:
3682 :param provider_name:
3693 :param provider_name:
3683 :return: ExternalIdentity
3694 :return: ExternalIdentity
3684 """
3695 """
3685 query = cls.query()
3696 query = cls.query()
3686 query = query.filter(cls.external_id == external_id)
3697 query = query.filter(cls.external_id == external_id)
3687 query = query.filter(cls.provider_name == provider_name)
3698 query = query.filter(cls.provider_name == provider_name)
3688 if local_user_id:
3699 if local_user_id:
3689 query = query.filter(cls.local_user_id == local_user_id)
3700 query = query.filter(cls.local_user_id == local_user_id)
3690 return query.first()
3701 return query.first()
3691
3702
3692 @classmethod
3703 @classmethod
3693 def user_by_external_id_and_provider(cls, external_id, provider_name):
3704 def user_by_external_id_and_provider(cls, external_id, provider_name):
3694 """
3705 """
3695 Returns User instance based on search params
3706 Returns User instance based on search params
3696
3707
3697 :param external_id:
3708 :param external_id:
3698 :param provider_name:
3709 :param provider_name:
3699 :return: User
3710 :return: User
3700 """
3711 """
3701 query = User.query()
3712 query = User.query()
3702 query = query.filter(cls.external_id == external_id)
3713 query = query.filter(cls.external_id == external_id)
3703 query = query.filter(cls.provider_name == provider_name)
3714 query = query.filter(cls.provider_name == provider_name)
3704 query = query.filter(User.user_id == cls.local_user_id)
3715 query = query.filter(User.user_id == cls.local_user_id)
3705 return query.first()
3716 return query.first()
3706
3717
3707 @classmethod
3718 @classmethod
3708 def by_local_user_id(cls, local_user_id):
3719 def by_local_user_id(cls, local_user_id):
3709 """
3720 """
3710 Returns all tokens for user
3721 Returns all tokens for user
3711
3722
3712 :param local_user_id:
3723 :param local_user_id:
3713 :return: ExternalIdentity
3724 :return: ExternalIdentity
3714 """
3725 """
3715 query = cls.query()
3726 query = cls.query()
3716 query = query.filter(cls.local_user_id == local_user_id)
3727 query = query.filter(cls.local_user_id == local_user_id)
3717 return query
3728 return query
3718
3729
3719
3730
3720 class Integration(Base, BaseModel):
3731 class Integration(Base, BaseModel):
3721 __tablename__ = 'integrations'
3732 __tablename__ = 'integrations'
3722 __table_args__ = (
3733 __table_args__ = (
3723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3735 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3725 )
3736 )
3726
3737
3727 integration_id = Column('integration_id', Integer(), primary_key=True)
3738 integration_id = Column('integration_id', Integer(), primary_key=True)
3728 integration_type = Column('integration_type', String(255))
3739 integration_type = Column('integration_type', String(255))
3729 enabled = Column('enabled', Boolean(), nullable=False)
3740 enabled = Column('enabled', Boolean(), nullable=False)
3730 name = Column('name', String(255), nullable=False)
3741 name = Column('name', String(255), nullable=False)
3731 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3742 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3732 default=False)
3743 default=False)
3733
3744
3734 settings = Column(
3745 settings = Column(
3735 'settings_json', MutationObj.as_mutable(
3746 'settings_json', MutationObj.as_mutable(
3736 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3747 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3737 repo_id = Column(
3748 repo_id = Column(
3738 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3749 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3739 nullable=True, unique=None, default=None)
3750 nullable=True, unique=None, default=None)
3740 repo = relationship('Repository', lazy='joined')
3751 repo = relationship('Repository', lazy='joined')
3741
3752
3742 repo_group_id = Column(
3753 repo_group_id = Column(
3743 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3754 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3744 nullable=True, unique=None, default=None)
3755 nullable=True, unique=None, default=None)
3745 repo_group = relationship('RepoGroup', lazy='joined')
3756 repo_group = relationship('RepoGroup', lazy='joined')
3746
3757
3747 @property
3758 @property
3748 def scope(self):
3759 def scope(self):
3749 if self.repo:
3760 if self.repo:
3750 return repr(self.repo)
3761 return repr(self.repo)
3751 if self.repo_group:
3762 if self.repo_group:
3752 if self.child_repos_only:
3763 if self.child_repos_only:
3753 return repr(self.repo_group) + ' (child repos only)'
3764 return repr(self.repo_group) + ' (child repos only)'
3754 else:
3765 else:
3755 return repr(self.repo_group) + ' (recursive)'
3766 return repr(self.repo_group) + ' (recursive)'
3756 if self.child_repos_only:
3767 if self.child_repos_only:
3757 return 'root_repos'
3768 return 'root_repos'
3758 return 'global'
3769 return 'global'
3759
3770
3760 def __repr__(self):
3771 def __repr__(self):
3761 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3772 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3762
3773
3763
3774
3764 class RepoReviewRuleUser(Base, BaseModel):
3775 class RepoReviewRuleUser(Base, BaseModel):
3765 __tablename__ = 'repo_review_rules_users'
3776 __tablename__ = 'repo_review_rules_users'
3766 __table_args__ = (
3777 __table_args__ = (
3767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3778 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3779 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3769 )
3780 )
3770 repo_review_rule_user_id = Column(
3781 repo_review_rule_user_id = Column(
3771 'repo_review_rule_user_id', Integer(), primary_key=True)
3782 'repo_review_rule_user_id', Integer(), primary_key=True)
3772 repo_review_rule_id = Column("repo_review_rule_id",
3783 repo_review_rule_id = Column("repo_review_rule_id",
3773 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3784 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3774 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3785 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3775 nullable=False)
3786 nullable=False)
3776 user = relationship('User')
3787 user = relationship('User')
3777
3788
3778
3789
3779 class RepoReviewRuleUserGroup(Base, BaseModel):
3790 class RepoReviewRuleUserGroup(Base, BaseModel):
3780 __tablename__ = 'repo_review_rules_users_groups'
3791 __tablename__ = 'repo_review_rules_users_groups'
3781 __table_args__ = (
3792 __table_args__ = (
3782 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3793 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3783 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3794 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3784 )
3795 )
3785 repo_review_rule_users_group_id = Column(
3796 repo_review_rule_users_group_id = Column(
3786 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3797 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3787 repo_review_rule_id = Column("repo_review_rule_id",
3798 repo_review_rule_id = Column("repo_review_rule_id",
3788 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3799 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3789 users_group_id = Column("users_group_id", Integer(),
3800 users_group_id = Column("users_group_id", Integer(),
3790 ForeignKey('users_groups.users_group_id'), nullable=False)
3801 ForeignKey('users_groups.users_group_id'), nullable=False)
3791 users_group = relationship('UserGroup')
3802 users_group = relationship('UserGroup')
3792
3803
3793
3804
3794 class RepoReviewRule(Base, BaseModel):
3805 class RepoReviewRule(Base, BaseModel):
3795 __tablename__ = 'repo_review_rules'
3806 __tablename__ = 'repo_review_rules'
3796 __table_args__ = (
3807 __table_args__ = (
3797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3798 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3809 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3799 )
3810 )
3800
3811
3801 repo_review_rule_id = Column(
3812 repo_review_rule_id = Column(
3802 'repo_review_rule_id', Integer(), primary_key=True)
3813 'repo_review_rule_id', Integer(), primary_key=True)
3803 repo_id = Column(
3814 repo_id = Column(
3804 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3815 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3805 repo = relationship('Repository', backref='review_rules')
3816 repo = relationship('Repository', backref='review_rules')
3806
3817
3807 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3818 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3808 default=u'*') # glob
3819 default=u'*') # glob
3809 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3820 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3810 default=u'*') # glob
3821 default=u'*') # glob
3811
3822
3812 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3823 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3813 nullable=False, default=False)
3824 nullable=False, default=False)
3814 rule_users = relationship('RepoReviewRuleUser')
3825 rule_users = relationship('RepoReviewRuleUser')
3815 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3826 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3816
3827
3817 @hybrid_property
3828 @hybrid_property
3818 def branch_pattern(self):
3829 def branch_pattern(self):
3819 return self._branch_pattern or '*'
3830 return self._branch_pattern or '*'
3820
3831
3821 def _validate_glob(self, value):
3832 def _validate_glob(self, value):
3822 re.compile('^' + glob2re(value) + '$')
3833 re.compile('^' + glob2re(value) + '$')
3823
3834
3824 @branch_pattern.setter
3835 @branch_pattern.setter
3825 def branch_pattern(self, value):
3836 def branch_pattern(self, value):
3826 self._validate_glob(value)
3837 self._validate_glob(value)
3827 self._branch_pattern = value or '*'
3838 self._branch_pattern = value or '*'
3828
3839
3829 @hybrid_property
3840 @hybrid_property
3830 def file_pattern(self):
3841 def file_pattern(self):
3831 return self._file_pattern or '*'
3842 return self._file_pattern or '*'
3832
3843
3833 @file_pattern.setter
3844 @file_pattern.setter
3834 def file_pattern(self, value):
3845 def file_pattern(self, value):
3835 self._validate_glob(value)
3846 self._validate_glob(value)
3836 self._file_pattern = value or '*'
3847 self._file_pattern = value or '*'
3837
3848
3838 def matches(self, branch, files_changed):
3849 def matches(self, branch, files_changed):
3839 """
3850 """
3840 Check if this review rule matches a branch/files in a pull request
3851 Check if this review rule matches a branch/files in a pull request
3841
3852
3842 :param branch: branch name for the commit
3853 :param branch: branch name for the commit
3843 :param files_changed: list of file paths changed in the pull request
3854 :param files_changed: list of file paths changed in the pull request
3844 """
3855 """
3845
3856
3846 branch = branch or ''
3857 branch = branch or ''
3847 files_changed = files_changed or []
3858 files_changed = files_changed or []
3848
3859
3849 branch_matches = True
3860 branch_matches = True
3850 if branch:
3861 if branch:
3851 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3862 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3852 branch_matches = bool(branch_regex.search(branch))
3863 branch_matches = bool(branch_regex.search(branch))
3853
3864
3854 files_matches = True
3865 files_matches = True
3855 if self.file_pattern != '*':
3866 if self.file_pattern != '*':
3856 files_matches = False
3867 files_matches = False
3857 file_regex = re.compile(glob2re(self.file_pattern))
3868 file_regex = re.compile(glob2re(self.file_pattern))
3858 for filename in files_changed:
3869 for filename in files_changed:
3859 if file_regex.search(filename):
3870 if file_regex.search(filename):
3860 files_matches = True
3871 files_matches = True
3861 break
3872 break
3862
3873
3863 return branch_matches and files_matches
3874 return branch_matches and files_matches
3864
3875
3865 @property
3876 @property
3866 def review_users(self):
3877 def review_users(self):
3867 """ Returns the users which this rule applies to """
3878 """ Returns the users which this rule applies to """
3868
3879
3869 users = set()
3880 users = set()
3870 users |= set([
3881 users |= set([
3871 rule_user.user for rule_user in self.rule_users
3882 rule_user.user for rule_user in self.rule_users
3872 if rule_user.user.active])
3883 if rule_user.user.active])
3873 users |= set(
3884 users |= set(
3874 member.user
3885 member.user
3875 for rule_user_group in self.rule_user_groups
3886 for rule_user_group in self.rule_user_groups
3876 for member in rule_user_group.users_group.members
3887 for member in rule_user_group.users_group.members
3877 if member.user.active
3888 if member.user.active
3878 )
3889 )
3879 return users
3890 return users
3880
3891
3881 def __repr__(self):
3892 def __repr__(self):
3882 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3893 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3883 self.repo_review_rule_id, self.repo)
3894 self.repo_review_rule_id, self.repo)
3884
3895
3885
3896
3886 class DbMigrateVersion(Base, BaseModel):
3897 class DbMigrateVersion(Base, BaseModel):
3887 __tablename__ = 'db_migrate_version'
3898 __tablename__ = 'db_migrate_version'
3888 __table_args__ = (
3899 __table_args__ = (
3889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3900 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3890 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3901 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3891 )
3902 )
3892 repository_id = Column('repository_id', String(250), primary_key=True)
3903 repository_id = Column('repository_id', String(250), primary_key=True)
3893 repository_path = Column('repository_path', Text)
3904 repository_path = Column('repository_path', Text)
3894 version = Column('version', Integer)
3905 version = Column('version', Integer)
3895
3906
3896
3907
3897 class DbSession(Base, BaseModel):
3908 class DbSession(Base, BaseModel):
3898 __tablename__ = 'db_session'
3909 __tablename__ = 'db_session'
3899 __table_args__ = (
3910 __table_args__ = (
3900 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3911 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3901 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3912 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3902 )
3913 )
3903
3914
3904 def __repr__(self):
3915 def __repr__(self):
3905 return '<DB:DbSession({})>'.format(self.id)
3916 return '<DB:DbSession({})>'.format(self.id)
3906
3917
3907 id = Column('id', Integer())
3918 id = Column('id', Integer())
3908 namespace = Column('namespace', String(255), primary_key=True)
3919 namespace = Column('namespace', String(255), primary_key=True)
3909 accessed = Column('accessed', DateTime, nullable=False)
3920 accessed = Column('accessed', DateTime, nullable=False)
3910 created = Column('created', DateTime, nullable=False)
3921 created = Column('created', DateTime, nullable=False)
3911 data = Column('data', PickleType, nullable=False)
3922 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now