##// END OF EJS Templates
tokens: auth-token is not only for git/hg but also for svn....
marcink -
r442:94cb44ca default
parent child Browse files
Show More
@@ -1,3516 +1,3516 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 os
25 import os
26 import sys
26 import sys
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.exc import IntegrityError
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
42 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
43 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
45 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
46 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48
48
49 from pylons import url
49 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
50 from pylons.i18n.translation import lazy_ugettext as _
51
51
52 from rhodecode.lib.vcs import get_backend
52 from rhodecode.lib.vcs import get_backend
53 from rhodecode.lib.vcs.utils.helpers import get_scm
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
55 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
56 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 # beaker.session.secret if first is not set.
75 # and initialized at environment.py
75 # and initialized at environment.py
76 ENCRYPTION_KEY = None
76 ENCRYPTION_KEY = None
77
77
78 # used to sort permissions by types, '#' used here is not allowed to be in
78 # used to sort permissions by types, '#' used here is not allowed to be in
79 # usernames, and it's very early in sorted string.printable table.
79 # usernames, and it's very early in sorted string.printable table.
80 PERMISSION_TYPE_SORT = {
80 PERMISSION_TYPE_SORT = {
81 'admin': '####',
81 'admin': '####',
82 'write': '###',
82 'write': '###',
83 'read': '##',
83 'read': '##',
84 'none': '#',
84 'none': '#',
85 }
85 }
86
86
87
87
88 def display_sort(obj):
88 def display_sort(obj):
89 """
89 """
90 Sort function used to sort permissions in .permissions() function of
90 Sort function used to sort permissions in .permissions() function of
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
92 of all other resources
92 of all other resources
93 """
93 """
94
94
95 if obj.username == User.DEFAULT_USER:
95 if obj.username == User.DEFAULT_USER:
96 return '#####'
96 return '#####'
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
98 return prefix + obj.username
98 return prefix + obj.username
99
99
100
100
101 def _hash_key(k):
101 def _hash_key(k):
102 return md5_safe(k)
102 return md5_safe(k)
103
103
104
104
105 class EncryptedTextValue(TypeDecorator):
105 class EncryptedTextValue(TypeDecorator):
106 """
106 """
107 Special column for encrypted long text data, use like::
107 Special column for encrypted long text data, use like::
108
108
109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
110
110
111 This column is intelligent so if value is in unencrypted form it return
111 This column is intelligent so if value is in unencrypted form it return
112 unencrypted form, but on save it always encrypts
112 unencrypted form, but on save it always encrypts
113 """
113 """
114 impl = Text
114 impl = Text
115
115
116 def process_bind_param(self, value, dialect):
116 def process_bind_param(self, value, dialect):
117 if not value:
117 if not value:
118 return value
118 return value
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
120 # protect against double encrypting if someone manually starts
120 # protect against double encrypting if someone manually starts
121 # doing
121 # doing
122 raise ValueError('value needs to be in unencrypted format, ie. '
122 raise ValueError('value needs to be in unencrypted format, ie. '
123 'not starting with enc$aes')
123 'not starting with enc$aes')
124 return 'enc$aes_hmac$%s' % AESCipher(
124 return 'enc$aes_hmac$%s' % AESCipher(
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
126
126
127 def process_result_value(self, value, dialect):
127 def process_result_value(self, value, dialect):
128 import rhodecode
128 import rhodecode
129
129
130 if not value:
130 if not value:
131 return value
131 return value
132
132
133 parts = value.split('$', 3)
133 parts = value.split('$', 3)
134 if not len(parts) == 3:
134 if not len(parts) == 3:
135 # probably not encrypted values
135 # probably not encrypted values
136 return value
136 return value
137 else:
137 else:
138 if parts[0] != 'enc':
138 if parts[0] != 'enc':
139 # parts ok but without our header ?
139 # parts ok but without our header ?
140 return value
140 return value
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
142 'rhodecode.encrypted_values.strict') or True)
142 'rhodecode.encrypted_values.strict') or True)
143 # at that stage we know it's our encryption
143 # at that stage we know it's our encryption
144 if parts[1] == 'aes':
144 if parts[1] == 'aes':
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
146 elif parts[1] == 'aes_hmac':
146 elif parts[1] == 'aes_hmac':
147 decrypted_data = AESCipher(
147 decrypted_data = AESCipher(
148 ENCRYPTION_KEY, hmac=True,
148 ENCRYPTION_KEY, hmac=True,
149 strict_verification=enc_strict_mode).decrypt(parts[2])
149 strict_verification=enc_strict_mode).decrypt(parts[2])
150 else:
150 else:
151 raise ValueError(
151 raise ValueError(
152 'Encryption type part is wrong, must be `aes` '
152 'Encryption type part is wrong, must be `aes` '
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
154 return decrypted_data
154 return decrypted_data
155
155
156
156
157 class BaseModel(object):
157 class BaseModel(object):
158 """
158 """
159 Base Model for all classes
159 Base Model for all classes
160 """
160 """
161
161
162 @classmethod
162 @classmethod
163 def _get_keys(cls):
163 def _get_keys(cls):
164 """return column names for this model """
164 """return column names for this model """
165 return class_mapper(cls).c.keys()
165 return class_mapper(cls).c.keys()
166
166
167 def get_dict(self):
167 def get_dict(self):
168 """
168 """
169 return dict with keys and values corresponding
169 return dict with keys and values corresponding
170 to this model data """
170 to this model data """
171
171
172 d = {}
172 d = {}
173 for k in self._get_keys():
173 for k in self._get_keys():
174 d[k] = getattr(self, k)
174 d[k] = getattr(self, k)
175
175
176 # also use __json__() if present to get additional fields
176 # also use __json__() if present to get additional fields
177 _json_attr = getattr(self, '__json__', None)
177 _json_attr = getattr(self, '__json__', None)
178 if _json_attr:
178 if _json_attr:
179 # update with attributes from __json__
179 # update with attributes from __json__
180 if callable(_json_attr):
180 if callable(_json_attr):
181 _json_attr = _json_attr()
181 _json_attr = _json_attr()
182 for k, val in _json_attr.iteritems():
182 for k, val in _json_attr.iteritems():
183 d[k] = val
183 d[k] = val
184 return d
184 return d
185
185
186 def get_appstruct(self):
186 def get_appstruct(self):
187 """return list with keys and values tuples corresponding
187 """return list with keys and values tuples corresponding
188 to this model data """
188 to this model data """
189
189
190 l = []
190 l = []
191 for k in self._get_keys():
191 for k in self._get_keys():
192 l.append((k, getattr(self, k),))
192 l.append((k, getattr(self, k),))
193 return l
193 return l
194
194
195 def populate_obj(self, populate_dict):
195 def populate_obj(self, populate_dict):
196 """populate model with data from given populate_dict"""
196 """populate model with data from given populate_dict"""
197
197
198 for k in self._get_keys():
198 for k in self._get_keys():
199 if k in populate_dict:
199 if k in populate_dict:
200 setattr(self, k, populate_dict[k])
200 setattr(self, k, populate_dict[k])
201
201
202 @classmethod
202 @classmethod
203 def query(cls):
203 def query(cls):
204 return Session().query(cls)
204 return Session().query(cls)
205
205
206 @classmethod
206 @classmethod
207 def get(cls, id_):
207 def get(cls, id_):
208 if id_:
208 if id_:
209 return cls.query().get(id_)
209 return cls.query().get(id_)
210
210
211 @classmethod
211 @classmethod
212 def get_or_404(cls, id_):
212 def get_or_404(cls, id_):
213 try:
213 try:
214 id_ = int(id_)
214 id_ = int(id_)
215 except (TypeError, ValueError):
215 except (TypeError, ValueError):
216 raise HTTPNotFound
216 raise HTTPNotFound
217
217
218 res = cls.query().get(id_)
218 res = cls.query().get(id_)
219 if not res:
219 if not res:
220 raise HTTPNotFound
220 raise HTTPNotFound
221 return res
221 return res
222
222
223 @classmethod
223 @classmethod
224 def getAll(cls):
224 def getAll(cls):
225 # deprecated and left for backward compatibility
225 # deprecated and left for backward compatibility
226 return cls.get_all()
226 return cls.get_all()
227
227
228 @classmethod
228 @classmethod
229 def get_all(cls):
229 def get_all(cls):
230 return cls.query().all()
230 return cls.query().all()
231
231
232 @classmethod
232 @classmethod
233 def delete(cls, id_):
233 def delete(cls, id_):
234 obj = cls.query().get(id_)
234 obj = cls.query().get(id_)
235 Session().delete(obj)
235 Session().delete(obj)
236
236
237 @classmethod
237 @classmethod
238 def identity_cache(cls, session, attr_name, value):
238 def identity_cache(cls, session, attr_name, value):
239 exist_in_session = []
239 exist_in_session = []
240 for (item_cls, pkey), instance in session.identity_map.items():
240 for (item_cls, pkey), instance in session.identity_map.items():
241 if cls == item_cls and getattr(instance, attr_name) == value:
241 if cls == item_cls and getattr(instance, attr_name) == value:
242 exist_in_session.append(instance)
242 exist_in_session.append(instance)
243 if exist_in_session:
243 if exist_in_session:
244 if len(exist_in_session) == 1:
244 if len(exist_in_session) == 1:
245 return exist_in_session[0]
245 return exist_in_session[0]
246 log.exception(
246 log.exception(
247 'multiple objects with attr %s and '
247 'multiple objects with attr %s and '
248 'value %s found with same name: %r',
248 'value %s found with same name: %r',
249 attr_name, value, exist_in_session)
249 attr_name, value, exist_in_session)
250
250
251 def __repr__(self):
251 def __repr__(self):
252 if hasattr(self, '__unicode__'):
252 if hasattr(self, '__unicode__'):
253 # python repr needs to return str
253 # python repr needs to return str
254 try:
254 try:
255 return safe_str(self.__unicode__())
255 return safe_str(self.__unicode__())
256 except UnicodeDecodeError:
256 except UnicodeDecodeError:
257 pass
257 pass
258 return '<DB:%s>' % (self.__class__.__name__)
258 return '<DB:%s>' % (self.__class__.__name__)
259
259
260
260
261 class RhodeCodeSetting(Base, BaseModel):
261 class RhodeCodeSetting(Base, BaseModel):
262 __tablename__ = 'rhodecode_settings'
262 __tablename__ = 'rhodecode_settings'
263 __table_args__ = (
263 __table_args__ = (
264 UniqueConstraint('app_settings_name'),
264 UniqueConstraint('app_settings_name'),
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
267 )
267 )
268
268
269 SETTINGS_TYPES = {
269 SETTINGS_TYPES = {
270 'str': safe_str,
270 'str': safe_str,
271 'int': safe_int,
271 'int': safe_int,
272 'unicode': safe_unicode,
272 'unicode': safe_unicode,
273 'bool': str2bool,
273 'bool': str2bool,
274 'list': functools.partial(aslist, sep=',')
274 'list': functools.partial(aslist, sep=',')
275 }
275 }
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
277 GLOBAL_CONF_KEY = 'app_settings'
277 GLOBAL_CONF_KEY = 'app_settings'
278
278
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
283
283
284 def __init__(self, key='', val='', type='unicode'):
284 def __init__(self, key='', val='', type='unicode'):
285 self.app_settings_name = key
285 self.app_settings_name = key
286 self.app_settings_type = type
286 self.app_settings_type = type
287 self.app_settings_value = val
287 self.app_settings_value = val
288
288
289 @validates('_app_settings_value')
289 @validates('_app_settings_value')
290 def validate_settings_value(self, key, val):
290 def validate_settings_value(self, key, val):
291 assert type(val) == unicode
291 assert type(val) == unicode
292 return val
292 return val
293
293
294 @hybrid_property
294 @hybrid_property
295 def app_settings_value(self):
295 def app_settings_value(self):
296 v = self._app_settings_value
296 v = self._app_settings_value
297 _type = self.app_settings_type
297 _type = self.app_settings_type
298 if _type:
298 if _type:
299 _type = self.app_settings_type.split('.')[0]
299 _type = self.app_settings_type.split('.')[0]
300 # decode the encrypted value
300 # decode the encrypted value
301 if 'encrypted' in self.app_settings_type:
301 if 'encrypted' in self.app_settings_type:
302 cipher = EncryptedTextValue()
302 cipher = EncryptedTextValue()
303 v = safe_unicode(cipher.process_result_value(v, None))
303 v = safe_unicode(cipher.process_result_value(v, None))
304
304
305 converter = self.SETTINGS_TYPES.get(_type) or \
305 converter = self.SETTINGS_TYPES.get(_type) or \
306 self.SETTINGS_TYPES['unicode']
306 self.SETTINGS_TYPES['unicode']
307 return converter(v)
307 return converter(v)
308
308
309 @app_settings_value.setter
309 @app_settings_value.setter
310 def app_settings_value(self, val):
310 def app_settings_value(self, val):
311 """
311 """
312 Setter that will always make sure we use unicode in app_settings_value
312 Setter that will always make sure we use unicode in app_settings_value
313
313
314 :param val:
314 :param val:
315 """
315 """
316 val = safe_unicode(val)
316 val = safe_unicode(val)
317 # encode the encrypted value
317 # encode the encrypted value
318 if 'encrypted' in self.app_settings_type:
318 if 'encrypted' in self.app_settings_type:
319 cipher = EncryptedTextValue()
319 cipher = EncryptedTextValue()
320 val = safe_unicode(cipher.process_bind_param(val, None))
320 val = safe_unicode(cipher.process_bind_param(val, None))
321 self._app_settings_value = val
321 self._app_settings_value = val
322
322
323 @hybrid_property
323 @hybrid_property
324 def app_settings_type(self):
324 def app_settings_type(self):
325 return self._app_settings_type
325 return self._app_settings_type
326
326
327 @app_settings_type.setter
327 @app_settings_type.setter
328 def app_settings_type(self, val):
328 def app_settings_type(self, val):
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
330 raise Exception('type must be one of %s got %s'
330 raise Exception('type must be one of %s got %s'
331 % (self.SETTINGS_TYPES.keys(), val))
331 % (self.SETTINGS_TYPES.keys(), val))
332 self._app_settings_type = val
332 self._app_settings_type = val
333
333
334 def __unicode__(self):
334 def __unicode__(self):
335 return u"<%s('%s:%s[%s]')>" % (
335 return u"<%s('%s:%s[%s]')>" % (
336 self.__class__.__name__,
336 self.__class__.__name__,
337 self.app_settings_name, self.app_settings_value,
337 self.app_settings_name, self.app_settings_value,
338 self.app_settings_type
338 self.app_settings_type
339 )
339 )
340
340
341
341
342 class RhodeCodeUi(Base, BaseModel):
342 class RhodeCodeUi(Base, BaseModel):
343 __tablename__ = 'rhodecode_ui'
343 __tablename__ = 'rhodecode_ui'
344 __table_args__ = (
344 __table_args__ = (
345 UniqueConstraint('ui_key'),
345 UniqueConstraint('ui_key'),
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
348 )
348 )
349
349
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
351 # HG
351 # HG
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
353 HOOK_PULL = 'outgoing.pull_logger'
353 HOOK_PULL = 'outgoing.pull_logger'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
355 HOOK_PUSH = 'changegroup.push_logger'
355 HOOK_PUSH = 'changegroup.push_logger'
356
356
357 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # TODO: johbo: Unify way how hooks are configured for git and hg,
358 # git part is currently hardcoded.
358 # git part is currently hardcoded.
359
359
360 # SVN PATTERNS
360 # SVN PATTERNS
361 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_BRANCH_ID = 'vcs_svn_branch'
362 SVN_TAG_ID = 'vcs_svn_tag'
362 SVN_TAG_ID = 'vcs_svn_tag'
363
363
364 ui_id = Column(
364 ui_id = Column(
365 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 "ui_id", Integer(), nullable=False, unique=True, default=None,
366 primary_key=True)
366 primary_key=True)
367 ui_section = Column(
367 ui_section = Column(
368 "ui_section", String(255), nullable=True, unique=None, default=None)
368 "ui_section", String(255), nullable=True, unique=None, default=None)
369 ui_key = Column(
369 ui_key = Column(
370 "ui_key", String(255), nullable=True, unique=None, default=None)
370 "ui_key", String(255), nullable=True, unique=None, default=None)
371 ui_value = Column(
371 ui_value = Column(
372 "ui_value", String(255), nullable=True, unique=None, default=None)
372 "ui_value", String(255), nullable=True, unique=None, default=None)
373 ui_active = Column(
373 ui_active = Column(
374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
375
375
376 def __repr__(self):
376 def __repr__(self):
377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
378 self.ui_key, self.ui_value)
378 self.ui_key, self.ui_value)
379
379
380
380
381 class RepoRhodeCodeSetting(Base, BaseModel):
381 class RepoRhodeCodeSetting(Base, BaseModel):
382 __tablename__ = 'repo_rhodecode_settings'
382 __tablename__ = 'repo_rhodecode_settings'
383 __table_args__ = (
383 __table_args__ = (
384 UniqueConstraint(
384 UniqueConstraint(
385 'app_settings_name', 'repository_id',
385 'app_settings_name', 'repository_id',
386 name='uq_repo_rhodecode_setting_name_repo_id'),
386 name='uq_repo_rhodecode_setting_name_repo_id'),
387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
389 )
389 )
390
390
391 repository_id = Column(
391 repository_id = Column(
392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
393 nullable=False)
393 nullable=False)
394 app_settings_id = Column(
394 app_settings_id = Column(
395 "app_settings_id", Integer(), nullable=False, unique=True,
395 "app_settings_id", Integer(), nullable=False, unique=True,
396 default=None, primary_key=True)
396 default=None, primary_key=True)
397 app_settings_name = Column(
397 app_settings_name = Column(
398 "app_settings_name", String(255), nullable=True, unique=None,
398 "app_settings_name", String(255), nullable=True, unique=None,
399 default=None)
399 default=None)
400 _app_settings_value = Column(
400 _app_settings_value = Column(
401 "app_settings_value", String(4096), nullable=True, unique=None,
401 "app_settings_value", String(4096), nullable=True, unique=None,
402 default=None)
402 default=None)
403 _app_settings_type = Column(
403 _app_settings_type = Column(
404 "app_settings_type", String(255), nullable=True, unique=None,
404 "app_settings_type", String(255), nullable=True, unique=None,
405 default=None)
405 default=None)
406
406
407 repository = relationship('Repository')
407 repository = relationship('Repository')
408
408
409 def __init__(self, repository_id, key='', val='', type='unicode'):
409 def __init__(self, repository_id, key='', val='', type='unicode'):
410 self.repository_id = repository_id
410 self.repository_id = repository_id
411 self.app_settings_name = key
411 self.app_settings_name = key
412 self.app_settings_type = type
412 self.app_settings_type = type
413 self.app_settings_value = val
413 self.app_settings_value = val
414
414
415 @validates('_app_settings_value')
415 @validates('_app_settings_value')
416 def validate_settings_value(self, key, val):
416 def validate_settings_value(self, key, val):
417 assert type(val) == unicode
417 assert type(val) == unicode
418 return val
418 return val
419
419
420 @hybrid_property
420 @hybrid_property
421 def app_settings_value(self):
421 def app_settings_value(self):
422 v = self._app_settings_value
422 v = self._app_settings_value
423 type_ = self.app_settings_type
423 type_ = self.app_settings_type
424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
426 return converter(v)
426 return converter(v)
427
427
428 @app_settings_value.setter
428 @app_settings_value.setter
429 def app_settings_value(self, val):
429 def app_settings_value(self, val):
430 """
430 """
431 Setter that will always make sure we use unicode in app_settings_value
431 Setter that will always make sure we use unicode in app_settings_value
432
432
433 :param val:
433 :param val:
434 """
434 """
435 self._app_settings_value = safe_unicode(val)
435 self._app_settings_value = safe_unicode(val)
436
436
437 @hybrid_property
437 @hybrid_property
438 def app_settings_type(self):
438 def app_settings_type(self):
439 return self._app_settings_type
439 return self._app_settings_type
440
440
441 @app_settings_type.setter
441 @app_settings_type.setter
442 def app_settings_type(self, val):
442 def app_settings_type(self, val):
443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
444 if val not in SETTINGS_TYPES:
444 if val not in SETTINGS_TYPES:
445 raise Exception('type must be one of %s got %s'
445 raise Exception('type must be one of %s got %s'
446 % (SETTINGS_TYPES.keys(), val))
446 % (SETTINGS_TYPES.keys(), val))
447 self._app_settings_type = val
447 self._app_settings_type = val
448
448
449 def __unicode__(self):
449 def __unicode__(self):
450 return u"<%s('%s:%s:%s[%s]')>" % (
450 return u"<%s('%s:%s:%s[%s]')>" % (
451 self.__class__.__name__, self.repository.repo_name,
451 self.__class__.__name__, self.repository.repo_name,
452 self.app_settings_name, self.app_settings_value,
452 self.app_settings_name, self.app_settings_value,
453 self.app_settings_type
453 self.app_settings_type
454 )
454 )
455
455
456
456
457 class RepoRhodeCodeUi(Base, BaseModel):
457 class RepoRhodeCodeUi(Base, BaseModel):
458 __tablename__ = 'repo_rhodecode_ui'
458 __tablename__ = 'repo_rhodecode_ui'
459 __table_args__ = (
459 __table_args__ = (
460 UniqueConstraint(
460 UniqueConstraint(
461 'repository_id', 'ui_section', 'ui_key',
461 'repository_id', 'ui_section', 'ui_key',
462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
465 )
465 )
466
466
467 repository_id = Column(
467 repository_id = Column(
468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
469 nullable=False)
469 nullable=False)
470 ui_id = Column(
470 ui_id = Column(
471 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 "ui_id", Integer(), nullable=False, unique=True, default=None,
472 primary_key=True)
472 primary_key=True)
473 ui_section = Column(
473 ui_section = Column(
474 "ui_section", String(255), nullable=True, unique=None, default=None)
474 "ui_section", String(255), nullable=True, unique=None, default=None)
475 ui_key = Column(
475 ui_key = Column(
476 "ui_key", String(255), nullable=True, unique=None, default=None)
476 "ui_key", String(255), nullable=True, unique=None, default=None)
477 ui_value = Column(
477 ui_value = Column(
478 "ui_value", String(255), nullable=True, unique=None, default=None)
478 "ui_value", String(255), nullable=True, unique=None, default=None)
479 ui_active = Column(
479 ui_active = Column(
480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
481
481
482 repository = relationship('Repository')
482 repository = relationship('Repository')
483
483
484 def __repr__(self):
484 def __repr__(self):
485 return '<%s[%s:%s]%s=>%s]>' % (
485 return '<%s[%s:%s]%s=>%s]>' % (
486 self.__class__.__name__, self.repository.repo_name,
486 self.__class__.__name__, self.repository.repo_name,
487 self.ui_section, self.ui_key, self.ui_value)
487 self.ui_section, self.ui_key, self.ui_value)
488
488
489
489
490 class User(Base, BaseModel):
490 class User(Base, BaseModel):
491 __tablename__ = 'users'
491 __tablename__ = 'users'
492 __table_args__ = (
492 __table_args__ = (
493 UniqueConstraint('username'), UniqueConstraint('email'),
493 UniqueConstraint('username'), UniqueConstraint('email'),
494 Index('u_username_idx', 'username'),
494 Index('u_username_idx', 'username'),
495 Index('u_email_idx', 'email'),
495 Index('u_email_idx', 'email'),
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
498 )
498 )
499 DEFAULT_USER = 'default'
499 DEFAULT_USER = 'default'
500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
502
502
503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
504 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 username = Column("username", String(255), nullable=True, unique=None, default=None)
505 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 password = Column("password", String(255), nullable=True, unique=None, default=None)
506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
518
518
519 user_log = relationship('UserLog')
519 user_log = relationship('UserLog')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
521
521
522 repositories = relationship('Repository')
522 repositories = relationship('Repository')
523 repository_groups = relationship('RepoGroup')
523 repository_groups = relationship('RepoGroup')
524 user_groups = relationship('UserGroup')
524 user_groups = relationship('UserGroup')
525
525
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
528
528
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
532
532
533 group_member = relationship('UserGroupMember', cascade='all')
533 group_member = relationship('UserGroupMember', cascade='all')
534
534
535 notifications = relationship('UserNotification', cascade='all')
535 notifications = relationship('UserNotification', cascade='all')
536 # notifications assigned to this user
536 # notifications assigned to this user
537 user_created_notifications = relationship('Notification', cascade='all')
537 user_created_notifications = relationship('Notification', cascade='all')
538 # comments created by this user
538 # comments created by this user
539 user_comments = relationship('ChangesetComment', cascade='all')
539 user_comments = relationship('ChangesetComment', cascade='all')
540 # user profile extra info
540 # user profile extra info
541 user_emails = relationship('UserEmailMap', cascade='all')
541 user_emails = relationship('UserEmailMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
544 # gists
544 # gists
545 user_gists = relationship('Gist', cascade='all')
545 user_gists = relationship('Gist', cascade='all')
546 # user pull requests
546 # user pull requests
547 user_pull_requests = relationship('PullRequest', cascade='all')
547 user_pull_requests = relationship('PullRequest', cascade='all')
548 # external identities
548 # external identities
549 extenal_identities = relationship(
549 extenal_identities = relationship(
550 'ExternalIdentity',
550 'ExternalIdentity',
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
552 cascade='all')
552 cascade='all')
553
553
554 def __unicode__(self):
554 def __unicode__(self):
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
556 self.user_id, self.username)
556 self.user_id, self.username)
557
557
558 @hybrid_property
558 @hybrid_property
559 def email(self):
559 def email(self):
560 return self._email
560 return self._email
561
561
562 @email.setter
562 @email.setter
563 def email(self, val):
563 def email(self, val):
564 self._email = val.lower() if val else None
564 self._email = val.lower() if val else None
565
565
566 @property
566 @property
567 def firstname(self):
567 def firstname(self):
568 # alias for future
568 # alias for future
569 return self.name
569 return self.name
570
570
571 @property
571 @property
572 def emails(self):
572 def emails(self):
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
574 return [self.email] + [x.email for x in other]
574 return [self.email] + [x.email for x in other]
575
575
576 @property
576 @property
577 def auth_tokens(self):
577 def auth_tokens(self):
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
579
579
580 @property
580 @property
581 def extra_auth_tokens(self):
581 def extra_auth_tokens(self):
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
583
583
584 @property
584 @property
585 def feed_token(self):
585 def feed_token(self):
586 feed_tokens = UserApiKeys.query()\
586 feed_tokens = UserApiKeys.query()\
587 .filter(UserApiKeys.user == self)\
587 .filter(UserApiKeys.user == self)\
588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
589 .all()
589 .all()
590 if feed_tokens:
590 if feed_tokens:
591 return feed_tokens[0].api_key
591 return feed_tokens[0].api_key
592 else:
592 else:
593 # use the main token so we don't end up with nothing...
593 # use the main token so we don't end up with nothing...
594 return self.api_key
594 return self.api_key
595
595
596 @classmethod
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
601 if role:
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
604 return tokens.all()
605
605
606 @property
606 @property
607 def ip_addresses(self):
607 def ip_addresses(self):
608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
609 return [x.ip_addr for x in ret]
609 return [x.ip_addr for x in ret]
610
610
611 @property
611 @property
612 def username_and_name(self):
612 def username_and_name(self):
613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
614
614
615 @property
615 @property
616 def username_or_name_or_email(self):
616 def username_or_name_or_email(self):
617 full_name = self.full_name if self.full_name is not ' ' else None
617 full_name = self.full_name if self.full_name is not ' ' else None
618 return self.username or full_name or self.email
618 return self.username or full_name or self.email
619
619
620 @property
620 @property
621 def full_name(self):
621 def full_name(self):
622 return '%s %s' % (self.firstname, self.lastname)
622 return '%s %s' % (self.firstname, self.lastname)
623
623
624 @property
624 @property
625 def full_name_or_username(self):
625 def full_name_or_username(self):
626 return ('%s %s' % (self.firstname, self.lastname)
626 return ('%s %s' % (self.firstname, self.lastname)
627 if (self.firstname and self.lastname) else self.username)
627 if (self.firstname and self.lastname) else self.username)
628
628
629 @property
629 @property
630 def full_contact(self):
630 def full_contact(self):
631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
632
632
633 @property
633 @property
634 def short_contact(self):
634 def short_contact(self):
635 return '%s %s' % (self.firstname, self.lastname)
635 return '%s %s' % (self.firstname, self.lastname)
636
636
637 @property
637 @property
638 def is_admin(self):
638 def is_admin(self):
639 return self.admin
639 return self.admin
640
640
641 @property
641 @property
642 def AuthUser(self):
642 def AuthUser(self):
643 """
643 """
644 Returns instance of AuthUser for this user
644 Returns instance of AuthUser for this user
645 """
645 """
646 from rhodecode.lib.auth import AuthUser
646 from rhodecode.lib.auth import AuthUser
647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
648 username=self.username)
648 username=self.username)
649
649
650 @hybrid_property
650 @hybrid_property
651 def user_data(self):
651 def user_data(self):
652 if not self._user_data:
652 if not self._user_data:
653 return {}
653 return {}
654
654
655 try:
655 try:
656 return json.loads(self._user_data)
656 return json.loads(self._user_data)
657 except TypeError:
657 except TypeError:
658 return {}
658 return {}
659
659
660 @user_data.setter
660 @user_data.setter
661 def user_data(self, val):
661 def user_data(self, val):
662 if not isinstance(val, dict):
662 if not isinstance(val, dict):
663 raise Exception('user_data must be dict, got %s' % type(val))
663 raise Exception('user_data must be dict, got %s' % type(val))
664 try:
664 try:
665 self._user_data = json.dumps(val)
665 self._user_data = json.dumps(val)
666 except Exception:
666 except Exception:
667 log.error(traceback.format_exc())
667 log.error(traceback.format_exc())
668
668
669 @classmethod
669 @classmethod
670 def get_by_username(cls, username, case_insensitive=False,
670 def get_by_username(cls, username, case_insensitive=False,
671 cache=False, identity_cache=False):
671 cache=False, identity_cache=False):
672 session = Session()
672 session = Session()
673
673
674 if case_insensitive:
674 if case_insensitive:
675 q = cls.query().filter(
675 q = cls.query().filter(
676 func.lower(cls.username) == func.lower(username))
676 func.lower(cls.username) == func.lower(username))
677 else:
677 else:
678 q = cls.query().filter(cls.username == username)
678 q = cls.query().filter(cls.username == username)
679
679
680 if cache:
680 if cache:
681 if identity_cache:
681 if identity_cache:
682 val = cls.identity_cache(session, 'username', username)
682 val = cls.identity_cache(session, 'username', username)
683 if val:
683 if val:
684 return val
684 return val
685 else:
685 else:
686 q = q.options(
686 q = q.options(
687 FromCache("sql_cache_short",
687 FromCache("sql_cache_short",
688 "get_user_by_name_%s" % _hash_key(username)))
688 "get_user_by_name_%s" % _hash_key(username)))
689
689
690 return q.scalar()
690 return q.scalar()
691
691
692 @classmethod
692 @classmethod
693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
694 q = cls.query().filter(cls.api_key == auth_token)
694 q = cls.query().filter(cls.api_key == auth_token)
695
695
696 if cache:
696 if cache:
697 q = q.options(FromCache("sql_cache_short",
697 q = q.options(FromCache("sql_cache_short",
698 "get_auth_token_%s" % auth_token))
698 "get_auth_token_%s" % auth_token))
699 res = q.scalar()
699 res = q.scalar()
700
700
701 if fallback and not res:
701 if fallback and not res:
702 #fallback to additional keys
702 #fallback to additional keys
703 _res = UserApiKeys.query()\
703 _res = UserApiKeys.query()\
704 .filter(UserApiKeys.api_key == auth_token)\
704 .filter(UserApiKeys.api_key == auth_token)\
705 .filter(or_(UserApiKeys.expires == -1,
705 .filter(or_(UserApiKeys.expires == -1,
706 UserApiKeys.expires >= time.time()))\
706 UserApiKeys.expires >= time.time()))\
707 .first()
707 .first()
708 if _res:
708 if _res:
709 res = _res.user
709 res = _res.user
710 return res
710 return res
711
711
712 @classmethod
712 @classmethod
713 def get_by_email(cls, email, case_insensitive=False, cache=False):
713 def get_by_email(cls, email, case_insensitive=False, cache=False):
714
714
715 if case_insensitive:
715 if case_insensitive:
716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
717
717
718 else:
718 else:
719 q = cls.query().filter(cls.email == email)
719 q = cls.query().filter(cls.email == email)
720
720
721 if cache:
721 if cache:
722 q = q.options(FromCache("sql_cache_short",
722 q = q.options(FromCache("sql_cache_short",
723 "get_email_key_%s" % email))
723 "get_email_key_%s" % email))
724
724
725 ret = q.scalar()
725 ret = q.scalar()
726 if ret is None:
726 if ret is None:
727 q = UserEmailMap.query()
727 q = UserEmailMap.query()
728 # try fetching in alternate email map
728 # try fetching in alternate email map
729 if case_insensitive:
729 if case_insensitive:
730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
731 else:
731 else:
732 q = q.filter(UserEmailMap.email == email)
732 q = q.filter(UserEmailMap.email == email)
733 q = q.options(joinedload(UserEmailMap.user))
733 q = q.options(joinedload(UserEmailMap.user))
734 if cache:
734 if cache:
735 q = q.options(FromCache("sql_cache_short",
735 q = q.options(FromCache("sql_cache_short",
736 "get_email_map_key_%s" % email))
736 "get_email_map_key_%s" % email))
737 ret = getattr(q.scalar(), 'user', None)
737 ret = getattr(q.scalar(), 'user', None)
738
738
739 return ret
739 return ret
740
740
741 @classmethod
741 @classmethod
742 def get_from_cs_author(cls, author):
742 def get_from_cs_author(cls, author):
743 """
743 """
744 Tries to get User objects out of commit author string
744 Tries to get User objects out of commit author string
745
745
746 :param author:
746 :param author:
747 """
747 """
748 from rhodecode.lib.helpers import email, author_name
748 from rhodecode.lib.helpers import email, author_name
749 # Valid email in the attribute passed, see if they're in the system
749 # Valid email in the attribute passed, see if they're in the system
750 _email = email(author)
750 _email = email(author)
751 if _email:
751 if _email:
752 user = cls.get_by_email(_email, case_insensitive=True)
752 user = cls.get_by_email(_email, case_insensitive=True)
753 if user:
753 if user:
754 return user
754 return user
755 # Maybe we can match by username?
755 # Maybe we can match by username?
756 _author = author_name(author)
756 _author = author_name(author)
757 user = cls.get_by_username(_author, case_insensitive=True)
757 user = cls.get_by_username(_author, case_insensitive=True)
758 if user:
758 if user:
759 return user
759 return user
760
760
761 def update_userdata(self, **kwargs):
761 def update_userdata(self, **kwargs):
762 usr = self
762 usr = self
763 old = usr.user_data
763 old = usr.user_data
764 old.update(**kwargs)
764 old.update(**kwargs)
765 usr.user_data = old
765 usr.user_data = old
766 Session().add(usr)
766 Session().add(usr)
767 log.debug('updated userdata with ', kwargs)
767 log.debug('updated userdata with ', kwargs)
768
768
769 def update_lastlogin(self):
769 def update_lastlogin(self):
770 """Update user lastlogin"""
770 """Update user lastlogin"""
771 self.last_login = datetime.datetime.now()
771 self.last_login = datetime.datetime.now()
772 Session().add(self)
772 Session().add(self)
773 log.debug('updated user %s lastlogin', self.username)
773 log.debug('updated user %s lastlogin', self.username)
774
774
775 def update_lastactivity(self):
775 def update_lastactivity(self):
776 """Update user lastactivity"""
776 """Update user lastactivity"""
777 usr = self
777 usr = self
778 old = usr.user_data
778 old = usr.user_data
779 old.update({'last_activity': time.time()})
779 old.update({'last_activity': time.time()})
780 usr.user_data = old
780 usr.user_data = old
781 Session().add(usr)
781 Session().add(usr)
782 log.debug('updated user %s lastactivity', usr.username)
782 log.debug('updated user %s lastactivity', usr.username)
783
783
784 def update_password(self, new_password, change_api_key=False):
784 def update_password(self, new_password, change_api_key=False):
785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
786
786
787 self.password = get_crypt_password(new_password)
787 self.password = get_crypt_password(new_password)
788 if change_api_key:
788 if change_api_key:
789 self.api_key = generate_auth_token(self.username)
789 self.api_key = generate_auth_token(self.username)
790 Session().add(self)
790 Session().add(self)
791
791
792 @classmethod
792 @classmethod
793 def get_first_super_admin(cls):
793 def get_first_super_admin(cls):
794 user = User.query().filter(User.admin == true()).first()
794 user = User.query().filter(User.admin == true()).first()
795 if user is None:
795 if user is None:
796 raise Exception('FATAL: Missing administrative account!')
796 raise Exception('FATAL: Missing administrative account!')
797 return user
797 return user
798
798
799 @classmethod
799 @classmethod
800 def get_all_super_admins(cls):
800 def get_all_super_admins(cls):
801 """
801 """
802 Returns all admin accounts sorted by username
802 Returns all admin accounts sorted by username
803 """
803 """
804 return User.query().filter(User.admin == true())\
804 return User.query().filter(User.admin == true())\
805 .order_by(User.username.asc()).all()
805 .order_by(User.username.asc()).all()
806
806
807 @classmethod
807 @classmethod
808 def get_default_user(cls, cache=False):
808 def get_default_user(cls, cache=False):
809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
810 if user is None:
810 if user is None:
811 raise Exception('FATAL: Missing default account!')
811 raise Exception('FATAL: Missing default account!')
812 return user
812 return user
813
813
814 def _get_default_perms(self, user, suffix=''):
814 def _get_default_perms(self, user, suffix=''):
815 from rhodecode.model.permission import PermissionModel
815 from rhodecode.model.permission import PermissionModel
816 return PermissionModel().get_default_perms(user.user_perms, suffix)
816 return PermissionModel().get_default_perms(user.user_perms, suffix)
817
817
818 def get_default_perms(self, suffix=''):
818 def get_default_perms(self, suffix=''):
819 return self._get_default_perms(self, suffix)
819 return self._get_default_perms(self, suffix)
820
820
821 def get_api_data(self, include_secrets=False, details='full'):
821 def get_api_data(self, include_secrets=False, details='full'):
822 """
822 """
823 Common function for generating user related data for API
823 Common function for generating user related data for API
824
824
825 :param include_secrets: By default secrets in the API data will be replaced
825 :param include_secrets: By default secrets in the API data will be replaced
826 by a placeholder value to prevent exposing this data by accident. In case
826 by a placeholder value to prevent exposing this data by accident. In case
827 this data shall be exposed, set this flag to ``True``.
827 this data shall be exposed, set this flag to ``True``.
828
828
829 :param details: details can be 'basic|full' basic gives only a subset of
829 :param details: details can be 'basic|full' basic gives only a subset of
830 the available user information that includes user_id, name and emails.
830 the available user information that includes user_id, name and emails.
831 """
831 """
832 user = self
832 user = self
833 user_data = self.user_data
833 user_data = self.user_data
834 data = {
834 data = {
835 'user_id': user.user_id,
835 'user_id': user.user_id,
836 'username': user.username,
836 'username': user.username,
837 'firstname': user.name,
837 'firstname': user.name,
838 'lastname': user.lastname,
838 'lastname': user.lastname,
839 'email': user.email,
839 'email': user.email,
840 'emails': user.emails,
840 'emails': user.emails,
841 }
841 }
842 if details == 'basic':
842 if details == 'basic':
843 return data
843 return data
844
844
845 api_key_length = 40
845 api_key_length = 40
846 api_key_replacement = '*' * api_key_length
846 api_key_replacement = '*' * api_key_length
847
847
848 extras = {
848 extras = {
849 'api_key': api_key_replacement,
849 'api_key': api_key_replacement,
850 'api_keys': [api_key_replacement],
850 'api_keys': [api_key_replacement],
851 'active': user.active,
851 'active': user.active,
852 'admin': user.admin,
852 'admin': user.admin,
853 'extern_type': user.extern_type,
853 'extern_type': user.extern_type,
854 'extern_name': user.extern_name,
854 'extern_name': user.extern_name,
855 'last_login': user.last_login,
855 'last_login': user.last_login,
856 'ip_addresses': user.ip_addresses,
856 'ip_addresses': user.ip_addresses,
857 'language': user_data.get('language')
857 'language': user_data.get('language')
858 }
858 }
859 data.update(extras)
859 data.update(extras)
860
860
861 if include_secrets:
861 if include_secrets:
862 data['api_key'] = user.api_key
862 data['api_key'] = user.api_key
863 data['api_keys'] = user.auth_tokens
863 data['api_keys'] = user.auth_tokens
864 return data
864 return data
865
865
866 def __json__(self):
866 def __json__(self):
867 data = {
867 data = {
868 'full_name': self.full_name,
868 'full_name': self.full_name,
869 'full_name_or_username': self.full_name_or_username,
869 'full_name_or_username': self.full_name_or_username,
870 'short_contact': self.short_contact,
870 'short_contact': self.short_contact,
871 'full_contact': self.full_contact,
871 'full_contact': self.full_contact,
872 }
872 }
873 data.update(self.get_api_data())
873 data.update(self.get_api_data())
874 return data
874 return data
875
875
876
876
877 class UserApiKeys(Base, BaseModel):
877 class UserApiKeys(Base, BaseModel):
878 __tablename__ = 'user_api_keys'
878 __tablename__ = 'user_api_keys'
879 __table_args__ = (
879 __table_args__ = (
880 Index('uak_api_key_idx', 'api_key'),
880 Index('uak_api_key_idx', 'api_key'),
881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
882 UniqueConstraint('api_key'),
882 UniqueConstraint('api_key'),
883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
885 )
885 )
886 __mapper_args__ = {}
886 __mapper_args__ = {}
887
887
888 # ApiKey role
888 # ApiKey role
889 ROLE_ALL = 'token_role_all'
889 ROLE_ALL = 'token_role_all'
890 ROLE_HTTP = 'token_role_http'
890 ROLE_HTTP = 'token_role_http'
891 ROLE_VCS = 'token_role_vcs'
891 ROLE_VCS = 'token_role_vcs'
892 ROLE_API = 'token_role_api'
892 ROLE_API = 'token_role_api'
893 ROLE_FEED = 'token_role_feed'
893 ROLE_FEED = 'token_role_feed'
894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
895
895
896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
898 api_key = Column("api_key", String(255), nullable=False, unique=True)
898 api_key = Column("api_key", String(255), nullable=False, unique=True)
899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
900 expires = Column('expires', Float(53), nullable=False)
900 expires = Column('expires', Float(53), nullable=False)
901 role = Column('role', String(255), nullable=True)
901 role = Column('role', String(255), nullable=True)
902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
903
903
904 user = relationship('User', lazy='joined')
904 user = relationship('User', lazy='joined')
905
905
906 @classmethod
906 @classmethod
907 def _get_role_name(cls, role):
907 def _get_role_name(cls, role):
908 return {
908 return {
909 cls.ROLE_ALL: _('all'),
909 cls.ROLE_ALL: _('all'),
910 cls.ROLE_HTTP: _('http/web interface'),
910 cls.ROLE_HTTP: _('http/web interface'),
911 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
911 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
912 cls.ROLE_API: _('api calls'),
912 cls.ROLE_API: _('api calls'),
913 cls.ROLE_FEED: _('feed access'),
913 cls.ROLE_FEED: _('feed access'),
914 }.get(role, role)
914 }.get(role, role)
915
915
916 @property
916 @property
917 def expired(self):
917 def expired(self):
918 if self.expires == -1:
918 if self.expires == -1:
919 return False
919 return False
920 return time.time() > self.expires
920 return time.time() > self.expires
921
921
922 @property
922 @property
923 def role_humanized(self):
923 def role_humanized(self):
924 return self._get_role_name(self.role)
924 return self._get_role_name(self.role)
925
925
926
926
927 class UserEmailMap(Base, BaseModel):
927 class UserEmailMap(Base, BaseModel):
928 __tablename__ = 'user_email_map'
928 __tablename__ = 'user_email_map'
929 __table_args__ = (
929 __table_args__ = (
930 Index('uem_email_idx', 'email'),
930 Index('uem_email_idx', 'email'),
931 UniqueConstraint('email'),
931 UniqueConstraint('email'),
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
934 )
934 )
935 __mapper_args__ = {}
935 __mapper_args__ = {}
936
936
937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
940 user = relationship('User', lazy='joined')
940 user = relationship('User', lazy='joined')
941
941
942 @validates('_email')
942 @validates('_email')
943 def validate_email(self, key, email):
943 def validate_email(self, key, email):
944 # check if this email is not main one
944 # check if this email is not main one
945 main_email = Session().query(User).filter(User.email == email).scalar()
945 main_email = Session().query(User).filter(User.email == email).scalar()
946 if main_email is not None:
946 if main_email is not None:
947 raise AttributeError('email %s is present is user table' % email)
947 raise AttributeError('email %s is present is user table' % email)
948 return email
948 return email
949
949
950 @hybrid_property
950 @hybrid_property
951 def email(self):
951 def email(self):
952 return self._email
952 return self._email
953
953
954 @email.setter
954 @email.setter
955 def email(self, val):
955 def email(self, val):
956 self._email = val.lower() if val else None
956 self._email = val.lower() if val else None
957
957
958
958
959 class UserIpMap(Base, BaseModel):
959 class UserIpMap(Base, BaseModel):
960 __tablename__ = 'user_ip_map'
960 __tablename__ = 'user_ip_map'
961 __table_args__ = (
961 __table_args__ = (
962 UniqueConstraint('user_id', 'ip_addr'),
962 UniqueConstraint('user_id', 'ip_addr'),
963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
965 )
965 )
966 __mapper_args__ = {}
966 __mapper_args__ = {}
967
967
968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
973 user = relationship('User', lazy='joined')
973 user = relationship('User', lazy='joined')
974
974
975 @classmethod
975 @classmethod
976 def _get_ip_range(cls, ip_addr):
976 def _get_ip_range(cls, ip_addr):
977 net = ipaddress.ip_network(ip_addr, strict=False)
977 net = ipaddress.ip_network(ip_addr, strict=False)
978 return [str(net.network_address), str(net.broadcast_address)]
978 return [str(net.network_address), str(net.broadcast_address)]
979
979
980 def __json__(self):
980 def __json__(self):
981 return {
981 return {
982 'ip_addr': self.ip_addr,
982 'ip_addr': self.ip_addr,
983 'ip_range': self._get_ip_range(self.ip_addr),
983 'ip_range': self._get_ip_range(self.ip_addr),
984 }
984 }
985
985
986 def __unicode__(self):
986 def __unicode__(self):
987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
988 self.user_id, self.ip_addr)
988 self.user_id, self.ip_addr)
989
989
990 class UserLog(Base, BaseModel):
990 class UserLog(Base, BaseModel):
991 __tablename__ = 'user_logs'
991 __tablename__ = 'user_logs'
992 __table_args__ = (
992 __table_args__ = (
993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
995 )
995 )
996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
998 username = Column("username", String(255), nullable=True, unique=None, default=None)
998 username = Column("username", String(255), nullable=True, unique=None, default=None)
999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1004
1004
1005 def __unicode__(self):
1005 def __unicode__(self):
1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1007 self.repository_name,
1007 self.repository_name,
1008 self.action)
1008 self.action)
1009
1009
1010 @property
1010 @property
1011 def action_as_day(self):
1011 def action_as_day(self):
1012 return datetime.date(*self.action_date.timetuple()[:3])
1012 return datetime.date(*self.action_date.timetuple()[:3])
1013
1013
1014 user = relationship('User')
1014 user = relationship('User')
1015 repository = relationship('Repository', cascade='')
1015 repository = relationship('Repository', cascade='')
1016
1016
1017
1017
1018 class UserGroup(Base, BaseModel):
1018 class UserGroup(Base, BaseModel):
1019 __tablename__ = 'users_groups'
1019 __tablename__ = 'users_groups'
1020 __table_args__ = (
1020 __table_args__ = (
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1023 )
1023 )
1024
1024
1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1033
1033
1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1040
1040
1041 user = relationship('User')
1041 user = relationship('User')
1042
1042
1043 @hybrid_property
1043 @hybrid_property
1044 def group_data(self):
1044 def group_data(self):
1045 if not self._group_data:
1045 if not self._group_data:
1046 return {}
1046 return {}
1047
1047
1048 try:
1048 try:
1049 return json.loads(self._group_data)
1049 return json.loads(self._group_data)
1050 except TypeError:
1050 except TypeError:
1051 return {}
1051 return {}
1052
1052
1053 @group_data.setter
1053 @group_data.setter
1054 def group_data(self, val):
1054 def group_data(self, val):
1055 try:
1055 try:
1056 self._group_data = json.dumps(val)
1056 self._group_data = json.dumps(val)
1057 except Exception:
1057 except Exception:
1058 log.error(traceback.format_exc())
1058 log.error(traceback.format_exc())
1059
1059
1060 def __unicode__(self):
1060 def __unicode__(self):
1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1062 self.users_group_id,
1062 self.users_group_id,
1063 self.users_group_name)
1063 self.users_group_name)
1064
1064
1065 @classmethod
1065 @classmethod
1066 def get_by_group_name(cls, group_name, cache=False,
1066 def get_by_group_name(cls, group_name, cache=False,
1067 case_insensitive=False):
1067 case_insensitive=False):
1068 if case_insensitive:
1068 if case_insensitive:
1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1070 func.lower(group_name))
1070 func.lower(group_name))
1071
1071
1072 else:
1072 else:
1073 q = cls.query().filter(cls.users_group_name == group_name)
1073 q = cls.query().filter(cls.users_group_name == group_name)
1074 if cache:
1074 if cache:
1075 q = q.options(FromCache(
1075 q = q.options(FromCache(
1076 "sql_cache_short",
1076 "sql_cache_short",
1077 "get_group_%s" % _hash_key(group_name)))
1077 "get_group_%s" % _hash_key(group_name)))
1078 return q.scalar()
1078 return q.scalar()
1079
1079
1080 @classmethod
1080 @classmethod
1081 def get(cls, user_group_id, cache=False):
1081 def get(cls, user_group_id, cache=False):
1082 user_group = cls.query()
1082 user_group = cls.query()
1083 if cache:
1083 if cache:
1084 user_group = user_group.options(FromCache("sql_cache_short",
1084 user_group = user_group.options(FromCache("sql_cache_short",
1085 "get_users_group_%s" % user_group_id))
1085 "get_users_group_%s" % user_group_id))
1086 return user_group.get(user_group_id)
1086 return user_group.get(user_group_id)
1087
1087
1088 def permissions(self, with_admins=True, with_owner=True):
1088 def permissions(self, with_admins=True, with_owner=True):
1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1091 joinedload(UserUserGroupToPerm.user),
1091 joinedload(UserUserGroupToPerm.user),
1092 joinedload(UserUserGroupToPerm.permission),)
1092 joinedload(UserUserGroupToPerm.permission),)
1093
1093
1094 # get owners and admins and permissions. We do a trick of re-writing
1094 # get owners and admins and permissions. We do a trick of re-writing
1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1096 # has a global reference and changing one object propagates to all
1096 # has a global reference and changing one object propagates to all
1097 # others. This means if admin is also an owner admin_row that change
1097 # others. This means if admin is also an owner admin_row that change
1098 # would propagate to both objects
1098 # would propagate to both objects
1099 perm_rows = []
1099 perm_rows = []
1100 for _usr in q.all():
1100 for _usr in q.all():
1101 usr = AttributeDict(_usr.user.get_dict())
1101 usr = AttributeDict(_usr.user.get_dict())
1102 usr.permission = _usr.permission.permission_name
1102 usr.permission = _usr.permission.permission_name
1103 perm_rows.append(usr)
1103 perm_rows.append(usr)
1104
1104
1105 # filter the perm rows by 'default' first and then sort them by
1105 # filter the perm rows by 'default' first and then sort them by
1106 # admin,write,read,none permissions sorted again alphabetically in
1106 # admin,write,read,none permissions sorted again alphabetically in
1107 # each group
1107 # each group
1108 perm_rows = sorted(perm_rows, key=display_sort)
1108 perm_rows = sorted(perm_rows, key=display_sort)
1109
1109
1110 _admin_perm = 'usergroup.admin'
1110 _admin_perm = 'usergroup.admin'
1111 owner_row = []
1111 owner_row = []
1112 if with_owner:
1112 if with_owner:
1113 usr = AttributeDict(self.user.get_dict())
1113 usr = AttributeDict(self.user.get_dict())
1114 usr.owner_row = True
1114 usr.owner_row = True
1115 usr.permission = _admin_perm
1115 usr.permission = _admin_perm
1116 owner_row.append(usr)
1116 owner_row.append(usr)
1117
1117
1118 super_admin_rows = []
1118 super_admin_rows = []
1119 if with_admins:
1119 if with_admins:
1120 for usr in User.get_all_super_admins():
1120 for usr in User.get_all_super_admins():
1121 # if this admin is also owner, don't double the record
1121 # if this admin is also owner, don't double the record
1122 if usr.user_id == owner_row[0].user_id:
1122 if usr.user_id == owner_row[0].user_id:
1123 owner_row[0].admin_row = True
1123 owner_row[0].admin_row = True
1124 else:
1124 else:
1125 usr = AttributeDict(usr.get_dict())
1125 usr = AttributeDict(usr.get_dict())
1126 usr.admin_row = True
1126 usr.admin_row = True
1127 usr.permission = _admin_perm
1127 usr.permission = _admin_perm
1128 super_admin_rows.append(usr)
1128 super_admin_rows.append(usr)
1129
1129
1130 return super_admin_rows + owner_row + perm_rows
1130 return super_admin_rows + owner_row + perm_rows
1131
1131
1132 def permission_user_groups(self):
1132 def permission_user_groups(self):
1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1136 joinedload(UserGroupUserGroupToPerm.permission),)
1136 joinedload(UserGroupUserGroupToPerm.permission),)
1137
1137
1138 perm_rows = []
1138 perm_rows = []
1139 for _user_group in q.all():
1139 for _user_group in q.all():
1140 usr = AttributeDict(_user_group.user_group.get_dict())
1140 usr = AttributeDict(_user_group.user_group.get_dict())
1141 usr.permission = _user_group.permission.permission_name
1141 usr.permission = _user_group.permission.permission_name
1142 perm_rows.append(usr)
1142 perm_rows.append(usr)
1143
1143
1144 return perm_rows
1144 return perm_rows
1145
1145
1146 def _get_default_perms(self, user_group, suffix=''):
1146 def _get_default_perms(self, user_group, suffix=''):
1147 from rhodecode.model.permission import PermissionModel
1147 from rhodecode.model.permission import PermissionModel
1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1149
1149
1150 def get_default_perms(self, suffix=''):
1150 def get_default_perms(self, suffix=''):
1151 return self._get_default_perms(self, suffix)
1151 return self._get_default_perms(self, suffix)
1152
1152
1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1154 """
1154 """
1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1156 basically forwarded.
1156 basically forwarded.
1157
1157
1158 """
1158 """
1159 user_group = self
1159 user_group = self
1160
1160
1161 data = {
1161 data = {
1162 'users_group_id': user_group.users_group_id,
1162 'users_group_id': user_group.users_group_id,
1163 'group_name': user_group.users_group_name,
1163 'group_name': user_group.users_group_name,
1164 'group_description': user_group.user_group_description,
1164 'group_description': user_group.user_group_description,
1165 'active': user_group.users_group_active,
1165 'active': user_group.users_group_active,
1166 'owner': user_group.user.username,
1166 'owner': user_group.user.username,
1167 }
1167 }
1168 if with_group_members:
1168 if with_group_members:
1169 users = []
1169 users = []
1170 for user in user_group.members:
1170 for user in user_group.members:
1171 user = user.user
1171 user = user.user
1172 users.append(user.get_api_data(include_secrets=include_secrets))
1172 users.append(user.get_api_data(include_secrets=include_secrets))
1173 data['users'] = users
1173 data['users'] = users
1174
1174
1175 return data
1175 return data
1176
1176
1177
1177
1178 class UserGroupMember(Base, BaseModel):
1178 class UserGroupMember(Base, BaseModel):
1179 __tablename__ = 'users_groups_members'
1179 __tablename__ = 'users_groups_members'
1180 __table_args__ = (
1180 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1183 )
1183 )
1184
1184
1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1188
1188
1189 user = relationship('User', lazy='joined')
1189 user = relationship('User', lazy='joined')
1190 users_group = relationship('UserGroup')
1190 users_group = relationship('UserGroup')
1191
1191
1192 def __init__(self, gr_id='', u_id=''):
1192 def __init__(self, gr_id='', u_id=''):
1193 self.users_group_id = gr_id
1193 self.users_group_id = gr_id
1194 self.user_id = u_id
1194 self.user_id = u_id
1195
1195
1196
1196
1197 class RepositoryField(Base, BaseModel):
1197 class RepositoryField(Base, BaseModel):
1198 __tablename__ = 'repositories_fields'
1198 __tablename__ = 'repositories_fields'
1199 __table_args__ = (
1199 __table_args__ = (
1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1203 )
1203 )
1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1205
1205
1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1208 field_key = Column("field_key", String(250))
1208 field_key = Column("field_key", String(250))
1209 field_label = Column("field_label", String(1024), nullable=False)
1209 field_label = Column("field_label", String(1024), nullable=False)
1210 field_value = Column("field_value", String(10000), nullable=False)
1210 field_value = Column("field_value", String(10000), nullable=False)
1211 field_desc = Column("field_desc", String(1024), nullable=False)
1211 field_desc = Column("field_desc", String(1024), nullable=False)
1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1214
1214
1215 repository = relationship('Repository')
1215 repository = relationship('Repository')
1216
1216
1217 @property
1217 @property
1218 def field_key_prefixed(self):
1218 def field_key_prefixed(self):
1219 return 'ex_%s' % self.field_key
1219 return 'ex_%s' % self.field_key
1220
1220
1221 @classmethod
1221 @classmethod
1222 def un_prefix_key(cls, key):
1222 def un_prefix_key(cls, key):
1223 if key.startswith(cls.PREFIX):
1223 if key.startswith(cls.PREFIX):
1224 return key[len(cls.PREFIX):]
1224 return key[len(cls.PREFIX):]
1225 return key
1225 return key
1226
1226
1227 @classmethod
1227 @classmethod
1228 def get_by_key_name(cls, key, repo):
1228 def get_by_key_name(cls, key, repo):
1229 row = cls.query()\
1229 row = cls.query()\
1230 .filter(cls.repository == repo)\
1230 .filter(cls.repository == repo)\
1231 .filter(cls.field_key == key).scalar()
1231 .filter(cls.field_key == key).scalar()
1232 return row
1232 return row
1233
1233
1234
1234
1235 class Repository(Base, BaseModel):
1235 class Repository(Base, BaseModel):
1236 __tablename__ = 'repositories'
1236 __tablename__ = 'repositories'
1237 __table_args__ = (
1237 __table_args__ = (
1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 )
1241 )
1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1244
1244
1245 STATE_CREATED = 'repo_state_created'
1245 STATE_CREATED = 'repo_state_created'
1246 STATE_PENDING = 'repo_state_pending'
1246 STATE_PENDING = 'repo_state_pending'
1247 STATE_ERROR = 'repo_state_error'
1247 STATE_ERROR = 'repo_state_error'
1248
1248
1249 LOCK_AUTOMATIC = 'lock_auto'
1249 LOCK_AUTOMATIC = 'lock_auto'
1250 LOCK_API = 'lock_api'
1250 LOCK_API = 'lock_api'
1251 LOCK_WEB = 'lock_web'
1251 LOCK_WEB = 'lock_web'
1252 LOCK_PULL = 'lock_pull'
1252 LOCK_PULL = 'lock_pull'
1253
1253
1254 NAME_SEP = URL_SEP
1254 NAME_SEP = URL_SEP
1255
1255
1256 repo_id = Column(
1256 repo_id = Column(
1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1258 primary_key=True)
1258 primary_key=True)
1259 _repo_name = Column(
1259 _repo_name = Column(
1260 "repo_name", Text(), nullable=False, default=None)
1260 "repo_name", Text(), nullable=False, default=None)
1261 _repo_name_hash = Column(
1261 _repo_name_hash = Column(
1262 "repo_name_hash", String(255), nullable=False, unique=True)
1262 "repo_name_hash", String(255), nullable=False, unique=True)
1263 repo_state = Column("repo_state", String(255), nullable=True)
1263 repo_state = Column("repo_state", String(255), nullable=True)
1264
1264
1265 clone_uri = Column(
1265 clone_uri = Column(
1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1267 default=None)
1267 default=None)
1268 repo_type = Column(
1268 repo_type = Column(
1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1270 user_id = Column(
1270 user_id = Column(
1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1272 unique=False, default=None)
1272 unique=False, default=None)
1273 private = Column(
1273 private = Column(
1274 "private", Boolean(), nullable=True, unique=None, default=None)
1274 "private", Boolean(), nullable=True, unique=None, default=None)
1275 enable_statistics = Column(
1275 enable_statistics = Column(
1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1277 enable_downloads = Column(
1277 enable_downloads = Column(
1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1279 description = Column(
1279 description = Column(
1280 "description", String(10000), nullable=True, unique=None, default=None)
1280 "description", String(10000), nullable=True, unique=None, default=None)
1281 created_on = Column(
1281 created_on = Column(
1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1283 default=datetime.datetime.now)
1283 default=datetime.datetime.now)
1284 updated_on = Column(
1284 updated_on = Column(
1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1286 default=datetime.datetime.now)
1286 default=datetime.datetime.now)
1287 _landing_revision = Column(
1287 _landing_revision = Column(
1288 "landing_revision", String(255), nullable=False, unique=False,
1288 "landing_revision", String(255), nullable=False, unique=False,
1289 default=None)
1289 default=None)
1290 enable_locking = Column(
1290 enable_locking = Column(
1291 "enable_locking", Boolean(), nullable=False, unique=None,
1291 "enable_locking", Boolean(), nullable=False, unique=None,
1292 default=False)
1292 default=False)
1293 _locked = Column(
1293 _locked = Column(
1294 "locked", String(255), nullable=True, unique=False, default=None)
1294 "locked", String(255), nullable=True, unique=False, default=None)
1295 _changeset_cache = Column(
1295 _changeset_cache = Column(
1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1297
1297
1298 fork_id = Column(
1298 fork_id = Column(
1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1300 nullable=True, unique=False, default=None)
1300 nullable=True, unique=False, default=None)
1301 group_id = Column(
1301 group_id = Column(
1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1303 unique=False, default=None)
1303 unique=False, default=None)
1304
1304
1305 user = relationship('User', lazy='joined')
1305 user = relationship('User', lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1308 repo_to_perm = relationship(
1308 repo_to_perm = relationship(
1309 'UserRepoToPerm', cascade='all',
1309 'UserRepoToPerm', cascade='all',
1310 order_by='UserRepoToPerm.repo_to_perm_id')
1310 order_by='UserRepoToPerm.repo_to_perm_id')
1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1312 stats = relationship('Statistics', cascade='all', uselist=False)
1312 stats = relationship('Statistics', cascade='all', uselist=False)
1313
1313
1314 followers = relationship(
1314 followers = relationship(
1315 'UserFollowing',
1315 'UserFollowing',
1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1317 cascade='all')
1317 cascade='all')
1318 extra_fields = relationship(
1318 extra_fields = relationship(
1319 'RepositoryField', cascade="all, delete, delete-orphan")
1319 'RepositoryField', cascade="all, delete, delete-orphan")
1320 logs = relationship('UserLog')
1320 logs = relationship('UserLog')
1321 comments = relationship(
1321 comments = relationship(
1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1323 pull_requests_source = relationship(
1323 pull_requests_source = relationship(
1324 'PullRequest',
1324 'PullRequest',
1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1326 cascade="all, delete, delete-orphan")
1326 cascade="all, delete, delete-orphan")
1327 pull_requests_target = relationship(
1327 pull_requests_target = relationship(
1328 'PullRequest',
1328 'PullRequest',
1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1330 cascade="all, delete, delete-orphan")
1330 cascade="all, delete, delete-orphan")
1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
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)
1977 _get_repo, self.repo_name, None)
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 repo_full_path = self.repo_full_path
1986 repo_full_path = self.repo_full_path
1987 try:
1987 try:
1988 vcs_alias = get_scm(repo_full_path)[0]
1988 vcs_alias = get_scm(repo_full_path)[0]
1989 log.debug(
1989 log.debug(
1990 'Creating instance of %s repository from %s',
1990 'Creating instance of %s repository from %s',
1991 vcs_alias, repo_full_path)
1991 vcs_alias, repo_full_path)
1992 backend = get_backend(vcs_alias)
1992 backend = get_backend(vcs_alias)
1993 except VCSError:
1993 except VCSError:
1994 log.exception(
1994 log.exception(
1995 'Perhaps this repository is in db and not in '
1995 'Perhaps this repository is in db and not in '
1996 'filesystem run rescan repositories with '
1996 'filesystem run rescan repositories with '
1997 '"destroy old data" option from admin panel')
1997 '"destroy old data" option from admin panel')
1998 return
1998 return
1999
1999
2000 config = config or self._config
2000 config = config or self._config
2001 custom_wire = {
2001 custom_wire = {
2002 'cache': cache # controls the vcs.remote cache
2002 'cache': cache # controls the vcs.remote cache
2003 }
2003 }
2004 repo = backend(
2004 repo = backend(
2005 safe_str(repo_full_path), config=config, create=False,
2005 safe_str(repo_full_path), config=config, create=False,
2006 with_wire=custom_wire)
2006 with_wire=custom_wire)
2007
2007
2008 return repo
2008 return repo
2009
2009
2010 def __json__(self):
2010 def __json__(self):
2011 return {'landing_rev': self.landing_rev}
2011 return {'landing_rev': self.landing_rev}
2012
2012
2013 def get_dict(self):
2013 def get_dict(self):
2014
2014
2015 # Since we transformed `repo_name` to a hybrid property, we need to
2015 # Since we transformed `repo_name` to a hybrid property, we need to
2016 # keep compatibility with the code which uses `repo_name` field.
2016 # keep compatibility with the code which uses `repo_name` field.
2017
2017
2018 result = super(Repository, self).get_dict()
2018 result = super(Repository, self).get_dict()
2019 result['repo_name'] = result.pop('_repo_name', None)
2019 result['repo_name'] = result.pop('_repo_name', None)
2020 return result
2020 return result
2021
2021
2022
2022
2023 class RepoGroup(Base, BaseModel):
2023 class RepoGroup(Base, BaseModel):
2024 __tablename__ = 'groups'
2024 __tablename__ = 'groups'
2025 __table_args__ = (
2025 __table_args__ = (
2026 UniqueConstraint('group_name', 'group_parent_id'),
2026 UniqueConstraint('group_name', 'group_parent_id'),
2027 CheckConstraint('group_id != group_parent_id'),
2027 CheckConstraint('group_id != group_parent_id'),
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2029 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2029 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2030 )
2030 )
2031 __mapper_args__ = {'order_by': 'group_name'}
2031 __mapper_args__ = {'order_by': 'group_name'}
2032
2032
2033 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2033 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2034
2034
2035 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2035 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2036 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2036 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2037 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2037 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2038 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2038 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2039 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2039 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2041 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2041 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2042
2042
2043 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2043 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2044 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2044 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2045 parent_group = relationship('RepoGroup', remote_side=group_id)
2045 parent_group = relationship('RepoGroup', remote_side=group_id)
2046 user = relationship('User')
2046 user = relationship('User')
2047
2047
2048 def __init__(self, group_name='', parent_group=None):
2048 def __init__(self, group_name='', parent_group=None):
2049 self.group_name = group_name
2049 self.group_name = group_name
2050 self.parent_group = parent_group
2050 self.parent_group = parent_group
2051
2051
2052 def __unicode__(self):
2052 def __unicode__(self):
2053 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2053 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2054 self.group_name)
2054 self.group_name)
2055
2055
2056 @classmethod
2056 @classmethod
2057 def _generate_choice(cls, repo_group):
2057 def _generate_choice(cls, repo_group):
2058 from webhelpers.html import literal as _literal
2058 from webhelpers.html import literal as _literal
2059 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2059 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2060 return repo_group.group_id, _name(repo_group.full_path_splitted)
2060 return repo_group.group_id, _name(repo_group.full_path_splitted)
2061
2061
2062 @classmethod
2062 @classmethod
2063 def groups_choices(cls, groups=None, show_empty_group=True):
2063 def groups_choices(cls, groups=None, show_empty_group=True):
2064 if not groups:
2064 if not groups:
2065 groups = cls.query().all()
2065 groups = cls.query().all()
2066
2066
2067 repo_groups = []
2067 repo_groups = []
2068 if show_empty_group:
2068 if show_empty_group:
2069 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2069 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2070
2070
2071 repo_groups.extend([cls._generate_choice(x) for x in groups])
2071 repo_groups.extend([cls._generate_choice(x) for x in groups])
2072
2072
2073 repo_groups = sorted(
2073 repo_groups = sorted(
2074 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2074 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2075 return repo_groups
2075 return repo_groups
2076
2076
2077 @classmethod
2077 @classmethod
2078 def url_sep(cls):
2078 def url_sep(cls):
2079 return URL_SEP
2079 return URL_SEP
2080
2080
2081 @classmethod
2081 @classmethod
2082 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2082 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2083 if case_insensitive:
2083 if case_insensitive:
2084 gr = cls.query().filter(func.lower(cls.group_name)
2084 gr = cls.query().filter(func.lower(cls.group_name)
2085 == func.lower(group_name))
2085 == func.lower(group_name))
2086 else:
2086 else:
2087 gr = cls.query().filter(cls.group_name == group_name)
2087 gr = cls.query().filter(cls.group_name == group_name)
2088 if cache:
2088 if cache:
2089 gr = gr.options(FromCache(
2089 gr = gr.options(FromCache(
2090 "sql_cache_short",
2090 "sql_cache_short",
2091 "get_group_%s" % _hash_key(group_name)))
2091 "get_group_%s" % _hash_key(group_name)))
2092 return gr.scalar()
2092 return gr.scalar()
2093
2093
2094 @classmethod
2094 @classmethod
2095 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2095 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2096 case_insensitive=True):
2096 case_insensitive=True):
2097 q = RepoGroup.query()
2097 q = RepoGroup.query()
2098
2098
2099 if not isinstance(user_id, Optional):
2099 if not isinstance(user_id, Optional):
2100 q = q.filter(RepoGroup.user_id == user_id)
2100 q = q.filter(RepoGroup.user_id == user_id)
2101
2101
2102 if not isinstance(group_id, Optional):
2102 if not isinstance(group_id, Optional):
2103 q = q.filter(RepoGroup.group_parent_id == group_id)
2103 q = q.filter(RepoGroup.group_parent_id == group_id)
2104
2104
2105 if case_insensitive:
2105 if case_insensitive:
2106 q = q.order_by(func.lower(RepoGroup.group_name))
2106 q = q.order_by(func.lower(RepoGroup.group_name))
2107 else:
2107 else:
2108 q = q.order_by(RepoGroup.group_name)
2108 q = q.order_by(RepoGroup.group_name)
2109 return q.all()
2109 return q.all()
2110
2110
2111 @property
2111 @property
2112 def parents(self):
2112 def parents(self):
2113 parents_recursion_limit = 10
2113 parents_recursion_limit = 10
2114 groups = []
2114 groups = []
2115 if self.parent_group is None:
2115 if self.parent_group is None:
2116 return groups
2116 return groups
2117 cur_gr = self.parent_group
2117 cur_gr = self.parent_group
2118 groups.insert(0, cur_gr)
2118 groups.insert(0, cur_gr)
2119 cnt = 0
2119 cnt = 0
2120 while 1:
2120 while 1:
2121 cnt += 1
2121 cnt += 1
2122 gr = getattr(cur_gr, 'parent_group', None)
2122 gr = getattr(cur_gr, 'parent_group', None)
2123 cur_gr = cur_gr.parent_group
2123 cur_gr = cur_gr.parent_group
2124 if gr is None:
2124 if gr is None:
2125 break
2125 break
2126 if cnt == parents_recursion_limit:
2126 if cnt == parents_recursion_limit:
2127 # this will prevent accidental infinit loops
2127 # this will prevent accidental infinit loops
2128 log.error(('more than %s parents found for group %s, stopping '
2128 log.error(('more than %s parents found for group %s, stopping '
2129 'recursive parent fetching' % (parents_recursion_limit, self)))
2129 'recursive parent fetching' % (parents_recursion_limit, self)))
2130 break
2130 break
2131
2131
2132 groups.insert(0, gr)
2132 groups.insert(0, gr)
2133 return groups
2133 return groups
2134
2134
2135 @property
2135 @property
2136 def children(self):
2136 def children(self):
2137 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2137 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2138
2138
2139 @property
2139 @property
2140 def name(self):
2140 def name(self):
2141 return self.group_name.split(RepoGroup.url_sep())[-1]
2141 return self.group_name.split(RepoGroup.url_sep())[-1]
2142
2142
2143 @property
2143 @property
2144 def full_path(self):
2144 def full_path(self):
2145 return self.group_name
2145 return self.group_name
2146
2146
2147 @property
2147 @property
2148 def full_path_splitted(self):
2148 def full_path_splitted(self):
2149 return self.group_name.split(RepoGroup.url_sep())
2149 return self.group_name.split(RepoGroup.url_sep())
2150
2150
2151 @property
2151 @property
2152 def repositories(self):
2152 def repositories(self):
2153 return Repository.query()\
2153 return Repository.query()\
2154 .filter(Repository.group == self)\
2154 .filter(Repository.group == self)\
2155 .order_by(Repository.repo_name)
2155 .order_by(Repository.repo_name)
2156
2156
2157 @property
2157 @property
2158 def repositories_recursive_count(self):
2158 def repositories_recursive_count(self):
2159 cnt = self.repositories.count()
2159 cnt = self.repositories.count()
2160
2160
2161 def children_count(group):
2161 def children_count(group):
2162 cnt = 0
2162 cnt = 0
2163 for child in group.children:
2163 for child in group.children:
2164 cnt += child.repositories.count()
2164 cnt += child.repositories.count()
2165 cnt += children_count(child)
2165 cnt += children_count(child)
2166 return cnt
2166 return cnt
2167
2167
2168 return cnt + children_count(self)
2168 return cnt + children_count(self)
2169
2169
2170 def _recursive_objects(self, include_repos=True):
2170 def _recursive_objects(self, include_repos=True):
2171 all_ = []
2171 all_ = []
2172
2172
2173 def _get_members(root_gr):
2173 def _get_members(root_gr):
2174 if include_repos:
2174 if include_repos:
2175 for r in root_gr.repositories:
2175 for r in root_gr.repositories:
2176 all_.append(r)
2176 all_.append(r)
2177 childs = root_gr.children.all()
2177 childs = root_gr.children.all()
2178 if childs:
2178 if childs:
2179 for gr in childs:
2179 for gr in childs:
2180 all_.append(gr)
2180 all_.append(gr)
2181 _get_members(gr)
2181 _get_members(gr)
2182
2182
2183 _get_members(self)
2183 _get_members(self)
2184 return [self] + all_
2184 return [self] + all_
2185
2185
2186 def recursive_groups_and_repos(self):
2186 def recursive_groups_and_repos(self):
2187 """
2187 """
2188 Recursive return all groups, with repositories in those groups
2188 Recursive return all groups, with repositories in those groups
2189 """
2189 """
2190 return self._recursive_objects()
2190 return self._recursive_objects()
2191
2191
2192 def recursive_groups(self):
2192 def recursive_groups(self):
2193 """
2193 """
2194 Returns all children groups for this group including children of children
2194 Returns all children groups for this group including children of children
2195 """
2195 """
2196 return self._recursive_objects(include_repos=False)
2196 return self._recursive_objects(include_repos=False)
2197
2197
2198 def get_new_name(self, group_name):
2198 def get_new_name(self, group_name):
2199 """
2199 """
2200 returns new full group name based on parent and new name
2200 returns new full group name based on parent and new name
2201
2201
2202 :param group_name:
2202 :param group_name:
2203 """
2203 """
2204 path_prefix = (self.parent_group.full_path_splitted if
2204 path_prefix = (self.parent_group.full_path_splitted if
2205 self.parent_group else [])
2205 self.parent_group else [])
2206 return RepoGroup.url_sep().join(path_prefix + [group_name])
2206 return RepoGroup.url_sep().join(path_prefix + [group_name])
2207
2207
2208 def permissions(self, with_admins=True, with_owner=True):
2208 def permissions(self, with_admins=True, with_owner=True):
2209 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2209 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2210 q = q.options(joinedload(UserRepoGroupToPerm.group),
2210 q = q.options(joinedload(UserRepoGroupToPerm.group),
2211 joinedload(UserRepoGroupToPerm.user),
2211 joinedload(UserRepoGroupToPerm.user),
2212 joinedload(UserRepoGroupToPerm.permission),)
2212 joinedload(UserRepoGroupToPerm.permission),)
2213
2213
2214 # get owners and admins and permissions. We do a trick of re-writing
2214 # get owners and admins and permissions. We do a trick of re-writing
2215 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2215 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2216 # has a global reference and changing one object propagates to all
2216 # has a global reference and changing one object propagates to all
2217 # others. This means if admin is also an owner admin_row that change
2217 # others. This means if admin is also an owner admin_row that change
2218 # would propagate to both objects
2218 # would propagate to both objects
2219 perm_rows = []
2219 perm_rows = []
2220 for _usr in q.all():
2220 for _usr in q.all():
2221 usr = AttributeDict(_usr.user.get_dict())
2221 usr = AttributeDict(_usr.user.get_dict())
2222 usr.permission = _usr.permission.permission_name
2222 usr.permission = _usr.permission.permission_name
2223 perm_rows.append(usr)
2223 perm_rows.append(usr)
2224
2224
2225 # filter the perm rows by 'default' first and then sort them by
2225 # filter the perm rows by 'default' first and then sort them by
2226 # admin,write,read,none permissions sorted again alphabetically in
2226 # admin,write,read,none permissions sorted again alphabetically in
2227 # each group
2227 # each group
2228 perm_rows = sorted(perm_rows, key=display_sort)
2228 perm_rows = sorted(perm_rows, key=display_sort)
2229
2229
2230 _admin_perm = 'group.admin'
2230 _admin_perm = 'group.admin'
2231 owner_row = []
2231 owner_row = []
2232 if with_owner:
2232 if with_owner:
2233 usr = AttributeDict(self.user.get_dict())
2233 usr = AttributeDict(self.user.get_dict())
2234 usr.owner_row = True
2234 usr.owner_row = True
2235 usr.permission = _admin_perm
2235 usr.permission = _admin_perm
2236 owner_row.append(usr)
2236 owner_row.append(usr)
2237
2237
2238 super_admin_rows = []
2238 super_admin_rows = []
2239 if with_admins:
2239 if with_admins:
2240 for usr in User.get_all_super_admins():
2240 for usr in User.get_all_super_admins():
2241 # if this admin is also owner, don't double the record
2241 # if this admin is also owner, don't double the record
2242 if usr.user_id == owner_row[0].user_id:
2242 if usr.user_id == owner_row[0].user_id:
2243 owner_row[0].admin_row = True
2243 owner_row[0].admin_row = True
2244 else:
2244 else:
2245 usr = AttributeDict(usr.get_dict())
2245 usr = AttributeDict(usr.get_dict())
2246 usr.admin_row = True
2246 usr.admin_row = True
2247 usr.permission = _admin_perm
2247 usr.permission = _admin_perm
2248 super_admin_rows.append(usr)
2248 super_admin_rows.append(usr)
2249
2249
2250 return super_admin_rows + owner_row + perm_rows
2250 return super_admin_rows + owner_row + perm_rows
2251
2251
2252 def permission_user_groups(self):
2252 def permission_user_groups(self):
2253 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2253 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2254 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2254 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2255 joinedload(UserGroupRepoGroupToPerm.users_group),
2255 joinedload(UserGroupRepoGroupToPerm.users_group),
2256 joinedload(UserGroupRepoGroupToPerm.permission),)
2256 joinedload(UserGroupRepoGroupToPerm.permission),)
2257
2257
2258 perm_rows = []
2258 perm_rows = []
2259 for _user_group in q.all():
2259 for _user_group in q.all():
2260 usr = AttributeDict(_user_group.users_group.get_dict())
2260 usr = AttributeDict(_user_group.users_group.get_dict())
2261 usr.permission = _user_group.permission.permission_name
2261 usr.permission = _user_group.permission.permission_name
2262 perm_rows.append(usr)
2262 perm_rows.append(usr)
2263
2263
2264 return perm_rows
2264 return perm_rows
2265
2265
2266 def get_api_data(self):
2266 def get_api_data(self):
2267 """
2267 """
2268 Common function for generating api data
2268 Common function for generating api data
2269
2269
2270 """
2270 """
2271 group = self
2271 group = self
2272 data = {
2272 data = {
2273 'group_id': group.group_id,
2273 'group_id': group.group_id,
2274 'group_name': group.group_name,
2274 'group_name': group.group_name,
2275 'group_description': group.group_description,
2275 'group_description': group.group_description,
2276 'parent_group': group.parent_group.group_name if group.parent_group else None,
2276 'parent_group': group.parent_group.group_name if group.parent_group else None,
2277 'repositories': [x.repo_name for x in group.repositories],
2277 'repositories': [x.repo_name for x in group.repositories],
2278 'owner': group.user.username,
2278 'owner': group.user.username,
2279 }
2279 }
2280 return data
2280 return data
2281
2281
2282
2282
2283 class Permission(Base, BaseModel):
2283 class Permission(Base, BaseModel):
2284 __tablename__ = 'permissions'
2284 __tablename__ = 'permissions'
2285 __table_args__ = (
2285 __table_args__ = (
2286 Index('p_perm_name_idx', 'permission_name'),
2286 Index('p_perm_name_idx', 'permission_name'),
2287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2289 )
2289 )
2290 PERMS = [
2290 PERMS = [
2291 ('hg.admin', _('RhodeCode Super Administrator')),
2291 ('hg.admin', _('RhodeCode Super Administrator')),
2292
2292
2293 ('repository.none', _('Repository no access')),
2293 ('repository.none', _('Repository no access')),
2294 ('repository.read', _('Repository read access')),
2294 ('repository.read', _('Repository read access')),
2295 ('repository.write', _('Repository write access')),
2295 ('repository.write', _('Repository write access')),
2296 ('repository.admin', _('Repository admin access')),
2296 ('repository.admin', _('Repository admin access')),
2297
2297
2298 ('group.none', _('Repository group no access')),
2298 ('group.none', _('Repository group no access')),
2299 ('group.read', _('Repository group read access')),
2299 ('group.read', _('Repository group read access')),
2300 ('group.write', _('Repository group write access')),
2300 ('group.write', _('Repository group write access')),
2301 ('group.admin', _('Repository group admin access')),
2301 ('group.admin', _('Repository group admin access')),
2302
2302
2303 ('usergroup.none', _('User group no access')),
2303 ('usergroup.none', _('User group no access')),
2304 ('usergroup.read', _('User group read access')),
2304 ('usergroup.read', _('User group read access')),
2305 ('usergroup.write', _('User group write access')),
2305 ('usergroup.write', _('User group write access')),
2306 ('usergroup.admin', _('User group admin access')),
2306 ('usergroup.admin', _('User group admin access')),
2307
2307
2308 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2308 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2309 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2309 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2310
2310
2311 ('hg.usergroup.create.false', _('User Group creation disabled')),
2311 ('hg.usergroup.create.false', _('User Group creation disabled')),
2312 ('hg.usergroup.create.true', _('User Group creation enabled')),
2312 ('hg.usergroup.create.true', _('User Group creation enabled')),
2313
2313
2314 ('hg.create.none', _('Repository creation disabled')),
2314 ('hg.create.none', _('Repository creation disabled')),
2315 ('hg.create.repository', _('Repository creation enabled')),
2315 ('hg.create.repository', _('Repository creation enabled')),
2316 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2316 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2317 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2317 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2318
2318
2319 ('hg.fork.none', _('Repository forking disabled')),
2319 ('hg.fork.none', _('Repository forking disabled')),
2320 ('hg.fork.repository', _('Repository forking enabled')),
2320 ('hg.fork.repository', _('Repository forking enabled')),
2321
2321
2322 ('hg.register.none', _('Registration disabled')),
2322 ('hg.register.none', _('Registration disabled')),
2323 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2323 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2324 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2324 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2325
2325
2326 ('hg.extern_activate.manual', _('Manual activation of external account')),
2326 ('hg.extern_activate.manual', _('Manual activation of external account')),
2327 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2327 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2328
2328
2329 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2329 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2330 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2330 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2331 ]
2331 ]
2332
2332
2333 # definition of system default permissions for DEFAULT user
2333 # definition of system default permissions for DEFAULT user
2334 DEFAULT_USER_PERMISSIONS = [
2334 DEFAULT_USER_PERMISSIONS = [
2335 'repository.read',
2335 'repository.read',
2336 'group.read',
2336 'group.read',
2337 'usergroup.read',
2337 'usergroup.read',
2338 'hg.create.repository',
2338 'hg.create.repository',
2339 'hg.repogroup.create.false',
2339 'hg.repogroup.create.false',
2340 'hg.usergroup.create.false',
2340 'hg.usergroup.create.false',
2341 'hg.create.write_on_repogroup.true',
2341 'hg.create.write_on_repogroup.true',
2342 'hg.fork.repository',
2342 'hg.fork.repository',
2343 'hg.register.manual_activate',
2343 'hg.register.manual_activate',
2344 'hg.extern_activate.auto',
2344 'hg.extern_activate.auto',
2345 'hg.inherit_default_perms.true',
2345 'hg.inherit_default_perms.true',
2346 ]
2346 ]
2347
2347
2348 # defines which permissions are more important higher the more important
2348 # defines which permissions are more important higher the more important
2349 # Weight defines which permissions are more important.
2349 # Weight defines which permissions are more important.
2350 # The higher number the more important.
2350 # The higher number the more important.
2351 PERM_WEIGHTS = {
2351 PERM_WEIGHTS = {
2352 'repository.none': 0,
2352 'repository.none': 0,
2353 'repository.read': 1,
2353 'repository.read': 1,
2354 'repository.write': 3,
2354 'repository.write': 3,
2355 'repository.admin': 4,
2355 'repository.admin': 4,
2356
2356
2357 'group.none': 0,
2357 'group.none': 0,
2358 'group.read': 1,
2358 'group.read': 1,
2359 'group.write': 3,
2359 'group.write': 3,
2360 'group.admin': 4,
2360 'group.admin': 4,
2361
2361
2362 'usergroup.none': 0,
2362 'usergroup.none': 0,
2363 'usergroup.read': 1,
2363 'usergroup.read': 1,
2364 'usergroup.write': 3,
2364 'usergroup.write': 3,
2365 'usergroup.admin': 4,
2365 'usergroup.admin': 4,
2366
2366
2367 'hg.repogroup.create.false': 0,
2367 'hg.repogroup.create.false': 0,
2368 'hg.repogroup.create.true': 1,
2368 'hg.repogroup.create.true': 1,
2369
2369
2370 'hg.usergroup.create.false': 0,
2370 'hg.usergroup.create.false': 0,
2371 'hg.usergroup.create.true': 1,
2371 'hg.usergroup.create.true': 1,
2372
2372
2373 'hg.fork.none': 0,
2373 'hg.fork.none': 0,
2374 'hg.fork.repository': 1,
2374 'hg.fork.repository': 1,
2375 'hg.create.none': 0,
2375 'hg.create.none': 0,
2376 'hg.create.repository': 1
2376 'hg.create.repository': 1
2377 }
2377 }
2378
2378
2379 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2379 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2380 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2380 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2381 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2381 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2382
2382
2383 def __unicode__(self):
2383 def __unicode__(self):
2384 return u"<%s('%s:%s')>" % (
2384 return u"<%s('%s:%s')>" % (
2385 self.__class__.__name__, self.permission_id, self.permission_name
2385 self.__class__.__name__, self.permission_id, self.permission_name
2386 )
2386 )
2387
2387
2388 @classmethod
2388 @classmethod
2389 def get_by_key(cls, key):
2389 def get_by_key(cls, key):
2390 return cls.query().filter(cls.permission_name == key).scalar()
2390 return cls.query().filter(cls.permission_name == key).scalar()
2391
2391
2392 @classmethod
2392 @classmethod
2393 def get_default_repo_perms(cls, user_id, repo_id=None):
2393 def get_default_repo_perms(cls, user_id, repo_id=None):
2394 q = Session().query(UserRepoToPerm, Repository, Permission)\
2394 q = Session().query(UserRepoToPerm, Repository, Permission)\
2395 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2395 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2396 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2396 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2397 .filter(UserRepoToPerm.user_id == user_id)
2397 .filter(UserRepoToPerm.user_id == user_id)
2398 if repo_id:
2398 if repo_id:
2399 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2399 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2400 return q.all()
2400 return q.all()
2401
2401
2402 @classmethod
2402 @classmethod
2403 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2403 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2404 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2404 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2405 .join(
2405 .join(
2406 Permission,
2406 Permission,
2407 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2407 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2408 .join(
2408 .join(
2409 Repository,
2409 Repository,
2410 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2410 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2411 .join(
2411 .join(
2412 UserGroup,
2412 UserGroup,
2413 UserGroupRepoToPerm.users_group_id ==
2413 UserGroupRepoToPerm.users_group_id ==
2414 UserGroup.users_group_id)\
2414 UserGroup.users_group_id)\
2415 .join(
2415 .join(
2416 UserGroupMember,
2416 UserGroupMember,
2417 UserGroupRepoToPerm.users_group_id ==
2417 UserGroupRepoToPerm.users_group_id ==
2418 UserGroupMember.users_group_id)\
2418 UserGroupMember.users_group_id)\
2419 .filter(
2419 .filter(
2420 UserGroupMember.user_id == user_id,
2420 UserGroupMember.user_id == user_id,
2421 UserGroup.users_group_active == true())
2421 UserGroup.users_group_active == true())
2422 if repo_id:
2422 if repo_id:
2423 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2423 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2424 return q.all()
2424 return q.all()
2425
2425
2426 @classmethod
2426 @classmethod
2427 def get_default_group_perms(cls, user_id, repo_group_id=None):
2427 def get_default_group_perms(cls, user_id, repo_group_id=None):
2428 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2428 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2429 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2429 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2430 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2430 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2431 .filter(UserRepoGroupToPerm.user_id == user_id)
2431 .filter(UserRepoGroupToPerm.user_id == user_id)
2432 if repo_group_id:
2432 if repo_group_id:
2433 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2433 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2434 return q.all()
2434 return q.all()
2435
2435
2436 @classmethod
2436 @classmethod
2437 def get_default_group_perms_from_user_group(
2437 def get_default_group_perms_from_user_group(
2438 cls, user_id, repo_group_id=None):
2438 cls, user_id, repo_group_id=None):
2439 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2439 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2440 .join(
2440 .join(
2441 Permission,
2441 Permission,
2442 UserGroupRepoGroupToPerm.permission_id ==
2442 UserGroupRepoGroupToPerm.permission_id ==
2443 Permission.permission_id)\
2443 Permission.permission_id)\
2444 .join(
2444 .join(
2445 RepoGroup,
2445 RepoGroup,
2446 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2446 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2447 .join(
2447 .join(
2448 UserGroup,
2448 UserGroup,
2449 UserGroupRepoGroupToPerm.users_group_id ==
2449 UserGroupRepoGroupToPerm.users_group_id ==
2450 UserGroup.users_group_id)\
2450 UserGroup.users_group_id)\
2451 .join(
2451 .join(
2452 UserGroupMember,
2452 UserGroupMember,
2453 UserGroupRepoGroupToPerm.users_group_id ==
2453 UserGroupRepoGroupToPerm.users_group_id ==
2454 UserGroupMember.users_group_id)\
2454 UserGroupMember.users_group_id)\
2455 .filter(
2455 .filter(
2456 UserGroupMember.user_id == user_id,
2456 UserGroupMember.user_id == user_id,
2457 UserGroup.users_group_active == true())
2457 UserGroup.users_group_active == true())
2458 if repo_group_id:
2458 if repo_group_id:
2459 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2459 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2460 return q.all()
2460 return q.all()
2461
2461
2462 @classmethod
2462 @classmethod
2463 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2463 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2464 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2464 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2465 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2465 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2466 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2466 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2467 .filter(UserUserGroupToPerm.user_id == user_id)
2467 .filter(UserUserGroupToPerm.user_id == user_id)
2468 if user_group_id:
2468 if user_group_id:
2469 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2469 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2470 return q.all()
2470 return q.all()
2471
2471
2472 @classmethod
2472 @classmethod
2473 def get_default_user_group_perms_from_user_group(
2473 def get_default_user_group_perms_from_user_group(
2474 cls, user_id, user_group_id=None):
2474 cls, user_id, user_group_id=None):
2475 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2475 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2476 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2476 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2477 .join(
2477 .join(
2478 Permission,
2478 Permission,
2479 UserGroupUserGroupToPerm.permission_id ==
2479 UserGroupUserGroupToPerm.permission_id ==
2480 Permission.permission_id)\
2480 Permission.permission_id)\
2481 .join(
2481 .join(
2482 TargetUserGroup,
2482 TargetUserGroup,
2483 UserGroupUserGroupToPerm.target_user_group_id ==
2483 UserGroupUserGroupToPerm.target_user_group_id ==
2484 TargetUserGroup.users_group_id)\
2484 TargetUserGroup.users_group_id)\
2485 .join(
2485 .join(
2486 UserGroup,
2486 UserGroup,
2487 UserGroupUserGroupToPerm.user_group_id ==
2487 UserGroupUserGroupToPerm.user_group_id ==
2488 UserGroup.users_group_id)\
2488 UserGroup.users_group_id)\
2489 .join(
2489 .join(
2490 UserGroupMember,
2490 UserGroupMember,
2491 UserGroupUserGroupToPerm.user_group_id ==
2491 UserGroupUserGroupToPerm.user_group_id ==
2492 UserGroupMember.users_group_id)\
2492 UserGroupMember.users_group_id)\
2493 .filter(
2493 .filter(
2494 UserGroupMember.user_id == user_id,
2494 UserGroupMember.user_id == user_id,
2495 UserGroup.users_group_active == true())
2495 UserGroup.users_group_active == true())
2496 if user_group_id:
2496 if user_group_id:
2497 q = q.filter(
2497 q = q.filter(
2498 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2498 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2499
2499
2500 return q.all()
2500 return q.all()
2501
2501
2502
2502
2503 class UserRepoToPerm(Base, BaseModel):
2503 class UserRepoToPerm(Base, BaseModel):
2504 __tablename__ = 'repo_to_perm'
2504 __tablename__ = 'repo_to_perm'
2505 __table_args__ = (
2505 __table_args__ = (
2506 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2506 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2508 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2508 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2509 )
2509 )
2510 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2510 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2511 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2511 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2514
2514
2515 user = relationship('User')
2515 user = relationship('User')
2516 repository = relationship('Repository')
2516 repository = relationship('Repository')
2517 permission = relationship('Permission')
2517 permission = relationship('Permission')
2518
2518
2519 @classmethod
2519 @classmethod
2520 def create(cls, user, repository, permission):
2520 def create(cls, user, repository, permission):
2521 n = cls()
2521 n = cls()
2522 n.user = user
2522 n.user = user
2523 n.repository = repository
2523 n.repository = repository
2524 n.permission = permission
2524 n.permission = permission
2525 Session().add(n)
2525 Session().add(n)
2526 return n
2526 return n
2527
2527
2528 def __unicode__(self):
2528 def __unicode__(self):
2529 return u'<%s => %s >' % (self.user, self.repository)
2529 return u'<%s => %s >' % (self.user, self.repository)
2530
2530
2531
2531
2532 class UserUserGroupToPerm(Base, BaseModel):
2532 class UserUserGroupToPerm(Base, BaseModel):
2533 __tablename__ = 'user_user_group_to_perm'
2533 __tablename__ = 'user_user_group_to_perm'
2534 __table_args__ = (
2534 __table_args__ = (
2535 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2535 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2537 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2537 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2538 )
2538 )
2539 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2539 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2540 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2540 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2542 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2542 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2543
2543
2544 user = relationship('User')
2544 user = relationship('User')
2545 user_group = relationship('UserGroup')
2545 user_group = relationship('UserGroup')
2546 permission = relationship('Permission')
2546 permission = relationship('Permission')
2547
2547
2548 @classmethod
2548 @classmethod
2549 def create(cls, user, user_group, permission):
2549 def create(cls, user, user_group, permission):
2550 n = cls()
2550 n = cls()
2551 n.user = user
2551 n.user = user
2552 n.user_group = user_group
2552 n.user_group = user_group
2553 n.permission = permission
2553 n.permission = permission
2554 Session().add(n)
2554 Session().add(n)
2555 return n
2555 return n
2556
2556
2557 def __unicode__(self):
2557 def __unicode__(self):
2558 return u'<%s => %s >' % (self.user, self.user_group)
2558 return u'<%s => %s >' % (self.user, self.user_group)
2559
2559
2560
2560
2561 class UserToPerm(Base, BaseModel):
2561 class UserToPerm(Base, BaseModel):
2562 __tablename__ = 'user_to_perm'
2562 __tablename__ = 'user_to_perm'
2563 __table_args__ = (
2563 __table_args__ = (
2564 UniqueConstraint('user_id', 'permission_id'),
2564 UniqueConstraint('user_id', 'permission_id'),
2565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2567 )
2567 )
2568 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2568 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2570 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2570 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2571
2571
2572 user = relationship('User')
2572 user = relationship('User')
2573 permission = relationship('Permission', lazy='joined')
2573 permission = relationship('Permission', lazy='joined')
2574
2574
2575 def __unicode__(self):
2575 def __unicode__(self):
2576 return u'<%s => %s >' % (self.user, self.permission)
2576 return u'<%s => %s >' % (self.user, self.permission)
2577
2577
2578
2578
2579 class UserGroupRepoToPerm(Base, BaseModel):
2579 class UserGroupRepoToPerm(Base, BaseModel):
2580 __tablename__ = 'users_group_repo_to_perm'
2580 __tablename__ = 'users_group_repo_to_perm'
2581 __table_args__ = (
2581 __table_args__ = (
2582 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2582 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2584 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2584 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2585 )
2585 )
2586 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2586 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2587 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2587 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2589 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2589 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2590
2590
2591 users_group = relationship('UserGroup')
2591 users_group = relationship('UserGroup')
2592 permission = relationship('Permission')
2592 permission = relationship('Permission')
2593 repository = relationship('Repository')
2593 repository = relationship('Repository')
2594
2594
2595 @classmethod
2595 @classmethod
2596 def create(cls, users_group, repository, permission):
2596 def create(cls, users_group, repository, permission):
2597 n = cls()
2597 n = cls()
2598 n.users_group = users_group
2598 n.users_group = users_group
2599 n.repository = repository
2599 n.repository = repository
2600 n.permission = permission
2600 n.permission = permission
2601 Session().add(n)
2601 Session().add(n)
2602 return n
2602 return n
2603
2603
2604 def __unicode__(self):
2604 def __unicode__(self):
2605 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2605 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2606
2606
2607
2607
2608 class UserGroupUserGroupToPerm(Base, BaseModel):
2608 class UserGroupUserGroupToPerm(Base, BaseModel):
2609 __tablename__ = 'user_group_user_group_to_perm'
2609 __tablename__ = 'user_group_user_group_to_perm'
2610 __table_args__ = (
2610 __table_args__ = (
2611 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2611 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2612 CheckConstraint('target_user_group_id != user_group_id'),
2612 CheckConstraint('target_user_group_id != user_group_id'),
2613 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2613 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2614 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2614 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2615 )
2615 )
2616 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)
2616 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)
2617 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2617 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2618 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2618 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2619 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2619 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2620
2620
2621 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2621 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2622 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2622 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2623 permission = relationship('Permission')
2623 permission = relationship('Permission')
2624
2624
2625 @classmethod
2625 @classmethod
2626 def create(cls, target_user_group, user_group, permission):
2626 def create(cls, target_user_group, user_group, permission):
2627 n = cls()
2627 n = cls()
2628 n.target_user_group = target_user_group
2628 n.target_user_group = target_user_group
2629 n.user_group = user_group
2629 n.user_group = user_group
2630 n.permission = permission
2630 n.permission = permission
2631 Session().add(n)
2631 Session().add(n)
2632 return n
2632 return n
2633
2633
2634 def __unicode__(self):
2634 def __unicode__(self):
2635 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2635 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2636
2636
2637
2637
2638 class UserGroupToPerm(Base, BaseModel):
2638 class UserGroupToPerm(Base, BaseModel):
2639 __tablename__ = 'users_group_to_perm'
2639 __tablename__ = 'users_group_to_perm'
2640 __table_args__ = (
2640 __table_args__ = (
2641 UniqueConstraint('users_group_id', 'permission_id',),
2641 UniqueConstraint('users_group_id', 'permission_id',),
2642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2644 )
2644 )
2645 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2646 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2646 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2647 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2647 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2648
2648
2649 users_group = relationship('UserGroup')
2649 users_group = relationship('UserGroup')
2650 permission = relationship('Permission')
2650 permission = relationship('Permission')
2651
2651
2652
2652
2653 class UserRepoGroupToPerm(Base, BaseModel):
2653 class UserRepoGroupToPerm(Base, BaseModel):
2654 __tablename__ = 'user_repo_group_to_perm'
2654 __tablename__ = 'user_repo_group_to_perm'
2655 __table_args__ = (
2655 __table_args__ = (
2656 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2656 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2657 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2657 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2658 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2658 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2659 )
2659 )
2660
2660
2661 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2661 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2662 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2662 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2663 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2663 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2664 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2664 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2665
2665
2666 user = relationship('User')
2666 user = relationship('User')
2667 group = relationship('RepoGroup')
2667 group = relationship('RepoGroup')
2668 permission = relationship('Permission')
2668 permission = relationship('Permission')
2669
2669
2670 @classmethod
2670 @classmethod
2671 def create(cls, user, repository_group, permission):
2671 def create(cls, user, repository_group, permission):
2672 n = cls()
2672 n = cls()
2673 n.user = user
2673 n.user = user
2674 n.group = repository_group
2674 n.group = repository_group
2675 n.permission = permission
2675 n.permission = permission
2676 Session().add(n)
2676 Session().add(n)
2677 return n
2677 return n
2678
2678
2679
2679
2680 class UserGroupRepoGroupToPerm(Base, BaseModel):
2680 class UserGroupRepoGroupToPerm(Base, BaseModel):
2681 __tablename__ = 'users_group_repo_group_to_perm'
2681 __tablename__ = 'users_group_repo_group_to_perm'
2682 __table_args__ = (
2682 __table_args__ = (
2683 UniqueConstraint('users_group_id', 'group_id'),
2683 UniqueConstraint('users_group_id', 'group_id'),
2684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2685 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2685 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2686 )
2686 )
2687
2687
2688 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)
2688 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)
2689 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2689 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2690 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2690 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2691 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2691 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2692
2692
2693 users_group = relationship('UserGroup')
2693 users_group = relationship('UserGroup')
2694 permission = relationship('Permission')
2694 permission = relationship('Permission')
2695 group = relationship('RepoGroup')
2695 group = relationship('RepoGroup')
2696
2696
2697 @classmethod
2697 @classmethod
2698 def create(cls, user_group, repository_group, permission):
2698 def create(cls, user_group, repository_group, permission):
2699 n = cls()
2699 n = cls()
2700 n.users_group = user_group
2700 n.users_group = user_group
2701 n.group = repository_group
2701 n.group = repository_group
2702 n.permission = permission
2702 n.permission = permission
2703 Session().add(n)
2703 Session().add(n)
2704 return n
2704 return n
2705
2705
2706 def __unicode__(self):
2706 def __unicode__(self):
2707 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2707 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2708
2708
2709
2709
2710 class Statistics(Base, BaseModel):
2710 class Statistics(Base, BaseModel):
2711 __tablename__ = 'statistics'
2711 __tablename__ = 'statistics'
2712 __table_args__ = (
2712 __table_args__ = (
2713 UniqueConstraint('repository_id'),
2713 UniqueConstraint('repository_id'),
2714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2716 )
2716 )
2717 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2717 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2718 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2718 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2719 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2719 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2720 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2720 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2721 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2721 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2722 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2722 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2723
2723
2724 repository = relationship('Repository', single_parent=True)
2724 repository = relationship('Repository', single_parent=True)
2725
2725
2726
2726
2727 class UserFollowing(Base, BaseModel):
2727 class UserFollowing(Base, BaseModel):
2728 __tablename__ = 'user_followings'
2728 __tablename__ = 'user_followings'
2729 __table_args__ = (
2729 __table_args__ = (
2730 UniqueConstraint('user_id', 'follows_repository_id'),
2730 UniqueConstraint('user_id', 'follows_repository_id'),
2731 UniqueConstraint('user_id', 'follows_user_id'),
2731 UniqueConstraint('user_id', 'follows_user_id'),
2732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2733 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2733 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2734 )
2734 )
2735
2735
2736 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2736 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2737 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2737 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2738 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2738 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2739 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2739 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2740 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2740 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2741
2741
2742 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2742 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2743
2743
2744 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2744 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2745 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2745 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2746
2746
2747 @classmethod
2747 @classmethod
2748 def get_repo_followers(cls, repo_id):
2748 def get_repo_followers(cls, repo_id):
2749 return cls.query().filter(cls.follows_repo_id == repo_id)
2749 return cls.query().filter(cls.follows_repo_id == repo_id)
2750
2750
2751
2751
2752 class CacheKey(Base, BaseModel):
2752 class CacheKey(Base, BaseModel):
2753 __tablename__ = 'cache_invalidation'
2753 __tablename__ = 'cache_invalidation'
2754 __table_args__ = (
2754 __table_args__ = (
2755 UniqueConstraint('cache_key'),
2755 UniqueConstraint('cache_key'),
2756 Index('key_idx', 'cache_key'),
2756 Index('key_idx', 'cache_key'),
2757 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2757 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2758 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2758 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2759 )
2759 )
2760 CACHE_TYPE_ATOM = 'ATOM'
2760 CACHE_TYPE_ATOM = 'ATOM'
2761 CACHE_TYPE_RSS = 'RSS'
2761 CACHE_TYPE_RSS = 'RSS'
2762 CACHE_TYPE_README = 'README'
2762 CACHE_TYPE_README = 'README'
2763
2763
2764 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2764 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2765 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2765 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2766 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2766 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2767 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2767 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2768
2768
2769 def __init__(self, cache_key, cache_args=''):
2769 def __init__(self, cache_key, cache_args=''):
2770 self.cache_key = cache_key
2770 self.cache_key = cache_key
2771 self.cache_args = cache_args
2771 self.cache_args = cache_args
2772 self.cache_active = False
2772 self.cache_active = False
2773
2773
2774 def __unicode__(self):
2774 def __unicode__(self):
2775 return u"<%s('%s:%s[%s]')>" % (
2775 return u"<%s('%s:%s[%s]')>" % (
2776 self.__class__.__name__,
2776 self.__class__.__name__,
2777 self.cache_id, self.cache_key, self.cache_active)
2777 self.cache_id, self.cache_key, self.cache_active)
2778
2778
2779 def _cache_key_partition(self):
2779 def _cache_key_partition(self):
2780 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2780 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2781 return prefix, repo_name, suffix
2781 return prefix, repo_name, suffix
2782
2782
2783 def get_prefix(self):
2783 def get_prefix(self):
2784 """
2784 """
2785 Try to extract prefix from existing cache key. The key could consist
2785 Try to extract prefix from existing cache key. The key could consist
2786 of prefix, repo_name, suffix
2786 of prefix, repo_name, suffix
2787 """
2787 """
2788 # this returns prefix, repo_name, suffix
2788 # this returns prefix, repo_name, suffix
2789 return self._cache_key_partition()[0]
2789 return self._cache_key_partition()[0]
2790
2790
2791 def get_suffix(self):
2791 def get_suffix(self):
2792 """
2792 """
2793 get suffix that might have been used in _get_cache_key to
2793 get suffix that might have been used in _get_cache_key to
2794 generate self.cache_key. Only used for informational purposes
2794 generate self.cache_key. Only used for informational purposes
2795 in repo_edit.html.
2795 in repo_edit.html.
2796 """
2796 """
2797 # prefix, repo_name, suffix
2797 # prefix, repo_name, suffix
2798 return self._cache_key_partition()[2]
2798 return self._cache_key_partition()[2]
2799
2799
2800 @classmethod
2800 @classmethod
2801 def delete_all_cache(cls):
2801 def delete_all_cache(cls):
2802 """
2802 """
2803 Delete all cache keys from database.
2803 Delete all cache keys from database.
2804 Should only be run when all instances are down and all entries
2804 Should only be run when all instances are down and all entries
2805 thus stale.
2805 thus stale.
2806 """
2806 """
2807 cls.query().delete()
2807 cls.query().delete()
2808 Session().commit()
2808 Session().commit()
2809
2809
2810 @classmethod
2810 @classmethod
2811 def get_cache_key(cls, repo_name, cache_type):
2811 def get_cache_key(cls, repo_name, cache_type):
2812 """
2812 """
2813
2813
2814 Generate a cache key for this process of RhodeCode instance.
2814 Generate a cache key for this process of RhodeCode instance.
2815 Prefix most likely will be process id or maybe explicitly set
2815 Prefix most likely will be process id or maybe explicitly set
2816 instance_id from .ini file.
2816 instance_id from .ini file.
2817 """
2817 """
2818 import rhodecode
2818 import rhodecode
2819 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2819 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2820
2820
2821 repo_as_unicode = safe_unicode(repo_name)
2821 repo_as_unicode = safe_unicode(repo_name)
2822 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2822 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2823 if cache_type else repo_as_unicode
2823 if cache_type else repo_as_unicode
2824
2824
2825 return u'{}{}'.format(prefix, key)
2825 return u'{}{}'.format(prefix, key)
2826
2826
2827 @classmethod
2827 @classmethod
2828 def set_invalidate(cls, repo_name, delete=False):
2828 def set_invalidate(cls, repo_name, delete=False):
2829 """
2829 """
2830 Mark all caches of a repo as invalid in the database.
2830 Mark all caches of a repo as invalid in the database.
2831 """
2831 """
2832
2832
2833 try:
2833 try:
2834 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2834 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2835 if delete:
2835 if delete:
2836 log.debug('cache objects deleted for repo %s',
2836 log.debug('cache objects deleted for repo %s',
2837 safe_str(repo_name))
2837 safe_str(repo_name))
2838 qry.delete()
2838 qry.delete()
2839 else:
2839 else:
2840 log.debug('cache objects marked as invalid for repo %s',
2840 log.debug('cache objects marked as invalid for repo %s',
2841 safe_str(repo_name))
2841 safe_str(repo_name))
2842 qry.update({"cache_active": False})
2842 qry.update({"cache_active": False})
2843
2843
2844 Session().commit()
2844 Session().commit()
2845 except Exception:
2845 except Exception:
2846 log.exception(
2846 log.exception(
2847 'Cache key invalidation failed for repository %s',
2847 'Cache key invalidation failed for repository %s',
2848 safe_str(repo_name))
2848 safe_str(repo_name))
2849 Session().rollback()
2849 Session().rollback()
2850
2850
2851 @classmethod
2851 @classmethod
2852 def get_active_cache(cls, cache_key):
2852 def get_active_cache(cls, cache_key):
2853 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2853 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2854 if inv_obj:
2854 if inv_obj:
2855 return inv_obj
2855 return inv_obj
2856 return None
2856 return None
2857
2857
2858 @classmethod
2858 @classmethod
2859 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2859 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2860 """
2860 """
2861 @cache_region('long_term')
2861 @cache_region('long_term')
2862 def _heavy_calculation(cache_key):
2862 def _heavy_calculation(cache_key):
2863 return 'result'
2863 return 'result'
2864
2864
2865 cache_context = CacheKey.repo_context_cache(
2865 cache_context = CacheKey.repo_context_cache(
2866 _heavy_calculation, repo_name, cache_type)
2866 _heavy_calculation, repo_name, cache_type)
2867
2867
2868 with cache_context as context:
2868 with cache_context as context:
2869 context.invalidate()
2869 context.invalidate()
2870 computed = context.compute()
2870 computed = context.compute()
2871
2871
2872 assert computed == 'result'
2872 assert computed == 'result'
2873 """
2873 """
2874 from rhodecode.lib import caches
2874 from rhodecode.lib import caches
2875 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2875 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2876
2876
2877
2877
2878 class ChangesetComment(Base, BaseModel):
2878 class ChangesetComment(Base, BaseModel):
2879 __tablename__ = 'changeset_comments'
2879 __tablename__ = 'changeset_comments'
2880 __table_args__ = (
2880 __table_args__ = (
2881 Index('cc_revision_idx', 'revision'),
2881 Index('cc_revision_idx', 'revision'),
2882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2884 )
2884 )
2885
2885
2886 COMMENT_OUTDATED = u'comment_outdated'
2886 COMMENT_OUTDATED = u'comment_outdated'
2887
2887
2888 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2888 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2889 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2889 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2890 revision = Column('revision', String(40), nullable=True)
2890 revision = Column('revision', String(40), nullable=True)
2891 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2891 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2892 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2892 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2893 line_no = Column('line_no', Unicode(10), nullable=True)
2893 line_no = Column('line_no', Unicode(10), nullable=True)
2894 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2894 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2895 f_path = Column('f_path', Unicode(1000), nullable=True)
2895 f_path = Column('f_path', Unicode(1000), nullable=True)
2896 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2896 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2897 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2897 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2898 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2898 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2899 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2899 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2900 renderer = Column('renderer', Unicode(64), nullable=True)
2900 renderer = Column('renderer', Unicode(64), nullable=True)
2901 display_state = Column('display_state', Unicode(128), nullable=True)
2901 display_state = Column('display_state', Unicode(128), nullable=True)
2902
2902
2903 author = relationship('User', lazy='joined')
2903 author = relationship('User', lazy='joined')
2904 repo = relationship('Repository')
2904 repo = relationship('Repository')
2905 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2905 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2906 pull_request = relationship('PullRequest', lazy='joined')
2906 pull_request = relationship('PullRequest', lazy='joined')
2907 pull_request_version = relationship('PullRequestVersion')
2907 pull_request_version = relationship('PullRequestVersion')
2908
2908
2909 @classmethod
2909 @classmethod
2910 def get_users(cls, revision=None, pull_request_id=None):
2910 def get_users(cls, revision=None, pull_request_id=None):
2911 """
2911 """
2912 Returns user associated with this ChangesetComment. ie those
2912 Returns user associated with this ChangesetComment. ie those
2913 who actually commented
2913 who actually commented
2914
2914
2915 :param cls:
2915 :param cls:
2916 :param revision:
2916 :param revision:
2917 """
2917 """
2918 q = Session().query(User)\
2918 q = Session().query(User)\
2919 .join(ChangesetComment.author)
2919 .join(ChangesetComment.author)
2920 if revision:
2920 if revision:
2921 q = q.filter(cls.revision == revision)
2921 q = q.filter(cls.revision == revision)
2922 elif pull_request_id:
2922 elif pull_request_id:
2923 q = q.filter(cls.pull_request_id == pull_request_id)
2923 q = q.filter(cls.pull_request_id == pull_request_id)
2924 return q.all()
2924 return q.all()
2925
2925
2926 def render(self, mentions=False):
2926 def render(self, mentions=False):
2927 from rhodecode.lib import helpers as h
2927 from rhodecode.lib import helpers as h
2928 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2928 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2929
2929
2930 def __repr__(self):
2930 def __repr__(self):
2931 if self.comment_id:
2931 if self.comment_id:
2932 return '<DB:ChangesetComment #%s>' % self.comment_id
2932 return '<DB:ChangesetComment #%s>' % self.comment_id
2933 else:
2933 else:
2934 return '<DB:ChangesetComment at %#x>' % id(self)
2934 return '<DB:ChangesetComment at %#x>' % id(self)
2935
2935
2936
2936
2937 class ChangesetStatus(Base, BaseModel):
2937 class ChangesetStatus(Base, BaseModel):
2938 __tablename__ = 'changeset_statuses'
2938 __tablename__ = 'changeset_statuses'
2939 __table_args__ = (
2939 __table_args__ = (
2940 Index('cs_revision_idx', 'revision'),
2940 Index('cs_revision_idx', 'revision'),
2941 Index('cs_version_idx', 'version'),
2941 Index('cs_version_idx', 'version'),
2942 UniqueConstraint('repo_id', 'revision', 'version'),
2942 UniqueConstraint('repo_id', 'revision', 'version'),
2943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2945 )
2945 )
2946 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2946 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2947 STATUS_APPROVED = 'approved'
2947 STATUS_APPROVED = 'approved'
2948 STATUS_REJECTED = 'rejected'
2948 STATUS_REJECTED = 'rejected'
2949 STATUS_UNDER_REVIEW = 'under_review'
2949 STATUS_UNDER_REVIEW = 'under_review'
2950
2950
2951 STATUSES = [
2951 STATUSES = [
2952 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2952 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2953 (STATUS_APPROVED, _("Approved")),
2953 (STATUS_APPROVED, _("Approved")),
2954 (STATUS_REJECTED, _("Rejected")),
2954 (STATUS_REJECTED, _("Rejected")),
2955 (STATUS_UNDER_REVIEW, _("Under Review")),
2955 (STATUS_UNDER_REVIEW, _("Under Review")),
2956 ]
2956 ]
2957
2957
2958 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2958 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2959 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2959 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2960 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2960 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2961 revision = Column('revision', String(40), nullable=False)
2961 revision = Column('revision', String(40), nullable=False)
2962 status = Column('status', String(128), nullable=False, default=DEFAULT)
2962 status = Column('status', String(128), nullable=False, default=DEFAULT)
2963 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2963 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2964 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2964 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2965 version = Column('version', Integer(), nullable=False, default=0)
2965 version = Column('version', Integer(), nullable=False, default=0)
2966 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2966 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2967
2967
2968 author = relationship('User', lazy='joined')
2968 author = relationship('User', lazy='joined')
2969 repo = relationship('Repository')
2969 repo = relationship('Repository')
2970 comment = relationship('ChangesetComment', lazy='joined')
2970 comment = relationship('ChangesetComment', lazy='joined')
2971 pull_request = relationship('PullRequest', lazy='joined')
2971 pull_request = relationship('PullRequest', lazy='joined')
2972
2972
2973 def __unicode__(self):
2973 def __unicode__(self):
2974 return u"<%s('%s[%s]:%s')>" % (
2974 return u"<%s('%s[%s]:%s')>" % (
2975 self.__class__.__name__,
2975 self.__class__.__name__,
2976 self.status, self.version, self.author
2976 self.status, self.version, self.author
2977 )
2977 )
2978
2978
2979 @classmethod
2979 @classmethod
2980 def get_status_lbl(cls, value):
2980 def get_status_lbl(cls, value):
2981 return dict(cls.STATUSES).get(value)
2981 return dict(cls.STATUSES).get(value)
2982
2982
2983 @property
2983 @property
2984 def status_lbl(self):
2984 def status_lbl(self):
2985 return ChangesetStatus.get_status_lbl(self.status)
2985 return ChangesetStatus.get_status_lbl(self.status)
2986
2986
2987
2987
2988 class _PullRequestBase(BaseModel):
2988 class _PullRequestBase(BaseModel):
2989 """
2989 """
2990 Common attributes of pull request and version entries.
2990 Common attributes of pull request and version entries.
2991 """
2991 """
2992
2992
2993 # .status values
2993 # .status values
2994 STATUS_NEW = u'new'
2994 STATUS_NEW = u'new'
2995 STATUS_OPEN = u'open'
2995 STATUS_OPEN = u'open'
2996 STATUS_CLOSED = u'closed'
2996 STATUS_CLOSED = u'closed'
2997
2997
2998 title = Column('title', Unicode(255), nullable=True)
2998 title = Column('title', Unicode(255), nullable=True)
2999 description = Column(
2999 description = Column(
3000 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3000 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3001 nullable=True)
3001 nullable=True)
3002 # new/open/closed status of pull request (not approve/reject/etc)
3002 # new/open/closed status of pull request (not approve/reject/etc)
3003 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3003 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3004 created_on = Column(
3004 created_on = Column(
3005 'created_on', DateTime(timezone=False), nullable=False,
3005 'created_on', DateTime(timezone=False), nullable=False,
3006 default=datetime.datetime.now)
3006 default=datetime.datetime.now)
3007 updated_on = Column(
3007 updated_on = Column(
3008 'updated_on', DateTime(timezone=False), nullable=False,
3008 'updated_on', DateTime(timezone=False), nullable=False,
3009 default=datetime.datetime.now)
3009 default=datetime.datetime.now)
3010
3010
3011 @declared_attr
3011 @declared_attr
3012 def user_id(cls):
3012 def user_id(cls):
3013 return Column(
3013 return Column(
3014 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3014 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3015 unique=None)
3015 unique=None)
3016
3016
3017 # 500 revisions max
3017 # 500 revisions max
3018 _revisions = Column(
3018 _revisions = Column(
3019 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3019 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3020
3020
3021 @declared_attr
3021 @declared_attr
3022 def source_repo_id(cls):
3022 def source_repo_id(cls):
3023 # TODO: dan: rename column to source_repo_id
3023 # TODO: dan: rename column to source_repo_id
3024 return Column(
3024 return Column(
3025 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3025 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3026 nullable=False)
3026 nullable=False)
3027
3027
3028 source_ref = Column('org_ref', Unicode(255), nullable=False)
3028 source_ref = Column('org_ref', Unicode(255), nullable=False)
3029
3029
3030 @declared_attr
3030 @declared_attr
3031 def target_repo_id(cls):
3031 def target_repo_id(cls):
3032 # TODO: dan: rename column to target_repo_id
3032 # TODO: dan: rename column to target_repo_id
3033 return Column(
3033 return Column(
3034 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3034 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3035 nullable=False)
3035 nullable=False)
3036
3036
3037 target_ref = Column('other_ref', Unicode(255), nullable=False)
3037 target_ref = Column('other_ref', Unicode(255), nullable=False)
3038
3038
3039 # TODO: dan: rename column to last_merge_source_rev
3039 # TODO: dan: rename column to last_merge_source_rev
3040 _last_merge_source_rev = Column(
3040 _last_merge_source_rev = Column(
3041 'last_merge_org_rev', String(40), nullable=True)
3041 'last_merge_org_rev', String(40), nullable=True)
3042 # TODO: dan: rename column to last_merge_target_rev
3042 # TODO: dan: rename column to last_merge_target_rev
3043 _last_merge_target_rev = Column(
3043 _last_merge_target_rev = Column(
3044 'last_merge_other_rev', String(40), nullable=True)
3044 'last_merge_other_rev', String(40), nullable=True)
3045 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3045 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3046 merge_rev = Column('merge_rev', String(40), nullable=True)
3046 merge_rev = Column('merge_rev', String(40), nullable=True)
3047
3047
3048 @hybrid_property
3048 @hybrid_property
3049 def revisions(self):
3049 def revisions(self):
3050 return self._revisions.split(':') if self._revisions else []
3050 return self._revisions.split(':') if self._revisions else []
3051
3051
3052 @revisions.setter
3052 @revisions.setter
3053 def revisions(self, val):
3053 def revisions(self, val):
3054 self._revisions = ':'.join(val)
3054 self._revisions = ':'.join(val)
3055
3055
3056 @declared_attr
3056 @declared_attr
3057 def author(cls):
3057 def author(cls):
3058 return relationship('User', lazy='joined')
3058 return relationship('User', lazy='joined')
3059
3059
3060 @declared_attr
3060 @declared_attr
3061 def source_repo(cls):
3061 def source_repo(cls):
3062 return relationship(
3062 return relationship(
3063 'Repository',
3063 'Repository',
3064 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3064 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3065
3065
3066 @property
3066 @property
3067 def source_ref_parts(self):
3067 def source_ref_parts(self):
3068 refs = self.source_ref.split(':')
3068 refs = self.source_ref.split(':')
3069 return Reference(refs[0], refs[1], refs[2])
3069 return Reference(refs[0], refs[1], refs[2])
3070
3070
3071 @declared_attr
3071 @declared_attr
3072 def target_repo(cls):
3072 def target_repo(cls):
3073 return relationship(
3073 return relationship(
3074 'Repository',
3074 'Repository',
3075 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3075 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3076
3076
3077 @property
3077 @property
3078 def target_ref_parts(self):
3078 def target_ref_parts(self):
3079 refs = self.target_ref.split(':')
3079 refs = self.target_ref.split(':')
3080 return Reference(refs[0], refs[1], refs[2])
3080 return Reference(refs[0], refs[1], refs[2])
3081
3081
3082
3082
3083 class PullRequest(Base, _PullRequestBase):
3083 class PullRequest(Base, _PullRequestBase):
3084 __tablename__ = 'pull_requests'
3084 __tablename__ = 'pull_requests'
3085 __table_args__ = (
3085 __table_args__ = (
3086 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3086 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3087 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3087 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3088 )
3088 )
3089
3089
3090 pull_request_id = Column(
3090 pull_request_id = Column(
3091 'pull_request_id', Integer(), nullable=False, primary_key=True)
3091 'pull_request_id', Integer(), nullable=False, primary_key=True)
3092
3092
3093 def __repr__(self):
3093 def __repr__(self):
3094 if self.pull_request_id:
3094 if self.pull_request_id:
3095 return '<DB:PullRequest #%s>' % self.pull_request_id
3095 return '<DB:PullRequest #%s>' % self.pull_request_id
3096 else:
3096 else:
3097 return '<DB:PullRequest at %#x>' % id(self)
3097 return '<DB:PullRequest at %#x>' % id(self)
3098
3098
3099 reviewers = relationship('PullRequestReviewers',
3099 reviewers = relationship('PullRequestReviewers',
3100 cascade="all, delete, delete-orphan")
3100 cascade="all, delete, delete-orphan")
3101 statuses = relationship('ChangesetStatus')
3101 statuses = relationship('ChangesetStatus')
3102 comments = relationship('ChangesetComment',
3102 comments = relationship('ChangesetComment',
3103 cascade="all, delete, delete-orphan")
3103 cascade="all, delete, delete-orphan")
3104 versions = relationship('PullRequestVersion',
3104 versions = relationship('PullRequestVersion',
3105 cascade="all, delete, delete-orphan")
3105 cascade="all, delete, delete-orphan")
3106
3106
3107 def is_closed(self):
3107 def is_closed(self):
3108 return self.status == self.STATUS_CLOSED
3108 return self.status == self.STATUS_CLOSED
3109
3109
3110 def get_api_data(self):
3110 def get_api_data(self):
3111 from rhodecode.model.pull_request import PullRequestModel
3111 from rhodecode.model.pull_request import PullRequestModel
3112 pull_request = self
3112 pull_request = self
3113 merge_status = PullRequestModel().merge_status(pull_request)
3113 merge_status = PullRequestModel().merge_status(pull_request)
3114 data = {
3114 data = {
3115 'pull_request_id': pull_request.pull_request_id,
3115 'pull_request_id': pull_request.pull_request_id,
3116 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3116 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3117 pull_request_id=self.pull_request_id,
3117 pull_request_id=self.pull_request_id,
3118 qualified=True),
3118 qualified=True),
3119 'title': pull_request.title,
3119 'title': pull_request.title,
3120 'description': pull_request.description,
3120 'description': pull_request.description,
3121 'status': pull_request.status,
3121 'status': pull_request.status,
3122 'created_on': pull_request.created_on,
3122 'created_on': pull_request.created_on,
3123 'updated_on': pull_request.updated_on,
3123 'updated_on': pull_request.updated_on,
3124 'commit_ids': pull_request.revisions,
3124 'commit_ids': pull_request.revisions,
3125 'review_status': pull_request.calculated_review_status(),
3125 'review_status': pull_request.calculated_review_status(),
3126 'mergeable': {
3126 'mergeable': {
3127 'status': merge_status[0],
3127 'status': merge_status[0],
3128 'message': unicode(merge_status[1]),
3128 'message': unicode(merge_status[1]),
3129 },
3129 },
3130 'source': {
3130 'source': {
3131 'clone_url': pull_request.source_repo.clone_url(),
3131 'clone_url': pull_request.source_repo.clone_url(),
3132 'repository': pull_request.source_repo.repo_name,
3132 'repository': pull_request.source_repo.repo_name,
3133 'reference': {
3133 'reference': {
3134 'name': pull_request.source_ref_parts.name,
3134 'name': pull_request.source_ref_parts.name,
3135 'type': pull_request.source_ref_parts.type,
3135 'type': pull_request.source_ref_parts.type,
3136 'commit_id': pull_request.source_ref_parts.commit_id,
3136 'commit_id': pull_request.source_ref_parts.commit_id,
3137 },
3137 },
3138 },
3138 },
3139 'target': {
3139 'target': {
3140 'clone_url': pull_request.target_repo.clone_url(),
3140 'clone_url': pull_request.target_repo.clone_url(),
3141 'repository': pull_request.target_repo.repo_name,
3141 'repository': pull_request.target_repo.repo_name,
3142 'reference': {
3142 'reference': {
3143 'name': pull_request.target_ref_parts.name,
3143 'name': pull_request.target_ref_parts.name,
3144 'type': pull_request.target_ref_parts.type,
3144 'type': pull_request.target_ref_parts.type,
3145 'commit_id': pull_request.target_ref_parts.commit_id,
3145 'commit_id': pull_request.target_ref_parts.commit_id,
3146 },
3146 },
3147 },
3147 },
3148 'author': pull_request.author.get_api_data(include_secrets=False,
3148 'author': pull_request.author.get_api_data(include_secrets=False,
3149 details='basic'),
3149 details='basic'),
3150 'reviewers': [
3150 'reviewers': [
3151 {
3151 {
3152 'user': reviewer.get_api_data(include_secrets=False,
3152 'user': reviewer.get_api_data(include_secrets=False,
3153 details='basic'),
3153 details='basic'),
3154 'review_status': st[0][1].status if st else 'not_reviewed',
3154 'review_status': st[0][1].status if st else 'not_reviewed',
3155 }
3155 }
3156 for reviewer, st in pull_request.reviewers_statuses()
3156 for reviewer, st in pull_request.reviewers_statuses()
3157 ]
3157 ]
3158 }
3158 }
3159
3159
3160 return data
3160 return data
3161
3161
3162 def __json__(self):
3162 def __json__(self):
3163 return {
3163 return {
3164 'revisions': self.revisions,
3164 'revisions': self.revisions,
3165 }
3165 }
3166
3166
3167 def calculated_review_status(self):
3167 def calculated_review_status(self):
3168 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3168 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3169 # because it's tricky on how to use ChangesetStatusModel from there
3169 # because it's tricky on how to use ChangesetStatusModel from there
3170 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3170 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3171 from rhodecode.model.changeset_status import ChangesetStatusModel
3171 from rhodecode.model.changeset_status import ChangesetStatusModel
3172 return ChangesetStatusModel().calculated_review_status(self)
3172 return ChangesetStatusModel().calculated_review_status(self)
3173
3173
3174 def reviewers_statuses(self):
3174 def reviewers_statuses(self):
3175 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3175 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3176 from rhodecode.model.changeset_status import ChangesetStatusModel
3176 from rhodecode.model.changeset_status import ChangesetStatusModel
3177 return ChangesetStatusModel().reviewers_statuses(self)
3177 return ChangesetStatusModel().reviewers_statuses(self)
3178
3178
3179
3179
3180 class PullRequestVersion(Base, _PullRequestBase):
3180 class PullRequestVersion(Base, _PullRequestBase):
3181 __tablename__ = 'pull_request_versions'
3181 __tablename__ = 'pull_request_versions'
3182 __table_args__ = (
3182 __table_args__ = (
3183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3184 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3184 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3185 )
3185 )
3186
3186
3187 pull_request_version_id = Column(
3187 pull_request_version_id = Column(
3188 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3188 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3189 pull_request_id = Column(
3189 pull_request_id = Column(
3190 'pull_request_id', Integer(),
3190 'pull_request_id', Integer(),
3191 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3191 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3192 pull_request = relationship('PullRequest')
3192 pull_request = relationship('PullRequest')
3193
3193
3194 def __repr__(self):
3194 def __repr__(self):
3195 if self.pull_request_version_id:
3195 if self.pull_request_version_id:
3196 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3196 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3197 else:
3197 else:
3198 return '<DB:PullRequestVersion at %#x>' % id(self)
3198 return '<DB:PullRequestVersion at %#x>' % id(self)
3199
3199
3200
3200
3201 class PullRequestReviewers(Base, BaseModel):
3201 class PullRequestReviewers(Base, BaseModel):
3202 __tablename__ = 'pull_request_reviewers'
3202 __tablename__ = 'pull_request_reviewers'
3203 __table_args__ = (
3203 __table_args__ = (
3204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3206 )
3206 )
3207
3207
3208 def __init__(self, user=None, pull_request=None):
3208 def __init__(self, user=None, pull_request=None):
3209 self.user = user
3209 self.user = user
3210 self.pull_request = pull_request
3210 self.pull_request = pull_request
3211
3211
3212 pull_requests_reviewers_id = Column(
3212 pull_requests_reviewers_id = Column(
3213 'pull_requests_reviewers_id', Integer(), nullable=False,
3213 'pull_requests_reviewers_id', Integer(), nullable=False,
3214 primary_key=True)
3214 primary_key=True)
3215 pull_request_id = Column(
3215 pull_request_id = Column(
3216 "pull_request_id", Integer(),
3216 "pull_request_id", Integer(),
3217 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3217 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3218 user_id = Column(
3218 user_id = Column(
3219 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3219 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3220
3220
3221 user = relationship('User')
3221 user = relationship('User')
3222 pull_request = relationship('PullRequest')
3222 pull_request = relationship('PullRequest')
3223
3223
3224
3224
3225 class Notification(Base, BaseModel):
3225 class Notification(Base, BaseModel):
3226 __tablename__ = 'notifications'
3226 __tablename__ = 'notifications'
3227 __table_args__ = (
3227 __table_args__ = (
3228 Index('notification_type_idx', 'type'),
3228 Index('notification_type_idx', 'type'),
3229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3230 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3230 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3231 )
3231 )
3232
3232
3233 TYPE_CHANGESET_COMMENT = u'cs_comment'
3233 TYPE_CHANGESET_COMMENT = u'cs_comment'
3234 TYPE_MESSAGE = u'message'
3234 TYPE_MESSAGE = u'message'
3235 TYPE_MENTION = u'mention'
3235 TYPE_MENTION = u'mention'
3236 TYPE_REGISTRATION = u'registration'
3236 TYPE_REGISTRATION = u'registration'
3237 TYPE_PULL_REQUEST = u'pull_request'
3237 TYPE_PULL_REQUEST = u'pull_request'
3238 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3238 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3239
3239
3240 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3240 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3241 subject = Column('subject', Unicode(512), nullable=True)
3241 subject = Column('subject', Unicode(512), nullable=True)
3242 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3242 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3243 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3243 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3244 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3244 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3245 type_ = Column('type', Unicode(255))
3245 type_ = Column('type', Unicode(255))
3246
3246
3247 created_by_user = relationship('User')
3247 created_by_user = relationship('User')
3248 notifications_to_users = relationship('UserNotification', lazy='joined',
3248 notifications_to_users = relationship('UserNotification', lazy='joined',
3249 cascade="all, delete, delete-orphan")
3249 cascade="all, delete, delete-orphan")
3250
3250
3251 @property
3251 @property
3252 def recipients(self):
3252 def recipients(self):
3253 return [x.user for x in UserNotification.query()\
3253 return [x.user for x in UserNotification.query()\
3254 .filter(UserNotification.notification == self)\
3254 .filter(UserNotification.notification == self)\
3255 .order_by(UserNotification.user_id.asc()).all()]
3255 .order_by(UserNotification.user_id.asc()).all()]
3256
3256
3257 @classmethod
3257 @classmethod
3258 def create(cls, created_by, subject, body, recipients, type_=None):
3258 def create(cls, created_by, subject, body, recipients, type_=None):
3259 if type_ is None:
3259 if type_ is None:
3260 type_ = Notification.TYPE_MESSAGE
3260 type_ = Notification.TYPE_MESSAGE
3261
3261
3262 notification = cls()
3262 notification = cls()
3263 notification.created_by_user = created_by
3263 notification.created_by_user = created_by
3264 notification.subject = subject
3264 notification.subject = subject
3265 notification.body = body
3265 notification.body = body
3266 notification.type_ = type_
3266 notification.type_ = type_
3267 notification.created_on = datetime.datetime.now()
3267 notification.created_on = datetime.datetime.now()
3268
3268
3269 for u in recipients:
3269 for u in recipients:
3270 assoc = UserNotification()
3270 assoc = UserNotification()
3271 assoc.notification = notification
3271 assoc.notification = notification
3272
3272
3273 # if created_by is inside recipients mark his notification
3273 # if created_by is inside recipients mark his notification
3274 # as read
3274 # as read
3275 if u.user_id == created_by.user_id:
3275 if u.user_id == created_by.user_id:
3276 assoc.read = True
3276 assoc.read = True
3277
3277
3278 u.notifications.append(assoc)
3278 u.notifications.append(assoc)
3279 Session().add(notification)
3279 Session().add(notification)
3280
3280
3281 return notification
3281 return notification
3282
3282
3283 @property
3283 @property
3284 def description(self):
3284 def description(self):
3285 from rhodecode.model.notification import NotificationModel
3285 from rhodecode.model.notification import NotificationModel
3286 return NotificationModel().make_description(self)
3286 return NotificationModel().make_description(self)
3287
3287
3288
3288
3289 class UserNotification(Base, BaseModel):
3289 class UserNotification(Base, BaseModel):
3290 __tablename__ = 'user_to_notification'
3290 __tablename__ = 'user_to_notification'
3291 __table_args__ = (
3291 __table_args__ = (
3292 UniqueConstraint('user_id', 'notification_id'),
3292 UniqueConstraint('user_id', 'notification_id'),
3293 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3293 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3294 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3294 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3295 )
3295 )
3296 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3296 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3297 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3297 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3298 read = Column('read', Boolean, default=False)
3298 read = Column('read', Boolean, default=False)
3299 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3299 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3300
3300
3301 user = relationship('User', lazy="joined")
3301 user = relationship('User', lazy="joined")
3302 notification = relationship('Notification', lazy="joined",
3302 notification = relationship('Notification', lazy="joined",
3303 order_by=lambda: Notification.created_on.desc(),)
3303 order_by=lambda: Notification.created_on.desc(),)
3304
3304
3305 def mark_as_read(self):
3305 def mark_as_read(self):
3306 self.read = True
3306 self.read = True
3307 Session().add(self)
3307 Session().add(self)
3308
3308
3309
3309
3310 class Gist(Base, BaseModel):
3310 class Gist(Base, BaseModel):
3311 __tablename__ = 'gists'
3311 __tablename__ = 'gists'
3312 __table_args__ = (
3312 __table_args__ = (
3313 Index('g_gist_access_id_idx', 'gist_access_id'),
3313 Index('g_gist_access_id_idx', 'gist_access_id'),
3314 Index('g_created_on_idx', 'created_on'),
3314 Index('g_created_on_idx', 'created_on'),
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3317 )
3317 )
3318 GIST_PUBLIC = u'public'
3318 GIST_PUBLIC = u'public'
3319 GIST_PRIVATE = u'private'
3319 GIST_PRIVATE = u'private'
3320 DEFAULT_FILENAME = u'gistfile1.txt'
3320 DEFAULT_FILENAME = u'gistfile1.txt'
3321
3321
3322 ACL_LEVEL_PUBLIC = u'acl_public'
3322 ACL_LEVEL_PUBLIC = u'acl_public'
3323 ACL_LEVEL_PRIVATE = u'acl_private'
3323 ACL_LEVEL_PRIVATE = u'acl_private'
3324
3324
3325 gist_id = Column('gist_id', Integer(), primary_key=True)
3325 gist_id = Column('gist_id', Integer(), primary_key=True)
3326 gist_access_id = Column('gist_access_id', Unicode(250))
3326 gist_access_id = Column('gist_access_id', Unicode(250))
3327 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3327 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3328 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3328 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3329 gist_expires = Column('gist_expires', Float(53), nullable=False)
3329 gist_expires = Column('gist_expires', Float(53), nullable=False)
3330 gist_type = Column('gist_type', Unicode(128), nullable=False)
3330 gist_type = Column('gist_type', Unicode(128), nullable=False)
3331 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3331 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3332 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3332 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3333 acl_level = Column('acl_level', Unicode(128), nullable=True)
3333 acl_level = Column('acl_level', Unicode(128), nullable=True)
3334
3334
3335 owner = relationship('User')
3335 owner = relationship('User')
3336
3336
3337 def __repr__(self):
3337 def __repr__(self):
3338 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3338 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3339
3339
3340 @classmethod
3340 @classmethod
3341 def get_or_404(cls, id_):
3341 def get_or_404(cls, id_):
3342 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3342 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3343 if not res:
3343 if not res:
3344 raise HTTPNotFound
3344 raise HTTPNotFound
3345 return res
3345 return res
3346
3346
3347 @classmethod
3347 @classmethod
3348 def get_by_access_id(cls, gist_access_id):
3348 def get_by_access_id(cls, gist_access_id):
3349 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3349 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3350
3350
3351 def gist_url(self):
3351 def gist_url(self):
3352 import rhodecode
3352 import rhodecode
3353 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3353 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3354 if alias_url:
3354 if alias_url:
3355 return alias_url.replace('{gistid}', self.gist_access_id)
3355 return alias_url.replace('{gistid}', self.gist_access_id)
3356
3356
3357 return url('gist', gist_id=self.gist_access_id, qualified=True)
3357 return url('gist', gist_id=self.gist_access_id, qualified=True)
3358
3358
3359 @classmethod
3359 @classmethod
3360 def base_path(cls):
3360 def base_path(cls):
3361 """
3361 """
3362 Returns base path when all gists are stored
3362 Returns base path when all gists are stored
3363
3363
3364 :param cls:
3364 :param cls:
3365 """
3365 """
3366 from rhodecode.model.gist import GIST_STORE_LOC
3366 from rhodecode.model.gist import GIST_STORE_LOC
3367 q = Session().query(RhodeCodeUi)\
3367 q = Session().query(RhodeCodeUi)\
3368 .filter(RhodeCodeUi.ui_key == URL_SEP)
3368 .filter(RhodeCodeUi.ui_key == URL_SEP)
3369 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3369 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3370 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3370 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3371
3371
3372 def get_api_data(self):
3372 def get_api_data(self):
3373 """
3373 """
3374 Common function for generating gist related data for API
3374 Common function for generating gist related data for API
3375 """
3375 """
3376 gist = self
3376 gist = self
3377 data = {
3377 data = {
3378 'gist_id': gist.gist_id,
3378 'gist_id': gist.gist_id,
3379 'type': gist.gist_type,
3379 'type': gist.gist_type,
3380 'access_id': gist.gist_access_id,
3380 'access_id': gist.gist_access_id,
3381 'description': gist.gist_description,
3381 'description': gist.gist_description,
3382 'url': gist.gist_url(),
3382 'url': gist.gist_url(),
3383 'expires': gist.gist_expires,
3383 'expires': gist.gist_expires,
3384 'created_on': gist.created_on,
3384 'created_on': gist.created_on,
3385 'modified_at': gist.modified_at,
3385 'modified_at': gist.modified_at,
3386 'content': None,
3386 'content': None,
3387 'acl_level': gist.acl_level,
3387 'acl_level': gist.acl_level,
3388 }
3388 }
3389 return data
3389 return data
3390
3390
3391 def __json__(self):
3391 def __json__(self):
3392 data = dict(
3392 data = dict(
3393 )
3393 )
3394 data.update(self.get_api_data())
3394 data.update(self.get_api_data())
3395 return data
3395 return data
3396 # SCM functions
3396 # SCM functions
3397
3397
3398 def scm_instance(self, **kwargs):
3398 def scm_instance(self, **kwargs):
3399 from rhodecode.lib.vcs import get_repo
3399 from rhodecode.lib.vcs import get_repo
3400 base_path = self.base_path()
3400 base_path = self.base_path()
3401 return get_repo(os.path.join(*map(safe_str,
3401 return get_repo(os.path.join(*map(safe_str,
3402 [base_path, self.gist_access_id])))
3402 [base_path, self.gist_access_id])))
3403
3403
3404
3404
3405 class DbMigrateVersion(Base, BaseModel):
3405 class DbMigrateVersion(Base, BaseModel):
3406 __tablename__ = 'db_migrate_version'
3406 __tablename__ = 'db_migrate_version'
3407 __table_args__ = (
3407 __table_args__ = (
3408 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3408 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3409 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3409 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3410 )
3410 )
3411 repository_id = Column('repository_id', String(250), primary_key=True)
3411 repository_id = Column('repository_id', String(250), primary_key=True)
3412 repository_path = Column('repository_path', Text)
3412 repository_path = Column('repository_path', Text)
3413 version = Column('version', Integer)
3413 version = Column('version', Integer)
3414
3414
3415
3415
3416 class ExternalIdentity(Base, BaseModel):
3416 class ExternalIdentity(Base, BaseModel):
3417 __tablename__ = 'external_identities'
3417 __tablename__ = 'external_identities'
3418 __table_args__ = (
3418 __table_args__ = (
3419 Index('local_user_id_idx', 'local_user_id'),
3419 Index('local_user_id_idx', 'local_user_id'),
3420 Index('external_id_idx', 'external_id'),
3420 Index('external_id_idx', 'external_id'),
3421 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3421 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3422 'mysql_charset': 'utf8'})
3422 'mysql_charset': 'utf8'})
3423
3423
3424 external_id = Column('external_id', Unicode(255), default=u'',
3424 external_id = Column('external_id', Unicode(255), default=u'',
3425 primary_key=True)
3425 primary_key=True)
3426 external_username = Column('external_username', Unicode(1024), default=u'')
3426 external_username = Column('external_username', Unicode(1024), default=u'')
3427 local_user_id = Column('local_user_id', Integer(),
3427 local_user_id = Column('local_user_id', Integer(),
3428 ForeignKey('users.user_id'), primary_key=True)
3428 ForeignKey('users.user_id'), primary_key=True)
3429 provider_name = Column('provider_name', Unicode(255), default=u'',
3429 provider_name = Column('provider_name', Unicode(255), default=u'',
3430 primary_key=True)
3430 primary_key=True)
3431 access_token = Column('access_token', String(1024), default=u'')
3431 access_token = Column('access_token', String(1024), default=u'')
3432 alt_token = Column('alt_token', String(1024), default=u'')
3432 alt_token = Column('alt_token', String(1024), default=u'')
3433 token_secret = Column('token_secret', String(1024), default=u'')
3433 token_secret = Column('token_secret', String(1024), default=u'')
3434
3434
3435 @classmethod
3435 @classmethod
3436 def by_external_id_and_provider(cls, external_id, provider_name,
3436 def by_external_id_and_provider(cls, external_id, provider_name,
3437 local_user_id=None):
3437 local_user_id=None):
3438 """
3438 """
3439 Returns ExternalIdentity instance based on search params
3439 Returns ExternalIdentity instance based on search params
3440
3440
3441 :param external_id:
3441 :param external_id:
3442 :param provider_name:
3442 :param provider_name:
3443 :return: ExternalIdentity
3443 :return: ExternalIdentity
3444 """
3444 """
3445 query = cls.query()
3445 query = cls.query()
3446 query = query.filter(cls.external_id == external_id)
3446 query = query.filter(cls.external_id == external_id)
3447 query = query.filter(cls.provider_name == provider_name)
3447 query = query.filter(cls.provider_name == provider_name)
3448 if local_user_id:
3448 if local_user_id:
3449 query = query.filter(cls.local_user_id == local_user_id)
3449 query = query.filter(cls.local_user_id == local_user_id)
3450 return query.first()
3450 return query.first()
3451
3451
3452 @classmethod
3452 @classmethod
3453 def user_by_external_id_and_provider(cls, external_id, provider_name):
3453 def user_by_external_id_and_provider(cls, external_id, provider_name):
3454 """
3454 """
3455 Returns User instance based on search params
3455 Returns User instance based on search params
3456
3456
3457 :param external_id:
3457 :param external_id:
3458 :param provider_name:
3458 :param provider_name:
3459 :return: User
3459 :return: User
3460 """
3460 """
3461 query = User.query()
3461 query = User.query()
3462 query = query.filter(cls.external_id == external_id)
3462 query = query.filter(cls.external_id == external_id)
3463 query = query.filter(cls.provider_name == provider_name)
3463 query = query.filter(cls.provider_name == provider_name)
3464 query = query.filter(User.user_id == cls.local_user_id)
3464 query = query.filter(User.user_id == cls.local_user_id)
3465 return query.first()
3465 return query.first()
3466
3466
3467 @classmethod
3467 @classmethod
3468 def by_local_user_id(cls, local_user_id):
3468 def by_local_user_id(cls, local_user_id):
3469 """
3469 """
3470 Returns all tokens for user
3470 Returns all tokens for user
3471
3471
3472 :param local_user_id:
3472 :param local_user_id:
3473 :return: ExternalIdentity
3473 :return: ExternalIdentity
3474 """
3474 """
3475 query = cls.query()
3475 query = cls.query()
3476 query = query.filter(cls.local_user_id == local_user_id)
3476 query = query.filter(cls.local_user_id == local_user_id)
3477 return query
3477 return query
3478
3478
3479
3479
3480 class Integration(Base, BaseModel):
3480 class Integration(Base, BaseModel):
3481 __tablename__ = 'integrations'
3481 __tablename__ = 'integrations'
3482 __table_args__ = (
3482 __table_args__ = (
3483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3485 )
3485 )
3486
3486
3487 integration_id = Column('integration_id', Integer(), primary_key=True)
3487 integration_id = Column('integration_id', Integer(), primary_key=True)
3488 integration_type = Column('integration_type', String(255))
3488 integration_type = Column('integration_type', String(255))
3489 enabled = Column("enabled", Boolean(), nullable=False)
3489 enabled = Column("enabled", Boolean(), nullable=False)
3490 name = Column('name', String(255), nullable=False)
3490 name = Column('name', String(255), nullable=False)
3491 settings_json = Column('settings_json',
3491 settings_json = Column('settings_json',
3492 UnicodeText().with_variant(UnicodeText(16384), 'mysql'))
3492 UnicodeText().with_variant(UnicodeText(16384), 'mysql'))
3493 repo_id = Column(
3493 repo_id = Column(
3494 "repo_id", Integer(), ForeignKey('repositories.repo_id'),
3494 "repo_id", Integer(), ForeignKey('repositories.repo_id'),
3495 nullable=True, unique=None, default=None)
3495 nullable=True, unique=None, default=None)
3496 repo = relationship('Repository', lazy='joined')
3496 repo = relationship('Repository', lazy='joined')
3497
3497
3498 @hybrid_property
3498 @hybrid_property
3499 def settings(self):
3499 def settings(self):
3500 data = json.loads(self.settings_json or '{}')
3500 data = json.loads(self.settings_json or '{}')
3501 return data
3501 return data
3502
3502
3503 @settings.setter
3503 @settings.setter
3504 def settings(self, dct):
3504 def settings(self, dct):
3505 self.settings_json = json.dumps(dct, indent=2)
3505 self.settings_json = json.dumps(dct, indent=2)
3506
3506
3507 def __repr__(self):
3507 def __repr__(self):
3508 if self.repo:
3508 if self.repo:
3509 scope = 'repo=%r' % self.repo
3509 scope = 'repo=%r' % self.repo
3510 else:
3510 else:
3511 scope = 'global'
3511 scope = 'global'
3512
3512
3513 return '<Integration(%r, %r)>' % (self.integration_type, scope)
3513 return '<Integration(%r, %r)>' % (self.integration_type, scope)
3514
3514
3515 def settings_as_dict(self):
3515 def settings_as_dict(self):
3516 return json.loads(self.settings_json)
3516 return json.loads(self.settings_json)
@@ -1,3523 +1,3523 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 os
25 import os
26 import sys
26 import sys
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.exc import IntegrityError
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
42 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
43 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
45 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
46 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48
48
49 from pylons import url
49 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
50 from pylons.i18n.translation import lazy_ugettext as _
51
51
52 from rhodecode.lib.vcs import get_backend
52 from rhodecode.lib.vcs import get_backend
53 from rhodecode.lib.vcs.utils.helpers import get_scm
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
55 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
56 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 # beaker.session.secret if first is not set.
75 # and initialized at environment.py
75 # and initialized at environment.py
76 ENCRYPTION_KEY = None
76 ENCRYPTION_KEY = None
77
77
78 # used to sort permissions by types, '#' used here is not allowed to be in
78 # used to sort permissions by types, '#' used here is not allowed to be in
79 # usernames, and it's very early in sorted string.printable table.
79 # usernames, and it's very early in sorted string.printable table.
80 PERMISSION_TYPE_SORT = {
80 PERMISSION_TYPE_SORT = {
81 'admin': '####',
81 'admin': '####',
82 'write': '###',
82 'write': '###',
83 'read': '##',
83 'read': '##',
84 'none': '#',
84 'none': '#',
85 }
85 }
86
86
87
87
88 def display_sort(obj):
88 def display_sort(obj):
89 """
89 """
90 Sort function used to sort permissions in .permissions() function of
90 Sort function used to sort permissions in .permissions() function of
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
92 of all other resources
92 of all other resources
93 """
93 """
94
94
95 if obj.username == User.DEFAULT_USER:
95 if obj.username == User.DEFAULT_USER:
96 return '#####'
96 return '#####'
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
98 return prefix + obj.username
98 return prefix + obj.username
99
99
100
100
101 def _hash_key(k):
101 def _hash_key(k):
102 return md5_safe(k)
102 return md5_safe(k)
103
103
104
104
105 class EncryptedTextValue(TypeDecorator):
105 class EncryptedTextValue(TypeDecorator):
106 """
106 """
107 Special column for encrypted long text data, use like::
107 Special column for encrypted long text data, use like::
108
108
109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
110
110
111 This column is intelligent so if value is in unencrypted form it return
111 This column is intelligent so if value is in unencrypted form it return
112 unencrypted form, but on save it always encrypts
112 unencrypted form, but on save it always encrypts
113 """
113 """
114 impl = Text
114 impl = Text
115
115
116 def process_bind_param(self, value, dialect):
116 def process_bind_param(self, value, dialect):
117 if not value:
117 if not value:
118 return value
118 return value
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
120 # protect against double encrypting if someone manually starts
120 # protect against double encrypting if someone manually starts
121 # doing
121 # doing
122 raise ValueError('value needs to be in unencrypted format, ie. '
122 raise ValueError('value needs to be in unencrypted format, ie. '
123 'not starting with enc$aes')
123 'not starting with enc$aes')
124 return 'enc$aes_hmac$%s' % AESCipher(
124 return 'enc$aes_hmac$%s' % AESCipher(
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
126
126
127 def process_result_value(self, value, dialect):
127 def process_result_value(self, value, dialect):
128 import rhodecode
128 import rhodecode
129
129
130 if not value:
130 if not value:
131 return value
131 return value
132
132
133 parts = value.split('$', 3)
133 parts = value.split('$', 3)
134 if not len(parts) == 3:
134 if not len(parts) == 3:
135 # probably not encrypted values
135 # probably not encrypted values
136 return value
136 return value
137 else:
137 else:
138 if parts[0] != 'enc':
138 if parts[0] != 'enc':
139 # parts ok but without our header ?
139 # parts ok but without our header ?
140 return value
140 return value
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
142 'rhodecode.encrypted_values.strict') or True)
142 'rhodecode.encrypted_values.strict') or True)
143 # at that stage we know it's our encryption
143 # at that stage we know it's our encryption
144 if parts[1] == 'aes':
144 if parts[1] == 'aes':
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
146 elif parts[1] == 'aes_hmac':
146 elif parts[1] == 'aes_hmac':
147 decrypted_data = AESCipher(
147 decrypted_data = AESCipher(
148 ENCRYPTION_KEY, hmac=True,
148 ENCRYPTION_KEY, hmac=True,
149 strict_verification=enc_strict_mode).decrypt(parts[2])
149 strict_verification=enc_strict_mode).decrypt(parts[2])
150 else:
150 else:
151 raise ValueError(
151 raise ValueError(
152 'Encryption type part is wrong, must be `aes` '
152 'Encryption type part is wrong, must be `aes` '
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
154 return decrypted_data
154 return decrypted_data
155
155
156
156
157 class BaseModel(object):
157 class BaseModel(object):
158 """
158 """
159 Base Model for all classes
159 Base Model for all classes
160 """
160 """
161
161
162 @classmethod
162 @classmethod
163 def _get_keys(cls):
163 def _get_keys(cls):
164 """return column names for this model """
164 """return column names for this model """
165 return class_mapper(cls).c.keys()
165 return class_mapper(cls).c.keys()
166
166
167 def get_dict(self):
167 def get_dict(self):
168 """
168 """
169 return dict with keys and values corresponding
169 return dict with keys and values corresponding
170 to this model data """
170 to this model data """
171
171
172 d = {}
172 d = {}
173 for k in self._get_keys():
173 for k in self._get_keys():
174 d[k] = getattr(self, k)
174 d[k] = getattr(self, k)
175
175
176 # also use __json__() if present to get additional fields
176 # also use __json__() if present to get additional fields
177 _json_attr = getattr(self, '__json__', None)
177 _json_attr = getattr(self, '__json__', None)
178 if _json_attr:
178 if _json_attr:
179 # update with attributes from __json__
179 # update with attributes from __json__
180 if callable(_json_attr):
180 if callable(_json_attr):
181 _json_attr = _json_attr()
181 _json_attr = _json_attr()
182 for k, val in _json_attr.iteritems():
182 for k, val in _json_attr.iteritems():
183 d[k] = val
183 d[k] = val
184 return d
184 return d
185
185
186 def get_appstruct(self):
186 def get_appstruct(self):
187 """return list with keys and values tuples corresponding
187 """return list with keys and values tuples corresponding
188 to this model data """
188 to this model data """
189
189
190 l = []
190 l = []
191 for k in self._get_keys():
191 for k in self._get_keys():
192 l.append((k, getattr(self, k),))
192 l.append((k, getattr(self, k),))
193 return l
193 return l
194
194
195 def populate_obj(self, populate_dict):
195 def populate_obj(self, populate_dict):
196 """populate model with data from given populate_dict"""
196 """populate model with data from given populate_dict"""
197
197
198 for k in self._get_keys():
198 for k in self._get_keys():
199 if k in populate_dict:
199 if k in populate_dict:
200 setattr(self, k, populate_dict[k])
200 setattr(self, k, populate_dict[k])
201
201
202 @classmethod
202 @classmethod
203 def query(cls):
203 def query(cls):
204 return Session().query(cls)
204 return Session().query(cls)
205
205
206 @classmethod
206 @classmethod
207 def get(cls, id_):
207 def get(cls, id_):
208 if id_:
208 if id_:
209 return cls.query().get(id_)
209 return cls.query().get(id_)
210
210
211 @classmethod
211 @classmethod
212 def get_or_404(cls, id_):
212 def get_or_404(cls, id_):
213 try:
213 try:
214 id_ = int(id_)
214 id_ = int(id_)
215 except (TypeError, ValueError):
215 except (TypeError, ValueError):
216 raise HTTPNotFound
216 raise HTTPNotFound
217
217
218 res = cls.query().get(id_)
218 res = cls.query().get(id_)
219 if not res:
219 if not res:
220 raise HTTPNotFound
220 raise HTTPNotFound
221 return res
221 return res
222
222
223 @classmethod
223 @classmethod
224 def getAll(cls):
224 def getAll(cls):
225 # deprecated and left for backward compatibility
225 # deprecated and left for backward compatibility
226 return cls.get_all()
226 return cls.get_all()
227
227
228 @classmethod
228 @classmethod
229 def get_all(cls):
229 def get_all(cls):
230 return cls.query().all()
230 return cls.query().all()
231
231
232 @classmethod
232 @classmethod
233 def delete(cls, id_):
233 def delete(cls, id_):
234 obj = cls.query().get(id_)
234 obj = cls.query().get(id_)
235 Session().delete(obj)
235 Session().delete(obj)
236
236
237 @classmethod
237 @classmethod
238 def identity_cache(cls, session, attr_name, value):
238 def identity_cache(cls, session, attr_name, value):
239 exist_in_session = []
239 exist_in_session = []
240 for (item_cls, pkey), instance in session.identity_map.items():
240 for (item_cls, pkey), instance in session.identity_map.items():
241 if cls == item_cls and getattr(instance, attr_name) == value:
241 if cls == item_cls and getattr(instance, attr_name) == value:
242 exist_in_session.append(instance)
242 exist_in_session.append(instance)
243 if exist_in_session:
243 if exist_in_session:
244 if len(exist_in_session) == 1:
244 if len(exist_in_session) == 1:
245 return exist_in_session[0]
245 return exist_in_session[0]
246 log.exception(
246 log.exception(
247 'multiple objects with attr %s and '
247 'multiple objects with attr %s and '
248 'value %s found with same name: %r',
248 'value %s found with same name: %r',
249 attr_name, value, exist_in_session)
249 attr_name, value, exist_in_session)
250
250
251 def __repr__(self):
251 def __repr__(self):
252 if hasattr(self, '__unicode__'):
252 if hasattr(self, '__unicode__'):
253 # python repr needs to return str
253 # python repr needs to return str
254 try:
254 try:
255 return safe_str(self.__unicode__())
255 return safe_str(self.__unicode__())
256 except UnicodeDecodeError:
256 except UnicodeDecodeError:
257 pass
257 pass
258 return '<DB:%s>' % (self.__class__.__name__)
258 return '<DB:%s>' % (self.__class__.__name__)
259
259
260
260
261 class RhodeCodeSetting(Base, BaseModel):
261 class RhodeCodeSetting(Base, BaseModel):
262 __tablename__ = 'rhodecode_settings'
262 __tablename__ = 'rhodecode_settings'
263 __table_args__ = (
263 __table_args__ = (
264 UniqueConstraint('app_settings_name'),
264 UniqueConstraint('app_settings_name'),
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
267 )
267 )
268
268
269 SETTINGS_TYPES = {
269 SETTINGS_TYPES = {
270 'str': safe_str,
270 'str': safe_str,
271 'int': safe_int,
271 'int': safe_int,
272 'unicode': safe_unicode,
272 'unicode': safe_unicode,
273 'bool': str2bool,
273 'bool': str2bool,
274 'list': functools.partial(aslist, sep=',')
274 'list': functools.partial(aslist, sep=',')
275 }
275 }
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
277 GLOBAL_CONF_KEY = 'app_settings'
277 GLOBAL_CONF_KEY = 'app_settings'
278
278
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
283
283
284 def __init__(self, key='', val='', type='unicode'):
284 def __init__(self, key='', val='', type='unicode'):
285 self.app_settings_name = key
285 self.app_settings_name = key
286 self.app_settings_type = type
286 self.app_settings_type = type
287 self.app_settings_value = val
287 self.app_settings_value = val
288
288
289 @validates('_app_settings_value')
289 @validates('_app_settings_value')
290 def validate_settings_value(self, key, val):
290 def validate_settings_value(self, key, val):
291 assert type(val) == unicode
291 assert type(val) == unicode
292 return val
292 return val
293
293
294 @hybrid_property
294 @hybrid_property
295 def app_settings_value(self):
295 def app_settings_value(self):
296 v = self._app_settings_value
296 v = self._app_settings_value
297 _type = self.app_settings_type
297 _type = self.app_settings_type
298 if _type:
298 if _type:
299 _type = self.app_settings_type.split('.')[0]
299 _type = self.app_settings_type.split('.')[0]
300 # decode the encrypted value
300 # decode the encrypted value
301 if 'encrypted' in self.app_settings_type:
301 if 'encrypted' in self.app_settings_type:
302 cipher = EncryptedTextValue()
302 cipher = EncryptedTextValue()
303 v = safe_unicode(cipher.process_result_value(v, None))
303 v = safe_unicode(cipher.process_result_value(v, None))
304
304
305 converter = self.SETTINGS_TYPES.get(_type) or \
305 converter = self.SETTINGS_TYPES.get(_type) or \
306 self.SETTINGS_TYPES['unicode']
306 self.SETTINGS_TYPES['unicode']
307 return converter(v)
307 return converter(v)
308
308
309 @app_settings_value.setter
309 @app_settings_value.setter
310 def app_settings_value(self, val):
310 def app_settings_value(self, val):
311 """
311 """
312 Setter that will always make sure we use unicode in app_settings_value
312 Setter that will always make sure we use unicode in app_settings_value
313
313
314 :param val:
314 :param val:
315 """
315 """
316 val = safe_unicode(val)
316 val = safe_unicode(val)
317 # encode the encrypted value
317 # encode the encrypted value
318 if 'encrypted' in self.app_settings_type:
318 if 'encrypted' in self.app_settings_type:
319 cipher = EncryptedTextValue()
319 cipher = EncryptedTextValue()
320 val = safe_unicode(cipher.process_bind_param(val, None))
320 val = safe_unicode(cipher.process_bind_param(val, None))
321 self._app_settings_value = val
321 self._app_settings_value = val
322
322
323 @hybrid_property
323 @hybrid_property
324 def app_settings_type(self):
324 def app_settings_type(self):
325 return self._app_settings_type
325 return self._app_settings_type
326
326
327 @app_settings_type.setter
327 @app_settings_type.setter
328 def app_settings_type(self, val):
328 def app_settings_type(self, val):
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
330 raise Exception('type must be one of %s got %s'
330 raise Exception('type must be one of %s got %s'
331 % (self.SETTINGS_TYPES.keys(), val))
331 % (self.SETTINGS_TYPES.keys(), val))
332 self._app_settings_type = val
332 self._app_settings_type = val
333
333
334 def __unicode__(self):
334 def __unicode__(self):
335 return u"<%s('%s:%s[%s]')>" % (
335 return u"<%s('%s:%s[%s]')>" % (
336 self.__class__.__name__,
336 self.__class__.__name__,
337 self.app_settings_name, self.app_settings_value,
337 self.app_settings_name, self.app_settings_value,
338 self.app_settings_type
338 self.app_settings_type
339 )
339 )
340
340
341
341
342 class RhodeCodeUi(Base, BaseModel):
342 class RhodeCodeUi(Base, BaseModel):
343 __tablename__ = 'rhodecode_ui'
343 __tablename__ = 'rhodecode_ui'
344 __table_args__ = (
344 __table_args__ = (
345 UniqueConstraint('ui_key'),
345 UniqueConstraint('ui_key'),
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
348 )
348 )
349
349
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
351 # HG
351 # HG
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
353 HOOK_PULL = 'outgoing.pull_logger'
353 HOOK_PULL = 'outgoing.pull_logger'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
355 HOOK_PUSH = 'changegroup.push_logger'
355 HOOK_PUSH = 'changegroup.push_logger'
356
356
357 # TODO: johbo: Unify way how hooks are configured for git and hg,
357 # TODO: johbo: Unify way how hooks are configured for git and hg,
358 # git part is currently hardcoded.
358 # git part is currently hardcoded.
359
359
360 # SVN PATTERNS
360 # SVN PATTERNS
361 SVN_BRANCH_ID = 'vcs_svn_branch'
361 SVN_BRANCH_ID = 'vcs_svn_branch'
362 SVN_TAG_ID = 'vcs_svn_tag'
362 SVN_TAG_ID = 'vcs_svn_tag'
363
363
364 ui_id = Column(
364 ui_id = Column(
365 "ui_id", Integer(), nullable=False, unique=True, default=None,
365 "ui_id", Integer(), nullable=False, unique=True, default=None,
366 primary_key=True)
366 primary_key=True)
367 ui_section = Column(
367 ui_section = Column(
368 "ui_section", String(255), nullable=True, unique=None, default=None)
368 "ui_section", String(255), nullable=True, unique=None, default=None)
369 ui_key = Column(
369 ui_key = Column(
370 "ui_key", String(255), nullable=True, unique=None, default=None)
370 "ui_key", String(255), nullable=True, unique=None, default=None)
371 ui_value = Column(
371 ui_value = Column(
372 "ui_value", String(255), nullable=True, unique=None, default=None)
372 "ui_value", String(255), nullable=True, unique=None, default=None)
373 ui_active = Column(
373 ui_active = Column(
374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
375
375
376 def __repr__(self):
376 def __repr__(self):
377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
378 self.ui_key, self.ui_value)
378 self.ui_key, self.ui_value)
379
379
380
380
381 class RepoRhodeCodeSetting(Base, BaseModel):
381 class RepoRhodeCodeSetting(Base, BaseModel):
382 __tablename__ = 'repo_rhodecode_settings'
382 __tablename__ = 'repo_rhodecode_settings'
383 __table_args__ = (
383 __table_args__ = (
384 UniqueConstraint(
384 UniqueConstraint(
385 'app_settings_name', 'repository_id',
385 'app_settings_name', 'repository_id',
386 name='uq_repo_rhodecode_setting_name_repo_id'),
386 name='uq_repo_rhodecode_setting_name_repo_id'),
387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
389 )
389 )
390
390
391 repository_id = Column(
391 repository_id = Column(
392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
393 nullable=False)
393 nullable=False)
394 app_settings_id = Column(
394 app_settings_id = Column(
395 "app_settings_id", Integer(), nullable=False, unique=True,
395 "app_settings_id", Integer(), nullable=False, unique=True,
396 default=None, primary_key=True)
396 default=None, primary_key=True)
397 app_settings_name = Column(
397 app_settings_name = Column(
398 "app_settings_name", String(255), nullable=True, unique=None,
398 "app_settings_name", String(255), nullable=True, unique=None,
399 default=None)
399 default=None)
400 _app_settings_value = Column(
400 _app_settings_value = Column(
401 "app_settings_value", String(4096), nullable=True, unique=None,
401 "app_settings_value", String(4096), nullable=True, unique=None,
402 default=None)
402 default=None)
403 _app_settings_type = Column(
403 _app_settings_type = Column(
404 "app_settings_type", String(255), nullable=True, unique=None,
404 "app_settings_type", String(255), nullable=True, unique=None,
405 default=None)
405 default=None)
406
406
407 repository = relationship('Repository')
407 repository = relationship('Repository')
408
408
409 def __init__(self, repository_id, key='', val='', type='unicode'):
409 def __init__(self, repository_id, key='', val='', type='unicode'):
410 self.repository_id = repository_id
410 self.repository_id = repository_id
411 self.app_settings_name = key
411 self.app_settings_name = key
412 self.app_settings_type = type
412 self.app_settings_type = type
413 self.app_settings_value = val
413 self.app_settings_value = val
414
414
415 @validates('_app_settings_value')
415 @validates('_app_settings_value')
416 def validate_settings_value(self, key, val):
416 def validate_settings_value(self, key, val):
417 assert type(val) == unicode
417 assert type(val) == unicode
418 return val
418 return val
419
419
420 @hybrid_property
420 @hybrid_property
421 def app_settings_value(self):
421 def app_settings_value(self):
422 v = self._app_settings_value
422 v = self._app_settings_value
423 type_ = self.app_settings_type
423 type_ = self.app_settings_type
424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
426 return converter(v)
426 return converter(v)
427
427
428 @app_settings_value.setter
428 @app_settings_value.setter
429 def app_settings_value(self, val):
429 def app_settings_value(self, val):
430 """
430 """
431 Setter that will always make sure we use unicode in app_settings_value
431 Setter that will always make sure we use unicode in app_settings_value
432
432
433 :param val:
433 :param val:
434 """
434 """
435 self._app_settings_value = safe_unicode(val)
435 self._app_settings_value = safe_unicode(val)
436
436
437 @hybrid_property
437 @hybrid_property
438 def app_settings_type(self):
438 def app_settings_type(self):
439 return self._app_settings_type
439 return self._app_settings_type
440
440
441 @app_settings_type.setter
441 @app_settings_type.setter
442 def app_settings_type(self, val):
442 def app_settings_type(self, val):
443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
444 if val not in SETTINGS_TYPES:
444 if val not in SETTINGS_TYPES:
445 raise Exception('type must be one of %s got %s'
445 raise Exception('type must be one of %s got %s'
446 % (SETTINGS_TYPES.keys(), val))
446 % (SETTINGS_TYPES.keys(), val))
447 self._app_settings_type = val
447 self._app_settings_type = val
448
448
449 def __unicode__(self):
449 def __unicode__(self):
450 return u"<%s('%s:%s:%s[%s]')>" % (
450 return u"<%s('%s:%s:%s[%s]')>" % (
451 self.__class__.__name__, self.repository.repo_name,
451 self.__class__.__name__, self.repository.repo_name,
452 self.app_settings_name, self.app_settings_value,
452 self.app_settings_name, self.app_settings_value,
453 self.app_settings_type
453 self.app_settings_type
454 )
454 )
455
455
456
456
457 class RepoRhodeCodeUi(Base, BaseModel):
457 class RepoRhodeCodeUi(Base, BaseModel):
458 __tablename__ = 'repo_rhodecode_ui'
458 __tablename__ = 'repo_rhodecode_ui'
459 __table_args__ = (
459 __table_args__ = (
460 UniqueConstraint(
460 UniqueConstraint(
461 'repository_id', 'ui_section', 'ui_key',
461 'repository_id', 'ui_section', 'ui_key',
462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
465 )
465 )
466
466
467 repository_id = Column(
467 repository_id = Column(
468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
469 nullable=False)
469 nullable=False)
470 ui_id = Column(
470 ui_id = Column(
471 "ui_id", Integer(), nullable=False, unique=True, default=None,
471 "ui_id", Integer(), nullable=False, unique=True, default=None,
472 primary_key=True)
472 primary_key=True)
473 ui_section = Column(
473 ui_section = Column(
474 "ui_section", String(255), nullable=True, unique=None, default=None)
474 "ui_section", String(255), nullable=True, unique=None, default=None)
475 ui_key = Column(
475 ui_key = Column(
476 "ui_key", String(255), nullable=True, unique=None, default=None)
476 "ui_key", String(255), nullable=True, unique=None, default=None)
477 ui_value = Column(
477 ui_value = Column(
478 "ui_value", String(255), nullable=True, unique=None, default=None)
478 "ui_value", String(255), nullable=True, unique=None, default=None)
479 ui_active = Column(
479 ui_active = Column(
480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
481
481
482 repository = relationship('Repository')
482 repository = relationship('Repository')
483
483
484 def __repr__(self):
484 def __repr__(self):
485 return '<%s[%s:%s]%s=>%s]>' % (
485 return '<%s[%s:%s]%s=>%s]>' % (
486 self.__class__.__name__, self.repository.repo_name,
486 self.__class__.__name__, self.repository.repo_name,
487 self.ui_section, self.ui_key, self.ui_value)
487 self.ui_section, self.ui_key, self.ui_value)
488
488
489
489
490 class User(Base, BaseModel):
490 class User(Base, BaseModel):
491 __tablename__ = 'users'
491 __tablename__ = 'users'
492 __table_args__ = (
492 __table_args__ = (
493 UniqueConstraint('username'), UniqueConstraint('email'),
493 UniqueConstraint('username'), UniqueConstraint('email'),
494 Index('u_username_idx', 'username'),
494 Index('u_username_idx', 'username'),
495 Index('u_email_idx', 'email'),
495 Index('u_email_idx', 'email'),
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
498 )
498 )
499 DEFAULT_USER = 'default'
499 DEFAULT_USER = 'default'
500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
502
502
503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
504 username = Column("username", String(255), nullable=True, unique=None, default=None)
504 username = Column("username", String(255), nullable=True, unique=None, default=None)
505 password = Column("password", String(255), nullable=True, unique=None, default=None)
505 password = Column("password", String(255), nullable=True, unique=None, default=None)
506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
518
518
519 user_log = relationship('UserLog')
519 user_log = relationship('UserLog')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
521
521
522 repositories = relationship('Repository')
522 repositories = relationship('Repository')
523 repository_groups = relationship('RepoGroup')
523 repository_groups = relationship('RepoGroup')
524 user_groups = relationship('UserGroup')
524 user_groups = relationship('UserGroup')
525
525
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
528
528
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
532
532
533 group_member = relationship('UserGroupMember', cascade='all')
533 group_member = relationship('UserGroupMember', cascade='all')
534
534
535 notifications = relationship('UserNotification', cascade='all')
535 notifications = relationship('UserNotification', cascade='all')
536 # notifications assigned to this user
536 # notifications assigned to this user
537 user_created_notifications = relationship('Notification', cascade='all')
537 user_created_notifications = relationship('Notification', cascade='all')
538 # comments created by this user
538 # comments created by this user
539 user_comments = relationship('ChangesetComment', cascade='all')
539 user_comments = relationship('ChangesetComment', cascade='all')
540 # user profile extra info
540 # user profile extra info
541 user_emails = relationship('UserEmailMap', cascade='all')
541 user_emails = relationship('UserEmailMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
544 # gists
544 # gists
545 user_gists = relationship('Gist', cascade='all')
545 user_gists = relationship('Gist', cascade='all')
546 # user pull requests
546 # user pull requests
547 user_pull_requests = relationship('PullRequest', cascade='all')
547 user_pull_requests = relationship('PullRequest', cascade='all')
548 # external identities
548 # external identities
549 extenal_identities = relationship(
549 extenal_identities = relationship(
550 'ExternalIdentity',
550 'ExternalIdentity',
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
552 cascade='all')
552 cascade='all')
553
553
554 def __unicode__(self):
554 def __unicode__(self):
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
556 self.user_id, self.username)
556 self.user_id, self.username)
557
557
558 @hybrid_property
558 @hybrid_property
559 def email(self):
559 def email(self):
560 return self._email
560 return self._email
561
561
562 @email.setter
562 @email.setter
563 def email(self, val):
563 def email(self, val):
564 self._email = val.lower() if val else None
564 self._email = val.lower() if val else None
565
565
566 @property
566 @property
567 def firstname(self):
567 def firstname(self):
568 # alias for future
568 # alias for future
569 return self.name
569 return self.name
570
570
571 @property
571 @property
572 def emails(self):
572 def emails(self):
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
574 return [self.email] + [x.email for x in other]
574 return [self.email] + [x.email for x in other]
575
575
576 @property
576 @property
577 def auth_tokens(self):
577 def auth_tokens(self):
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
579
579
580 @property
580 @property
581 def extra_auth_tokens(self):
581 def extra_auth_tokens(self):
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
583
583
584 @property
584 @property
585 def feed_token(self):
585 def feed_token(self):
586 feed_tokens = UserApiKeys.query()\
586 feed_tokens = UserApiKeys.query()\
587 .filter(UserApiKeys.user == self)\
587 .filter(UserApiKeys.user == self)\
588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
589 .all()
589 .all()
590 if feed_tokens:
590 if feed_tokens:
591 return feed_tokens[0].api_key
591 return feed_tokens[0].api_key
592 else:
592 else:
593 # use the main token so we don't end up with nothing...
593 # use the main token so we don't end up with nothing...
594 return self.api_key
594 return self.api_key
595
595
596 @classmethod
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
600 UserApiKeys.expires >= time.time()))
601 if role:
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
604 return tokens.all()
605
605
606 @property
606 @property
607 def ip_addresses(self):
607 def ip_addresses(self):
608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
609 return [x.ip_addr for x in ret]
609 return [x.ip_addr for x in ret]
610
610
611 @property
611 @property
612 def username_and_name(self):
612 def username_and_name(self):
613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
614
614
615 @property
615 @property
616 def username_or_name_or_email(self):
616 def username_or_name_or_email(self):
617 full_name = self.full_name if self.full_name is not ' ' else None
617 full_name = self.full_name if self.full_name is not ' ' else None
618 return self.username or full_name or self.email
618 return self.username or full_name or self.email
619
619
620 @property
620 @property
621 def full_name(self):
621 def full_name(self):
622 return '%s %s' % (self.firstname, self.lastname)
622 return '%s %s' % (self.firstname, self.lastname)
623
623
624 @property
624 @property
625 def full_name_or_username(self):
625 def full_name_or_username(self):
626 return ('%s %s' % (self.firstname, self.lastname)
626 return ('%s %s' % (self.firstname, self.lastname)
627 if (self.firstname and self.lastname) else self.username)
627 if (self.firstname and self.lastname) else self.username)
628
628
629 @property
629 @property
630 def full_contact(self):
630 def full_contact(self):
631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
632
632
633 @property
633 @property
634 def short_contact(self):
634 def short_contact(self):
635 return '%s %s' % (self.firstname, self.lastname)
635 return '%s %s' % (self.firstname, self.lastname)
636
636
637 @property
637 @property
638 def is_admin(self):
638 def is_admin(self):
639 return self.admin
639 return self.admin
640
640
641 @property
641 @property
642 def AuthUser(self):
642 def AuthUser(self):
643 """
643 """
644 Returns instance of AuthUser for this user
644 Returns instance of AuthUser for this user
645 """
645 """
646 from rhodecode.lib.auth import AuthUser
646 from rhodecode.lib.auth import AuthUser
647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
648 username=self.username)
648 username=self.username)
649
649
650 @hybrid_property
650 @hybrid_property
651 def user_data(self):
651 def user_data(self):
652 if not self._user_data:
652 if not self._user_data:
653 return {}
653 return {}
654
654
655 try:
655 try:
656 return json.loads(self._user_data)
656 return json.loads(self._user_data)
657 except TypeError:
657 except TypeError:
658 return {}
658 return {}
659
659
660 @user_data.setter
660 @user_data.setter
661 def user_data(self, val):
661 def user_data(self, val):
662 if not isinstance(val, dict):
662 if not isinstance(val, dict):
663 raise Exception('user_data must be dict, got %s' % type(val))
663 raise Exception('user_data must be dict, got %s' % type(val))
664 try:
664 try:
665 self._user_data = json.dumps(val)
665 self._user_data = json.dumps(val)
666 except Exception:
666 except Exception:
667 log.error(traceback.format_exc())
667 log.error(traceback.format_exc())
668
668
669 @classmethod
669 @classmethod
670 def get_by_username(cls, username, case_insensitive=False,
670 def get_by_username(cls, username, case_insensitive=False,
671 cache=False, identity_cache=False):
671 cache=False, identity_cache=False):
672 session = Session()
672 session = Session()
673
673
674 if case_insensitive:
674 if case_insensitive:
675 q = cls.query().filter(
675 q = cls.query().filter(
676 func.lower(cls.username) == func.lower(username))
676 func.lower(cls.username) == func.lower(username))
677 else:
677 else:
678 q = cls.query().filter(cls.username == username)
678 q = cls.query().filter(cls.username == username)
679
679
680 if cache:
680 if cache:
681 if identity_cache:
681 if identity_cache:
682 val = cls.identity_cache(session, 'username', username)
682 val = cls.identity_cache(session, 'username', username)
683 if val:
683 if val:
684 return val
684 return val
685 else:
685 else:
686 q = q.options(
686 q = q.options(
687 FromCache("sql_cache_short",
687 FromCache("sql_cache_short",
688 "get_user_by_name_%s" % _hash_key(username)))
688 "get_user_by_name_%s" % _hash_key(username)))
689
689
690 return q.scalar()
690 return q.scalar()
691
691
692 @classmethod
692 @classmethod
693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
694 q = cls.query().filter(cls.api_key == auth_token)
694 q = cls.query().filter(cls.api_key == auth_token)
695
695
696 if cache:
696 if cache:
697 q = q.options(FromCache("sql_cache_short",
697 q = q.options(FromCache("sql_cache_short",
698 "get_auth_token_%s" % auth_token))
698 "get_auth_token_%s" % auth_token))
699 res = q.scalar()
699 res = q.scalar()
700
700
701 if fallback and not res:
701 if fallback and not res:
702 #fallback to additional keys
702 #fallback to additional keys
703 _res = UserApiKeys.query()\
703 _res = UserApiKeys.query()\
704 .filter(UserApiKeys.api_key == auth_token)\
704 .filter(UserApiKeys.api_key == auth_token)\
705 .filter(or_(UserApiKeys.expires == -1,
705 .filter(or_(UserApiKeys.expires == -1,
706 UserApiKeys.expires >= time.time()))\
706 UserApiKeys.expires >= time.time()))\
707 .first()
707 .first()
708 if _res:
708 if _res:
709 res = _res.user
709 res = _res.user
710 return res
710 return res
711
711
712 @classmethod
712 @classmethod
713 def get_by_email(cls, email, case_insensitive=False, cache=False):
713 def get_by_email(cls, email, case_insensitive=False, cache=False):
714
714
715 if case_insensitive:
715 if case_insensitive:
716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
717
717
718 else:
718 else:
719 q = cls.query().filter(cls.email == email)
719 q = cls.query().filter(cls.email == email)
720
720
721 if cache:
721 if cache:
722 q = q.options(FromCache("sql_cache_short",
722 q = q.options(FromCache("sql_cache_short",
723 "get_email_key_%s" % email))
723 "get_email_key_%s" % email))
724
724
725 ret = q.scalar()
725 ret = q.scalar()
726 if ret is None:
726 if ret is None:
727 q = UserEmailMap.query()
727 q = UserEmailMap.query()
728 # try fetching in alternate email map
728 # try fetching in alternate email map
729 if case_insensitive:
729 if case_insensitive:
730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
731 else:
731 else:
732 q = q.filter(UserEmailMap.email == email)
732 q = q.filter(UserEmailMap.email == email)
733 q = q.options(joinedload(UserEmailMap.user))
733 q = q.options(joinedload(UserEmailMap.user))
734 if cache:
734 if cache:
735 q = q.options(FromCache("sql_cache_short",
735 q = q.options(FromCache("sql_cache_short",
736 "get_email_map_key_%s" % email))
736 "get_email_map_key_%s" % email))
737 ret = getattr(q.scalar(), 'user', None)
737 ret = getattr(q.scalar(), 'user', None)
738
738
739 return ret
739 return ret
740
740
741 @classmethod
741 @classmethod
742 def get_from_cs_author(cls, author):
742 def get_from_cs_author(cls, author):
743 """
743 """
744 Tries to get User objects out of commit author string
744 Tries to get User objects out of commit author string
745
745
746 :param author:
746 :param author:
747 """
747 """
748 from rhodecode.lib.helpers import email, author_name
748 from rhodecode.lib.helpers import email, author_name
749 # Valid email in the attribute passed, see if they're in the system
749 # Valid email in the attribute passed, see if they're in the system
750 _email = email(author)
750 _email = email(author)
751 if _email:
751 if _email:
752 user = cls.get_by_email(_email, case_insensitive=True)
752 user = cls.get_by_email(_email, case_insensitive=True)
753 if user:
753 if user:
754 return user
754 return user
755 # Maybe we can match by username?
755 # Maybe we can match by username?
756 _author = author_name(author)
756 _author = author_name(author)
757 user = cls.get_by_username(_author, case_insensitive=True)
757 user = cls.get_by_username(_author, case_insensitive=True)
758 if user:
758 if user:
759 return user
759 return user
760
760
761 def update_userdata(self, **kwargs):
761 def update_userdata(self, **kwargs):
762 usr = self
762 usr = self
763 old = usr.user_data
763 old = usr.user_data
764 old.update(**kwargs)
764 old.update(**kwargs)
765 usr.user_data = old
765 usr.user_data = old
766 Session().add(usr)
766 Session().add(usr)
767 log.debug('updated userdata with ', kwargs)
767 log.debug('updated userdata with ', kwargs)
768
768
769 def update_lastlogin(self):
769 def update_lastlogin(self):
770 """Update user lastlogin"""
770 """Update user lastlogin"""
771 self.last_login = datetime.datetime.now()
771 self.last_login = datetime.datetime.now()
772 Session().add(self)
772 Session().add(self)
773 log.debug('updated user %s lastlogin', self.username)
773 log.debug('updated user %s lastlogin', self.username)
774
774
775 def update_lastactivity(self):
775 def update_lastactivity(self):
776 """Update user lastactivity"""
776 """Update user lastactivity"""
777 usr = self
777 usr = self
778 old = usr.user_data
778 old = usr.user_data
779 old.update({'last_activity': time.time()})
779 old.update({'last_activity': time.time()})
780 usr.user_data = old
780 usr.user_data = old
781 Session().add(usr)
781 Session().add(usr)
782 log.debug('updated user %s lastactivity', usr.username)
782 log.debug('updated user %s lastactivity', usr.username)
783
783
784 def update_password(self, new_password, change_api_key=False):
784 def update_password(self, new_password, change_api_key=False):
785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
786
786
787 self.password = get_crypt_password(new_password)
787 self.password = get_crypt_password(new_password)
788 if change_api_key:
788 if change_api_key:
789 self.api_key = generate_auth_token(self.username)
789 self.api_key = generate_auth_token(self.username)
790 Session().add(self)
790 Session().add(self)
791
791
792 @classmethod
792 @classmethod
793 def get_first_super_admin(cls):
793 def get_first_super_admin(cls):
794 user = User.query().filter(User.admin == true()).first()
794 user = User.query().filter(User.admin == true()).first()
795 if user is None:
795 if user is None:
796 raise Exception('FATAL: Missing administrative account!')
796 raise Exception('FATAL: Missing administrative account!')
797 return user
797 return user
798
798
799 @classmethod
799 @classmethod
800 def get_all_super_admins(cls):
800 def get_all_super_admins(cls):
801 """
801 """
802 Returns all admin accounts sorted by username
802 Returns all admin accounts sorted by username
803 """
803 """
804 return User.query().filter(User.admin == true())\
804 return User.query().filter(User.admin == true())\
805 .order_by(User.username.asc()).all()
805 .order_by(User.username.asc()).all()
806
806
807 @classmethod
807 @classmethod
808 def get_default_user(cls, cache=False):
808 def get_default_user(cls, cache=False):
809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
810 if user is None:
810 if user is None:
811 raise Exception('FATAL: Missing default account!')
811 raise Exception('FATAL: Missing default account!')
812 return user
812 return user
813
813
814 def _get_default_perms(self, user, suffix=''):
814 def _get_default_perms(self, user, suffix=''):
815 from rhodecode.model.permission import PermissionModel
815 from rhodecode.model.permission import PermissionModel
816 return PermissionModel().get_default_perms(user.user_perms, suffix)
816 return PermissionModel().get_default_perms(user.user_perms, suffix)
817
817
818 def get_default_perms(self, suffix=''):
818 def get_default_perms(self, suffix=''):
819 return self._get_default_perms(self, suffix)
819 return self._get_default_perms(self, suffix)
820
820
821 def get_api_data(self, include_secrets=False, details='full'):
821 def get_api_data(self, include_secrets=False, details='full'):
822 """
822 """
823 Common function for generating user related data for API
823 Common function for generating user related data for API
824
824
825 :param include_secrets: By default secrets in the API data will be replaced
825 :param include_secrets: By default secrets in the API data will be replaced
826 by a placeholder value to prevent exposing this data by accident. In case
826 by a placeholder value to prevent exposing this data by accident. In case
827 this data shall be exposed, set this flag to ``True``.
827 this data shall be exposed, set this flag to ``True``.
828
828
829 :param details: details can be 'basic|full' basic gives only a subset of
829 :param details: details can be 'basic|full' basic gives only a subset of
830 the available user information that includes user_id, name and emails.
830 the available user information that includes user_id, name and emails.
831 """
831 """
832 user = self
832 user = self
833 user_data = self.user_data
833 user_data = self.user_data
834 data = {
834 data = {
835 'user_id': user.user_id,
835 'user_id': user.user_id,
836 'username': user.username,
836 'username': user.username,
837 'firstname': user.name,
837 'firstname': user.name,
838 'lastname': user.lastname,
838 'lastname': user.lastname,
839 'email': user.email,
839 'email': user.email,
840 'emails': user.emails,
840 'emails': user.emails,
841 }
841 }
842 if details == 'basic':
842 if details == 'basic':
843 return data
843 return data
844
844
845 api_key_length = 40
845 api_key_length = 40
846 api_key_replacement = '*' * api_key_length
846 api_key_replacement = '*' * api_key_length
847
847
848 extras = {
848 extras = {
849 'api_key': api_key_replacement,
849 'api_key': api_key_replacement,
850 'api_keys': [api_key_replacement],
850 'api_keys': [api_key_replacement],
851 'active': user.active,
851 'active': user.active,
852 'admin': user.admin,
852 'admin': user.admin,
853 'extern_type': user.extern_type,
853 'extern_type': user.extern_type,
854 'extern_name': user.extern_name,
854 'extern_name': user.extern_name,
855 'last_login': user.last_login,
855 'last_login': user.last_login,
856 'ip_addresses': user.ip_addresses,
856 'ip_addresses': user.ip_addresses,
857 'language': user_data.get('language')
857 'language': user_data.get('language')
858 }
858 }
859 data.update(extras)
859 data.update(extras)
860
860
861 if include_secrets:
861 if include_secrets:
862 data['api_key'] = user.api_key
862 data['api_key'] = user.api_key
863 data['api_keys'] = user.auth_tokens
863 data['api_keys'] = user.auth_tokens
864 return data
864 return data
865
865
866 def __json__(self):
866 def __json__(self):
867 data = {
867 data = {
868 'full_name': self.full_name,
868 'full_name': self.full_name,
869 'full_name_or_username': self.full_name_or_username,
869 'full_name_or_username': self.full_name_or_username,
870 'short_contact': self.short_contact,
870 'short_contact': self.short_contact,
871 'full_contact': self.full_contact,
871 'full_contact': self.full_contact,
872 }
872 }
873 data.update(self.get_api_data())
873 data.update(self.get_api_data())
874 return data
874 return data
875
875
876
876
877 class UserApiKeys(Base, BaseModel):
877 class UserApiKeys(Base, BaseModel):
878 __tablename__ = 'user_api_keys'
878 __tablename__ = 'user_api_keys'
879 __table_args__ = (
879 __table_args__ = (
880 Index('uak_api_key_idx', 'api_key'),
880 Index('uak_api_key_idx', 'api_key'),
881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
882 UniqueConstraint('api_key'),
882 UniqueConstraint('api_key'),
883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
885 )
885 )
886 __mapper_args__ = {}
886 __mapper_args__ = {}
887
887
888 # ApiKey role
888 # ApiKey role
889 ROLE_ALL = 'token_role_all'
889 ROLE_ALL = 'token_role_all'
890 ROLE_HTTP = 'token_role_http'
890 ROLE_HTTP = 'token_role_http'
891 ROLE_VCS = 'token_role_vcs'
891 ROLE_VCS = 'token_role_vcs'
892 ROLE_API = 'token_role_api'
892 ROLE_API = 'token_role_api'
893 ROLE_FEED = 'token_role_feed'
893 ROLE_FEED = 'token_role_feed'
894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
895
895
896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
898 api_key = Column("api_key", String(255), nullable=False, unique=True)
898 api_key = Column("api_key", String(255), nullable=False, unique=True)
899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
900 expires = Column('expires', Float(53), nullable=False)
900 expires = Column('expires', Float(53), nullable=False)
901 role = Column('role', String(255), nullable=True)
901 role = Column('role', String(255), nullable=True)
902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
903
903
904 user = relationship('User', lazy='joined')
904 user = relationship('User', lazy='joined')
905
905
906 @classmethod
906 @classmethod
907 def _get_role_name(cls, role):
907 def _get_role_name(cls, role):
908 return {
908 return {
909 cls.ROLE_ALL: _('all'),
909 cls.ROLE_ALL: _('all'),
910 cls.ROLE_HTTP: _('http/web interface'),
910 cls.ROLE_HTTP: _('http/web interface'),
911 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
911 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
912 cls.ROLE_API: _('api calls'),
912 cls.ROLE_API: _('api calls'),
913 cls.ROLE_FEED: _('feed access'),
913 cls.ROLE_FEED: _('feed access'),
914 }.get(role, role)
914 }.get(role, role)
915
915
916 @property
916 @property
917 def expired(self):
917 def expired(self):
918 if self.expires == -1:
918 if self.expires == -1:
919 return False
919 return False
920 return time.time() > self.expires
920 return time.time() > self.expires
921
921
922 @property
922 @property
923 def role_humanized(self):
923 def role_humanized(self):
924 return self._get_role_name(self.role)
924 return self._get_role_name(self.role)
925
925
926
926
927 class UserEmailMap(Base, BaseModel):
927 class UserEmailMap(Base, BaseModel):
928 __tablename__ = 'user_email_map'
928 __tablename__ = 'user_email_map'
929 __table_args__ = (
929 __table_args__ = (
930 Index('uem_email_idx', 'email'),
930 Index('uem_email_idx', 'email'),
931 UniqueConstraint('email'),
931 UniqueConstraint('email'),
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
934 )
934 )
935 __mapper_args__ = {}
935 __mapper_args__ = {}
936
936
937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
940 user = relationship('User', lazy='joined')
940 user = relationship('User', lazy='joined')
941
941
942 @validates('_email')
942 @validates('_email')
943 def validate_email(self, key, email):
943 def validate_email(self, key, email):
944 # check if this email is not main one
944 # check if this email is not main one
945 main_email = Session().query(User).filter(User.email == email).scalar()
945 main_email = Session().query(User).filter(User.email == email).scalar()
946 if main_email is not None:
946 if main_email is not None:
947 raise AttributeError('email %s is present is user table' % email)
947 raise AttributeError('email %s is present is user table' % email)
948 return email
948 return email
949
949
950 @hybrid_property
950 @hybrid_property
951 def email(self):
951 def email(self):
952 return self._email
952 return self._email
953
953
954 @email.setter
954 @email.setter
955 def email(self, val):
955 def email(self, val):
956 self._email = val.lower() if val else None
956 self._email = val.lower() if val else None
957
957
958
958
959 class UserIpMap(Base, BaseModel):
959 class UserIpMap(Base, BaseModel):
960 __tablename__ = 'user_ip_map'
960 __tablename__ = 'user_ip_map'
961 __table_args__ = (
961 __table_args__ = (
962 UniqueConstraint('user_id', 'ip_addr'),
962 UniqueConstraint('user_id', 'ip_addr'),
963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
965 )
965 )
966 __mapper_args__ = {}
966 __mapper_args__ = {}
967
967
968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
973 user = relationship('User', lazy='joined')
973 user = relationship('User', lazy='joined')
974
974
975 @classmethod
975 @classmethod
976 def _get_ip_range(cls, ip_addr):
976 def _get_ip_range(cls, ip_addr):
977 net = ipaddress.ip_network(ip_addr, strict=False)
977 net = ipaddress.ip_network(ip_addr, strict=False)
978 return [str(net.network_address), str(net.broadcast_address)]
978 return [str(net.network_address), str(net.broadcast_address)]
979
979
980 def __json__(self):
980 def __json__(self):
981 return {
981 return {
982 'ip_addr': self.ip_addr,
982 'ip_addr': self.ip_addr,
983 'ip_range': self._get_ip_range(self.ip_addr),
983 'ip_range': self._get_ip_range(self.ip_addr),
984 }
984 }
985
985
986 def __unicode__(self):
986 def __unicode__(self):
987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
988 self.user_id, self.ip_addr)
988 self.user_id, self.ip_addr)
989
989
990 class UserLog(Base, BaseModel):
990 class UserLog(Base, BaseModel):
991 __tablename__ = 'user_logs'
991 __tablename__ = 'user_logs'
992 __table_args__ = (
992 __table_args__ = (
993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
995 )
995 )
996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
998 username = Column("username", String(255), nullable=True, unique=None, default=None)
998 username = Column("username", String(255), nullable=True, unique=None, default=None)
999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1004
1004
1005 def __unicode__(self):
1005 def __unicode__(self):
1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1007 self.repository_name,
1007 self.repository_name,
1008 self.action)
1008 self.action)
1009
1009
1010 @property
1010 @property
1011 def action_as_day(self):
1011 def action_as_day(self):
1012 return datetime.date(*self.action_date.timetuple()[:3])
1012 return datetime.date(*self.action_date.timetuple()[:3])
1013
1013
1014 user = relationship('User')
1014 user = relationship('User')
1015 repository = relationship('Repository', cascade='')
1015 repository = relationship('Repository', cascade='')
1016
1016
1017
1017
1018 class UserGroup(Base, BaseModel):
1018 class UserGroup(Base, BaseModel):
1019 __tablename__ = 'users_groups'
1019 __tablename__ = 'users_groups'
1020 __table_args__ = (
1020 __table_args__ = (
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1023 )
1023 )
1024
1024
1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1033
1033
1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1040
1040
1041 user = relationship('User')
1041 user = relationship('User')
1042
1042
1043 @hybrid_property
1043 @hybrid_property
1044 def group_data(self):
1044 def group_data(self):
1045 if not self._group_data:
1045 if not self._group_data:
1046 return {}
1046 return {}
1047
1047
1048 try:
1048 try:
1049 return json.loads(self._group_data)
1049 return json.loads(self._group_data)
1050 except TypeError:
1050 except TypeError:
1051 return {}
1051 return {}
1052
1052
1053 @group_data.setter
1053 @group_data.setter
1054 def group_data(self, val):
1054 def group_data(self, val):
1055 try:
1055 try:
1056 self._group_data = json.dumps(val)
1056 self._group_data = json.dumps(val)
1057 except Exception:
1057 except Exception:
1058 log.error(traceback.format_exc())
1058 log.error(traceback.format_exc())
1059
1059
1060 def __unicode__(self):
1060 def __unicode__(self):
1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1062 self.users_group_id,
1062 self.users_group_id,
1063 self.users_group_name)
1063 self.users_group_name)
1064
1064
1065 @classmethod
1065 @classmethod
1066 def get_by_group_name(cls, group_name, cache=False,
1066 def get_by_group_name(cls, group_name, cache=False,
1067 case_insensitive=False):
1067 case_insensitive=False):
1068 if case_insensitive:
1068 if case_insensitive:
1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1070 func.lower(group_name))
1070 func.lower(group_name))
1071
1071
1072 else:
1072 else:
1073 q = cls.query().filter(cls.users_group_name == group_name)
1073 q = cls.query().filter(cls.users_group_name == group_name)
1074 if cache:
1074 if cache:
1075 q = q.options(FromCache(
1075 q = q.options(FromCache(
1076 "sql_cache_short",
1076 "sql_cache_short",
1077 "get_group_%s" % _hash_key(group_name)))
1077 "get_group_%s" % _hash_key(group_name)))
1078 return q.scalar()
1078 return q.scalar()
1079
1079
1080 @classmethod
1080 @classmethod
1081 def get(cls, user_group_id, cache=False):
1081 def get(cls, user_group_id, cache=False):
1082 user_group = cls.query()
1082 user_group = cls.query()
1083 if cache:
1083 if cache:
1084 user_group = user_group.options(FromCache("sql_cache_short",
1084 user_group = user_group.options(FromCache("sql_cache_short",
1085 "get_users_group_%s" % user_group_id))
1085 "get_users_group_%s" % user_group_id))
1086 return user_group.get(user_group_id)
1086 return user_group.get(user_group_id)
1087
1087
1088 def permissions(self, with_admins=True, with_owner=True):
1088 def permissions(self, with_admins=True, with_owner=True):
1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1091 joinedload(UserUserGroupToPerm.user),
1091 joinedload(UserUserGroupToPerm.user),
1092 joinedload(UserUserGroupToPerm.permission),)
1092 joinedload(UserUserGroupToPerm.permission),)
1093
1093
1094 # get owners and admins and permissions. We do a trick of re-writing
1094 # get owners and admins and permissions. We do a trick of re-writing
1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1096 # has a global reference and changing one object propagates to all
1096 # has a global reference and changing one object propagates to all
1097 # others. This means if admin is also an owner admin_row that change
1097 # others. This means if admin is also an owner admin_row that change
1098 # would propagate to both objects
1098 # would propagate to both objects
1099 perm_rows = []
1099 perm_rows = []
1100 for _usr in q.all():
1100 for _usr in q.all():
1101 usr = AttributeDict(_usr.user.get_dict())
1101 usr = AttributeDict(_usr.user.get_dict())
1102 usr.permission = _usr.permission.permission_name
1102 usr.permission = _usr.permission.permission_name
1103 perm_rows.append(usr)
1103 perm_rows.append(usr)
1104
1104
1105 # filter the perm rows by 'default' first and then sort them by
1105 # filter the perm rows by 'default' first and then sort them by
1106 # admin,write,read,none permissions sorted again alphabetically in
1106 # admin,write,read,none permissions sorted again alphabetically in
1107 # each group
1107 # each group
1108 perm_rows = sorted(perm_rows, key=display_sort)
1108 perm_rows = sorted(perm_rows, key=display_sort)
1109
1109
1110 _admin_perm = 'usergroup.admin'
1110 _admin_perm = 'usergroup.admin'
1111 owner_row = []
1111 owner_row = []
1112 if with_owner:
1112 if with_owner:
1113 usr = AttributeDict(self.user.get_dict())
1113 usr = AttributeDict(self.user.get_dict())
1114 usr.owner_row = True
1114 usr.owner_row = True
1115 usr.permission = _admin_perm
1115 usr.permission = _admin_perm
1116 owner_row.append(usr)
1116 owner_row.append(usr)
1117
1117
1118 super_admin_rows = []
1118 super_admin_rows = []
1119 if with_admins:
1119 if with_admins:
1120 for usr in User.get_all_super_admins():
1120 for usr in User.get_all_super_admins():
1121 # if this admin is also owner, don't double the record
1121 # if this admin is also owner, don't double the record
1122 if usr.user_id == owner_row[0].user_id:
1122 if usr.user_id == owner_row[0].user_id:
1123 owner_row[0].admin_row = True
1123 owner_row[0].admin_row = True
1124 else:
1124 else:
1125 usr = AttributeDict(usr.get_dict())
1125 usr = AttributeDict(usr.get_dict())
1126 usr.admin_row = True
1126 usr.admin_row = True
1127 usr.permission = _admin_perm
1127 usr.permission = _admin_perm
1128 super_admin_rows.append(usr)
1128 super_admin_rows.append(usr)
1129
1129
1130 return super_admin_rows + owner_row + perm_rows
1130 return super_admin_rows + owner_row + perm_rows
1131
1131
1132 def permission_user_groups(self):
1132 def permission_user_groups(self):
1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1136 joinedload(UserGroupUserGroupToPerm.permission),)
1136 joinedload(UserGroupUserGroupToPerm.permission),)
1137
1137
1138 perm_rows = []
1138 perm_rows = []
1139 for _user_group in q.all():
1139 for _user_group in q.all():
1140 usr = AttributeDict(_user_group.user_group.get_dict())
1140 usr = AttributeDict(_user_group.user_group.get_dict())
1141 usr.permission = _user_group.permission.permission_name
1141 usr.permission = _user_group.permission.permission_name
1142 perm_rows.append(usr)
1142 perm_rows.append(usr)
1143
1143
1144 return perm_rows
1144 return perm_rows
1145
1145
1146 def _get_default_perms(self, user_group, suffix=''):
1146 def _get_default_perms(self, user_group, suffix=''):
1147 from rhodecode.model.permission import PermissionModel
1147 from rhodecode.model.permission import PermissionModel
1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1149
1149
1150 def get_default_perms(self, suffix=''):
1150 def get_default_perms(self, suffix=''):
1151 return self._get_default_perms(self, suffix)
1151 return self._get_default_perms(self, suffix)
1152
1152
1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1154 """
1154 """
1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1156 basically forwarded.
1156 basically forwarded.
1157
1157
1158 """
1158 """
1159 user_group = self
1159 user_group = self
1160
1160
1161 data = {
1161 data = {
1162 'users_group_id': user_group.users_group_id,
1162 'users_group_id': user_group.users_group_id,
1163 'group_name': user_group.users_group_name,
1163 'group_name': user_group.users_group_name,
1164 'group_description': user_group.user_group_description,
1164 'group_description': user_group.user_group_description,
1165 'active': user_group.users_group_active,
1165 'active': user_group.users_group_active,
1166 'owner': user_group.user.username,
1166 'owner': user_group.user.username,
1167 }
1167 }
1168 if with_group_members:
1168 if with_group_members:
1169 users = []
1169 users = []
1170 for user in user_group.members:
1170 for user in user_group.members:
1171 user = user.user
1171 user = user.user
1172 users.append(user.get_api_data(include_secrets=include_secrets))
1172 users.append(user.get_api_data(include_secrets=include_secrets))
1173 data['users'] = users
1173 data['users'] = users
1174
1174
1175 return data
1175 return data
1176
1176
1177
1177
1178 class UserGroupMember(Base, BaseModel):
1178 class UserGroupMember(Base, BaseModel):
1179 __tablename__ = 'users_groups_members'
1179 __tablename__ = 'users_groups_members'
1180 __table_args__ = (
1180 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1183 )
1183 )
1184
1184
1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1188
1188
1189 user = relationship('User', lazy='joined')
1189 user = relationship('User', lazy='joined')
1190 users_group = relationship('UserGroup')
1190 users_group = relationship('UserGroup')
1191
1191
1192 def __init__(self, gr_id='', u_id=''):
1192 def __init__(self, gr_id='', u_id=''):
1193 self.users_group_id = gr_id
1193 self.users_group_id = gr_id
1194 self.user_id = u_id
1194 self.user_id = u_id
1195
1195
1196
1196
1197 class RepositoryField(Base, BaseModel):
1197 class RepositoryField(Base, BaseModel):
1198 __tablename__ = 'repositories_fields'
1198 __tablename__ = 'repositories_fields'
1199 __table_args__ = (
1199 __table_args__ = (
1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1203 )
1203 )
1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1205
1205
1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1208 field_key = Column("field_key", String(250))
1208 field_key = Column("field_key", String(250))
1209 field_label = Column("field_label", String(1024), nullable=False)
1209 field_label = Column("field_label", String(1024), nullable=False)
1210 field_value = Column("field_value", String(10000), nullable=False)
1210 field_value = Column("field_value", String(10000), nullable=False)
1211 field_desc = Column("field_desc", String(1024), nullable=False)
1211 field_desc = Column("field_desc", String(1024), nullable=False)
1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1214
1214
1215 repository = relationship('Repository')
1215 repository = relationship('Repository')
1216
1216
1217 @property
1217 @property
1218 def field_key_prefixed(self):
1218 def field_key_prefixed(self):
1219 return 'ex_%s' % self.field_key
1219 return 'ex_%s' % self.field_key
1220
1220
1221 @classmethod
1221 @classmethod
1222 def un_prefix_key(cls, key):
1222 def un_prefix_key(cls, key):
1223 if key.startswith(cls.PREFIX):
1223 if key.startswith(cls.PREFIX):
1224 return key[len(cls.PREFIX):]
1224 return key[len(cls.PREFIX):]
1225 return key
1225 return key
1226
1226
1227 @classmethod
1227 @classmethod
1228 def get_by_key_name(cls, key, repo):
1228 def get_by_key_name(cls, key, repo):
1229 row = cls.query()\
1229 row = cls.query()\
1230 .filter(cls.repository == repo)\
1230 .filter(cls.repository == repo)\
1231 .filter(cls.field_key == key).scalar()
1231 .filter(cls.field_key == key).scalar()
1232 return row
1232 return row
1233
1233
1234
1234
1235 class Repository(Base, BaseModel):
1235 class Repository(Base, BaseModel):
1236 __tablename__ = 'repositories'
1236 __tablename__ = 'repositories'
1237 __table_args__ = (
1237 __table_args__ = (
1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 )
1241 )
1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1244
1244
1245 STATE_CREATED = 'repo_state_created'
1245 STATE_CREATED = 'repo_state_created'
1246 STATE_PENDING = 'repo_state_pending'
1246 STATE_PENDING = 'repo_state_pending'
1247 STATE_ERROR = 'repo_state_error'
1247 STATE_ERROR = 'repo_state_error'
1248
1248
1249 LOCK_AUTOMATIC = 'lock_auto'
1249 LOCK_AUTOMATIC = 'lock_auto'
1250 LOCK_API = 'lock_api'
1250 LOCK_API = 'lock_api'
1251 LOCK_WEB = 'lock_web'
1251 LOCK_WEB = 'lock_web'
1252 LOCK_PULL = 'lock_pull'
1252 LOCK_PULL = 'lock_pull'
1253
1253
1254 NAME_SEP = URL_SEP
1254 NAME_SEP = URL_SEP
1255
1255
1256 repo_id = Column(
1256 repo_id = Column(
1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1258 primary_key=True)
1258 primary_key=True)
1259 _repo_name = Column(
1259 _repo_name = Column(
1260 "repo_name", Text(), nullable=False, default=None)
1260 "repo_name", Text(), nullable=False, default=None)
1261 _repo_name_hash = Column(
1261 _repo_name_hash = Column(
1262 "repo_name_hash", String(255), nullable=False, unique=True)
1262 "repo_name_hash", String(255), nullable=False, unique=True)
1263 repo_state = Column("repo_state", String(255), nullable=True)
1263 repo_state = Column("repo_state", String(255), nullable=True)
1264
1264
1265 clone_uri = Column(
1265 clone_uri = Column(
1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1267 default=None)
1267 default=None)
1268 repo_type = Column(
1268 repo_type = Column(
1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1270 user_id = Column(
1270 user_id = Column(
1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1272 unique=False, default=None)
1272 unique=False, default=None)
1273 private = Column(
1273 private = Column(
1274 "private", Boolean(), nullable=True, unique=None, default=None)
1274 "private", Boolean(), nullable=True, unique=None, default=None)
1275 enable_statistics = Column(
1275 enable_statistics = Column(
1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1277 enable_downloads = Column(
1277 enable_downloads = Column(
1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1279 description = Column(
1279 description = Column(
1280 "description", String(10000), nullable=True, unique=None, default=None)
1280 "description", String(10000), nullable=True, unique=None, default=None)
1281 created_on = Column(
1281 created_on = Column(
1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1283 default=datetime.datetime.now)
1283 default=datetime.datetime.now)
1284 updated_on = Column(
1284 updated_on = Column(
1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1286 default=datetime.datetime.now)
1286 default=datetime.datetime.now)
1287 _landing_revision = Column(
1287 _landing_revision = Column(
1288 "landing_revision", String(255), nullable=False, unique=False,
1288 "landing_revision", String(255), nullable=False, unique=False,
1289 default=None)
1289 default=None)
1290 enable_locking = Column(
1290 enable_locking = Column(
1291 "enable_locking", Boolean(), nullable=False, unique=None,
1291 "enable_locking", Boolean(), nullable=False, unique=None,
1292 default=False)
1292 default=False)
1293 _locked = Column(
1293 _locked = Column(
1294 "locked", String(255), nullable=True, unique=False, default=None)
1294 "locked", String(255), nullable=True, unique=False, default=None)
1295 _changeset_cache = Column(
1295 _changeset_cache = Column(
1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1297
1297
1298 fork_id = Column(
1298 fork_id = Column(
1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1300 nullable=True, unique=False, default=None)
1300 nullable=True, unique=False, default=None)
1301 group_id = Column(
1301 group_id = Column(
1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1303 unique=False, default=None)
1303 unique=False, default=None)
1304
1304
1305 user = relationship('User', lazy='joined')
1305 user = relationship('User', lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1308 repo_to_perm = relationship(
1308 repo_to_perm = relationship(
1309 'UserRepoToPerm', cascade='all',
1309 'UserRepoToPerm', cascade='all',
1310 order_by='UserRepoToPerm.repo_to_perm_id')
1310 order_by='UserRepoToPerm.repo_to_perm_id')
1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1312 stats = relationship('Statistics', cascade='all', uselist=False)
1312 stats = relationship('Statistics', cascade='all', uselist=False)
1313
1313
1314 followers = relationship(
1314 followers = relationship(
1315 'UserFollowing',
1315 'UserFollowing',
1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1317 cascade='all')
1317 cascade='all')
1318 extra_fields = relationship(
1318 extra_fields = relationship(
1319 'RepositoryField', cascade="all, delete, delete-orphan")
1319 'RepositoryField', cascade="all, delete, delete-orphan")
1320 logs = relationship('UserLog')
1320 logs = relationship('UserLog')
1321 comments = relationship(
1321 comments = relationship(
1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1323 pull_requests_source = relationship(
1323 pull_requests_source = relationship(
1324 'PullRequest',
1324 'PullRequest',
1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1326 cascade="all, delete, delete-orphan")
1326 cascade="all, delete, delete-orphan")
1327 pull_requests_target = relationship(
1327 pull_requests_target = relationship(
1328 'PullRequest',
1328 'PullRequest',
1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1330 cascade="all, delete, delete-orphan")
1330 cascade="all, delete, delete-orphan")
1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1333 integrations = relationship('Integration',
1333 integrations = relationship('Integration',
1334 cascade="all, delete, delete-orphan")
1334 cascade="all, delete, delete-orphan")
1335
1335
1336 def __unicode__(self):
1336 def __unicode__(self):
1337 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1337 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1338 safe_unicode(self.repo_name))
1338 safe_unicode(self.repo_name))
1339
1339
1340 @hybrid_property
1340 @hybrid_property
1341 def landing_rev(self):
1341 def landing_rev(self):
1342 # always should return [rev_type, rev]
1342 # always should return [rev_type, rev]
1343 if self._landing_revision:
1343 if self._landing_revision:
1344 _rev_info = self._landing_revision.split(':')
1344 _rev_info = self._landing_revision.split(':')
1345 if len(_rev_info) < 2:
1345 if len(_rev_info) < 2:
1346 _rev_info.insert(0, 'rev')
1346 _rev_info.insert(0, 'rev')
1347 return [_rev_info[0], _rev_info[1]]
1347 return [_rev_info[0], _rev_info[1]]
1348 return [None, None]
1348 return [None, None]
1349
1349
1350 @landing_rev.setter
1350 @landing_rev.setter
1351 def landing_rev(self, val):
1351 def landing_rev(self, val):
1352 if ':' not in val:
1352 if ':' not in val:
1353 raise ValueError('value must be delimited with `:` and consist '
1353 raise ValueError('value must be delimited with `:` and consist '
1354 'of <rev_type>:<rev>, got %s instead' % val)
1354 'of <rev_type>:<rev>, got %s instead' % val)
1355 self._landing_revision = val
1355 self._landing_revision = val
1356
1356
1357 @hybrid_property
1357 @hybrid_property
1358 def locked(self):
1358 def locked(self):
1359 if self._locked:
1359 if self._locked:
1360 user_id, timelocked, reason = self._locked.split(':')
1360 user_id, timelocked, reason = self._locked.split(':')
1361 lock_values = int(user_id), timelocked, reason
1361 lock_values = int(user_id), timelocked, reason
1362 else:
1362 else:
1363 lock_values = [None, None, None]
1363 lock_values = [None, None, None]
1364 return lock_values
1364 return lock_values
1365
1365
1366 @locked.setter
1366 @locked.setter
1367 def locked(self, val):
1367 def locked(self, val):
1368 if val and isinstance(val, (list, tuple)):
1368 if val and isinstance(val, (list, tuple)):
1369 self._locked = ':'.join(map(str, val))
1369 self._locked = ':'.join(map(str, val))
1370 else:
1370 else:
1371 self._locked = None
1371 self._locked = None
1372
1372
1373 @hybrid_property
1373 @hybrid_property
1374 def changeset_cache(self):
1374 def changeset_cache(self):
1375 from rhodecode.lib.vcs.backends.base import EmptyCommit
1375 from rhodecode.lib.vcs.backends.base import EmptyCommit
1376 dummy = EmptyCommit().__json__()
1376 dummy = EmptyCommit().__json__()
1377 if not self._changeset_cache:
1377 if not self._changeset_cache:
1378 return dummy
1378 return dummy
1379 try:
1379 try:
1380 return json.loads(self._changeset_cache)
1380 return json.loads(self._changeset_cache)
1381 except TypeError:
1381 except TypeError:
1382 return dummy
1382 return dummy
1383 except Exception:
1383 except Exception:
1384 log.error(traceback.format_exc())
1384 log.error(traceback.format_exc())
1385 return dummy
1385 return dummy
1386
1386
1387 @changeset_cache.setter
1387 @changeset_cache.setter
1388 def changeset_cache(self, val):
1388 def changeset_cache(self, val):
1389 try:
1389 try:
1390 self._changeset_cache = json.dumps(val)
1390 self._changeset_cache = json.dumps(val)
1391 except Exception:
1391 except Exception:
1392 log.error(traceback.format_exc())
1392 log.error(traceback.format_exc())
1393
1393
1394 @hybrid_property
1394 @hybrid_property
1395 def repo_name(self):
1395 def repo_name(self):
1396 return self._repo_name
1396 return self._repo_name
1397
1397
1398 @repo_name.setter
1398 @repo_name.setter
1399 def repo_name(self, value):
1399 def repo_name(self, value):
1400 self._repo_name = value
1400 self._repo_name = value
1401 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1401 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1402
1402
1403 @classmethod
1403 @classmethod
1404 def normalize_repo_name(cls, repo_name):
1404 def normalize_repo_name(cls, repo_name):
1405 """
1405 """
1406 Normalizes os specific repo_name to the format internally stored inside
1406 Normalizes os specific repo_name to the format internally stored inside
1407 database using URL_SEP
1407 database using URL_SEP
1408
1408
1409 :param cls:
1409 :param cls:
1410 :param repo_name:
1410 :param repo_name:
1411 """
1411 """
1412 return cls.NAME_SEP.join(repo_name.split(os.sep))
1412 return cls.NAME_SEP.join(repo_name.split(os.sep))
1413
1413
1414 @classmethod
1414 @classmethod
1415 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1415 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1416 session = Session()
1416 session = Session()
1417 q = session.query(cls).filter(cls.repo_name == repo_name)
1417 q = session.query(cls).filter(cls.repo_name == repo_name)
1418
1418
1419 if cache:
1419 if cache:
1420 if identity_cache:
1420 if identity_cache:
1421 val = cls.identity_cache(session, 'repo_name', repo_name)
1421 val = cls.identity_cache(session, 'repo_name', repo_name)
1422 if val:
1422 if val:
1423 return val
1423 return val
1424 else:
1424 else:
1425 q = q.options(
1425 q = q.options(
1426 FromCache("sql_cache_short",
1426 FromCache("sql_cache_short",
1427 "get_repo_by_name_%s" % _hash_key(repo_name)))
1427 "get_repo_by_name_%s" % _hash_key(repo_name)))
1428
1428
1429 return q.scalar()
1429 return q.scalar()
1430
1430
1431 @classmethod
1431 @classmethod
1432 def get_by_full_path(cls, repo_full_path):
1432 def get_by_full_path(cls, repo_full_path):
1433 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1433 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1434 repo_name = cls.normalize_repo_name(repo_name)
1434 repo_name = cls.normalize_repo_name(repo_name)
1435 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1435 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1436
1436
1437 @classmethod
1437 @classmethod
1438 def get_repo_forks(cls, repo_id):
1438 def get_repo_forks(cls, repo_id):
1439 return cls.query().filter(Repository.fork_id == repo_id)
1439 return cls.query().filter(Repository.fork_id == repo_id)
1440
1440
1441 @classmethod
1441 @classmethod
1442 def base_path(cls):
1442 def base_path(cls):
1443 """
1443 """
1444 Returns base path when all repos are stored
1444 Returns base path when all repos are stored
1445
1445
1446 :param cls:
1446 :param cls:
1447 """
1447 """
1448 q = Session().query(RhodeCodeUi)\
1448 q = Session().query(RhodeCodeUi)\
1449 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1449 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1450 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1450 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1451 return q.one().ui_value
1451 return q.one().ui_value
1452
1452
1453 @classmethod
1453 @classmethod
1454 def is_valid(cls, repo_name):
1454 def is_valid(cls, repo_name):
1455 """
1455 """
1456 returns True if given repo name is a valid filesystem repository
1456 returns True if given repo name is a valid filesystem repository
1457
1457
1458 :param cls:
1458 :param cls:
1459 :param repo_name:
1459 :param repo_name:
1460 """
1460 """
1461 from rhodecode.lib.utils import is_valid_repo
1461 from rhodecode.lib.utils import is_valid_repo
1462
1462
1463 return is_valid_repo(repo_name, cls.base_path())
1463 return is_valid_repo(repo_name, cls.base_path())
1464
1464
1465 @classmethod
1465 @classmethod
1466 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1466 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1467 case_insensitive=True):
1467 case_insensitive=True):
1468 q = Repository.query()
1468 q = Repository.query()
1469
1469
1470 if not isinstance(user_id, Optional):
1470 if not isinstance(user_id, Optional):
1471 q = q.filter(Repository.user_id == user_id)
1471 q = q.filter(Repository.user_id == user_id)
1472
1472
1473 if not isinstance(group_id, Optional):
1473 if not isinstance(group_id, Optional):
1474 q = q.filter(Repository.group_id == group_id)
1474 q = q.filter(Repository.group_id == group_id)
1475
1475
1476 if case_insensitive:
1476 if case_insensitive:
1477 q = q.order_by(func.lower(Repository.repo_name))
1477 q = q.order_by(func.lower(Repository.repo_name))
1478 else:
1478 else:
1479 q = q.order_by(Repository.repo_name)
1479 q = q.order_by(Repository.repo_name)
1480 return q.all()
1480 return q.all()
1481
1481
1482 @property
1482 @property
1483 def forks(self):
1483 def forks(self):
1484 """
1484 """
1485 Return forks of this repo
1485 Return forks of this repo
1486 """
1486 """
1487 return Repository.get_repo_forks(self.repo_id)
1487 return Repository.get_repo_forks(self.repo_id)
1488
1488
1489 @property
1489 @property
1490 def parent(self):
1490 def parent(self):
1491 """
1491 """
1492 Returns fork parent
1492 Returns fork parent
1493 """
1493 """
1494 return self.fork
1494 return self.fork
1495
1495
1496 @property
1496 @property
1497 def just_name(self):
1497 def just_name(self):
1498 return self.repo_name.split(self.NAME_SEP)[-1]
1498 return self.repo_name.split(self.NAME_SEP)[-1]
1499
1499
1500 @property
1500 @property
1501 def groups_with_parents(self):
1501 def groups_with_parents(self):
1502 groups = []
1502 groups = []
1503 if self.group is None:
1503 if self.group is None:
1504 return groups
1504 return groups
1505
1505
1506 cur_gr = self.group
1506 cur_gr = self.group
1507 groups.insert(0, cur_gr)
1507 groups.insert(0, cur_gr)
1508 while 1:
1508 while 1:
1509 gr = getattr(cur_gr, 'parent_group', None)
1509 gr = getattr(cur_gr, 'parent_group', None)
1510 cur_gr = cur_gr.parent_group
1510 cur_gr = cur_gr.parent_group
1511 if gr is None:
1511 if gr is None:
1512 break
1512 break
1513 groups.insert(0, gr)
1513 groups.insert(0, gr)
1514
1514
1515 return groups
1515 return groups
1516
1516
1517 @property
1517 @property
1518 def groups_and_repo(self):
1518 def groups_and_repo(self):
1519 return self.groups_with_parents, self
1519 return self.groups_with_parents, self
1520
1520
1521 @LazyProperty
1521 @LazyProperty
1522 def repo_path(self):
1522 def repo_path(self):
1523 """
1523 """
1524 Returns base full path for that repository means where it actually
1524 Returns base full path for that repository means where it actually
1525 exists on a filesystem
1525 exists on a filesystem
1526 """
1526 """
1527 q = Session().query(RhodeCodeUi).filter(
1527 q = Session().query(RhodeCodeUi).filter(
1528 RhodeCodeUi.ui_key == self.NAME_SEP)
1528 RhodeCodeUi.ui_key == self.NAME_SEP)
1529 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1529 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1530 return q.one().ui_value
1530 return q.one().ui_value
1531
1531
1532 @property
1532 @property
1533 def repo_full_path(self):
1533 def repo_full_path(self):
1534 p = [self.repo_path]
1534 p = [self.repo_path]
1535 # we need to split the name by / since this is how we store the
1535 # we need to split the name by / since this is how we store the
1536 # names in the database, but that eventually needs to be converted
1536 # names in the database, but that eventually needs to be converted
1537 # into a valid system path
1537 # into a valid system path
1538 p += self.repo_name.split(self.NAME_SEP)
1538 p += self.repo_name.split(self.NAME_SEP)
1539 return os.path.join(*map(safe_unicode, p))
1539 return os.path.join(*map(safe_unicode, p))
1540
1540
1541 @property
1541 @property
1542 def cache_keys(self):
1542 def cache_keys(self):
1543 """
1543 """
1544 Returns associated cache keys for that repo
1544 Returns associated cache keys for that repo
1545 """
1545 """
1546 return CacheKey.query()\
1546 return CacheKey.query()\
1547 .filter(CacheKey.cache_args == self.repo_name)\
1547 .filter(CacheKey.cache_args == self.repo_name)\
1548 .order_by(CacheKey.cache_key)\
1548 .order_by(CacheKey.cache_key)\
1549 .all()
1549 .all()
1550
1550
1551 def get_new_name(self, repo_name):
1551 def get_new_name(self, repo_name):
1552 """
1552 """
1553 returns new full repository name based on assigned group and new new
1553 returns new full repository name based on assigned group and new new
1554
1554
1555 :param group_name:
1555 :param group_name:
1556 """
1556 """
1557 path_prefix = self.group.full_path_splitted if self.group else []
1557 path_prefix = self.group.full_path_splitted if self.group else []
1558 return self.NAME_SEP.join(path_prefix + [repo_name])
1558 return self.NAME_SEP.join(path_prefix + [repo_name])
1559
1559
1560 @property
1560 @property
1561 def _config(self):
1561 def _config(self):
1562 """
1562 """
1563 Returns db based config object.
1563 Returns db based config object.
1564 """
1564 """
1565 from rhodecode.lib.utils import make_db_config
1565 from rhodecode.lib.utils import make_db_config
1566 return make_db_config(clear_session=False, repo=self)
1566 return make_db_config(clear_session=False, repo=self)
1567
1567
1568 def permissions(self, with_admins=True, with_owner=True):
1568 def permissions(self, with_admins=True, with_owner=True):
1569 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1569 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1570 q = q.options(joinedload(UserRepoToPerm.repository),
1570 q = q.options(joinedload(UserRepoToPerm.repository),
1571 joinedload(UserRepoToPerm.user),
1571 joinedload(UserRepoToPerm.user),
1572 joinedload(UserRepoToPerm.permission),)
1572 joinedload(UserRepoToPerm.permission),)
1573
1573
1574 # get owners and admins and permissions. We do a trick of re-writing
1574 # get owners and admins and permissions. We do a trick of re-writing
1575 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1575 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1576 # has a global reference and changing one object propagates to all
1576 # has a global reference and changing one object propagates to all
1577 # others. This means if admin is also an owner admin_row that change
1577 # others. This means if admin is also an owner admin_row that change
1578 # would propagate to both objects
1578 # would propagate to both objects
1579 perm_rows = []
1579 perm_rows = []
1580 for _usr in q.all():
1580 for _usr in q.all():
1581 usr = AttributeDict(_usr.user.get_dict())
1581 usr = AttributeDict(_usr.user.get_dict())
1582 usr.permission = _usr.permission.permission_name
1582 usr.permission = _usr.permission.permission_name
1583 perm_rows.append(usr)
1583 perm_rows.append(usr)
1584
1584
1585 # filter the perm rows by 'default' first and then sort them by
1585 # filter the perm rows by 'default' first and then sort them by
1586 # admin,write,read,none permissions sorted again alphabetically in
1586 # admin,write,read,none permissions sorted again alphabetically in
1587 # each group
1587 # each group
1588 perm_rows = sorted(perm_rows, key=display_sort)
1588 perm_rows = sorted(perm_rows, key=display_sort)
1589
1589
1590 _admin_perm = 'repository.admin'
1590 _admin_perm = 'repository.admin'
1591 owner_row = []
1591 owner_row = []
1592 if with_owner:
1592 if with_owner:
1593 usr = AttributeDict(self.user.get_dict())
1593 usr = AttributeDict(self.user.get_dict())
1594 usr.owner_row = True
1594 usr.owner_row = True
1595 usr.permission = _admin_perm
1595 usr.permission = _admin_perm
1596 owner_row.append(usr)
1596 owner_row.append(usr)
1597
1597
1598 super_admin_rows = []
1598 super_admin_rows = []
1599 if with_admins:
1599 if with_admins:
1600 for usr in User.get_all_super_admins():
1600 for usr in User.get_all_super_admins():
1601 # if this admin is also owner, don't double the record
1601 # if this admin is also owner, don't double the record
1602 if usr.user_id == owner_row[0].user_id:
1602 if usr.user_id == owner_row[0].user_id:
1603 owner_row[0].admin_row = True
1603 owner_row[0].admin_row = True
1604 else:
1604 else:
1605 usr = AttributeDict(usr.get_dict())
1605 usr = AttributeDict(usr.get_dict())
1606 usr.admin_row = True
1606 usr.admin_row = True
1607 usr.permission = _admin_perm
1607 usr.permission = _admin_perm
1608 super_admin_rows.append(usr)
1608 super_admin_rows.append(usr)
1609
1609
1610 return super_admin_rows + owner_row + perm_rows
1610 return super_admin_rows + owner_row + perm_rows
1611
1611
1612 def permission_user_groups(self):
1612 def permission_user_groups(self):
1613 q = UserGroupRepoToPerm.query().filter(
1613 q = UserGroupRepoToPerm.query().filter(
1614 UserGroupRepoToPerm.repository == self)
1614 UserGroupRepoToPerm.repository == self)
1615 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1615 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1616 joinedload(UserGroupRepoToPerm.users_group),
1616 joinedload(UserGroupRepoToPerm.users_group),
1617 joinedload(UserGroupRepoToPerm.permission),)
1617 joinedload(UserGroupRepoToPerm.permission),)
1618
1618
1619 perm_rows = []
1619 perm_rows = []
1620 for _user_group in q.all():
1620 for _user_group in q.all():
1621 usr = AttributeDict(_user_group.users_group.get_dict())
1621 usr = AttributeDict(_user_group.users_group.get_dict())
1622 usr.permission = _user_group.permission.permission_name
1622 usr.permission = _user_group.permission.permission_name
1623 perm_rows.append(usr)
1623 perm_rows.append(usr)
1624
1624
1625 return perm_rows
1625 return perm_rows
1626
1626
1627 def get_api_data(self, include_secrets=False):
1627 def get_api_data(self, include_secrets=False):
1628 """
1628 """
1629 Common function for generating repo api data
1629 Common function for generating repo api data
1630
1630
1631 :param include_secrets: See :meth:`User.get_api_data`.
1631 :param include_secrets: See :meth:`User.get_api_data`.
1632
1632
1633 """
1633 """
1634 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1634 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1635 # move this methods on models level.
1635 # move this methods on models level.
1636 from rhodecode.model.settings import SettingsModel
1636 from rhodecode.model.settings import SettingsModel
1637
1637
1638 repo = self
1638 repo = self
1639 _user_id, _time, _reason = self.locked
1639 _user_id, _time, _reason = self.locked
1640
1640
1641 data = {
1641 data = {
1642 'repo_id': repo.repo_id,
1642 'repo_id': repo.repo_id,
1643 'repo_name': repo.repo_name,
1643 'repo_name': repo.repo_name,
1644 'repo_type': repo.repo_type,
1644 'repo_type': repo.repo_type,
1645 'clone_uri': repo.clone_uri or '',
1645 'clone_uri': repo.clone_uri or '',
1646 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1646 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1647 'private': repo.private,
1647 'private': repo.private,
1648 'created_on': repo.created_on,
1648 'created_on': repo.created_on,
1649 'description': repo.description,
1649 'description': repo.description,
1650 'landing_rev': repo.landing_rev,
1650 'landing_rev': repo.landing_rev,
1651 'owner': repo.user.username,
1651 'owner': repo.user.username,
1652 'fork_of': repo.fork.repo_name if repo.fork else None,
1652 'fork_of': repo.fork.repo_name if repo.fork else None,
1653 'enable_statistics': repo.enable_statistics,
1653 'enable_statistics': repo.enable_statistics,
1654 'enable_locking': repo.enable_locking,
1654 'enable_locking': repo.enable_locking,
1655 'enable_downloads': repo.enable_downloads,
1655 'enable_downloads': repo.enable_downloads,
1656 'last_changeset': repo.changeset_cache,
1656 'last_changeset': repo.changeset_cache,
1657 'locked_by': User.get(_user_id).get_api_data(
1657 'locked_by': User.get(_user_id).get_api_data(
1658 include_secrets=include_secrets) if _user_id else None,
1658 include_secrets=include_secrets) if _user_id else None,
1659 'locked_date': time_to_datetime(_time) if _time else None,
1659 'locked_date': time_to_datetime(_time) if _time else None,
1660 'lock_reason': _reason if _reason else None,
1660 'lock_reason': _reason if _reason else None,
1661 }
1661 }
1662
1662
1663 # TODO: mikhail: should be per-repo settings here
1663 # TODO: mikhail: should be per-repo settings here
1664 rc_config = SettingsModel().get_all_settings()
1664 rc_config = SettingsModel().get_all_settings()
1665 repository_fields = str2bool(
1665 repository_fields = str2bool(
1666 rc_config.get('rhodecode_repository_fields'))
1666 rc_config.get('rhodecode_repository_fields'))
1667 if repository_fields:
1667 if repository_fields:
1668 for f in self.extra_fields:
1668 for f in self.extra_fields:
1669 data[f.field_key_prefixed] = f.field_value
1669 data[f.field_key_prefixed] = f.field_value
1670
1670
1671 return data
1671 return data
1672
1672
1673 @classmethod
1673 @classmethod
1674 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1674 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1675 if not lock_time:
1675 if not lock_time:
1676 lock_time = time.time()
1676 lock_time = time.time()
1677 if not lock_reason:
1677 if not lock_reason:
1678 lock_reason = cls.LOCK_AUTOMATIC
1678 lock_reason = cls.LOCK_AUTOMATIC
1679 repo.locked = [user_id, lock_time, lock_reason]
1679 repo.locked = [user_id, lock_time, lock_reason]
1680 Session().add(repo)
1680 Session().add(repo)
1681 Session().commit()
1681 Session().commit()
1682
1682
1683 @classmethod
1683 @classmethod
1684 def unlock(cls, repo):
1684 def unlock(cls, repo):
1685 repo.locked = None
1685 repo.locked = None
1686 Session().add(repo)
1686 Session().add(repo)
1687 Session().commit()
1687 Session().commit()
1688
1688
1689 @classmethod
1689 @classmethod
1690 def getlock(cls, repo):
1690 def getlock(cls, repo):
1691 return repo.locked
1691 return repo.locked
1692
1692
1693 def is_user_lock(self, user_id):
1693 def is_user_lock(self, user_id):
1694 if self.lock[0]:
1694 if self.lock[0]:
1695 lock_user_id = safe_int(self.lock[0])
1695 lock_user_id = safe_int(self.lock[0])
1696 user_id = safe_int(user_id)
1696 user_id = safe_int(user_id)
1697 # both are ints, and they are equal
1697 # both are ints, and they are equal
1698 return all([lock_user_id, user_id]) and lock_user_id == user_id
1698 return all([lock_user_id, user_id]) and lock_user_id == user_id
1699
1699
1700 return False
1700 return False
1701
1701
1702 def get_locking_state(self, action, user_id, only_when_enabled=True):
1702 def get_locking_state(self, action, user_id, only_when_enabled=True):
1703 """
1703 """
1704 Checks locking on this repository, if locking is enabled and lock is
1704 Checks locking on this repository, if locking is enabled and lock is
1705 present returns a tuple of make_lock, locked, locked_by.
1705 present returns a tuple of make_lock, locked, locked_by.
1706 make_lock can have 3 states None (do nothing) True, make lock
1706 make_lock can have 3 states None (do nothing) True, make lock
1707 False release lock, This value is later propagated to hooks, which
1707 False release lock, This value is later propagated to hooks, which
1708 do the locking. Think about this as signals passed to hooks what to do.
1708 do the locking. Think about this as signals passed to hooks what to do.
1709
1709
1710 """
1710 """
1711 # TODO: johbo: This is part of the business logic and should be moved
1711 # TODO: johbo: This is part of the business logic and should be moved
1712 # into the RepositoryModel.
1712 # into the RepositoryModel.
1713
1713
1714 if action not in ('push', 'pull'):
1714 if action not in ('push', 'pull'):
1715 raise ValueError("Invalid action value: %s" % repr(action))
1715 raise ValueError("Invalid action value: %s" % repr(action))
1716
1716
1717 # defines if locked error should be thrown to user
1717 # defines if locked error should be thrown to user
1718 currently_locked = False
1718 currently_locked = False
1719 # defines if new lock should be made, tri-state
1719 # defines if new lock should be made, tri-state
1720 make_lock = None
1720 make_lock = None
1721 repo = self
1721 repo = self
1722 user = User.get(user_id)
1722 user = User.get(user_id)
1723
1723
1724 lock_info = repo.locked
1724 lock_info = repo.locked
1725
1725
1726 if repo and (repo.enable_locking or not only_when_enabled):
1726 if repo and (repo.enable_locking or not only_when_enabled):
1727 if action == 'push':
1727 if action == 'push':
1728 # check if it's already locked !, if it is compare users
1728 # check if it's already locked !, if it is compare users
1729 locked_by_user_id = lock_info[0]
1729 locked_by_user_id = lock_info[0]
1730 if user.user_id == locked_by_user_id:
1730 if user.user_id == locked_by_user_id:
1731 log.debug(
1731 log.debug(
1732 'Got `push` action from user %s, now unlocking', user)
1732 'Got `push` action from user %s, now unlocking', user)
1733 # unlock if we have push from user who locked
1733 # unlock if we have push from user who locked
1734 make_lock = False
1734 make_lock = False
1735 else:
1735 else:
1736 # we're not the same user who locked, ban with
1736 # we're not the same user who locked, ban with
1737 # code defined in settings (default is 423 HTTP Locked) !
1737 # code defined in settings (default is 423 HTTP Locked) !
1738 log.debug('Repo %s is currently locked by %s', repo, user)
1738 log.debug('Repo %s is currently locked by %s', repo, user)
1739 currently_locked = True
1739 currently_locked = True
1740 elif action == 'pull':
1740 elif action == 'pull':
1741 # [0] user [1] date
1741 # [0] user [1] date
1742 if lock_info[0] and lock_info[1]:
1742 if lock_info[0] and lock_info[1]:
1743 log.debug('Repo %s is currently locked by %s', repo, user)
1743 log.debug('Repo %s is currently locked by %s', repo, user)
1744 currently_locked = True
1744 currently_locked = True
1745 else:
1745 else:
1746 log.debug('Setting lock on repo %s by %s', repo, user)
1746 log.debug('Setting lock on repo %s by %s', repo, user)
1747 make_lock = True
1747 make_lock = True
1748
1748
1749 else:
1749 else:
1750 log.debug('Repository %s do not have locking enabled', repo)
1750 log.debug('Repository %s do not have locking enabled', repo)
1751
1751
1752 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1752 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1753 make_lock, currently_locked, lock_info)
1753 make_lock, currently_locked, lock_info)
1754
1754
1755 from rhodecode.lib.auth import HasRepoPermissionAny
1755 from rhodecode.lib.auth import HasRepoPermissionAny
1756 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1756 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1757 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1757 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1758 # if we don't have at least write permission we cannot make a lock
1758 # if we don't have at least write permission we cannot make a lock
1759 log.debug('lock state reset back to FALSE due to lack '
1759 log.debug('lock state reset back to FALSE due to lack '
1760 'of at least read permission')
1760 'of at least read permission')
1761 make_lock = False
1761 make_lock = False
1762
1762
1763 return make_lock, currently_locked, lock_info
1763 return make_lock, currently_locked, lock_info
1764
1764
1765 @property
1765 @property
1766 def last_db_change(self):
1766 def last_db_change(self):
1767 return self.updated_on
1767 return self.updated_on
1768
1768
1769 @property
1769 @property
1770 def clone_uri_hidden(self):
1770 def clone_uri_hidden(self):
1771 clone_uri = self.clone_uri
1771 clone_uri = self.clone_uri
1772 if clone_uri:
1772 if clone_uri:
1773 import urlobject
1773 import urlobject
1774 url_obj = urlobject.URLObject(clone_uri)
1774 url_obj = urlobject.URLObject(clone_uri)
1775 if url_obj.password:
1775 if url_obj.password:
1776 clone_uri = url_obj.with_password('*****')
1776 clone_uri = url_obj.with_password('*****')
1777 return clone_uri
1777 return clone_uri
1778
1778
1779 def clone_url(self, **override):
1779 def clone_url(self, **override):
1780 qualified_home_url = url('home', qualified=True)
1780 qualified_home_url = url('home', qualified=True)
1781
1781
1782 uri_tmpl = None
1782 uri_tmpl = None
1783 if 'with_id' in override:
1783 if 'with_id' in override:
1784 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1784 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1785 del override['with_id']
1785 del override['with_id']
1786
1786
1787 if 'uri_tmpl' in override:
1787 if 'uri_tmpl' in override:
1788 uri_tmpl = override['uri_tmpl']
1788 uri_tmpl = override['uri_tmpl']
1789 del override['uri_tmpl']
1789 del override['uri_tmpl']
1790
1790
1791 # we didn't override our tmpl from **overrides
1791 # we didn't override our tmpl from **overrides
1792 if not uri_tmpl:
1792 if not uri_tmpl:
1793 uri_tmpl = self.DEFAULT_CLONE_URI
1793 uri_tmpl = self.DEFAULT_CLONE_URI
1794 try:
1794 try:
1795 from pylons import tmpl_context as c
1795 from pylons import tmpl_context as c
1796 uri_tmpl = c.clone_uri_tmpl
1796 uri_tmpl = c.clone_uri_tmpl
1797 except Exception:
1797 except Exception:
1798 # in any case if we call this outside of request context,
1798 # in any case if we call this outside of request context,
1799 # ie, not having tmpl_context set up
1799 # ie, not having tmpl_context set up
1800 pass
1800 pass
1801
1801
1802 return get_clone_url(uri_tmpl=uri_tmpl,
1802 return get_clone_url(uri_tmpl=uri_tmpl,
1803 qualifed_home_url=qualified_home_url,
1803 qualifed_home_url=qualified_home_url,
1804 repo_name=self.repo_name,
1804 repo_name=self.repo_name,
1805 repo_id=self.repo_id, **override)
1805 repo_id=self.repo_id, **override)
1806
1806
1807 def set_state(self, state):
1807 def set_state(self, state):
1808 self.repo_state = state
1808 self.repo_state = state
1809 Session().add(self)
1809 Session().add(self)
1810 #==========================================================================
1810 #==========================================================================
1811 # SCM PROPERTIES
1811 # SCM PROPERTIES
1812 #==========================================================================
1812 #==========================================================================
1813
1813
1814 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1814 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1815 return get_commit_safe(
1815 return get_commit_safe(
1816 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1816 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1817
1817
1818 def get_changeset(self, rev=None, pre_load=None):
1818 def get_changeset(self, rev=None, pre_load=None):
1819 warnings.warn("Use get_commit", DeprecationWarning)
1819 warnings.warn("Use get_commit", DeprecationWarning)
1820 commit_id = None
1820 commit_id = None
1821 commit_idx = None
1821 commit_idx = None
1822 if isinstance(rev, basestring):
1822 if isinstance(rev, basestring):
1823 commit_id = rev
1823 commit_id = rev
1824 else:
1824 else:
1825 commit_idx = rev
1825 commit_idx = rev
1826 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1826 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1827 pre_load=pre_load)
1827 pre_load=pre_load)
1828
1828
1829 def get_landing_commit(self):
1829 def get_landing_commit(self):
1830 """
1830 """
1831 Returns landing commit, or if that doesn't exist returns the tip
1831 Returns landing commit, or if that doesn't exist returns the tip
1832 """
1832 """
1833 _rev_type, _rev = self.landing_rev
1833 _rev_type, _rev = self.landing_rev
1834 commit = self.get_commit(_rev)
1834 commit = self.get_commit(_rev)
1835 if isinstance(commit, EmptyCommit):
1835 if isinstance(commit, EmptyCommit):
1836 return self.get_commit()
1836 return self.get_commit()
1837 return commit
1837 return commit
1838
1838
1839 def update_commit_cache(self, cs_cache=None, config=None):
1839 def update_commit_cache(self, cs_cache=None, config=None):
1840 """
1840 """
1841 Update cache of last changeset for repository, keys should be::
1841 Update cache of last changeset for repository, keys should be::
1842
1842
1843 short_id
1843 short_id
1844 raw_id
1844 raw_id
1845 revision
1845 revision
1846 parents
1846 parents
1847 message
1847 message
1848 date
1848 date
1849 author
1849 author
1850
1850
1851 :param cs_cache:
1851 :param cs_cache:
1852 """
1852 """
1853 from rhodecode.lib.vcs.backends.base import BaseChangeset
1853 from rhodecode.lib.vcs.backends.base import BaseChangeset
1854 if cs_cache is None:
1854 if cs_cache is None:
1855 # use no-cache version here
1855 # use no-cache version here
1856 scm_repo = self.scm_instance(cache=False, config=config)
1856 scm_repo = self.scm_instance(cache=False, config=config)
1857 if scm_repo:
1857 if scm_repo:
1858 cs_cache = scm_repo.get_commit(
1858 cs_cache = scm_repo.get_commit(
1859 pre_load=["author", "date", "message", "parents"])
1859 pre_load=["author", "date", "message", "parents"])
1860 else:
1860 else:
1861 cs_cache = EmptyCommit()
1861 cs_cache = EmptyCommit()
1862
1862
1863 if isinstance(cs_cache, BaseChangeset):
1863 if isinstance(cs_cache, BaseChangeset):
1864 cs_cache = cs_cache.__json__()
1864 cs_cache = cs_cache.__json__()
1865
1865
1866 def is_outdated(new_cs_cache):
1866 def is_outdated(new_cs_cache):
1867 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1867 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1868 new_cs_cache['revision'] != self.changeset_cache['revision']):
1868 new_cs_cache['revision'] != self.changeset_cache['revision']):
1869 return True
1869 return True
1870 return False
1870 return False
1871
1871
1872 # check if we have maybe already latest cached revision
1872 # check if we have maybe already latest cached revision
1873 if is_outdated(cs_cache) or not self.changeset_cache:
1873 if is_outdated(cs_cache) or not self.changeset_cache:
1874 _default = datetime.datetime.fromtimestamp(0)
1874 _default = datetime.datetime.fromtimestamp(0)
1875 last_change = cs_cache.get('date') or _default
1875 last_change = cs_cache.get('date') or _default
1876 log.debug('updated repo %s with new cs cache %s',
1876 log.debug('updated repo %s with new cs cache %s',
1877 self.repo_name, cs_cache)
1877 self.repo_name, cs_cache)
1878 self.updated_on = last_change
1878 self.updated_on = last_change
1879 self.changeset_cache = cs_cache
1879 self.changeset_cache = cs_cache
1880 Session().add(self)
1880 Session().add(self)
1881 Session().commit()
1881 Session().commit()
1882 else:
1882 else:
1883 log.debug('Skipping update_commit_cache for repo:`%s` '
1883 log.debug('Skipping update_commit_cache for repo:`%s` '
1884 'commit already with latest changes', self.repo_name)
1884 'commit already with latest changes', self.repo_name)
1885
1885
1886 @property
1886 @property
1887 def tip(self):
1887 def tip(self):
1888 return self.get_commit('tip')
1888 return self.get_commit('tip')
1889
1889
1890 @property
1890 @property
1891 def author(self):
1891 def author(self):
1892 return self.tip.author
1892 return self.tip.author
1893
1893
1894 @property
1894 @property
1895 def last_change(self):
1895 def last_change(self):
1896 return self.scm_instance().last_change
1896 return self.scm_instance().last_change
1897
1897
1898 def get_comments(self, revisions=None):
1898 def get_comments(self, revisions=None):
1899 """
1899 """
1900 Returns comments for this repository grouped by revisions
1900 Returns comments for this repository grouped by revisions
1901
1901
1902 :param revisions: filter query by revisions only
1902 :param revisions: filter query by revisions only
1903 """
1903 """
1904 cmts = ChangesetComment.query()\
1904 cmts = ChangesetComment.query()\
1905 .filter(ChangesetComment.repo == self)
1905 .filter(ChangesetComment.repo == self)
1906 if revisions:
1906 if revisions:
1907 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1907 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1908 grouped = collections.defaultdict(list)
1908 grouped = collections.defaultdict(list)
1909 for cmt in cmts.all():
1909 for cmt in cmts.all():
1910 grouped[cmt.revision].append(cmt)
1910 grouped[cmt.revision].append(cmt)
1911 return grouped
1911 return grouped
1912
1912
1913 def statuses(self, revisions=None):
1913 def statuses(self, revisions=None):
1914 """
1914 """
1915 Returns statuses for this repository
1915 Returns statuses for this repository
1916
1916
1917 :param revisions: list of revisions to get statuses for
1917 :param revisions: list of revisions to get statuses for
1918 """
1918 """
1919 statuses = ChangesetStatus.query()\
1919 statuses = ChangesetStatus.query()\
1920 .filter(ChangesetStatus.repo == self)\
1920 .filter(ChangesetStatus.repo == self)\
1921 .filter(ChangesetStatus.version == 0)
1921 .filter(ChangesetStatus.version == 0)
1922
1922
1923 if revisions:
1923 if revisions:
1924 # Try doing the filtering in chunks to avoid hitting limits
1924 # Try doing the filtering in chunks to avoid hitting limits
1925 size = 500
1925 size = 500
1926 status_results = []
1926 status_results = []
1927 for chunk in xrange(0, len(revisions), size):
1927 for chunk in xrange(0, len(revisions), size):
1928 status_results += statuses.filter(
1928 status_results += statuses.filter(
1929 ChangesetStatus.revision.in_(
1929 ChangesetStatus.revision.in_(
1930 revisions[chunk: chunk+size])
1930 revisions[chunk: chunk+size])
1931 ).all()
1931 ).all()
1932 else:
1932 else:
1933 status_results = statuses.all()
1933 status_results = statuses.all()
1934
1934
1935 grouped = {}
1935 grouped = {}
1936
1936
1937 # maybe we have open new pullrequest without a status?
1937 # maybe we have open new pullrequest without a status?
1938 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1938 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1939 status_lbl = ChangesetStatus.get_status_lbl(stat)
1939 status_lbl = ChangesetStatus.get_status_lbl(stat)
1940 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1940 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1941 for rev in pr.revisions:
1941 for rev in pr.revisions:
1942 pr_id = pr.pull_request_id
1942 pr_id = pr.pull_request_id
1943 pr_repo = pr.target_repo.repo_name
1943 pr_repo = pr.target_repo.repo_name
1944 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1944 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1945
1945
1946 for stat in status_results:
1946 for stat in status_results:
1947 pr_id = pr_repo = None
1947 pr_id = pr_repo = None
1948 if stat.pull_request:
1948 if stat.pull_request:
1949 pr_id = stat.pull_request.pull_request_id
1949 pr_id = stat.pull_request.pull_request_id
1950 pr_repo = stat.pull_request.target_repo.repo_name
1950 pr_repo = stat.pull_request.target_repo.repo_name
1951 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1951 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1952 pr_id, pr_repo]
1952 pr_id, pr_repo]
1953 return grouped
1953 return grouped
1954
1954
1955 # ==========================================================================
1955 # ==========================================================================
1956 # SCM CACHE INSTANCE
1956 # SCM CACHE INSTANCE
1957 # ==========================================================================
1957 # ==========================================================================
1958
1958
1959 def scm_instance(self, **kwargs):
1959 def scm_instance(self, **kwargs):
1960 import rhodecode
1960 import rhodecode
1961
1961
1962 # Passing a config will not hit the cache currently only used
1962 # Passing a config will not hit the cache currently only used
1963 # for repo2dbmapper
1963 # for repo2dbmapper
1964 config = kwargs.pop('config', None)
1964 config = kwargs.pop('config', None)
1965 cache = kwargs.pop('cache', None)
1965 cache = kwargs.pop('cache', None)
1966 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1966 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1967 # if cache is NOT defined use default global, else we have a full
1967 # if cache is NOT defined use default global, else we have a full
1968 # control over cache behaviour
1968 # control over cache behaviour
1969 if cache is None and full_cache and not config:
1969 if cache is None and full_cache and not config:
1970 return self._get_instance_cached()
1970 return self._get_instance_cached()
1971 return self._get_instance(cache=bool(cache), config=config)
1971 return self._get_instance(cache=bool(cache), config=config)
1972
1972
1973 def _get_instance_cached(self):
1973 def _get_instance_cached(self):
1974 @cache_region('long_term')
1974 @cache_region('long_term')
1975 def _get_repo(cache_key):
1975 def _get_repo(cache_key):
1976 return self._get_instance()
1976 return self._get_instance()
1977
1977
1978 invalidator_context = CacheKey.repo_context_cache(
1978 invalidator_context = CacheKey.repo_context_cache(
1979 _get_repo, self.repo_name, None)
1979 _get_repo, self.repo_name, None)
1980
1980
1981 with invalidator_context as context:
1981 with invalidator_context as context:
1982 context.invalidate()
1982 context.invalidate()
1983 repo = context.compute()
1983 repo = context.compute()
1984
1984
1985 return repo
1985 return repo
1986
1986
1987 def _get_instance(self, cache=True, config=None):
1987 def _get_instance(self, cache=True, config=None):
1988 repo_full_path = self.repo_full_path
1988 repo_full_path = self.repo_full_path
1989 try:
1989 try:
1990 vcs_alias = get_scm(repo_full_path)[0]
1990 vcs_alias = get_scm(repo_full_path)[0]
1991 log.debug(
1991 log.debug(
1992 'Creating instance of %s repository from %s',
1992 'Creating instance of %s repository from %s',
1993 vcs_alias, repo_full_path)
1993 vcs_alias, repo_full_path)
1994 backend = get_backend(vcs_alias)
1994 backend = get_backend(vcs_alias)
1995 except VCSError:
1995 except VCSError:
1996 log.exception(
1996 log.exception(
1997 'Perhaps this repository is in db and not in '
1997 'Perhaps this repository is in db and not in '
1998 'filesystem run rescan repositories with '
1998 'filesystem run rescan repositories with '
1999 '"destroy old data" option from admin panel')
1999 '"destroy old data" option from admin panel')
2000 return
2000 return
2001
2001
2002 config = config or self._config
2002 config = config or self._config
2003 custom_wire = {
2003 custom_wire = {
2004 'cache': cache # controls the vcs.remote cache
2004 'cache': cache # controls the vcs.remote cache
2005 }
2005 }
2006 repo = backend(
2006 repo = backend(
2007 safe_str(repo_full_path), config=config, create=False,
2007 safe_str(repo_full_path), config=config, create=False,
2008 with_wire=custom_wire)
2008 with_wire=custom_wire)
2009
2009
2010 return repo
2010 return repo
2011
2011
2012 def __json__(self):
2012 def __json__(self):
2013 return {'landing_rev': self.landing_rev}
2013 return {'landing_rev': self.landing_rev}
2014
2014
2015 def get_dict(self):
2015 def get_dict(self):
2016
2016
2017 # Since we transformed `repo_name` to a hybrid property, we need to
2017 # Since we transformed `repo_name` to a hybrid property, we need to
2018 # keep compatibility with the code which uses `repo_name` field.
2018 # keep compatibility with the code which uses `repo_name` field.
2019
2019
2020 result = super(Repository, self).get_dict()
2020 result = super(Repository, self).get_dict()
2021 result['repo_name'] = result.pop('_repo_name', None)
2021 result['repo_name'] = result.pop('_repo_name', None)
2022 return result
2022 return result
2023
2023
2024
2024
2025 class RepoGroup(Base, BaseModel):
2025 class RepoGroup(Base, BaseModel):
2026 __tablename__ = 'groups'
2026 __tablename__ = 'groups'
2027 __table_args__ = (
2027 __table_args__ = (
2028 UniqueConstraint('group_name', 'group_parent_id'),
2028 UniqueConstraint('group_name', 'group_parent_id'),
2029 CheckConstraint('group_id != group_parent_id'),
2029 CheckConstraint('group_id != group_parent_id'),
2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2031 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2031 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2032 )
2032 )
2033 __mapper_args__ = {'order_by': 'group_name'}
2033 __mapper_args__ = {'order_by': 'group_name'}
2034
2034
2035 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2035 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2036
2036
2037 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2037 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2038 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2038 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2039 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2039 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2040 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2040 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2041 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2041 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2043 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2043 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2044
2044
2045 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2045 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2046 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2046 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2047 parent_group = relationship('RepoGroup', remote_side=group_id)
2047 parent_group = relationship('RepoGroup', remote_side=group_id)
2048 user = relationship('User')
2048 user = relationship('User')
2049
2049
2050 def __init__(self, group_name='', parent_group=None):
2050 def __init__(self, group_name='', parent_group=None):
2051 self.group_name = group_name
2051 self.group_name = group_name
2052 self.parent_group = parent_group
2052 self.parent_group = parent_group
2053
2053
2054 def __unicode__(self):
2054 def __unicode__(self):
2055 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2055 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2056 self.group_name)
2056 self.group_name)
2057
2057
2058 @classmethod
2058 @classmethod
2059 def _generate_choice(cls, repo_group):
2059 def _generate_choice(cls, repo_group):
2060 from webhelpers.html import literal as _literal
2060 from webhelpers.html import literal as _literal
2061 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2061 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2062 return repo_group.group_id, _name(repo_group.full_path_splitted)
2062 return repo_group.group_id, _name(repo_group.full_path_splitted)
2063
2063
2064 @classmethod
2064 @classmethod
2065 def groups_choices(cls, groups=None, show_empty_group=True):
2065 def groups_choices(cls, groups=None, show_empty_group=True):
2066 if not groups:
2066 if not groups:
2067 groups = cls.query().all()
2067 groups = cls.query().all()
2068
2068
2069 repo_groups = []
2069 repo_groups = []
2070 if show_empty_group:
2070 if show_empty_group:
2071 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2071 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2072
2072
2073 repo_groups.extend([cls._generate_choice(x) for x in groups])
2073 repo_groups.extend([cls._generate_choice(x) for x in groups])
2074
2074
2075 repo_groups = sorted(
2075 repo_groups = sorted(
2076 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2076 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2077 return repo_groups
2077 return repo_groups
2078
2078
2079 @classmethod
2079 @classmethod
2080 def url_sep(cls):
2080 def url_sep(cls):
2081 return URL_SEP
2081 return URL_SEP
2082
2082
2083 @classmethod
2083 @classmethod
2084 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2084 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2085 if case_insensitive:
2085 if case_insensitive:
2086 gr = cls.query().filter(func.lower(cls.group_name)
2086 gr = cls.query().filter(func.lower(cls.group_name)
2087 == func.lower(group_name))
2087 == func.lower(group_name))
2088 else:
2088 else:
2089 gr = cls.query().filter(cls.group_name == group_name)
2089 gr = cls.query().filter(cls.group_name == group_name)
2090 if cache:
2090 if cache:
2091 gr = gr.options(FromCache(
2091 gr = gr.options(FromCache(
2092 "sql_cache_short",
2092 "sql_cache_short",
2093 "get_group_%s" % _hash_key(group_name)))
2093 "get_group_%s" % _hash_key(group_name)))
2094 return gr.scalar()
2094 return gr.scalar()
2095
2095
2096 @classmethod
2096 @classmethod
2097 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2097 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2098 case_insensitive=True):
2098 case_insensitive=True):
2099 q = RepoGroup.query()
2099 q = RepoGroup.query()
2100
2100
2101 if not isinstance(user_id, Optional):
2101 if not isinstance(user_id, Optional):
2102 q = q.filter(RepoGroup.user_id == user_id)
2102 q = q.filter(RepoGroup.user_id == user_id)
2103
2103
2104 if not isinstance(group_id, Optional):
2104 if not isinstance(group_id, Optional):
2105 q = q.filter(RepoGroup.group_parent_id == group_id)
2105 q = q.filter(RepoGroup.group_parent_id == group_id)
2106
2106
2107 if case_insensitive:
2107 if case_insensitive:
2108 q = q.order_by(func.lower(RepoGroup.group_name))
2108 q = q.order_by(func.lower(RepoGroup.group_name))
2109 else:
2109 else:
2110 q = q.order_by(RepoGroup.group_name)
2110 q = q.order_by(RepoGroup.group_name)
2111 return q.all()
2111 return q.all()
2112
2112
2113 @property
2113 @property
2114 def parents(self):
2114 def parents(self):
2115 parents_recursion_limit = 10
2115 parents_recursion_limit = 10
2116 groups = []
2116 groups = []
2117 if self.parent_group is None:
2117 if self.parent_group is None:
2118 return groups
2118 return groups
2119 cur_gr = self.parent_group
2119 cur_gr = self.parent_group
2120 groups.insert(0, cur_gr)
2120 groups.insert(0, cur_gr)
2121 cnt = 0
2121 cnt = 0
2122 while 1:
2122 while 1:
2123 cnt += 1
2123 cnt += 1
2124 gr = getattr(cur_gr, 'parent_group', None)
2124 gr = getattr(cur_gr, 'parent_group', None)
2125 cur_gr = cur_gr.parent_group
2125 cur_gr = cur_gr.parent_group
2126 if gr is None:
2126 if gr is None:
2127 break
2127 break
2128 if cnt == parents_recursion_limit:
2128 if cnt == parents_recursion_limit:
2129 # this will prevent accidental infinit loops
2129 # this will prevent accidental infinit loops
2130 log.error(('more than %s parents found for group %s, stopping '
2130 log.error(('more than %s parents found for group %s, stopping '
2131 'recursive parent fetching' % (parents_recursion_limit, self)))
2131 'recursive parent fetching' % (parents_recursion_limit, self)))
2132 break
2132 break
2133
2133
2134 groups.insert(0, gr)
2134 groups.insert(0, gr)
2135 return groups
2135 return groups
2136
2136
2137 @property
2137 @property
2138 def children(self):
2138 def children(self):
2139 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2139 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2140
2140
2141 @property
2141 @property
2142 def name(self):
2142 def name(self):
2143 return self.group_name.split(RepoGroup.url_sep())[-1]
2143 return self.group_name.split(RepoGroup.url_sep())[-1]
2144
2144
2145 @property
2145 @property
2146 def full_path(self):
2146 def full_path(self):
2147 return self.group_name
2147 return self.group_name
2148
2148
2149 @property
2149 @property
2150 def full_path_splitted(self):
2150 def full_path_splitted(self):
2151 return self.group_name.split(RepoGroup.url_sep())
2151 return self.group_name.split(RepoGroup.url_sep())
2152
2152
2153 @property
2153 @property
2154 def repositories(self):
2154 def repositories(self):
2155 return Repository.query()\
2155 return Repository.query()\
2156 .filter(Repository.group == self)\
2156 .filter(Repository.group == self)\
2157 .order_by(Repository.repo_name)
2157 .order_by(Repository.repo_name)
2158
2158
2159 @property
2159 @property
2160 def repositories_recursive_count(self):
2160 def repositories_recursive_count(self):
2161 cnt = self.repositories.count()
2161 cnt = self.repositories.count()
2162
2162
2163 def children_count(group):
2163 def children_count(group):
2164 cnt = 0
2164 cnt = 0
2165 for child in group.children:
2165 for child in group.children:
2166 cnt += child.repositories.count()
2166 cnt += child.repositories.count()
2167 cnt += children_count(child)
2167 cnt += children_count(child)
2168 return cnt
2168 return cnt
2169
2169
2170 return cnt + children_count(self)
2170 return cnt + children_count(self)
2171
2171
2172 def _recursive_objects(self, include_repos=True):
2172 def _recursive_objects(self, include_repos=True):
2173 all_ = []
2173 all_ = []
2174
2174
2175 def _get_members(root_gr):
2175 def _get_members(root_gr):
2176 if include_repos:
2176 if include_repos:
2177 for r in root_gr.repositories:
2177 for r in root_gr.repositories:
2178 all_.append(r)
2178 all_.append(r)
2179 childs = root_gr.children.all()
2179 childs = root_gr.children.all()
2180 if childs:
2180 if childs:
2181 for gr in childs:
2181 for gr in childs:
2182 all_.append(gr)
2182 all_.append(gr)
2183 _get_members(gr)
2183 _get_members(gr)
2184
2184
2185 _get_members(self)
2185 _get_members(self)
2186 return [self] + all_
2186 return [self] + all_
2187
2187
2188 def recursive_groups_and_repos(self):
2188 def recursive_groups_and_repos(self):
2189 """
2189 """
2190 Recursive return all groups, with repositories in those groups
2190 Recursive return all groups, with repositories in those groups
2191 """
2191 """
2192 return self._recursive_objects()
2192 return self._recursive_objects()
2193
2193
2194 def recursive_groups(self):
2194 def recursive_groups(self):
2195 """
2195 """
2196 Returns all children groups for this group including children of children
2196 Returns all children groups for this group including children of children
2197 """
2197 """
2198 return self._recursive_objects(include_repos=False)
2198 return self._recursive_objects(include_repos=False)
2199
2199
2200 def get_new_name(self, group_name):
2200 def get_new_name(self, group_name):
2201 """
2201 """
2202 returns new full group name based on parent and new name
2202 returns new full group name based on parent and new name
2203
2203
2204 :param group_name:
2204 :param group_name:
2205 """
2205 """
2206 path_prefix = (self.parent_group.full_path_splitted if
2206 path_prefix = (self.parent_group.full_path_splitted if
2207 self.parent_group else [])
2207 self.parent_group else [])
2208 return RepoGroup.url_sep().join(path_prefix + [group_name])
2208 return RepoGroup.url_sep().join(path_prefix + [group_name])
2209
2209
2210 def permissions(self, with_admins=True, with_owner=True):
2210 def permissions(self, with_admins=True, with_owner=True):
2211 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2211 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2212 q = q.options(joinedload(UserRepoGroupToPerm.group),
2212 q = q.options(joinedload(UserRepoGroupToPerm.group),
2213 joinedload(UserRepoGroupToPerm.user),
2213 joinedload(UserRepoGroupToPerm.user),
2214 joinedload(UserRepoGroupToPerm.permission),)
2214 joinedload(UserRepoGroupToPerm.permission),)
2215
2215
2216 # get owners and admins and permissions. We do a trick of re-writing
2216 # get owners and admins and permissions. We do a trick of re-writing
2217 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2217 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2218 # has a global reference and changing one object propagates to all
2218 # has a global reference and changing one object propagates to all
2219 # others. This means if admin is also an owner admin_row that change
2219 # others. This means if admin is also an owner admin_row that change
2220 # would propagate to both objects
2220 # would propagate to both objects
2221 perm_rows = []
2221 perm_rows = []
2222 for _usr in q.all():
2222 for _usr in q.all():
2223 usr = AttributeDict(_usr.user.get_dict())
2223 usr = AttributeDict(_usr.user.get_dict())
2224 usr.permission = _usr.permission.permission_name
2224 usr.permission = _usr.permission.permission_name
2225 perm_rows.append(usr)
2225 perm_rows.append(usr)
2226
2226
2227 # filter the perm rows by 'default' first and then sort them by
2227 # filter the perm rows by 'default' first and then sort them by
2228 # admin,write,read,none permissions sorted again alphabetically in
2228 # admin,write,read,none permissions sorted again alphabetically in
2229 # each group
2229 # each group
2230 perm_rows = sorted(perm_rows, key=display_sort)
2230 perm_rows = sorted(perm_rows, key=display_sort)
2231
2231
2232 _admin_perm = 'group.admin'
2232 _admin_perm = 'group.admin'
2233 owner_row = []
2233 owner_row = []
2234 if with_owner:
2234 if with_owner:
2235 usr = AttributeDict(self.user.get_dict())
2235 usr = AttributeDict(self.user.get_dict())
2236 usr.owner_row = True
2236 usr.owner_row = True
2237 usr.permission = _admin_perm
2237 usr.permission = _admin_perm
2238 owner_row.append(usr)
2238 owner_row.append(usr)
2239
2239
2240 super_admin_rows = []
2240 super_admin_rows = []
2241 if with_admins:
2241 if with_admins:
2242 for usr in User.get_all_super_admins():
2242 for usr in User.get_all_super_admins():
2243 # if this admin is also owner, don't double the record
2243 # if this admin is also owner, don't double the record
2244 if usr.user_id == owner_row[0].user_id:
2244 if usr.user_id == owner_row[0].user_id:
2245 owner_row[0].admin_row = True
2245 owner_row[0].admin_row = True
2246 else:
2246 else:
2247 usr = AttributeDict(usr.get_dict())
2247 usr = AttributeDict(usr.get_dict())
2248 usr.admin_row = True
2248 usr.admin_row = True
2249 usr.permission = _admin_perm
2249 usr.permission = _admin_perm
2250 super_admin_rows.append(usr)
2250 super_admin_rows.append(usr)
2251
2251
2252 return super_admin_rows + owner_row + perm_rows
2252 return super_admin_rows + owner_row + perm_rows
2253
2253
2254 def permission_user_groups(self):
2254 def permission_user_groups(self):
2255 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2255 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2256 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2256 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2257 joinedload(UserGroupRepoGroupToPerm.users_group),
2257 joinedload(UserGroupRepoGroupToPerm.users_group),
2258 joinedload(UserGroupRepoGroupToPerm.permission),)
2258 joinedload(UserGroupRepoGroupToPerm.permission),)
2259
2259
2260 perm_rows = []
2260 perm_rows = []
2261 for _user_group in q.all():
2261 for _user_group in q.all():
2262 usr = AttributeDict(_user_group.users_group.get_dict())
2262 usr = AttributeDict(_user_group.users_group.get_dict())
2263 usr.permission = _user_group.permission.permission_name
2263 usr.permission = _user_group.permission.permission_name
2264 perm_rows.append(usr)
2264 perm_rows.append(usr)
2265
2265
2266 return perm_rows
2266 return perm_rows
2267
2267
2268 def get_api_data(self):
2268 def get_api_data(self):
2269 """
2269 """
2270 Common function for generating api data
2270 Common function for generating api data
2271
2271
2272 """
2272 """
2273 group = self
2273 group = self
2274 data = {
2274 data = {
2275 'group_id': group.group_id,
2275 'group_id': group.group_id,
2276 'group_name': group.group_name,
2276 'group_name': group.group_name,
2277 'group_description': group.group_description,
2277 'group_description': group.group_description,
2278 'parent_group': group.parent_group.group_name if group.parent_group else None,
2278 'parent_group': group.parent_group.group_name if group.parent_group else None,
2279 'repositories': [x.repo_name for x in group.repositories],
2279 'repositories': [x.repo_name for x in group.repositories],
2280 'owner': group.user.username,
2280 'owner': group.user.username,
2281 }
2281 }
2282 return data
2282 return data
2283
2283
2284
2284
2285 class Permission(Base, BaseModel):
2285 class Permission(Base, BaseModel):
2286 __tablename__ = 'permissions'
2286 __tablename__ = 'permissions'
2287 __table_args__ = (
2287 __table_args__ = (
2288 Index('p_perm_name_idx', 'permission_name'),
2288 Index('p_perm_name_idx', 'permission_name'),
2289 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2289 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2290 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2290 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2291 )
2291 )
2292 PERMS = [
2292 PERMS = [
2293 ('hg.admin', _('RhodeCode Super Administrator')),
2293 ('hg.admin', _('RhodeCode Super Administrator')),
2294
2294
2295 ('repository.none', _('Repository no access')),
2295 ('repository.none', _('Repository no access')),
2296 ('repository.read', _('Repository read access')),
2296 ('repository.read', _('Repository read access')),
2297 ('repository.write', _('Repository write access')),
2297 ('repository.write', _('Repository write access')),
2298 ('repository.admin', _('Repository admin access')),
2298 ('repository.admin', _('Repository admin access')),
2299
2299
2300 ('group.none', _('Repository group no access')),
2300 ('group.none', _('Repository group no access')),
2301 ('group.read', _('Repository group read access')),
2301 ('group.read', _('Repository group read access')),
2302 ('group.write', _('Repository group write access')),
2302 ('group.write', _('Repository group write access')),
2303 ('group.admin', _('Repository group admin access')),
2303 ('group.admin', _('Repository group admin access')),
2304
2304
2305 ('usergroup.none', _('User group no access')),
2305 ('usergroup.none', _('User group no access')),
2306 ('usergroup.read', _('User group read access')),
2306 ('usergroup.read', _('User group read access')),
2307 ('usergroup.write', _('User group write access')),
2307 ('usergroup.write', _('User group write access')),
2308 ('usergroup.admin', _('User group admin access')),
2308 ('usergroup.admin', _('User group admin access')),
2309
2309
2310 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2310 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2311 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2311 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2312
2312
2313 ('hg.usergroup.create.false', _('User Group creation disabled')),
2313 ('hg.usergroup.create.false', _('User Group creation disabled')),
2314 ('hg.usergroup.create.true', _('User Group creation enabled')),
2314 ('hg.usergroup.create.true', _('User Group creation enabled')),
2315
2315
2316 ('hg.create.none', _('Repository creation disabled')),
2316 ('hg.create.none', _('Repository creation disabled')),
2317 ('hg.create.repository', _('Repository creation enabled')),
2317 ('hg.create.repository', _('Repository creation enabled')),
2318 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2318 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2319 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2319 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2320
2320
2321 ('hg.fork.none', _('Repository forking disabled')),
2321 ('hg.fork.none', _('Repository forking disabled')),
2322 ('hg.fork.repository', _('Repository forking enabled')),
2322 ('hg.fork.repository', _('Repository forking enabled')),
2323
2323
2324 ('hg.register.none', _('Registration disabled')),
2324 ('hg.register.none', _('Registration disabled')),
2325 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2325 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2326 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2326 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2327
2327
2328 ('hg.extern_activate.manual', _('Manual activation of external account')),
2328 ('hg.extern_activate.manual', _('Manual activation of external account')),
2329 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2329 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2330
2330
2331 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2331 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2332 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2332 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2333 ]
2333 ]
2334
2334
2335 # definition of system default permissions for DEFAULT user
2335 # definition of system default permissions for DEFAULT user
2336 DEFAULT_USER_PERMISSIONS = [
2336 DEFAULT_USER_PERMISSIONS = [
2337 'repository.read',
2337 'repository.read',
2338 'group.read',
2338 'group.read',
2339 'usergroup.read',
2339 'usergroup.read',
2340 'hg.create.repository',
2340 'hg.create.repository',
2341 'hg.repogroup.create.false',
2341 'hg.repogroup.create.false',
2342 'hg.usergroup.create.false',
2342 'hg.usergroup.create.false',
2343 'hg.create.write_on_repogroup.true',
2343 'hg.create.write_on_repogroup.true',
2344 'hg.fork.repository',
2344 'hg.fork.repository',
2345 'hg.register.manual_activate',
2345 'hg.register.manual_activate',
2346 'hg.extern_activate.auto',
2346 'hg.extern_activate.auto',
2347 'hg.inherit_default_perms.true',
2347 'hg.inherit_default_perms.true',
2348 ]
2348 ]
2349
2349
2350 # defines which permissions are more important higher the more important
2350 # defines which permissions are more important higher the more important
2351 # Weight defines which permissions are more important.
2351 # Weight defines which permissions are more important.
2352 # The higher number the more important.
2352 # The higher number the more important.
2353 PERM_WEIGHTS = {
2353 PERM_WEIGHTS = {
2354 'repository.none': 0,
2354 'repository.none': 0,
2355 'repository.read': 1,
2355 'repository.read': 1,
2356 'repository.write': 3,
2356 'repository.write': 3,
2357 'repository.admin': 4,
2357 'repository.admin': 4,
2358
2358
2359 'group.none': 0,
2359 'group.none': 0,
2360 'group.read': 1,
2360 'group.read': 1,
2361 'group.write': 3,
2361 'group.write': 3,
2362 'group.admin': 4,
2362 'group.admin': 4,
2363
2363
2364 'usergroup.none': 0,
2364 'usergroup.none': 0,
2365 'usergroup.read': 1,
2365 'usergroup.read': 1,
2366 'usergroup.write': 3,
2366 'usergroup.write': 3,
2367 'usergroup.admin': 4,
2367 'usergroup.admin': 4,
2368
2368
2369 'hg.repogroup.create.false': 0,
2369 'hg.repogroup.create.false': 0,
2370 'hg.repogroup.create.true': 1,
2370 'hg.repogroup.create.true': 1,
2371
2371
2372 'hg.usergroup.create.false': 0,
2372 'hg.usergroup.create.false': 0,
2373 'hg.usergroup.create.true': 1,
2373 'hg.usergroup.create.true': 1,
2374
2374
2375 'hg.fork.none': 0,
2375 'hg.fork.none': 0,
2376 'hg.fork.repository': 1,
2376 'hg.fork.repository': 1,
2377 'hg.create.none': 0,
2377 'hg.create.none': 0,
2378 'hg.create.repository': 1
2378 'hg.create.repository': 1
2379 }
2379 }
2380
2380
2381 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2381 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2382 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2382 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2383 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2383 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2384
2384
2385 def __unicode__(self):
2385 def __unicode__(self):
2386 return u"<%s('%s:%s')>" % (
2386 return u"<%s('%s:%s')>" % (
2387 self.__class__.__name__, self.permission_id, self.permission_name
2387 self.__class__.__name__, self.permission_id, self.permission_name
2388 )
2388 )
2389
2389
2390 @classmethod
2390 @classmethod
2391 def get_by_key(cls, key):
2391 def get_by_key(cls, key):
2392 return cls.query().filter(cls.permission_name == key).scalar()
2392 return cls.query().filter(cls.permission_name == key).scalar()
2393
2393
2394 @classmethod
2394 @classmethod
2395 def get_default_repo_perms(cls, user_id, repo_id=None):
2395 def get_default_repo_perms(cls, user_id, repo_id=None):
2396 q = Session().query(UserRepoToPerm, Repository, Permission)\
2396 q = Session().query(UserRepoToPerm, Repository, Permission)\
2397 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2397 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2398 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2398 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2399 .filter(UserRepoToPerm.user_id == user_id)
2399 .filter(UserRepoToPerm.user_id == user_id)
2400 if repo_id:
2400 if repo_id:
2401 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2401 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2402 return q.all()
2402 return q.all()
2403
2403
2404 @classmethod
2404 @classmethod
2405 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2405 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2406 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2406 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2407 .join(
2407 .join(
2408 Permission,
2408 Permission,
2409 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2409 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2410 .join(
2410 .join(
2411 Repository,
2411 Repository,
2412 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2412 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2413 .join(
2413 .join(
2414 UserGroup,
2414 UserGroup,
2415 UserGroupRepoToPerm.users_group_id ==
2415 UserGroupRepoToPerm.users_group_id ==
2416 UserGroup.users_group_id)\
2416 UserGroup.users_group_id)\
2417 .join(
2417 .join(
2418 UserGroupMember,
2418 UserGroupMember,
2419 UserGroupRepoToPerm.users_group_id ==
2419 UserGroupRepoToPerm.users_group_id ==
2420 UserGroupMember.users_group_id)\
2420 UserGroupMember.users_group_id)\
2421 .filter(
2421 .filter(
2422 UserGroupMember.user_id == user_id,
2422 UserGroupMember.user_id == user_id,
2423 UserGroup.users_group_active == true())
2423 UserGroup.users_group_active == true())
2424 if repo_id:
2424 if repo_id:
2425 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2425 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2426 return q.all()
2426 return q.all()
2427
2427
2428 @classmethod
2428 @classmethod
2429 def get_default_group_perms(cls, user_id, repo_group_id=None):
2429 def get_default_group_perms(cls, user_id, repo_group_id=None):
2430 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2430 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2431 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2431 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2432 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2432 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2433 .filter(UserRepoGroupToPerm.user_id == user_id)
2433 .filter(UserRepoGroupToPerm.user_id == user_id)
2434 if repo_group_id:
2434 if repo_group_id:
2435 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2435 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2436 return q.all()
2436 return q.all()
2437
2437
2438 @classmethod
2438 @classmethod
2439 def get_default_group_perms_from_user_group(
2439 def get_default_group_perms_from_user_group(
2440 cls, user_id, repo_group_id=None):
2440 cls, user_id, repo_group_id=None):
2441 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2441 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2442 .join(
2442 .join(
2443 Permission,
2443 Permission,
2444 UserGroupRepoGroupToPerm.permission_id ==
2444 UserGroupRepoGroupToPerm.permission_id ==
2445 Permission.permission_id)\
2445 Permission.permission_id)\
2446 .join(
2446 .join(
2447 RepoGroup,
2447 RepoGroup,
2448 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2448 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2449 .join(
2449 .join(
2450 UserGroup,
2450 UserGroup,
2451 UserGroupRepoGroupToPerm.users_group_id ==
2451 UserGroupRepoGroupToPerm.users_group_id ==
2452 UserGroup.users_group_id)\
2452 UserGroup.users_group_id)\
2453 .join(
2453 .join(
2454 UserGroupMember,
2454 UserGroupMember,
2455 UserGroupRepoGroupToPerm.users_group_id ==
2455 UserGroupRepoGroupToPerm.users_group_id ==
2456 UserGroupMember.users_group_id)\
2456 UserGroupMember.users_group_id)\
2457 .filter(
2457 .filter(
2458 UserGroupMember.user_id == user_id,
2458 UserGroupMember.user_id == user_id,
2459 UserGroup.users_group_active == true())
2459 UserGroup.users_group_active == true())
2460 if repo_group_id:
2460 if repo_group_id:
2461 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2461 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2462 return q.all()
2462 return q.all()
2463
2463
2464 @classmethod
2464 @classmethod
2465 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2465 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2466 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2466 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2467 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2467 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2468 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2468 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2469 .filter(UserUserGroupToPerm.user_id == user_id)
2469 .filter(UserUserGroupToPerm.user_id == user_id)
2470 if user_group_id:
2470 if user_group_id:
2471 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2471 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2472 return q.all()
2472 return q.all()
2473
2473
2474 @classmethod
2474 @classmethod
2475 def get_default_user_group_perms_from_user_group(
2475 def get_default_user_group_perms_from_user_group(
2476 cls, user_id, user_group_id=None):
2476 cls, user_id, user_group_id=None):
2477 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2477 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2478 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2478 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2479 .join(
2479 .join(
2480 Permission,
2480 Permission,
2481 UserGroupUserGroupToPerm.permission_id ==
2481 UserGroupUserGroupToPerm.permission_id ==
2482 Permission.permission_id)\
2482 Permission.permission_id)\
2483 .join(
2483 .join(
2484 TargetUserGroup,
2484 TargetUserGroup,
2485 UserGroupUserGroupToPerm.target_user_group_id ==
2485 UserGroupUserGroupToPerm.target_user_group_id ==
2486 TargetUserGroup.users_group_id)\
2486 TargetUserGroup.users_group_id)\
2487 .join(
2487 .join(
2488 UserGroup,
2488 UserGroup,
2489 UserGroupUserGroupToPerm.user_group_id ==
2489 UserGroupUserGroupToPerm.user_group_id ==
2490 UserGroup.users_group_id)\
2490 UserGroup.users_group_id)\
2491 .join(
2491 .join(
2492 UserGroupMember,
2492 UserGroupMember,
2493 UserGroupUserGroupToPerm.user_group_id ==
2493 UserGroupUserGroupToPerm.user_group_id ==
2494 UserGroupMember.users_group_id)\
2494 UserGroupMember.users_group_id)\
2495 .filter(
2495 .filter(
2496 UserGroupMember.user_id == user_id,
2496 UserGroupMember.user_id == user_id,
2497 UserGroup.users_group_active == true())
2497 UserGroup.users_group_active == true())
2498 if user_group_id:
2498 if user_group_id:
2499 q = q.filter(
2499 q = q.filter(
2500 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2500 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2501
2501
2502 return q.all()
2502 return q.all()
2503
2503
2504
2504
2505 class UserRepoToPerm(Base, BaseModel):
2505 class UserRepoToPerm(Base, BaseModel):
2506 __tablename__ = 'repo_to_perm'
2506 __tablename__ = 'repo_to_perm'
2507 __table_args__ = (
2507 __table_args__ = (
2508 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2508 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2510 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2510 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2511 )
2511 )
2512 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2512 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2513 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2513 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2514 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2514 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2515 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2515 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2516
2516
2517 user = relationship('User')
2517 user = relationship('User')
2518 repository = relationship('Repository')
2518 repository = relationship('Repository')
2519 permission = relationship('Permission')
2519 permission = relationship('Permission')
2520
2520
2521 @classmethod
2521 @classmethod
2522 def create(cls, user, repository, permission):
2522 def create(cls, user, repository, permission):
2523 n = cls()
2523 n = cls()
2524 n.user = user
2524 n.user = user
2525 n.repository = repository
2525 n.repository = repository
2526 n.permission = permission
2526 n.permission = permission
2527 Session().add(n)
2527 Session().add(n)
2528 return n
2528 return n
2529
2529
2530 def __unicode__(self):
2530 def __unicode__(self):
2531 return u'<%s => %s >' % (self.user, self.repository)
2531 return u'<%s => %s >' % (self.user, self.repository)
2532
2532
2533
2533
2534 class UserUserGroupToPerm(Base, BaseModel):
2534 class UserUserGroupToPerm(Base, BaseModel):
2535 __tablename__ = 'user_user_group_to_perm'
2535 __tablename__ = 'user_user_group_to_perm'
2536 __table_args__ = (
2536 __table_args__ = (
2537 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2537 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2539 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2540 )
2540 )
2541 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2541 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2542 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2542 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2543 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2543 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2544 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2544 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2545
2545
2546 user = relationship('User')
2546 user = relationship('User')
2547 user_group = relationship('UserGroup')
2547 user_group = relationship('UserGroup')
2548 permission = relationship('Permission')
2548 permission = relationship('Permission')
2549
2549
2550 @classmethod
2550 @classmethod
2551 def create(cls, user, user_group, permission):
2551 def create(cls, user, user_group, permission):
2552 n = cls()
2552 n = cls()
2553 n.user = user
2553 n.user = user
2554 n.user_group = user_group
2554 n.user_group = user_group
2555 n.permission = permission
2555 n.permission = permission
2556 Session().add(n)
2556 Session().add(n)
2557 return n
2557 return n
2558
2558
2559 def __unicode__(self):
2559 def __unicode__(self):
2560 return u'<%s => %s >' % (self.user, self.user_group)
2560 return u'<%s => %s >' % (self.user, self.user_group)
2561
2561
2562
2562
2563 class UserToPerm(Base, BaseModel):
2563 class UserToPerm(Base, BaseModel):
2564 __tablename__ = 'user_to_perm'
2564 __tablename__ = 'user_to_perm'
2565 __table_args__ = (
2565 __table_args__ = (
2566 UniqueConstraint('user_id', 'permission_id'),
2566 UniqueConstraint('user_id', 'permission_id'),
2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2567 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2568 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2569 )
2569 )
2570 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2570 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2571 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2571 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2573
2573
2574 user = relationship('User')
2574 user = relationship('User')
2575 permission = relationship('Permission', lazy='joined')
2575 permission = relationship('Permission', lazy='joined')
2576
2576
2577 def __unicode__(self):
2577 def __unicode__(self):
2578 return u'<%s => %s >' % (self.user, self.permission)
2578 return u'<%s => %s >' % (self.user, self.permission)
2579
2579
2580
2580
2581 class UserGroupRepoToPerm(Base, BaseModel):
2581 class UserGroupRepoToPerm(Base, BaseModel):
2582 __tablename__ = 'users_group_repo_to_perm'
2582 __tablename__ = 'users_group_repo_to_perm'
2583 __table_args__ = (
2583 __table_args__ = (
2584 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2584 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2585 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2585 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2586 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2586 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2587 )
2587 )
2588 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2588 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2589 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2589 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2590 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2590 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2592
2592
2593 users_group = relationship('UserGroup')
2593 users_group = relationship('UserGroup')
2594 permission = relationship('Permission')
2594 permission = relationship('Permission')
2595 repository = relationship('Repository')
2595 repository = relationship('Repository')
2596
2596
2597 @classmethod
2597 @classmethod
2598 def create(cls, users_group, repository, permission):
2598 def create(cls, users_group, repository, permission):
2599 n = cls()
2599 n = cls()
2600 n.users_group = users_group
2600 n.users_group = users_group
2601 n.repository = repository
2601 n.repository = repository
2602 n.permission = permission
2602 n.permission = permission
2603 Session().add(n)
2603 Session().add(n)
2604 return n
2604 return n
2605
2605
2606 def __unicode__(self):
2606 def __unicode__(self):
2607 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2607 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2608
2608
2609
2609
2610 class UserGroupUserGroupToPerm(Base, BaseModel):
2610 class UserGroupUserGroupToPerm(Base, BaseModel):
2611 __tablename__ = 'user_group_user_group_to_perm'
2611 __tablename__ = 'user_group_user_group_to_perm'
2612 __table_args__ = (
2612 __table_args__ = (
2613 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2613 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2614 CheckConstraint('target_user_group_id != user_group_id'),
2614 CheckConstraint('target_user_group_id != user_group_id'),
2615 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2615 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2616 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2616 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2617 )
2617 )
2618 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)
2618 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)
2619 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2619 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2620 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2620 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2621 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2621 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2622
2622
2623 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2623 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2624 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2624 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2625 permission = relationship('Permission')
2625 permission = relationship('Permission')
2626
2626
2627 @classmethod
2627 @classmethod
2628 def create(cls, target_user_group, user_group, permission):
2628 def create(cls, target_user_group, user_group, permission):
2629 n = cls()
2629 n = cls()
2630 n.target_user_group = target_user_group
2630 n.target_user_group = target_user_group
2631 n.user_group = user_group
2631 n.user_group = user_group
2632 n.permission = permission
2632 n.permission = permission
2633 Session().add(n)
2633 Session().add(n)
2634 return n
2634 return n
2635
2635
2636 def __unicode__(self):
2636 def __unicode__(self):
2637 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2637 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2638
2638
2639
2639
2640 class UserGroupToPerm(Base, BaseModel):
2640 class UserGroupToPerm(Base, BaseModel):
2641 __tablename__ = 'users_group_to_perm'
2641 __tablename__ = 'users_group_to_perm'
2642 __table_args__ = (
2642 __table_args__ = (
2643 UniqueConstraint('users_group_id', 'permission_id',),
2643 UniqueConstraint('users_group_id', 'permission_id',),
2644 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2644 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2645 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2645 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2646 )
2646 )
2647 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2647 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2648 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2648 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2649 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2649 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2650
2650
2651 users_group = relationship('UserGroup')
2651 users_group = relationship('UserGroup')
2652 permission = relationship('Permission')
2652 permission = relationship('Permission')
2653
2653
2654
2654
2655 class UserRepoGroupToPerm(Base, BaseModel):
2655 class UserRepoGroupToPerm(Base, BaseModel):
2656 __tablename__ = 'user_repo_group_to_perm'
2656 __tablename__ = 'user_repo_group_to_perm'
2657 __table_args__ = (
2657 __table_args__ = (
2658 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2658 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2660 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2660 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2661 )
2661 )
2662
2662
2663 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2663 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2665 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2665 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2666 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2666 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2667
2667
2668 user = relationship('User')
2668 user = relationship('User')
2669 group = relationship('RepoGroup')
2669 group = relationship('RepoGroup')
2670 permission = relationship('Permission')
2670 permission = relationship('Permission')
2671
2671
2672 @classmethod
2672 @classmethod
2673 def create(cls, user, repository_group, permission):
2673 def create(cls, user, repository_group, permission):
2674 n = cls()
2674 n = cls()
2675 n.user = user
2675 n.user = user
2676 n.group = repository_group
2676 n.group = repository_group
2677 n.permission = permission
2677 n.permission = permission
2678 Session().add(n)
2678 Session().add(n)
2679 return n
2679 return n
2680
2680
2681
2681
2682 class UserGroupRepoGroupToPerm(Base, BaseModel):
2682 class UserGroupRepoGroupToPerm(Base, BaseModel):
2683 __tablename__ = 'users_group_repo_group_to_perm'
2683 __tablename__ = 'users_group_repo_group_to_perm'
2684 __table_args__ = (
2684 __table_args__ = (
2685 UniqueConstraint('users_group_id', 'group_id'),
2685 UniqueConstraint('users_group_id', 'group_id'),
2686 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2686 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2687 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2687 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2688 )
2688 )
2689
2689
2690 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)
2690 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)
2691 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2691 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2692 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2692 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2693 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2693 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2694
2694
2695 users_group = relationship('UserGroup')
2695 users_group = relationship('UserGroup')
2696 permission = relationship('Permission')
2696 permission = relationship('Permission')
2697 group = relationship('RepoGroup')
2697 group = relationship('RepoGroup')
2698
2698
2699 @classmethod
2699 @classmethod
2700 def create(cls, user_group, repository_group, permission):
2700 def create(cls, user_group, repository_group, permission):
2701 n = cls()
2701 n = cls()
2702 n.users_group = user_group
2702 n.users_group = user_group
2703 n.group = repository_group
2703 n.group = repository_group
2704 n.permission = permission
2704 n.permission = permission
2705 Session().add(n)
2705 Session().add(n)
2706 return n
2706 return n
2707
2707
2708 def __unicode__(self):
2708 def __unicode__(self):
2709 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2709 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2710
2710
2711
2711
2712 class Statistics(Base, BaseModel):
2712 class Statistics(Base, BaseModel):
2713 __tablename__ = 'statistics'
2713 __tablename__ = 'statistics'
2714 __table_args__ = (
2714 __table_args__ = (
2715 UniqueConstraint('repository_id'),
2715 UniqueConstraint('repository_id'),
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2718 )
2718 )
2719 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2719 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2720 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2720 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2721 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2721 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2722 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2722 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2723 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2723 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2724 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2724 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2725
2725
2726 repository = relationship('Repository', single_parent=True)
2726 repository = relationship('Repository', single_parent=True)
2727
2727
2728
2728
2729 class UserFollowing(Base, BaseModel):
2729 class UserFollowing(Base, BaseModel):
2730 __tablename__ = 'user_followings'
2730 __tablename__ = 'user_followings'
2731 __table_args__ = (
2731 __table_args__ = (
2732 UniqueConstraint('user_id', 'follows_repository_id'),
2732 UniqueConstraint('user_id', 'follows_repository_id'),
2733 UniqueConstraint('user_id', 'follows_user_id'),
2733 UniqueConstraint('user_id', 'follows_user_id'),
2734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2735 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2735 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2736 )
2736 )
2737
2737
2738 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2738 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2739 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2739 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2740 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2740 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2741 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2741 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2742 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2742 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2743
2743
2744 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2744 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2745
2745
2746 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2746 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2747 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2747 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2748
2748
2749 @classmethod
2749 @classmethod
2750 def get_repo_followers(cls, repo_id):
2750 def get_repo_followers(cls, repo_id):
2751 return cls.query().filter(cls.follows_repo_id == repo_id)
2751 return cls.query().filter(cls.follows_repo_id == repo_id)
2752
2752
2753
2753
2754 class CacheKey(Base, BaseModel):
2754 class CacheKey(Base, BaseModel):
2755 __tablename__ = 'cache_invalidation'
2755 __tablename__ = 'cache_invalidation'
2756 __table_args__ = (
2756 __table_args__ = (
2757 UniqueConstraint('cache_key'),
2757 UniqueConstraint('cache_key'),
2758 Index('key_idx', 'cache_key'),
2758 Index('key_idx', 'cache_key'),
2759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2759 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2760 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2760 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2761 )
2761 )
2762 CACHE_TYPE_ATOM = 'ATOM'
2762 CACHE_TYPE_ATOM = 'ATOM'
2763 CACHE_TYPE_RSS = 'RSS'
2763 CACHE_TYPE_RSS = 'RSS'
2764 CACHE_TYPE_README = 'README'
2764 CACHE_TYPE_README = 'README'
2765
2765
2766 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2766 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2767 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2767 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2768 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2768 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2769 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2769 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2770
2770
2771 def __init__(self, cache_key, cache_args=''):
2771 def __init__(self, cache_key, cache_args=''):
2772 self.cache_key = cache_key
2772 self.cache_key = cache_key
2773 self.cache_args = cache_args
2773 self.cache_args = cache_args
2774 self.cache_active = False
2774 self.cache_active = False
2775
2775
2776 def __unicode__(self):
2776 def __unicode__(self):
2777 return u"<%s('%s:%s[%s]')>" % (
2777 return u"<%s('%s:%s[%s]')>" % (
2778 self.__class__.__name__,
2778 self.__class__.__name__,
2779 self.cache_id, self.cache_key, self.cache_active)
2779 self.cache_id, self.cache_key, self.cache_active)
2780
2780
2781 def _cache_key_partition(self):
2781 def _cache_key_partition(self):
2782 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2782 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2783 return prefix, repo_name, suffix
2783 return prefix, repo_name, suffix
2784
2784
2785 def get_prefix(self):
2785 def get_prefix(self):
2786 """
2786 """
2787 Try to extract prefix from existing cache key. The key could consist
2787 Try to extract prefix from existing cache key. The key could consist
2788 of prefix, repo_name, suffix
2788 of prefix, repo_name, suffix
2789 """
2789 """
2790 # this returns prefix, repo_name, suffix
2790 # this returns prefix, repo_name, suffix
2791 return self._cache_key_partition()[0]
2791 return self._cache_key_partition()[0]
2792
2792
2793 def get_suffix(self):
2793 def get_suffix(self):
2794 """
2794 """
2795 get suffix that might have been used in _get_cache_key to
2795 get suffix that might have been used in _get_cache_key to
2796 generate self.cache_key. Only used for informational purposes
2796 generate self.cache_key. Only used for informational purposes
2797 in repo_edit.html.
2797 in repo_edit.html.
2798 """
2798 """
2799 # prefix, repo_name, suffix
2799 # prefix, repo_name, suffix
2800 return self._cache_key_partition()[2]
2800 return self._cache_key_partition()[2]
2801
2801
2802 @classmethod
2802 @classmethod
2803 def delete_all_cache(cls):
2803 def delete_all_cache(cls):
2804 """
2804 """
2805 Delete all cache keys from database.
2805 Delete all cache keys from database.
2806 Should only be run when all instances are down and all entries
2806 Should only be run when all instances are down and all entries
2807 thus stale.
2807 thus stale.
2808 """
2808 """
2809 cls.query().delete()
2809 cls.query().delete()
2810 Session().commit()
2810 Session().commit()
2811
2811
2812 @classmethod
2812 @classmethod
2813 def get_cache_key(cls, repo_name, cache_type):
2813 def get_cache_key(cls, repo_name, cache_type):
2814 """
2814 """
2815
2815
2816 Generate a cache key for this process of RhodeCode instance.
2816 Generate a cache key for this process of RhodeCode instance.
2817 Prefix most likely will be process id or maybe explicitly set
2817 Prefix most likely will be process id or maybe explicitly set
2818 instance_id from .ini file.
2818 instance_id from .ini file.
2819 """
2819 """
2820 import rhodecode
2820 import rhodecode
2821 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2821 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2822
2822
2823 repo_as_unicode = safe_unicode(repo_name)
2823 repo_as_unicode = safe_unicode(repo_name)
2824 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2824 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2825 if cache_type else repo_as_unicode
2825 if cache_type else repo_as_unicode
2826
2826
2827 return u'{}{}'.format(prefix, key)
2827 return u'{}{}'.format(prefix, key)
2828
2828
2829 @classmethod
2829 @classmethod
2830 def set_invalidate(cls, repo_name, delete=False):
2830 def set_invalidate(cls, repo_name, delete=False):
2831 """
2831 """
2832 Mark all caches of a repo as invalid in the database.
2832 Mark all caches of a repo as invalid in the database.
2833 """
2833 """
2834
2834
2835 try:
2835 try:
2836 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2836 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2837 if delete:
2837 if delete:
2838 log.debug('cache objects deleted for repo %s',
2838 log.debug('cache objects deleted for repo %s',
2839 safe_str(repo_name))
2839 safe_str(repo_name))
2840 qry.delete()
2840 qry.delete()
2841 else:
2841 else:
2842 log.debug('cache objects marked as invalid for repo %s',
2842 log.debug('cache objects marked as invalid for repo %s',
2843 safe_str(repo_name))
2843 safe_str(repo_name))
2844 qry.update({"cache_active": False})
2844 qry.update({"cache_active": False})
2845
2845
2846 Session().commit()
2846 Session().commit()
2847 except Exception:
2847 except Exception:
2848 log.exception(
2848 log.exception(
2849 'Cache key invalidation failed for repository %s',
2849 'Cache key invalidation failed for repository %s',
2850 safe_str(repo_name))
2850 safe_str(repo_name))
2851 Session().rollback()
2851 Session().rollback()
2852
2852
2853 @classmethod
2853 @classmethod
2854 def get_active_cache(cls, cache_key):
2854 def get_active_cache(cls, cache_key):
2855 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2855 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2856 if inv_obj:
2856 if inv_obj:
2857 return inv_obj
2857 return inv_obj
2858 return None
2858 return None
2859
2859
2860 @classmethod
2860 @classmethod
2861 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2861 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2862 """
2862 """
2863 @cache_region('long_term')
2863 @cache_region('long_term')
2864 def _heavy_calculation(cache_key):
2864 def _heavy_calculation(cache_key):
2865 return 'result'
2865 return 'result'
2866
2866
2867 cache_context = CacheKey.repo_context_cache(
2867 cache_context = CacheKey.repo_context_cache(
2868 _heavy_calculation, repo_name, cache_type)
2868 _heavy_calculation, repo_name, cache_type)
2869
2869
2870 with cache_context as context:
2870 with cache_context as context:
2871 context.invalidate()
2871 context.invalidate()
2872 computed = context.compute()
2872 computed = context.compute()
2873
2873
2874 assert computed == 'result'
2874 assert computed == 'result'
2875 """
2875 """
2876 from rhodecode.lib import caches
2876 from rhodecode.lib import caches
2877 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2877 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2878
2878
2879
2879
2880 class ChangesetComment(Base, BaseModel):
2880 class ChangesetComment(Base, BaseModel):
2881 __tablename__ = 'changeset_comments'
2881 __tablename__ = 'changeset_comments'
2882 __table_args__ = (
2882 __table_args__ = (
2883 Index('cc_revision_idx', 'revision'),
2883 Index('cc_revision_idx', 'revision'),
2884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2885 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2885 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2886 )
2886 )
2887
2887
2888 COMMENT_OUTDATED = u'comment_outdated'
2888 COMMENT_OUTDATED = u'comment_outdated'
2889
2889
2890 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2890 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2891 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2891 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2892 revision = Column('revision', String(40), nullable=True)
2892 revision = Column('revision', String(40), nullable=True)
2893 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2893 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2894 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2894 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2895 line_no = Column('line_no', Unicode(10), nullable=True)
2895 line_no = Column('line_no', Unicode(10), nullable=True)
2896 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2896 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2897 f_path = Column('f_path', Unicode(1000), nullable=True)
2897 f_path = Column('f_path', Unicode(1000), nullable=True)
2898 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2898 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2899 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2899 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2901 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2901 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2902 renderer = Column('renderer', Unicode(64), nullable=True)
2902 renderer = Column('renderer', Unicode(64), nullable=True)
2903 display_state = Column('display_state', Unicode(128), nullable=True)
2903 display_state = Column('display_state', Unicode(128), nullable=True)
2904
2904
2905 author = relationship('User', lazy='joined')
2905 author = relationship('User', lazy='joined')
2906 repo = relationship('Repository')
2906 repo = relationship('Repository')
2907 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2907 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2908 pull_request = relationship('PullRequest', lazy='joined')
2908 pull_request = relationship('PullRequest', lazy='joined')
2909 pull_request_version = relationship('PullRequestVersion')
2909 pull_request_version = relationship('PullRequestVersion')
2910
2910
2911 @classmethod
2911 @classmethod
2912 def get_users(cls, revision=None, pull_request_id=None):
2912 def get_users(cls, revision=None, pull_request_id=None):
2913 """
2913 """
2914 Returns user associated with this ChangesetComment. ie those
2914 Returns user associated with this ChangesetComment. ie those
2915 who actually commented
2915 who actually commented
2916
2916
2917 :param cls:
2917 :param cls:
2918 :param revision:
2918 :param revision:
2919 """
2919 """
2920 q = Session().query(User)\
2920 q = Session().query(User)\
2921 .join(ChangesetComment.author)
2921 .join(ChangesetComment.author)
2922 if revision:
2922 if revision:
2923 q = q.filter(cls.revision == revision)
2923 q = q.filter(cls.revision == revision)
2924 elif pull_request_id:
2924 elif pull_request_id:
2925 q = q.filter(cls.pull_request_id == pull_request_id)
2925 q = q.filter(cls.pull_request_id == pull_request_id)
2926 return q.all()
2926 return q.all()
2927
2927
2928 def render(self, mentions=False):
2928 def render(self, mentions=False):
2929 from rhodecode.lib import helpers as h
2929 from rhodecode.lib import helpers as h
2930 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2930 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2931
2931
2932 def __repr__(self):
2932 def __repr__(self):
2933 if self.comment_id:
2933 if self.comment_id:
2934 return '<DB:ChangesetComment #%s>' % self.comment_id
2934 return '<DB:ChangesetComment #%s>' % self.comment_id
2935 else:
2935 else:
2936 return '<DB:ChangesetComment at %#x>' % id(self)
2936 return '<DB:ChangesetComment at %#x>' % id(self)
2937
2937
2938
2938
2939 class ChangesetStatus(Base, BaseModel):
2939 class ChangesetStatus(Base, BaseModel):
2940 __tablename__ = 'changeset_statuses'
2940 __tablename__ = 'changeset_statuses'
2941 __table_args__ = (
2941 __table_args__ = (
2942 Index('cs_revision_idx', 'revision'),
2942 Index('cs_revision_idx', 'revision'),
2943 Index('cs_version_idx', 'version'),
2943 Index('cs_version_idx', 'version'),
2944 UniqueConstraint('repo_id', 'revision', 'version'),
2944 UniqueConstraint('repo_id', 'revision', 'version'),
2945 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2945 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2946 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2946 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2947 )
2947 )
2948 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2948 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2949 STATUS_APPROVED = 'approved'
2949 STATUS_APPROVED = 'approved'
2950 STATUS_REJECTED = 'rejected'
2950 STATUS_REJECTED = 'rejected'
2951 STATUS_UNDER_REVIEW = 'under_review'
2951 STATUS_UNDER_REVIEW = 'under_review'
2952
2952
2953 STATUSES = [
2953 STATUSES = [
2954 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2954 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2955 (STATUS_APPROVED, _("Approved")),
2955 (STATUS_APPROVED, _("Approved")),
2956 (STATUS_REJECTED, _("Rejected")),
2956 (STATUS_REJECTED, _("Rejected")),
2957 (STATUS_UNDER_REVIEW, _("Under Review")),
2957 (STATUS_UNDER_REVIEW, _("Under Review")),
2958 ]
2958 ]
2959
2959
2960 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2960 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2961 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2961 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2963 revision = Column('revision', String(40), nullable=False)
2963 revision = Column('revision', String(40), nullable=False)
2964 status = Column('status', String(128), nullable=False, default=DEFAULT)
2964 status = Column('status', String(128), nullable=False, default=DEFAULT)
2965 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2965 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2966 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2966 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2967 version = Column('version', Integer(), nullable=False, default=0)
2967 version = Column('version', Integer(), nullable=False, default=0)
2968 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2968 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2969
2969
2970 author = relationship('User', lazy='joined')
2970 author = relationship('User', lazy='joined')
2971 repo = relationship('Repository')
2971 repo = relationship('Repository')
2972 comment = relationship('ChangesetComment', lazy='joined')
2972 comment = relationship('ChangesetComment', lazy='joined')
2973 pull_request = relationship('PullRequest', lazy='joined')
2973 pull_request = relationship('PullRequest', lazy='joined')
2974
2974
2975 def __unicode__(self):
2975 def __unicode__(self):
2976 return u"<%s('%s[%s]:%s')>" % (
2976 return u"<%s('%s[%s]:%s')>" % (
2977 self.__class__.__name__,
2977 self.__class__.__name__,
2978 self.status, self.version, self.author
2978 self.status, self.version, self.author
2979 )
2979 )
2980
2980
2981 @classmethod
2981 @classmethod
2982 def get_status_lbl(cls, value):
2982 def get_status_lbl(cls, value):
2983 return dict(cls.STATUSES).get(value)
2983 return dict(cls.STATUSES).get(value)
2984
2984
2985 @property
2985 @property
2986 def status_lbl(self):
2986 def status_lbl(self):
2987 return ChangesetStatus.get_status_lbl(self.status)
2987 return ChangesetStatus.get_status_lbl(self.status)
2988
2988
2989
2989
2990 class _PullRequestBase(BaseModel):
2990 class _PullRequestBase(BaseModel):
2991 """
2991 """
2992 Common attributes of pull request and version entries.
2992 Common attributes of pull request and version entries.
2993 """
2993 """
2994
2994
2995 # .status values
2995 # .status values
2996 STATUS_NEW = u'new'
2996 STATUS_NEW = u'new'
2997 STATUS_OPEN = u'open'
2997 STATUS_OPEN = u'open'
2998 STATUS_CLOSED = u'closed'
2998 STATUS_CLOSED = u'closed'
2999
2999
3000 title = Column('title', Unicode(255), nullable=True)
3000 title = Column('title', Unicode(255), nullable=True)
3001 description = Column(
3001 description = Column(
3002 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3002 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3003 nullable=True)
3003 nullable=True)
3004 # new/open/closed status of pull request (not approve/reject/etc)
3004 # new/open/closed status of pull request (not approve/reject/etc)
3005 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3005 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3006 created_on = Column(
3006 created_on = Column(
3007 'created_on', DateTime(timezone=False), nullable=False,
3007 'created_on', DateTime(timezone=False), nullable=False,
3008 default=datetime.datetime.now)
3008 default=datetime.datetime.now)
3009 updated_on = Column(
3009 updated_on = Column(
3010 'updated_on', DateTime(timezone=False), nullable=False,
3010 'updated_on', DateTime(timezone=False), nullable=False,
3011 default=datetime.datetime.now)
3011 default=datetime.datetime.now)
3012
3012
3013 @declared_attr
3013 @declared_attr
3014 def user_id(cls):
3014 def user_id(cls):
3015 return Column(
3015 return Column(
3016 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3016 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3017 unique=None)
3017 unique=None)
3018
3018
3019 # 500 revisions max
3019 # 500 revisions max
3020 _revisions = Column(
3020 _revisions = Column(
3021 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3021 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3022
3022
3023 @declared_attr
3023 @declared_attr
3024 def source_repo_id(cls):
3024 def source_repo_id(cls):
3025 # TODO: dan: rename column to source_repo_id
3025 # TODO: dan: rename column to source_repo_id
3026 return Column(
3026 return Column(
3027 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3027 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3028 nullable=False)
3028 nullable=False)
3029
3029
3030 source_ref = Column('org_ref', Unicode(255), nullable=False)
3030 source_ref = Column('org_ref', Unicode(255), nullable=False)
3031
3031
3032 @declared_attr
3032 @declared_attr
3033 def target_repo_id(cls):
3033 def target_repo_id(cls):
3034 # TODO: dan: rename column to target_repo_id
3034 # TODO: dan: rename column to target_repo_id
3035 return Column(
3035 return Column(
3036 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3036 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3037 nullable=False)
3037 nullable=False)
3038
3038
3039 target_ref = Column('other_ref', Unicode(255), nullable=False)
3039 target_ref = Column('other_ref', Unicode(255), nullable=False)
3040
3040
3041 # TODO: dan: rename column to last_merge_source_rev
3041 # TODO: dan: rename column to last_merge_source_rev
3042 _last_merge_source_rev = Column(
3042 _last_merge_source_rev = Column(
3043 'last_merge_org_rev', String(40), nullable=True)
3043 'last_merge_org_rev', String(40), nullable=True)
3044 # TODO: dan: rename column to last_merge_target_rev
3044 # TODO: dan: rename column to last_merge_target_rev
3045 _last_merge_target_rev = Column(
3045 _last_merge_target_rev = Column(
3046 'last_merge_other_rev', String(40), nullable=True)
3046 'last_merge_other_rev', String(40), nullable=True)
3047 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3047 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3048 merge_rev = Column('merge_rev', String(40), nullable=True)
3048 merge_rev = Column('merge_rev', String(40), nullable=True)
3049
3049
3050 @hybrid_property
3050 @hybrid_property
3051 def revisions(self):
3051 def revisions(self):
3052 return self._revisions.split(':') if self._revisions else []
3052 return self._revisions.split(':') if self._revisions else []
3053
3053
3054 @revisions.setter
3054 @revisions.setter
3055 def revisions(self, val):
3055 def revisions(self, val):
3056 self._revisions = ':'.join(val)
3056 self._revisions = ':'.join(val)
3057
3057
3058 @declared_attr
3058 @declared_attr
3059 def author(cls):
3059 def author(cls):
3060 return relationship('User', lazy='joined')
3060 return relationship('User', lazy='joined')
3061
3061
3062 @declared_attr
3062 @declared_attr
3063 def source_repo(cls):
3063 def source_repo(cls):
3064 return relationship(
3064 return relationship(
3065 'Repository',
3065 'Repository',
3066 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3066 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3067
3067
3068 @property
3068 @property
3069 def source_ref_parts(self):
3069 def source_ref_parts(self):
3070 refs = self.source_ref.split(':')
3070 refs = self.source_ref.split(':')
3071 return Reference(refs[0], refs[1], refs[2])
3071 return Reference(refs[0], refs[1], refs[2])
3072
3072
3073 @declared_attr
3073 @declared_attr
3074 def target_repo(cls):
3074 def target_repo(cls):
3075 return relationship(
3075 return relationship(
3076 'Repository',
3076 'Repository',
3077 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3077 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3078
3078
3079 @property
3079 @property
3080 def target_ref_parts(self):
3080 def target_ref_parts(self):
3081 refs = self.target_ref.split(':')
3081 refs = self.target_ref.split(':')
3082 return Reference(refs[0], refs[1], refs[2])
3082 return Reference(refs[0], refs[1], refs[2])
3083
3083
3084
3084
3085 class PullRequest(Base, _PullRequestBase):
3085 class PullRequest(Base, _PullRequestBase):
3086 __tablename__ = 'pull_requests'
3086 __tablename__ = 'pull_requests'
3087 __table_args__ = (
3087 __table_args__ = (
3088 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3088 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3089 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3089 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3090 )
3090 )
3091
3091
3092 pull_request_id = Column(
3092 pull_request_id = Column(
3093 'pull_request_id', Integer(), nullable=False, primary_key=True)
3093 'pull_request_id', Integer(), nullable=False, primary_key=True)
3094
3094
3095 def __repr__(self):
3095 def __repr__(self):
3096 if self.pull_request_id:
3096 if self.pull_request_id:
3097 return '<DB:PullRequest #%s>' % self.pull_request_id
3097 return '<DB:PullRequest #%s>' % self.pull_request_id
3098 else:
3098 else:
3099 return '<DB:PullRequest at %#x>' % id(self)
3099 return '<DB:PullRequest at %#x>' % id(self)
3100
3100
3101 reviewers = relationship('PullRequestReviewers',
3101 reviewers = relationship('PullRequestReviewers',
3102 cascade="all, delete, delete-orphan")
3102 cascade="all, delete, delete-orphan")
3103 statuses = relationship('ChangesetStatus')
3103 statuses = relationship('ChangesetStatus')
3104 comments = relationship('ChangesetComment',
3104 comments = relationship('ChangesetComment',
3105 cascade="all, delete, delete-orphan")
3105 cascade="all, delete, delete-orphan")
3106 versions = relationship('PullRequestVersion',
3106 versions = relationship('PullRequestVersion',
3107 cascade="all, delete, delete-orphan")
3107 cascade="all, delete, delete-orphan")
3108
3108
3109 def is_closed(self):
3109 def is_closed(self):
3110 return self.status == self.STATUS_CLOSED
3110 return self.status == self.STATUS_CLOSED
3111
3111
3112 def get_api_data(self):
3112 def get_api_data(self):
3113 from rhodecode.model.pull_request import PullRequestModel
3113 from rhodecode.model.pull_request import PullRequestModel
3114 pull_request = self
3114 pull_request = self
3115 merge_status = PullRequestModel().merge_status(pull_request)
3115 merge_status = PullRequestModel().merge_status(pull_request)
3116 data = {
3116 data = {
3117 'pull_request_id': pull_request.pull_request_id,
3117 'pull_request_id': pull_request.pull_request_id,
3118 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3118 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3119 pull_request_id=self.pull_request_id,
3119 pull_request_id=self.pull_request_id,
3120 qualified=True),
3120 qualified=True),
3121 'title': pull_request.title,
3121 'title': pull_request.title,
3122 'description': pull_request.description,
3122 'description': pull_request.description,
3123 'status': pull_request.status,
3123 'status': pull_request.status,
3124 'created_on': pull_request.created_on,
3124 'created_on': pull_request.created_on,
3125 'updated_on': pull_request.updated_on,
3125 'updated_on': pull_request.updated_on,
3126 'commit_ids': pull_request.revisions,
3126 'commit_ids': pull_request.revisions,
3127 'review_status': pull_request.calculated_review_status(),
3127 'review_status': pull_request.calculated_review_status(),
3128 'mergeable': {
3128 'mergeable': {
3129 'status': merge_status[0],
3129 'status': merge_status[0],
3130 'message': unicode(merge_status[1]),
3130 'message': unicode(merge_status[1]),
3131 },
3131 },
3132 'source': {
3132 'source': {
3133 'clone_url': pull_request.source_repo.clone_url(),
3133 'clone_url': pull_request.source_repo.clone_url(),
3134 'repository': pull_request.source_repo.repo_name,
3134 'repository': pull_request.source_repo.repo_name,
3135 'reference': {
3135 'reference': {
3136 'name': pull_request.source_ref_parts.name,
3136 'name': pull_request.source_ref_parts.name,
3137 'type': pull_request.source_ref_parts.type,
3137 'type': pull_request.source_ref_parts.type,
3138 'commit_id': pull_request.source_ref_parts.commit_id,
3138 'commit_id': pull_request.source_ref_parts.commit_id,
3139 },
3139 },
3140 },
3140 },
3141 'target': {
3141 'target': {
3142 'clone_url': pull_request.target_repo.clone_url(),
3142 'clone_url': pull_request.target_repo.clone_url(),
3143 'repository': pull_request.target_repo.repo_name,
3143 'repository': pull_request.target_repo.repo_name,
3144 'reference': {
3144 'reference': {
3145 'name': pull_request.target_ref_parts.name,
3145 'name': pull_request.target_ref_parts.name,
3146 'type': pull_request.target_ref_parts.type,
3146 'type': pull_request.target_ref_parts.type,
3147 'commit_id': pull_request.target_ref_parts.commit_id,
3147 'commit_id': pull_request.target_ref_parts.commit_id,
3148 },
3148 },
3149 },
3149 },
3150 'author': pull_request.author.get_api_data(include_secrets=False,
3150 'author': pull_request.author.get_api_data(include_secrets=False,
3151 details='basic'),
3151 details='basic'),
3152 'reviewers': [
3152 'reviewers': [
3153 {
3153 {
3154 'user': reviewer.get_api_data(include_secrets=False,
3154 'user': reviewer.get_api_data(include_secrets=False,
3155 details='basic'),
3155 details='basic'),
3156 'review_status': st[0][1].status if st else 'not_reviewed',
3156 'review_status': st[0][1].status if st else 'not_reviewed',
3157 }
3157 }
3158 for reviewer, st in pull_request.reviewers_statuses()
3158 for reviewer, st in pull_request.reviewers_statuses()
3159 ]
3159 ]
3160 }
3160 }
3161
3161
3162 return data
3162 return data
3163
3163
3164 def __json__(self):
3164 def __json__(self):
3165 return {
3165 return {
3166 'revisions': self.revisions,
3166 'revisions': self.revisions,
3167 }
3167 }
3168
3168
3169 def calculated_review_status(self):
3169 def calculated_review_status(self):
3170 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3170 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3171 # because it's tricky on how to use ChangesetStatusModel from there
3171 # because it's tricky on how to use ChangesetStatusModel from there
3172 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3172 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3173 from rhodecode.model.changeset_status import ChangesetStatusModel
3173 from rhodecode.model.changeset_status import ChangesetStatusModel
3174 return ChangesetStatusModel().calculated_review_status(self)
3174 return ChangesetStatusModel().calculated_review_status(self)
3175
3175
3176 def reviewers_statuses(self):
3176 def reviewers_statuses(self):
3177 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3177 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3178 from rhodecode.model.changeset_status import ChangesetStatusModel
3178 from rhodecode.model.changeset_status import ChangesetStatusModel
3179 return ChangesetStatusModel().reviewers_statuses(self)
3179 return ChangesetStatusModel().reviewers_statuses(self)
3180
3180
3181
3181
3182 class PullRequestVersion(Base, _PullRequestBase):
3182 class PullRequestVersion(Base, _PullRequestBase):
3183 __tablename__ = 'pull_request_versions'
3183 __tablename__ = 'pull_request_versions'
3184 __table_args__ = (
3184 __table_args__ = (
3185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3186 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3186 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3187 )
3187 )
3188
3188
3189 pull_request_version_id = Column(
3189 pull_request_version_id = Column(
3190 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3190 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3191 pull_request_id = Column(
3191 pull_request_id = Column(
3192 'pull_request_id', Integer(),
3192 'pull_request_id', Integer(),
3193 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3193 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3194 pull_request = relationship('PullRequest')
3194 pull_request = relationship('PullRequest')
3195
3195
3196 def __repr__(self):
3196 def __repr__(self):
3197 if self.pull_request_version_id:
3197 if self.pull_request_version_id:
3198 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3198 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3199 else:
3199 else:
3200 return '<DB:PullRequestVersion at %#x>' % id(self)
3200 return '<DB:PullRequestVersion at %#x>' % id(self)
3201
3201
3202
3202
3203 class PullRequestReviewers(Base, BaseModel):
3203 class PullRequestReviewers(Base, BaseModel):
3204 __tablename__ = 'pull_request_reviewers'
3204 __tablename__ = 'pull_request_reviewers'
3205 __table_args__ = (
3205 __table_args__ = (
3206 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3206 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3207 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3207 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3208 )
3208 )
3209
3209
3210 def __init__(self, user=None, pull_request=None):
3210 def __init__(self, user=None, pull_request=None):
3211 self.user = user
3211 self.user = user
3212 self.pull_request = pull_request
3212 self.pull_request = pull_request
3213
3213
3214 pull_requests_reviewers_id = Column(
3214 pull_requests_reviewers_id = Column(
3215 'pull_requests_reviewers_id', Integer(), nullable=False,
3215 'pull_requests_reviewers_id', Integer(), nullable=False,
3216 primary_key=True)
3216 primary_key=True)
3217 pull_request_id = Column(
3217 pull_request_id = Column(
3218 "pull_request_id", Integer(),
3218 "pull_request_id", Integer(),
3219 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3219 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3220 user_id = Column(
3220 user_id = Column(
3221 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3221 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3222
3222
3223 user = relationship('User')
3223 user = relationship('User')
3224 pull_request = relationship('PullRequest')
3224 pull_request = relationship('PullRequest')
3225
3225
3226
3226
3227 class Notification(Base, BaseModel):
3227 class Notification(Base, BaseModel):
3228 __tablename__ = 'notifications'
3228 __tablename__ = 'notifications'
3229 __table_args__ = (
3229 __table_args__ = (
3230 Index('notification_type_idx', 'type'),
3230 Index('notification_type_idx', 'type'),
3231 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3231 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3232 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3232 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3233 )
3233 )
3234
3234
3235 TYPE_CHANGESET_COMMENT = u'cs_comment'
3235 TYPE_CHANGESET_COMMENT = u'cs_comment'
3236 TYPE_MESSAGE = u'message'
3236 TYPE_MESSAGE = u'message'
3237 TYPE_MENTION = u'mention'
3237 TYPE_MENTION = u'mention'
3238 TYPE_REGISTRATION = u'registration'
3238 TYPE_REGISTRATION = u'registration'
3239 TYPE_PULL_REQUEST = u'pull_request'
3239 TYPE_PULL_REQUEST = u'pull_request'
3240 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3240 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3241
3241
3242 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3242 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3243 subject = Column('subject', Unicode(512), nullable=True)
3243 subject = Column('subject', Unicode(512), nullable=True)
3244 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3244 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3245 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3245 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3246 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3247 type_ = Column('type', Unicode(255))
3247 type_ = Column('type', Unicode(255))
3248
3248
3249 created_by_user = relationship('User')
3249 created_by_user = relationship('User')
3250 notifications_to_users = relationship('UserNotification', lazy='joined',
3250 notifications_to_users = relationship('UserNotification', lazy='joined',
3251 cascade="all, delete, delete-orphan")
3251 cascade="all, delete, delete-orphan")
3252
3252
3253 @property
3253 @property
3254 def recipients(self):
3254 def recipients(self):
3255 return [x.user for x in UserNotification.query()\
3255 return [x.user for x in UserNotification.query()\
3256 .filter(UserNotification.notification == self)\
3256 .filter(UserNotification.notification == self)\
3257 .order_by(UserNotification.user_id.asc()).all()]
3257 .order_by(UserNotification.user_id.asc()).all()]
3258
3258
3259 @classmethod
3259 @classmethod
3260 def create(cls, created_by, subject, body, recipients, type_=None):
3260 def create(cls, created_by, subject, body, recipients, type_=None):
3261 if type_ is None:
3261 if type_ is None:
3262 type_ = Notification.TYPE_MESSAGE
3262 type_ = Notification.TYPE_MESSAGE
3263
3263
3264 notification = cls()
3264 notification = cls()
3265 notification.created_by_user = created_by
3265 notification.created_by_user = created_by
3266 notification.subject = subject
3266 notification.subject = subject
3267 notification.body = body
3267 notification.body = body
3268 notification.type_ = type_
3268 notification.type_ = type_
3269 notification.created_on = datetime.datetime.now()
3269 notification.created_on = datetime.datetime.now()
3270
3270
3271 for u in recipients:
3271 for u in recipients:
3272 assoc = UserNotification()
3272 assoc = UserNotification()
3273 assoc.notification = notification
3273 assoc.notification = notification
3274
3274
3275 # if created_by is inside recipients mark his notification
3275 # if created_by is inside recipients mark his notification
3276 # as read
3276 # as read
3277 if u.user_id == created_by.user_id:
3277 if u.user_id == created_by.user_id:
3278 assoc.read = True
3278 assoc.read = True
3279
3279
3280 u.notifications.append(assoc)
3280 u.notifications.append(assoc)
3281 Session().add(notification)
3281 Session().add(notification)
3282
3282
3283 return notification
3283 return notification
3284
3284
3285 @property
3285 @property
3286 def description(self):
3286 def description(self):
3287 from rhodecode.model.notification import NotificationModel
3287 from rhodecode.model.notification import NotificationModel
3288 return NotificationModel().make_description(self)
3288 return NotificationModel().make_description(self)
3289
3289
3290
3290
3291 class UserNotification(Base, BaseModel):
3291 class UserNotification(Base, BaseModel):
3292 __tablename__ = 'user_to_notification'
3292 __tablename__ = 'user_to_notification'
3293 __table_args__ = (
3293 __table_args__ = (
3294 UniqueConstraint('user_id', 'notification_id'),
3294 UniqueConstraint('user_id', 'notification_id'),
3295 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3295 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3296 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3296 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3297 )
3297 )
3298 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3298 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3299 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3299 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3300 read = Column('read', Boolean, default=False)
3300 read = Column('read', Boolean, default=False)
3301 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3301 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3302
3302
3303 user = relationship('User', lazy="joined")
3303 user = relationship('User', lazy="joined")
3304 notification = relationship('Notification', lazy="joined",
3304 notification = relationship('Notification', lazy="joined",
3305 order_by=lambda: Notification.created_on.desc(),)
3305 order_by=lambda: Notification.created_on.desc(),)
3306
3306
3307 def mark_as_read(self):
3307 def mark_as_read(self):
3308 self.read = True
3308 self.read = True
3309 Session().add(self)
3309 Session().add(self)
3310
3310
3311
3311
3312 class Gist(Base, BaseModel):
3312 class Gist(Base, BaseModel):
3313 __tablename__ = 'gists'
3313 __tablename__ = 'gists'
3314 __table_args__ = (
3314 __table_args__ = (
3315 Index('g_gist_access_id_idx', 'gist_access_id'),
3315 Index('g_gist_access_id_idx', 'gist_access_id'),
3316 Index('g_created_on_idx', 'created_on'),
3316 Index('g_created_on_idx', 'created_on'),
3317 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3317 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3318 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3318 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3319 )
3319 )
3320 GIST_PUBLIC = u'public'
3320 GIST_PUBLIC = u'public'
3321 GIST_PRIVATE = u'private'
3321 GIST_PRIVATE = u'private'
3322 DEFAULT_FILENAME = u'gistfile1.txt'
3322 DEFAULT_FILENAME = u'gistfile1.txt'
3323
3323
3324 ACL_LEVEL_PUBLIC = u'acl_public'
3324 ACL_LEVEL_PUBLIC = u'acl_public'
3325 ACL_LEVEL_PRIVATE = u'acl_private'
3325 ACL_LEVEL_PRIVATE = u'acl_private'
3326
3326
3327 gist_id = Column('gist_id', Integer(), primary_key=True)
3327 gist_id = Column('gist_id', Integer(), primary_key=True)
3328 gist_access_id = Column('gist_access_id', Unicode(250))
3328 gist_access_id = Column('gist_access_id', Unicode(250))
3329 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3329 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3330 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3330 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3331 gist_expires = Column('gist_expires', Float(53), nullable=False)
3331 gist_expires = Column('gist_expires', Float(53), nullable=False)
3332 gist_type = Column('gist_type', Unicode(128), nullable=False)
3332 gist_type = Column('gist_type', Unicode(128), nullable=False)
3333 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3333 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3334 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3334 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3335 acl_level = Column('acl_level', Unicode(128), nullable=True)
3335 acl_level = Column('acl_level', Unicode(128), nullable=True)
3336
3336
3337 owner = relationship('User')
3337 owner = relationship('User')
3338
3338
3339 def __repr__(self):
3339 def __repr__(self):
3340 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3340 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3341
3341
3342 @classmethod
3342 @classmethod
3343 def get_or_404(cls, id_):
3343 def get_or_404(cls, id_):
3344 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3344 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3345 if not res:
3345 if not res:
3346 raise HTTPNotFound
3346 raise HTTPNotFound
3347 return res
3347 return res
3348
3348
3349 @classmethod
3349 @classmethod
3350 def get_by_access_id(cls, gist_access_id):
3350 def get_by_access_id(cls, gist_access_id):
3351 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3351 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3352
3352
3353 def gist_url(self):
3353 def gist_url(self):
3354 import rhodecode
3354 import rhodecode
3355 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3355 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3356 if alias_url:
3356 if alias_url:
3357 return alias_url.replace('{gistid}', self.gist_access_id)
3357 return alias_url.replace('{gistid}', self.gist_access_id)
3358
3358
3359 return url('gist', gist_id=self.gist_access_id, qualified=True)
3359 return url('gist', gist_id=self.gist_access_id, qualified=True)
3360
3360
3361 @classmethod
3361 @classmethod
3362 def base_path(cls):
3362 def base_path(cls):
3363 """
3363 """
3364 Returns base path when all gists are stored
3364 Returns base path when all gists are stored
3365
3365
3366 :param cls:
3366 :param cls:
3367 """
3367 """
3368 from rhodecode.model.gist import GIST_STORE_LOC
3368 from rhodecode.model.gist import GIST_STORE_LOC
3369 q = Session().query(RhodeCodeUi)\
3369 q = Session().query(RhodeCodeUi)\
3370 .filter(RhodeCodeUi.ui_key == URL_SEP)
3370 .filter(RhodeCodeUi.ui_key == URL_SEP)
3371 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3371 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3372 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3372 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3373
3373
3374 def get_api_data(self):
3374 def get_api_data(self):
3375 """
3375 """
3376 Common function for generating gist related data for API
3376 Common function for generating gist related data for API
3377 """
3377 """
3378 gist = self
3378 gist = self
3379 data = {
3379 data = {
3380 'gist_id': gist.gist_id,
3380 'gist_id': gist.gist_id,
3381 'type': gist.gist_type,
3381 'type': gist.gist_type,
3382 'access_id': gist.gist_access_id,
3382 'access_id': gist.gist_access_id,
3383 'description': gist.gist_description,
3383 'description': gist.gist_description,
3384 'url': gist.gist_url(),
3384 'url': gist.gist_url(),
3385 'expires': gist.gist_expires,
3385 'expires': gist.gist_expires,
3386 'created_on': gist.created_on,
3386 'created_on': gist.created_on,
3387 'modified_at': gist.modified_at,
3387 'modified_at': gist.modified_at,
3388 'content': None,
3388 'content': None,
3389 'acl_level': gist.acl_level,
3389 'acl_level': gist.acl_level,
3390 }
3390 }
3391 return data
3391 return data
3392
3392
3393 def __json__(self):
3393 def __json__(self):
3394 data = dict(
3394 data = dict(
3395 )
3395 )
3396 data.update(self.get_api_data())
3396 data.update(self.get_api_data())
3397 return data
3397 return data
3398 # SCM functions
3398 # SCM functions
3399
3399
3400 def scm_instance(self, **kwargs):
3400 def scm_instance(self, **kwargs):
3401 from rhodecode.lib.vcs import get_repo
3401 from rhodecode.lib.vcs import get_repo
3402 base_path = self.base_path()
3402 base_path = self.base_path()
3403 return get_repo(os.path.join(*map(safe_str,
3403 return get_repo(os.path.join(*map(safe_str,
3404 [base_path, self.gist_access_id])))
3404 [base_path, self.gist_access_id])))
3405
3405
3406
3406
3407 class DbMigrateVersion(Base, BaseModel):
3407 class DbMigrateVersion(Base, BaseModel):
3408 __tablename__ = 'db_migrate_version'
3408 __tablename__ = 'db_migrate_version'
3409 __table_args__ = (
3409 __table_args__ = (
3410 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3410 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3411 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3411 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3412 )
3412 )
3413 repository_id = Column('repository_id', String(250), primary_key=True)
3413 repository_id = Column('repository_id', String(250), primary_key=True)
3414 repository_path = Column('repository_path', Text)
3414 repository_path = Column('repository_path', Text)
3415 version = Column('version', Integer)
3415 version = Column('version', Integer)
3416
3416
3417
3417
3418 class ExternalIdentity(Base, BaseModel):
3418 class ExternalIdentity(Base, BaseModel):
3419 __tablename__ = 'external_identities'
3419 __tablename__ = 'external_identities'
3420 __table_args__ = (
3420 __table_args__ = (
3421 Index('local_user_id_idx', 'local_user_id'),
3421 Index('local_user_id_idx', 'local_user_id'),
3422 Index('external_id_idx', 'external_id'),
3422 Index('external_id_idx', 'external_id'),
3423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3423 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3424 'mysql_charset': 'utf8'})
3424 'mysql_charset': 'utf8'})
3425
3425
3426 external_id = Column('external_id', Unicode(255), default=u'',
3426 external_id = Column('external_id', Unicode(255), default=u'',
3427 primary_key=True)
3427 primary_key=True)
3428 external_username = Column('external_username', Unicode(1024), default=u'')
3428 external_username = Column('external_username', Unicode(1024), default=u'')
3429 local_user_id = Column('local_user_id', Integer(),
3429 local_user_id = Column('local_user_id', Integer(),
3430 ForeignKey('users.user_id'), primary_key=True)
3430 ForeignKey('users.user_id'), primary_key=True)
3431 provider_name = Column('provider_name', Unicode(255), default=u'',
3431 provider_name = Column('provider_name', Unicode(255), default=u'',
3432 primary_key=True)
3432 primary_key=True)
3433 access_token = Column('access_token', String(1024), default=u'')
3433 access_token = Column('access_token', String(1024), default=u'')
3434 alt_token = Column('alt_token', String(1024), default=u'')
3434 alt_token = Column('alt_token', String(1024), default=u'')
3435 token_secret = Column('token_secret', String(1024), default=u'')
3435 token_secret = Column('token_secret', String(1024), default=u'')
3436
3436
3437 @classmethod
3437 @classmethod
3438 def by_external_id_and_provider(cls, external_id, provider_name,
3438 def by_external_id_and_provider(cls, external_id, provider_name,
3439 local_user_id=None):
3439 local_user_id=None):
3440 """
3440 """
3441 Returns ExternalIdentity instance based on search params
3441 Returns ExternalIdentity instance based on search params
3442
3442
3443 :param external_id:
3443 :param external_id:
3444 :param provider_name:
3444 :param provider_name:
3445 :return: ExternalIdentity
3445 :return: ExternalIdentity
3446 """
3446 """
3447 query = cls.query()
3447 query = cls.query()
3448 query = query.filter(cls.external_id == external_id)
3448 query = query.filter(cls.external_id == external_id)
3449 query = query.filter(cls.provider_name == provider_name)
3449 query = query.filter(cls.provider_name == provider_name)
3450 if local_user_id:
3450 if local_user_id:
3451 query = query.filter(cls.local_user_id == local_user_id)
3451 query = query.filter(cls.local_user_id == local_user_id)
3452 return query.first()
3452 return query.first()
3453
3453
3454 @classmethod
3454 @classmethod
3455 def user_by_external_id_and_provider(cls, external_id, provider_name):
3455 def user_by_external_id_and_provider(cls, external_id, provider_name):
3456 """
3456 """
3457 Returns User instance based on search params
3457 Returns User instance based on search params
3458
3458
3459 :param external_id:
3459 :param external_id:
3460 :param provider_name:
3460 :param provider_name:
3461 :return: User
3461 :return: User
3462 """
3462 """
3463 query = User.query()
3463 query = User.query()
3464 query = query.filter(cls.external_id == external_id)
3464 query = query.filter(cls.external_id == external_id)
3465 query = query.filter(cls.provider_name == provider_name)
3465 query = query.filter(cls.provider_name == provider_name)
3466 query = query.filter(User.user_id == cls.local_user_id)
3466 query = query.filter(User.user_id == cls.local_user_id)
3467 return query.first()
3467 return query.first()
3468
3468
3469 @classmethod
3469 @classmethod
3470 def by_local_user_id(cls, local_user_id):
3470 def by_local_user_id(cls, local_user_id):
3471 """
3471 """
3472 Returns all tokens for user
3472 Returns all tokens for user
3473
3473
3474 :param local_user_id:
3474 :param local_user_id:
3475 :return: ExternalIdentity
3475 :return: ExternalIdentity
3476 """
3476 """
3477 query = cls.query()
3477 query = cls.query()
3478 query = query.filter(cls.local_user_id == local_user_id)
3478 query = query.filter(cls.local_user_id == local_user_id)
3479 return query
3479 return query
3480
3480
3481
3481
3482 class Integration(Base, BaseModel):
3482 class Integration(Base, BaseModel):
3483 __tablename__ = 'integrations'
3483 __tablename__ = 'integrations'
3484 __table_args__ = (
3484 __table_args__ = (
3485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3486 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3486 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3487 )
3487 )
3488
3488
3489 integration_id = Column('integration_id', Integer(), primary_key=True)
3489 integration_id = Column('integration_id', Integer(), primary_key=True)
3490 integration_type = Column('integration_type', String(255))
3490 integration_type = Column('integration_type', String(255))
3491 enabled = Column("enabled", Boolean(), nullable=False)
3491 enabled = Column("enabled", Boolean(), nullable=False)
3492 name = Column('name', String(255), nullable=False)
3492 name = Column('name', String(255), nullable=False)
3493 settings_json = Column('settings_json',
3493 settings_json = Column('settings_json',
3494 UnicodeText().with_variant(UnicodeText(16384), 'mysql'))
3494 UnicodeText().with_variant(UnicodeText(16384), 'mysql'))
3495 repo_id = Column(
3495 repo_id = Column(
3496 "repo_id", Integer(), ForeignKey('repositories.repo_id'),
3496 "repo_id", Integer(), ForeignKey('repositories.repo_id'),
3497 nullable=True, unique=None, default=None)
3497 nullable=True, unique=None, default=None)
3498 repo = relationship('Repository', lazy='joined')
3498 repo = relationship('Repository', lazy='joined')
3499
3499
3500 def __init__(self, **kw):
3500 def __init__(self, **kw):
3501 settings = kw.pop('settings', {})
3501 settings = kw.pop('settings', {})
3502 self.settings = settings
3502 self.settings = settings
3503 super(Integration, self).__init__(**kw)
3503 super(Integration, self).__init__(**kw)
3504
3504
3505 @hybrid_property
3505 @hybrid_property
3506 def settings(self):
3506 def settings(self):
3507 data = json.loads(self.settings_json or '{}')
3507 data = json.loads(self.settings_json or '{}')
3508 return data
3508 return data
3509
3509
3510 @settings.setter
3510 @settings.setter
3511 def settings(self, dct):
3511 def settings(self, dct):
3512 self.settings_json = json.dumps(dct, indent=2)
3512 self.settings_json = json.dumps(dct, indent=2)
3513
3513
3514 def __repr__(self):
3514 def __repr__(self):
3515 if self.repo:
3515 if self.repo:
3516 scope = 'repo=%r' % self.repo
3516 scope = 'repo=%r' % self.repo
3517 else:
3517 else:
3518 scope = 'global'
3518 scope = 'global'
3519
3519
3520 return '<Integration(%r, %r)>' % (self.integration_type, scope)
3520 return '<Integration(%r, %r)>' % (self.integration_type, scope)
3521
3521
3522 def settings_as_dict(self):
3522 def settings_as_dict(self):
3523 return json.loads(self.settings_json)
3523 return json.loads(self.settings_json)
@@ -1,102 +1,102 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <p>
6 <p>
7 ${_('Built-in tokens can be used to authenticate with all possible options.')}<br/>
7 ${_('Built-in tokens can be used to authenticate with all possible options.')}<br/>
8 ${_('Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg operations.')}
8 ${_('Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations.')}
9 </p>
9 </p>
10 <table class="rctable auth_tokens">
10 <table class="rctable auth_tokens">
11 <tr>
11 <tr>
12 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${c.user.api_key}</code></div></td>
12 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${c.user.api_key}</code></div></td>
13 <td class="td-buttons">
13 <td class="td-buttons">
14 <span class="btn btn-mini btn-info disabled">${_('Built-in')}</span>
14 <span class="btn btn-mini btn-info disabled">${_('Built-in')}</span>
15 </td>
15 </td>
16 <td class="td-buttons">
16 <td class="td-buttons">
17 <span class="btn btn-mini btn-info disabled">all</span>
17 <span class="btn btn-mini btn-info disabled">all</span>
18 </td>
18 </td>
19 <td class="td-exp">${_('expires')}: ${_('never')}</td>
19 <td class="td-exp">${_('expires')}: ${_('never')}</td>
20 <td class="td-action">
20 <td class="td-action">
21 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
21 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
22 ${h.hidden('del_auth_token',c.user.api_key)}
22 ${h.hidden('del_auth_token',c.user.api_key)}
23 ${h.hidden('del_auth_token_builtin',1)}
23 ${h.hidden('del_auth_token_builtin',1)}
24 <button class="btn-link btn-danger" type="submit"
24 <button class="btn-link btn-danger" type="submit"
25 onclick="return confirm('${_('Confirm to reset this auth token: %s') % c.user.api_key}');">
25 onclick="return confirm('${_('Confirm to reset this auth token: %s') % c.user.api_key}');">
26 <i class="icon-refresh"></i>
26 <i class="icon-refresh"></i>
27 ${_('Reset')}
27 ${_('Reset')}
28 </button>
28 </button>
29 ${h.end_form()}
29 ${h.end_form()}
30 </td>
30 </td>
31 </tr>
31 </tr>
32 %if c.user_auth_tokens:
32 %if c.user_auth_tokens:
33 %for auth_token in c.user_auth_tokens:
33 %for auth_token in c.user_auth_tokens:
34 <tr class="${'expired' if auth_token.expired else ''}">
34 <tr class="${'expired' if auth_token.expired else ''}">
35 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
35 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
36 <td class="td-wrap">${auth_token.description}</td>
36 <td class="td-wrap">${auth_token.description}</td>
37 <td class="td-buttons">
37 <td class="td-buttons">
38 <span class="btn btn-mini btn-info disabled">${auth_token.role_humanized}</span>
38 <span class="btn btn-mini btn-info disabled">${auth_token.role_humanized}</span>
39 </td>
39 </td>
40 <td class="td-exp">
40 <td class="td-exp">
41 %if auth_token.expires == -1:
41 %if auth_token.expires == -1:
42 ${_('expires')}: ${_('never')}
42 ${_('expires')}: ${_('never')}
43 %else:
43 %else:
44 %if auth_token.expired:
44 %if auth_token.expired:
45 ${_('expired')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
45 ${_('expired')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
46 %else:
46 %else:
47 ${_('expires')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
47 ${_('expires')}: ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
48 %endif
48 %endif
49 %endif
49 %endif
50 </td>
50 </td>
51 <td class="td-action">
51 <td class="td-action">
52 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
52 ${h.secure_form(url('my_account_auth_tokens'),method='delete')}
53 ${h.hidden('del_auth_token',auth_token.api_key)}
53 ${h.hidden('del_auth_token',auth_token.api_key)}
54 <button class="btn btn-link btn-danger" type="submit"
54 <button class="btn btn-link btn-danger" type="submit"
55 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
55 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
56 ${_('Delete')}
56 ${_('Delete')}
57 </button>
57 </button>
58 ${h.end_form()}
58 ${h.end_form()}
59 </td>
59 </td>
60 </tr>
60 </tr>
61 %endfor
61 %endfor
62 %else:
62 %else:
63 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
63 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
64 %endif
64 %endif
65 </table>
65 </table>
66
66
67 <div class="user_auth_tokens">
67 <div class="user_auth_tokens">
68 ${h.secure_form(url('my_account_auth_tokens'), method='post')}
68 ${h.secure_form(url('my_account_auth_tokens'), method='post')}
69 <div class="form form-vertical">
69 <div class="form form-vertical">
70 <!-- fields -->
70 <!-- fields -->
71 <div class="fields">
71 <div class="fields">
72 <div class="field">
72 <div class="field">
73 <div class="label">
73 <div class="label">
74 <label for="new_email">${_('New authentication token')}:</label>
74 <label for="new_email">${_('New authentication token')}:</label>
75 </div>
75 </div>
76 <div class="input">
76 <div class="input">
77 ${h.text('description', placeholder=_('Description'))}
77 ${h.text('description', placeholder=_('Description'))}
78 ${h.select('lifetime', '', c.lifetime_options)}
78 ${h.select('lifetime', '', c.lifetime_options)}
79 ${h.select('role', '', c.role_options)}
79 ${h.select('role', '', c.role_options)}
80 </div>
80 </div>
81 </div>
81 </div>
82 <div class="buttons">
82 <div class="buttons">
83 ${h.submit('save',_('Add'),class_="btn")}
83 ${h.submit('save',_('Add'),class_="btn")}
84 ${h.reset('reset',_('Reset'),class_="btn")}
84 ${h.reset('reset',_('Reset'),class_="btn")}
85 </div>
85 </div>
86 </div>
86 </div>
87 </div>
87 </div>
88 ${h.end_form()}
88 ${h.end_form()}
89 </div>
89 </div>
90 </div>
90 </div>
91 </div>
91 </div>
92 <script>
92 <script>
93 $(document).ready(function(){
93 $(document).ready(function(){
94 var select2Options = {
94 var select2Options = {
95 'containerCssClass': "drop-menu",
95 'containerCssClass': "drop-menu",
96 'dropdownCssClass': "drop-menu-dropdown",
96 'dropdownCssClass': "drop-menu-dropdown",
97 'dropdownAutoWidth': true
97 'dropdownAutoWidth': true
98 };
98 };
99 $("#lifetime").select2(select2Options);
99 $("#lifetime").select2(select2Options);
100 $("#role").select2(select2Options);
100 $("#role").select2(select2Options);
101 });
101 });
102 </script>
102 </script>
General Comments 0
You need to be logged in to leave comments. Login now