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