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