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