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