##// END OF EJS Templates
scm: change update_commit_cache to check revision as well
dan -
r339:3ef1f660 default
parent child Browse files
Show More
@@ -1,3476 +1,3477 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 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 'private': repo.private,
1644 'private': repo.private,
1645 'created_on': repo.created_on,
1645 'created_on': repo.created_on,
1646 'description': repo.description,
1646 'description': repo.description,
1647 'landing_rev': repo.landing_rev,
1647 'landing_rev': repo.landing_rev,
1648 'owner': repo.user.username,
1648 'owner': repo.user.username,
1649 'fork_of': repo.fork.repo_name if repo.fork else None,
1649 'fork_of': repo.fork.repo_name if repo.fork else None,
1650 'enable_statistics': repo.enable_statistics,
1650 'enable_statistics': repo.enable_statistics,
1651 'enable_locking': repo.enable_locking,
1651 'enable_locking': repo.enable_locking,
1652 'enable_downloads': repo.enable_downloads,
1652 'enable_downloads': repo.enable_downloads,
1653 'last_changeset': repo.changeset_cache,
1653 'last_changeset': repo.changeset_cache,
1654 'locked_by': User.get(_user_id).get_api_data(
1654 'locked_by': User.get(_user_id).get_api_data(
1655 include_secrets=include_secrets) if _user_id else None,
1655 include_secrets=include_secrets) if _user_id else None,
1656 'locked_date': time_to_datetime(_time) if _time else None,
1656 'locked_date': time_to_datetime(_time) if _time else None,
1657 'lock_reason': _reason if _reason else None,
1657 'lock_reason': _reason if _reason else None,
1658 }
1658 }
1659
1659
1660 # TODO: mikhail: should be per-repo settings here
1660 # TODO: mikhail: should be per-repo settings here
1661 rc_config = SettingsModel().get_all_settings()
1661 rc_config = SettingsModel().get_all_settings()
1662 repository_fields = str2bool(
1662 repository_fields = str2bool(
1663 rc_config.get('rhodecode_repository_fields'))
1663 rc_config.get('rhodecode_repository_fields'))
1664 if repository_fields:
1664 if repository_fields:
1665 for f in self.extra_fields:
1665 for f in self.extra_fields:
1666 data[f.field_key_prefixed] = f.field_value
1666 data[f.field_key_prefixed] = f.field_value
1667
1667
1668 return data
1668 return data
1669
1669
1670 @classmethod
1670 @classmethod
1671 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1671 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1672 if not lock_time:
1672 if not lock_time:
1673 lock_time = time.time()
1673 lock_time = time.time()
1674 if not lock_reason:
1674 if not lock_reason:
1675 lock_reason = cls.LOCK_AUTOMATIC
1675 lock_reason = cls.LOCK_AUTOMATIC
1676 repo.locked = [user_id, lock_time, lock_reason]
1676 repo.locked = [user_id, lock_time, lock_reason]
1677 Session().add(repo)
1677 Session().add(repo)
1678 Session().commit()
1678 Session().commit()
1679
1679
1680 @classmethod
1680 @classmethod
1681 def unlock(cls, repo):
1681 def unlock(cls, repo):
1682 repo.locked = None
1682 repo.locked = None
1683 Session().add(repo)
1683 Session().add(repo)
1684 Session().commit()
1684 Session().commit()
1685
1685
1686 @classmethod
1686 @classmethod
1687 def getlock(cls, repo):
1687 def getlock(cls, repo):
1688 return repo.locked
1688 return repo.locked
1689
1689
1690 def is_user_lock(self, user_id):
1690 def is_user_lock(self, user_id):
1691 if self.lock[0]:
1691 if self.lock[0]:
1692 lock_user_id = safe_int(self.lock[0])
1692 lock_user_id = safe_int(self.lock[0])
1693 user_id = safe_int(user_id)
1693 user_id = safe_int(user_id)
1694 # both are ints, and they are equal
1694 # both are ints, and they are equal
1695 return all([lock_user_id, user_id]) and lock_user_id == user_id
1695 return all([lock_user_id, user_id]) and lock_user_id == user_id
1696
1696
1697 return False
1697 return False
1698
1698
1699 def get_locking_state(self, action, user_id, only_when_enabled=True):
1699 def get_locking_state(self, action, user_id, only_when_enabled=True):
1700 """
1700 """
1701 Checks locking on this repository, if locking is enabled and lock is
1701 Checks locking on this repository, if locking is enabled and lock is
1702 present returns a tuple of make_lock, locked, locked_by.
1702 present returns a tuple of make_lock, locked, locked_by.
1703 make_lock can have 3 states None (do nothing) True, make lock
1703 make_lock can have 3 states None (do nothing) True, make lock
1704 False release lock, This value is later propagated to hooks, which
1704 False release lock, This value is later propagated to hooks, which
1705 do the locking. Think about this as signals passed to hooks what to do.
1705 do the locking. Think about this as signals passed to hooks what to do.
1706
1706
1707 """
1707 """
1708 # TODO: johbo: This is part of the business logic and should be moved
1708 # TODO: johbo: This is part of the business logic and should be moved
1709 # into the RepositoryModel.
1709 # into the RepositoryModel.
1710
1710
1711 if action not in ('push', 'pull'):
1711 if action not in ('push', 'pull'):
1712 raise ValueError("Invalid action value: %s" % repr(action))
1712 raise ValueError("Invalid action value: %s" % repr(action))
1713
1713
1714 # defines if locked error should be thrown to user
1714 # defines if locked error should be thrown to user
1715 currently_locked = False
1715 currently_locked = False
1716 # defines if new lock should be made, tri-state
1716 # defines if new lock should be made, tri-state
1717 make_lock = None
1717 make_lock = None
1718 repo = self
1718 repo = self
1719 user = User.get(user_id)
1719 user = User.get(user_id)
1720
1720
1721 lock_info = repo.locked
1721 lock_info = repo.locked
1722
1722
1723 if repo and (repo.enable_locking or not only_when_enabled):
1723 if repo and (repo.enable_locking or not only_when_enabled):
1724 if action == 'push':
1724 if action == 'push':
1725 # check if it's already locked !, if it is compare users
1725 # check if it's already locked !, if it is compare users
1726 locked_by_user_id = lock_info[0]
1726 locked_by_user_id = lock_info[0]
1727 if user.user_id == locked_by_user_id:
1727 if user.user_id == locked_by_user_id:
1728 log.debug(
1728 log.debug(
1729 'Got `push` action from user %s, now unlocking', user)
1729 'Got `push` action from user %s, now unlocking', user)
1730 # unlock if we have push from user who locked
1730 # unlock if we have push from user who locked
1731 make_lock = False
1731 make_lock = False
1732 else:
1732 else:
1733 # we're not the same user who locked, ban with
1733 # we're not the same user who locked, ban with
1734 # code defined in settings (default is 423 HTTP Locked) !
1734 # code defined in settings (default is 423 HTTP Locked) !
1735 log.debug('Repo %s is currently locked by %s', repo, user)
1735 log.debug('Repo %s is currently locked by %s', repo, user)
1736 currently_locked = True
1736 currently_locked = True
1737 elif action == 'pull':
1737 elif action == 'pull':
1738 # [0] user [1] date
1738 # [0] user [1] date
1739 if lock_info[0] and lock_info[1]:
1739 if lock_info[0] and lock_info[1]:
1740 log.debug('Repo %s is currently locked by %s', repo, user)
1740 log.debug('Repo %s is currently locked by %s', repo, user)
1741 currently_locked = True
1741 currently_locked = True
1742 else:
1742 else:
1743 log.debug('Setting lock on repo %s by %s', repo, user)
1743 log.debug('Setting lock on repo %s by %s', repo, user)
1744 make_lock = True
1744 make_lock = True
1745
1745
1746 else:
1746 else:
1747 log.debug('Repository %s do not have locking enabled', repo)
1747 log.debug('Repository %s do not have locking enabled', repo)
1748
1748
1749 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1749 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1750 make_lock, currently_locked, lock_info)
1750 make_lock, currently_locked, lock_info)
1751
1751
1752 from rhodecode.lib.auth import HasRepoPermissionAny
1752 from rhodecode.lib.auth import HasRepoPermissionAny
1753 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1753 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1754 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1754 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1755 # if we don't have at least write permission we cannot make a lock
1755 # if we don't have at least write permission we cannot make a lock
1756 log.debug('lock state reset back to FALSE due to lack '
1756 log.debug('lock state reset back to FALSE due to lack '
1757 'of at least read permission')
1757 'of at least read permission')
1758 make_lock = False
1758 make_lock = False
1759
1759
1760 return make_lock, currently_locked, lock_info
1760 return make_lock, currently_locked, lock_info
1761
1761
1762 @property
1762 @property
1763 def last_db_change(self):
1763 def last_db_change(self):
1764 return self.updated_on
1764 return self.updated_on
1765
1765
1766 @property
1766 @property
1767 def clone_uri_hidden(self):
1767 def clone_uri_hidden(self):
1768 clone_uri = self.clone_uri
1768 clone_uri = self.clone_uri
1769 if clone_uri:
1769 if clone_uri:
1770 import urlobject
1770 import urlobject
1771 url_obj = urlobject.URLObject(clone_uri)
1771 url_obj = urlobject.URLObject(clone_uri)
1772 if url_obj.password:
1772 if url_obj.password:
1773 clone_uri = url_obj.with_password('*****')
1773 clone_uri = url_obj.with_password('*****')
1774 return clone_uri
1774 return clone_uri
1775
1775
1776 def clone_url(self, **override):
1776 def clone_url(self, **override):
1777 qualified_home_url = url('home', qualified=True)
1777 qualified_home_url = url('home', qualified=True)
1778
1778
1779 uri_tmpl = None
1779 uri_tmpl = None
1780 if 'with_id' in override:
1780 if 'with_id' in override:
1781 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1781 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1782 del override['with_id']
1782 del override['with_id']
1783
1783
1784 if 'uri_tmpl' in override:
1784 if 'uri_tmpl' in override:
1785 uri_tmpl = override['uri_tmpl']
1785 uri_tmpl = override['uri_tmpl']
1786 del override['uri_tmpl']
1786 del override['uri_tmpl']
1787
1787
1788 # we didn't override our tmpl from **overrides
1788 # we didn't override our tmpl from **overrides
1789 if not uri_tmpl:
1789 if not uri_tmpl:
1790 uri_tmpl = self.DEFAULT_CLONE_URI
1790 uri_tmpl = self.DEFAULT_CLONE_URI
1791 try:
1791 try:
1792 from pylons import tmpl_context as c
1792 from pylons import tmpl_context as c
1793 uri_tmpl = c.clone_uri_tmpl
1793 uri_tmpl = c.clone_uri_tmpl
1794 except Exception:
1794 except Exception:
1795 # in any case if we call this outside of request context,
1795 # in any case if we call this outside of request context,
1796 # ie, not having tmpl_context set up
1796 # ie, not having tmpl_context set up
1797 pass
1797 pass
1798
1798
1799 return get_clone_url(uri_tmpl=uri_tmpl,
1799 return get_clone_url(uri_tmpl=uri_tmpl,
1800 qualifed_home_url=qualified_home_url,
1800 qualifed_home_url=qualified_home_url,
1801 repo_name=self.repo_name,
1801 repo_name=self.repo_name,
1802 repo_id=self.repo_id, **override)
1802 repo_id=self.repo_id, **override)
1803
1803
1804 def set_state(self, state):
1804 def set_state(self, state):
1805 self.repo_state = state
1805 self.repo_state = state
1806 Session().add(self)
1806 Session().add(self)
1807 #==========================================================================
1807 #==========================================================================
1808 # SCM PROPERTIES
1808 # SCM PROPERTIES
1809 #==========================================================================
1809 #==========================================================================
1810
1810
1811 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1811 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1812 return get_commit_safe(
1812 return get_commit_safe(
1813 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1813 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1814
1814
1815 def get_changeset(self, rev=None, pre_load=None):
1815 def get_changeset(self, rev=None, pre_load=None):
1816 warnings.warn("Use get_commit", DeprecationWarning)
1816 warnings.warn("Use get_commit", DeprecationWarning)
1817 commit_id = None
1817 commit_id = None
1818 commit_idx = None
1818 commit_idx = None
1819 if isinstance(rev, basestring):
1819 if isinstance(rev, basestring):
1820 commit_id = rev
1820 commit_id = rev
1821 else:
1821 else:
1822 commit_idx = rev
1822 commit_idx = rev
1823 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1823 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1824 pre_load=pre_load)
1824 pre_load=pre_load)
1825
1825
1826 def get_landing_commit(self):
1826 def get_landing_commit(self):
1827 """
1827 """
1828 Returns landing commit, or if that doesn't exist returns the tip
1828 Returns landing commit, or if that doesn't exist returns the tip
1829 """
1829 """
1830 _rev_type, _rev = self.landing_rev
1830 _rev_type, _rev = self.landing_rev
1831 commit = self.get_commit(_rev)
1831 commit = self.get_commit(_rev)
1832 if isinstance(commit, EmptyCommit):
1832 if isinstance(commit, EmptyCommit):
1833 return self.get_commit()
1833 return self.get_commit()
1834 return commit
1834 return commit
1835
1835
1836 def update_commit_cache(self, cs_cache=None, config=None):
1836 def update_commit_cache(self, cs_cache=None, config=None):
1837 """
1837 """
1838 Update cache of last changeset for repository, keys should be::
1838 Update cache of last changeset for repository, keys should be::
1839
1839
1840 short_id
1840 short_id
1841 raw_id
1841 raw_id
1842 revision
1842 revision
1843 parents
1843 parents
1844 message
1844 message
1845 date
1845 date
1846 author
1846 author
1847
1847
1848 :param cs_cache:
1848 :param cs_cache:
1849 """
1849 """
1850 from rhodecode.lib.vcs.backends.base import BaseChangeset
1850 from rhodecode.lib.vcs.backends.base import BaseChangeset
1851 if cs_cache is None:
1851 if cs_cache is None:
1852 # use no-cache version here
1852 # use no-cache version here
1853 scm_repo = self.scm_instance(cache=False, config=config)
1853 scm_repo = self.scm_instance(cache=False, config=config)
1854 if scm_repo:
1854 if scm_repo:
1855 cs_cache = scm_repo.get_commit(
1855 cs_cache = scm_repo.get_commit(
1856 pre_load=["author", "date", "message", "parents"])
1856 pre_load=["author", "date", "message", "parents"])
1857 else:
1857 else:
1858 cs_cache = EmptyCommit()
1858 cs_cache = EmptyCommit()
1859
1859
1860 if isinstance(cs_cache, BaseChangeset):
1860 if isinstance(cs_cache, BaseChangeset):
1861 cs_cache = cs_cache.__json__()
1861 cs_cache = cs_cache.__json__()
1862
1862
1863 def is_outdated(new_cs_cache):
1863 def is_outdated(new_cs_cache):
1864 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1864 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1865 new_cs_cache['revision'] != self.changeset_cache['revision']):
1865 return True
1866 return True
1866 return False
1867 return False
1867
1868
1868 # check if we have maybe already latest cached revision
1869 # check if we have maybe already latest cached revision
1869 if is_outdated(cs_cache) or not self.changeset_cache:
1870 if is_outdated(cs_cache) or not self.changeset_cache:
1870 _default = datetime.datetime.fromtimestamp(0)
1871 _default = datetime.datetime.fromtimestamp(0)
1871 last_change = cs_cache.get('date') or _default
1872 last_change = cs_cache.get('date') or _default
1872 log.debug('updated repo %s with new cs cache %s',
1873 log.debug('updated repo %s with new cs cache %s',
1873 self.repo_name, cs_cache)
1874 self.repo_name, cs_cache)
1874 self.updated_on = last_change
1875 self.updated_on = last_change
1875 self.changeset_cache = cs_cache
1876 self.changeset_cache = cs_cache
1876 Session().add(self)
1877 Session().add(self)
1877 Session().commit()
1878 Session().commit()
1878 else:
1879 else:
1879 log.debug('Skipping update_commit_cache for repo:`%s` '
1880 log.debug('Skipping update_commit_cache for repo:`%s` '
1880 'commit already with latest changes', self.repo_name)
1881 'commit already with latest changes', self.repo_name)
1881
1882
1882 @property
1883 @property
1883 def tip(self):
1884 def tip(self):
1884 return self.get_commit('tip')
1885 return self.get_commit('tip')
1885
1886
1886 @property
1887 @property
1887 def author(self):
1888 def author(self):
1888 return self.tip.author
1889 return self.tip.author
1889
1890
1890 @property
1891 @property
1891 def last_change(self):
1892 def last_change(self):
1892 return self.scm_instance().last_change
1893 return self.scm_instance().last_change
1893
1894
1894 def get_comments(self, revisions=None):
1895 def get_comments(self, revisions=None):
1895 """
1896 """
1896 Returns comments for this repository grouped by revisions
1897 Returns comments for this repository grouped by revisions
1897
1898
1898 :param revisions: filter query by revisions only
1899 :param revisions: filter query by revisions only
1899 """
1900 """
1900 cmts = ChangesetComment.query()\
1901 cmts = ChangesetComment.query()\
1901 .filter(ChangesetComment.repo == self)
1902 .filter(ChangesetComment.repo == self)
1902 if revisions:
1903 if revisions:
1903 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1904 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1904 grouped = collections.defaultdict(list)
1905 grouped = collections.defaultdict(list)
1905 for cmt in cmts.all():
1906 for cmt in cmts.all():
1906 grouped[cmt.revision].append(cmt)
1907 grouped[cmt.revision].append(cmt)
1907 return grouped
1908 return grouped
1908
1909
1909 def statuses(self, revisions=None):
1910 def statuses(self, revisions=None):
1910 """
1911 """
1911 Returns statuses for this repository
1912 Returns statuses for this repository
1912
1913
1913 :param revisions: list of revisions to get statuses for
1914 :param revisions: list of revisions to get statuses for
1914 """
1915 """
1915 statuses = ChangesetStatus.query()\
1916 statuses = ChangesetStatus.query()\
1916 .filter(ChangesetStatus.repo == self)\
1917 .filter(ChangesetStatus.repo == self)\
1917 .filter(ChangesetStatus.version == 0)
1918 .filter(ChangesetStatus.version == 0)
1918
1919
1919 if revisions:
1920 if revisions:
1920 # Try doing the filtering in chunks to avoid hitting limits
1921 # Try doing the filtering in chunks to avoid hitting limits
1921 size = 500
1922 size = 500
1922 status_results = []
1923 status_results = []
1923 for chunk in xrange(0, len(revisions), size):
1924 for chunk in xrange(0, len(revisions), size):
1924 status_results += statuses.filter(
1925 status_results += statuses.filter(
1925 ChangesetStatus.revision.in_(
1926 ChangesetStatus.revision.in_(
1926 revisions[chunk: chunk+size])
1927 revisions[chunk: chunk+size])
1927 ).all()
1928 ).all()
1928 else:
1929 else:
1929 status_results = statuses.all()
1930 status_results = statuses.all()
1930
1931
1931 grouped = {}
1932 grouped = {}
1932
1933
1933 # maybe we have open new pullrequest without a status?
1934 # maybe we have open new pullrequest without a status?
1934 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1935 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1935 status_lbl = ChangesetStatus.get_status_lbl(stat)
1936 status_lbl = ChangesetStatus.get_status_lbl(stat)
1936 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1937 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1937 for rev in pr.revisions:
1938 for rev in pr.revisions:
1938 pr_id = pr.pull_request_id
1939 pr_id = pr.pull_request_id
1939 pr_repo = pr.target_repo.repo_name
1940 pr_repo = pr.target_repo.repo_name
1940 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1941 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1941
1942
1942 for stat in status_results:
1943 for stat in status_results:
1943 pr_id = pr_repo = None
1944 pr_id = pr_repo = None
1944 if stat.pull_request:
1945 if stat.pull_request:
1945 pr_id = stat.pull_request.pull_request_id
1946 pr_id = stat.pull_request.pull_request_id
1946 pr_repo = stat.pull_request.target_repo.repo_name
1947 pr_repo = stat.pull_request.target_repo.repo_name
1947 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1948 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1948 pr_id, pr_repo]
1949 pr_id, pr_repo]
1949 return grouped
1950 return grouped
1950
1951
1951 # ==========================================================================
1952 # ==========================================================================
1952 # SCM CACHE INSTANCE
1953 # SCM CACHE INSTANCE
1953 # ==========================================================================
1954 # ==========================================================================
1954
1955
1955 def scm_instance(self, **kwargs):
1956 def scm_instance(self, **kwargs):
1956 import rhodecode
1957 import rhodecode
1957
1958
1958 # Passing a config will not hit the cache currently only used
1959 # Passing a config will not hit the cache currently only used
1959 # for repo2dbmapper
1960 # for repo2dbmapper
1960 config = kwargs.pop('config', None)
1961 config = kwargs.pop('config', None)
1961 cache = kwargs.pop('cache', None)
1962 cache = kwargs.pop('cache', None)
1962 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1963 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1963 # if cache is NOT defined use default global, else we have a full
1964 # if cache is NOT defined use default global, else we have a full
1964 # control over cache behaviour
1965 # control over cache behaviour
1965 if cache is None and full_cache and not config:
1966 if cache is None and full_cache and not config:
1966 return self._get_instance_cached()
1967 return self._get_instance_cached()
1967 return self._get_instance(cache=bool(cache), config=config)
1968 return self._get_instance(cache=bool(cache), config=config)
1968
1969
1969 def _get_instance_cached(self):
1970 def _get_instance_cached(self):
1970 @cache_region('long_term')
1971 @cache_region('long_term')
1971 def _get_repo(cache_key):
1972 def _get_repo(cache_key):
1972 return self._get_instance()
1973 return self._get_instance()
1973
1974
1974 invalidator_context = CacheKey.repo_context_cache(
1975 invalidator_context = CacheKey.repo_context_cache(
1975 _get_repo, self.repo_name, None)
1976 _get_repo, self.repo_name, None)
1976
1977
1977 with invalidator_context as context:
1978 with invalidator_context as context:
1978 context.invalidate()
1979 context.invalidate()
1979 repo = context.compute()
1980 repo = context.compute()
1980
1981
1981 return repo
1982 return repo
1982
1983
1983 def _get_instance(self, cache=True, config=None):
1984 def _get_instance(self, cache=True, config=None):
1984 repo_full_path = self.repo_full_path
1985 repo_full_path = self.repo_full_path
1985 try:
1986 try:
1986 vcs_alias = get_scm(repo_full_path)[0]
1987 vcs_alias = get_scm(repo_full_path)[0]
1987 log.debug(
1988 log.debug(
1988 'Creating instance of %s repository from %s',
1989 'Creating instance of %s repository from %s',
1989 vcs_alias, repo_full_path)
1990 vcs_alias, repo_full_path)
1990 backend = get_backend(vcs_alias)
1991 backend = get_backend(vcs_alias)
1991 except VCSError:
1992 except VCSError:
1992 log.exception(
1993 log.exception(
1993 'Perhaps this repository is in db and not in '
1994 'Perhaps this repository is in db and not in '
1994 'filesystem run rescan repositories with '
1995 'filesystem run rescan repositories with '
1995 '"destroy old data" option from admin panel')
1996 '"destroy old data" option from admin panel')
1996 return
1997 return
1997
1998
1998 config = config or self._config
1999 config = config or self._config
1999 custom_wire = {
2000 custom_wire = {
2000 'cache': cache # controls the vcs.remote cache
2001 'cache': cache # controls the vcs.remote cache
2001 }
2002 }
2002 repo = backend(
2003 repo = backend(
2003 safe_str(repo_full_path), config=config, create=False,
2004 safe_str(repo_full_path), config=config, create=False,
2004 with_wire=custom_wire)
2005 with_wire=custom_wire)
2005
2006
2006 return repo
2007 return repo
2007
2008
2008 def __json__(self):
2009 def __json__(self):
2009 return {'landing_rev': self.landing_rev}
2010 return {'landing_rev': self.landing_rev}
2010
2011
2011 def get_dict(self):
2012 def get_dict(self):
2012
2013
2013 # Since we transformed `repo_name` to a hybrid property, we need to
2014 # Since we transformed `repo_name` to a hybrid property, we need to
2014 # keep compatibility with the code which uses `repo_name` field.
2015 # keep compatibility with the code which uses `repo_name` field.
2015
2016
2016 result = super(Repository, self).get_dict()
2017 result = super(Repository, self).get_dict()
2017 result['repo_name'] = result.pop('_repo_name', None)
2018 result['repo_name'] = result.pop('_repo_name', None)
2018 return result
2019 return result
2019
2020
2020
2021
2021 class RepoGroup(Base, BaseModel):
2022 class RepoGroup(Base, BaseModel):
2022 __tablename__ = 'groups'
2023 __tablename__ = 'groups'
2023 __table_args__ = (
2024 __table_args__ = (
2024 UniqueConstraint('group_name', 'group_parent_id'),
2025 UniqueConstraint('group_name', 'group_parent_id'),
2025 CheckConstraint('group_id != group_parent_id'),
2026 CheckConstraint('group_id != group_parent_id'),
2026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2027 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2027 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2028 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2028 )
2029 )
2029 __mapper_args__ = {'order_by': 'group_name'}
2030 __mapper_args__ = {'order_by': 'group_name'}
2030
2031
2031 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2032 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2032
2033
2033 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2034 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2034 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2035 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2035 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2036 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2036 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2037 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2037 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2038 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2038 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2039 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2039 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2040 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2040
2041
2041 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2042 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2042 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2043 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2043 parent_group = relationship('RepoGroup', remote_side=group_id)
2044 parent_group = relationship('RepoGroup', remote_side=group_id)
2044 user = relationship('User')
2045 user = relationship('User')
2045
2046
2046 def __init__(self, group_name='', parent_group=None):
2047 def __init__(self, group_name='', parent_group=None):
2047 self.group_name = group_name
2048 self.group_name = group_name
2048 self.parent_group = parent_group
2049 self.parent_group = parent_group
2049
2050
2050 def __unicode__(self):
2051 def __unicode__(self):
2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2052 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2052 self.group_name)
2053 self.group_name)
2053
2054
2054 @classmethod
2055 @classmethod
2055 def _generate_choice(cls, repo_group):
2056 def _generate_choice(cls, repo_group):
2056 from webhelpers.html import literal as _literal
2057 from webhelpers.html import literal as _literal
2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2058 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2059 return repo_group.group_id, _name(repo_group.full_path_splitted)
2059
2060
2060 @classmethod
2061 @classmethod
2061 def groups_choices(cls, groups=None, show_empty_group=True):
2062 def groups_choices(cls, groups=None, show_empty_group=True):
2062 if not groups:
2063 if not groups:
2063 groups = cls.query().all()
2064 groups = cls.query().all()
2064
2065
2065 repo_groups = []
2066 repo_groups = []
2066 if show_empty_group:
2067 if show_empty_group:
2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2068 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2068
2069
2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2070 repo_groups.extend([cls._generate_choice(x) for x in groups])
2070
2071
2071 repo_groups = sorted(
2072 repo_groups = sorted(
2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2073 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2073 return repo_groups
2074 return repo_groups
2074
2075
2075 @classmethod
2076 @classmethod
2076 def url_sep(cls):
2077 def url_sep(cls):
2077 return URL_SEP
2078 return URL_SEP
2078
2079
2079 @classmethod
2080 @classmethod
2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2081 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2081 if case_insensitive:
2082 if case_insensitive:
2082 gr = cls.query().filter(func.lower(cls.group_name)
2083 gr = cls.query().filter(func.lower(cls.group_name)
2083 == func.lower(group_name))
2084 == func.lower(group_name))
2084 else:
2085 else:
2085 gr = cls.query().filter(cls.group_name == group_name)
2086 gr = cls.query().filter(cls.group_name == group_name)
2086 if cache:
2087 if cache:
2087 gr = gr.options(FromCache(
2088 gr = gr.options(FromCache(
2088 "sql_cache_short",
2089 "sql_cache_short",
2089 "get_group_%s" % _hash_key(group_name)))
2090 "get_group_%s" % _hash_key(group_name)))
2090 return gr.scalar()
2091 return gr.scalar()
2091
2092
2092 @classmethod
2093 @classmethod
2093 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2094 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2094 case_insensitive=True):
2095 case_insensitive=True):
2095 q = RepoGroup.query()
2096 q = RepoGroup.query()
2096
2097
2097 if not isinstance(user_id, Optional):
2098 if not isinstance(user_id, Optional):
2098 q = q.filter(RepoGroup.user_id == user_id)
2099 q = q.filter(RepoGroup.user_id == user_id)
2099
2100
2100 if not isinstance(group_id, Optional):
2101 if not isinstance(group_id, Optional):
2101 q = q.filter(RepoGroup.group_parent_id == group_id)
2102 q = q.filter(RepoGroup.group_parent_id == group_id)
2102
2103
2103 if case_insensitive:
2104 if case_insensitive:
2104 q = q.order_by(func.lower(RepoGroup.group_name))
2105 q = q.order_by(func.lower(RepoGroup.group_name))
2105 else:
2106 else:
2106 q = q.order_by(RepoGroup.group_name)
2107 q = q.order_by(RepoGroup.group_name)
2107 return q.all()
2108 return q.all()
2108
2109
2109 @property
2110 @property
2110 def parents(self):
2111 def parents(self):
2111 parents_recursion_limit = 10
2112 parents_recursion_limit = 10
2112 groups = []
2113 groups = []
2113 if self.parent_group is None:
2114 if self.parent_group is None:
2114 return groups
2115 return groups
2115 cur_gr = self.parent_group
2116 cur_gr = self.parent_group
2116 groups.insert(0, cur_gr)
2117 groups.insert(0, cur_gr)
2117 cnt = 0
2118 cnt = 0
2118 while 1:
2119 while 1:
2119 cnt += 1
2120 cnt += 1
2120 gr = getattr(cur_gr, 'parent_group', None)
2121 gr = getattr(cur_gr, 'parent_group', None)
2121 cur_gr = cur_gr.parent_group
2122 cur_gr = cur_gr.parent_group
2122 if gr is None:
2123 if gr is None:
2123 break
2124 break
2124 if cnt == parents_recursion_limit:
2125 if cnt == parents_recursion_limit:
2125 # this will prevent accidental infinit loops
2126 # this will prevent accidental infinit loops
2126 log.error(('more than %s parents found for group %s, stopping '
2127 log.error(('more than %s parents found for group %s, stopping '
2127 'recursive parent fetching' % (parents_recursion_limit, self)))
2128 'recursive parent fetching' % (parents_recursion_limit, self)))
2128 break
2129 break
2129
2130
2130 groups.insert(0, gr)
2131 groups.insert(0, gr)
2131 return groups
2132 return groups
2132
2133
2133 @property
2134 @property
2134 def children(self):
2135 def children(self):
2135 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2136 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2136
2137
2137 @property
2138 @property
2138 def name(self):
2139 def name(self):
2139 return self.group_name.split(RepoGroup.url_sep())[-1]
2140 return self.group_name.split(RepoGroup.url_sep())[-1]
2140
2141
2141 @property
2142 @property
2142 def full_path(self):
2143 def full_path(self):
2143 return self.group_name
2144 return self.group_name
2144
2145
2145 @property
2146 @property
2146 def full_path_splitted(self):
2147 def full_path_splitted(self):
2147 return self.group_name.split(RepoGroup.url_sep())
2148 return self.group_name.split(RepoGroup.url_sep())
2148
2149
2149 @property
2150 @property
2150 def repositories(self):
2151 def repositories(self):
2151 return Repository.query()\
2152 return Repository.query()\
2152 .filter(Repository.group == self)\
2153 .filter(Repository.group == self)\
2153 .order_by(Repository.repo_name)
2154 .order_by(Repository.repo_name)
2154
2155
2155 @property
2156 @property
2156 def repositories_recursive_count(self):
2157 def repositories_recursive_count(self):
2157 cnt = self.repositories.count()
2158 cnt = self.repositories.count()
2158
2159
2159 def children_count(group):
2160 def children_count(group):
2160 cnt = 0
2161 cnt = 0
2161 for child in group.children:
2162 for child in group.children:
2162 cnt += child.repositories.count()
2163 cnt += child.repositories.count()
2163 cnt += children_count(child)
2164 cnt += children_count(child)
2164 return cnt
2165 return cnt
2165
2166
2166 return cnt + children_count(self)
2167 return cnt + children_count(self)
2167
2168
2168 def _recursive_objects(self, include_repos=True):
2169 def _recursive_objects(self, include_repos=True):
2169 all_ = []
2170 all_ = []
2170
2171
2171 def _get_members(root_gr):
2172 def _get_members(root_gr):
2172 if include_repos:
2173 if include_repos:
2173 for r in root_gr.repositories:
2174 for r in root_gr.repositories:
2174 all_.append(r)
2175 all_.append(r)
2175 childs = root_gr.children.all()
2176 childs = root_gr.children.all()
2176 if childs:
2177 if childs:
2177 for gr in childs:
2178 for gr in childs:
2178 all_.append(gr)
2179 all_.append(gr)
2179 _get_members(gr)
2180 _get_members(gr)
2180
2181
2181 _get_members(self)
2182 _get_members(self)
2182 return [self] + all_
2183 return [self] + all_
2183
2184
2184 def recursive_groups_and_repos(self):
2185 def recursive_groups_and_repos(self):
2185 """
2186 """
2186 Recursive return all groups, with repositories in those groups
2187 Recursive return all groups, with repositories in those groups
2187 """
2188 """
2188 return self._recursive_objects()
2189 return self._recursive_objects()
2189
2190
2190 def recursive_groups(self):
2191 def recursive_groups(self):
2191 """
2192 """
2192 Returns all children groups for this group including children of children
2193 Returns all children groups for this group including children of children
2193 """
2194 """
2194 return self._recursive_objects(include_repos=False)
2195 return self._recursive_objects(include_repos=False)
2195
2196
2196 def get_new_name(self, group_name):
2197 def get_new_name(self, group_name):
2197 """
2198 """
2198 returns new full group name based on parent and new name
2199 returns new full group name based on parent and new name
2199
2200
2200 :param group_name:
2201 :param group_name:
2201 """
2202 """
2202 path_prefix = (self.parent_group.full_path_splitted if
2203 path_prefix = (self.parent_group.full_path_splitted if
2203 self.parent_group else [])
2204 self.parent_group else [])
2204 return RepoGroup.url_sep().join(path_prefix + [group_name])
2205 return RepoGroup.url_sep().join(path_prefix + [group_name])
2205
2206
2206 def permissions(self, with_admins=True, with_owner=True):
2207 def permissions(self, with_admins=True, with_owner=True):
2207 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2208 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2208 q = q.options(joinedload(UserRepoGroupToPerm.group),
2209 q = q.options(joinedload(UserRepoGroupToPerm.group),
2209 joinedload(UserRepoGroupToPerm.user),
2210 joinedload(UserRepoGroupToPerm.user),
2210 joinedload(UserRepoGroupToPerm.permission),)
2211 joinedload(UserRepoGroupToPerm.permission),)
2211
2212
2212 # get owners and admins and permissions. We do a trick of re-writing
2213 # get owners and admins and permissions. We do a trick of re-writing
2213 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2214 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2214 # has a global reference and changing one object propagates to all
2215 # has a global reference and changing one object propagates to all
2215 # others. This means if admin is also an owner admin_row that change
2216 # others. This means if admin is also an owner admin_row that change
2216 # would propagate to both objects
2217 # would propagate to both objects
2217 perm_rows = []
2218 perm_rows = []
2218 for _usr in q.all():
2219 for _usr in q.all():
2219 usr = AttributeDict(_usr.user.get_dict())
2220 usr = AttributeDict(_usr.user.get_dict())
2220 usr.permission = _usr.permission.permission_name
2221 usr.permission = _usr.permission.permission_name
2221 perm_rows.append(usr)
2222 perm_rows.append(usr)
2222
2223
2223 # filter the perm rows by 'default' first and then sort them by
2224 # filter the perm rows by 'default' first and then sort them by
2224 # admin,write,read,none permissions sorted again alphabetically in
2225 # admin,write,read,none permissions sorted again alphabetically in
2225 # each group
2226 # each group
2226 perm_rows = sorted(perm_rows, key=display_sort)
2227 perm_rows = sorted(perm_rows, key=display_sort)
2227
2228
2228 _admin_perm = 'group.admin'
2229 _admin_perm = 'group.admin'
2229 owner_row = []
2230 owner_row = []
2230 if with_owner:
2231 if with_owner:
2231 usr = AttributeDict(self.user.get_dict())
2232 usr = AttributeDict(self.user.get_dict())
2232 usr.owner_row = True
2233 usr.owner_row = True
2233 usr.permission = _admin_perm
2234 usr.permission = _admin_perm
2234 owner_row.append(usr)
2235 owner_row.append(usr)
2235
2236
2236 super_admin_rows = []
2237 super_admin_rows = []
2237 if with_admins:
2238 if with_admins:
2238 for usr in User.get_all_super_admins():
2239 for usr in User.get_all_super_admins():
2239 # if this admin is also owner, don't double the record
2240 # if this admin is also owner, don't double the record
2240 if usr.user_id == owner_row[0].user_id:
2241 if usr.user_id == owner_row[0].user_id:
2241 owner_row[0].admin_row = True
2242 owner_row[0].admin_row = True
2242 else:
2243 else:
2243 usr = AttributeDict(usr.get_dict())
2244 usr = AttributeDict(usr.get_dict())
2244 usr.admin_row = True
2245 usr.admin_row = True
2245 usr.permission = _admin_perm
2246 usr.permission = _admin_perm
2246 super_admin_rows.append(usr)
2247 super_admin_rows.append(usr)
2247
2248
2248 return super_admin_rows + owner_row + perm_rows
2249 return super_admin_rows + owner_row + perm_rows
2249
2250
2250 def permission_user_groups(self):
2251 def permission_user_groups(self):
2251 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2252 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2252 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2253 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2253 joinedload(UserGroupRepoGroupToPerm.users_group),
2254 joinedload(UserGroupRepoGroupToPerm.users_group),
2254 joinedload(UserGroupRepoGroupToPerm.permission),)
2255 joinedload(UserGroupRepoGroupToPerm.permission),)
2255
2256
2256 perm_rows = []
2257 perm_rows = []
2257 for _user_group in q.all():
2258 for _user_group in q.all():
2258 usr = AttributeDict(_user_group.users_group.get_dict())
2259 usr = AttributeDict(_user_group.users_group.get_dict())
2259 usr.permission = _user_group.permission.permission_name
2260 usr.permission = _user_group.permission.permission_name
2260 perm_rows.append(usr)
2261 perm_rows.append(usr)
2261
2262
2262 return perm_rows
2263 return perm_rows
2263
2264
2264 def get_api_data(self):
2265 def get_api_data(self):
2265 """
2266 """
2266 Common function for generating api data
2267 Common function for generating api data
2267
2268
2268 """
2269 """
2269 group = self
2270 group = self
2270 data = {
2271 data = {
2271 'group_id': group.group_id,
2272 'group_id': group.group_id,
2272 'group_name': group.group_name,
2273 'group_name': group.group_name,
2273 'group_description': group.group_description,
2274 'group_description': group.group_description,
2274 'parent_group': group.parent_group.group_name if group.parent_group else None,
2275 'parent_group': group.parent_group.group_name if group.parent_group else None,
2275 'repositories': [x.repo_name for x in group.repositories],
2276 'repositories': [x.repo_name for x in group.repositories],
2276 'owner': group.user.username,
2277 'owner': group.user.username,
2277 }
2278 }
2278 return data
2279 return data
2279
2280
2280
2281
2281 class Permission(Base, BaseModel):
2282 class Permission(Base, BaseModel):
2282 __tablename__ = 'permissions'
2283 __tablename__ = 'permissions'
2283 __table_args__ = (
2284 __table_args__ = (
2284 Index('p_perm_name_idx', 'permission_name'),
2285 Index('p_perm_name_idx', 'permission_name'),
2285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2287 )
2288 )
2288 PERMS = [
2289 PERMS = [
2289 ('hg.admin', _('RhodeCode Super Administrator')),
2290 ('hg.admin', _('RhodeCode Super Administrator')),
2290
2291
2291 ('repository.none', _('Repository no access')),
2292 ('repository.none', _('Repository no access')),
2292 ('repository.read', _('Repository read access')),
2293 ('repository.read', _('Repository read access')),
2293 ('repository.write', _('Repository write access')),
2294 ('repository.write', _('Repository write access')),
2294 ('repository.admin', _('Repository admin access')),
2295 ('repository.admin', _('Repository admin access')),
2295
2296
2296 ('group.none', _('Repository group no access')),
2297 ('group.none', _('Repository group no access')),
2297 ('group.read', _('Repository group read access')),
2298 ('group.read', _('Repository group read access')),
2298 ('group.write', _('Repository group write access')),
2299 ('group.write', _('Repository group write access')),
2299 ('group.admin', _('Repository group admin access')),
2300 ('group.admin', _('Repository group admin access')),
2300
2301
2301 ('usergroup.none', _('User group no access')),
2302 ('usergroup.none', _('User group no access')),
2302 ('usergroup.read', _('User group read access')),
2303 ('usergroup.read', _('User group read access')),
2303 ('usergroup.write', _('User group write access')),
2304 ('usergroup.write', _('User group write access')),
2304 ('usergroup.admin', _('User group admin access')),
2305 ('usergroup.admin', _('User group admin access')),
2305
2306
2306 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2307 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2307 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2308 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2308
2309
2309 ('hg.usergroup.create.false', _('User Group creation disabled')),
2310 ('hg.usergroup.create.false', _('User Group creation disabled')),
2310 ('hg.usergroup.create.true', _('User Group creation enabled')),
2311 ('hg.usergroup.create.true', _('User Group creation enabled')),
2311
2312
2312 ('hg.create.none', _('Repository creation disabled')),
2313 ('hg.create.none', _('Repository creation disabled')),
2313 ('hg.create.repository', _('Repository creation enabled')),
2314 ('hg.create.repository', _('Repository creation enabled')),
2314 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2315 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2315 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2316 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2316
2317
2317 ('hg.fork.none', _('Repository forking disabled')),
2318 ('hg.fork.none', _('Repository forking disabled')),
2318 ('hg.fork.repository', _('Repository forking enabled')),
2319 ('hg.fork.repository', _('Repository forking enabled')),
2319
2320
2320 ('hg.register.none', _('Registration disabled')),
2321 ('hg.register.none', _('Registration disabled')),
2321 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2322 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2322 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2323 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2323
2324
2324 ('hg.extern_activate.manual', _('Manual activation of external account')),
2325 ('hg.extern_activate.manual', _('Manual activation of external account')),
2325 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2326 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2326
2327
2327 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2328 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2328 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2329 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2329 ]
2330 ]
2330
2331
2331 # definition of system default permissions for DEFAULT user
2332 # definition of system default permissions for DEFAULT user
2332 DEFAULT_USER_PERMISSIONS = [
2333 DEFAULT_USER_PERMISSIONS = [
2333 'repository.read',
2334 'repository.read',
2334 'group.read',
2335 'group.read',
2335 'usergroup.read',
2336 'usergroup.read',
2336 'hg.create.repository',
2337 'hg.create.repository',
2337 'hg.repogroup.create.false',
2338 'hg.repogroup.create.false',
2338 'hg.usergroup.create.false',
2339 'hg.usergroup.create.false',
2339 'hg.create.write_on_repogroup.true',
2340 'hg.create.write_on_repogroup.true',
2340 'hg.fork.repository',
2341 'hg.fork.repository',
2341 'hg.register.manual_activate',
2342 'hg.register.manual_activate',
2342 'hg.extern_activate.auto',
2343 'hg.extern_activate.auto',
2343 'hg.inherit_default_perms.true',
2344 'hg.inherit_default_perms.true',
2344 ]
2345 ]
2345
2346
2346 # defines which permissions are more important higher the more important
2347 # defines which permissions are more important higher the more important
2347 # Weight defines which permissions are more important.
2348 # Weight defines which permissions are more important.
2348 # The higher number the more important.
2349 # The higher number the more important.
2349 PERM_WEIGHTS = {
2350 PERM_WEIGHTS = {
2350 'repository.none': 0,
2351 'repository.none': 0,
2351 'repository.read': 1,
2352 'repository.read': 1,
2352 'repository.write': 3,
2353 'repository.write': 3,
2353 'repository.admin': 4,
2354 'repository.admin': 4,
2354
2355
2355 'group.none': 0,
2356 'group.none': 0,
2356 'group.read': 1,
2357 'group.read': 1,
2357 'group.write': 3,
2358 'group.write': 3,
2358 'group.admin': 4,
2359 'group.admin': 4,
2359
2360
2360 'usergroup.none': 0,
2361 'usergroup.none': 0,
2361 'usergroup.read': 1,
2362 'usergroup.read': 1,
2362 'usergroup.write': 3,
2363 'usergroup.write': 3,
2363 'usergroup.admin': 4,
2364 'usergroup.admin': 4,
2364
2365
2365 'hg.repogroup.create.false': 0,
2366 'hg.repogroup.create.false': 0,
2366 'hg.repogroup.create.true': 1,
2367 'hg.repogroup.create.true': 1,
2367
2368
2368 'hg.usergroup.create.false': 0,
2369 'hg.usergroup.create.false': 0,
2369 'hg.usergroup.create.true': 1,
2370 'hg.usergroup.create.true': 1,
2370
2371
2371 'hg.fork.none': 0,
2372 'hg.fork.none': 0,
2372 'hg.fork.repository': 1,
2373 'hg.fork.repository': 1,
2373 'hg.create.none': 0,
2374 'hg.create.none': 0,
2374 'hg.create.repository': 1
2375 'hg.create.repository': 1
2375 }
2376 }
2376
2377
2377 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2378 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2378 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2379 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2379 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2380 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2380
2381
2381 def __unicode__(self):
2382 def __unicode__(self):
2382 return u"<%s('%s:%s')>" % (
2383 return u"<%s('%s:%s')>" % (
2383 self.__class__.__name__, self.permission_id, self.permission_name
2384 self.__class__.__name__, self.permission_id, self.permission_name
2384 )
2385 )
2385
2386
2386 @classmethod
2387 @classmethod
2387 def get_by_key(cls, key):
2388 def get_by_key(cls, key):
2388 return cls.query().filter(cls.permission_name == key).scalar()
2389 return cls.query().filter(cls.permission_name == key).scalar()
2389
2390
2390 @classmethod
2391 @classmethod
2391 def get_default_repo_perms(cls, user_id, repo_id=None):
2392 def get_default_repo_perms(cls, user_id, repo_id=None):
2392 q = Session().query(UserRepoToPerm, Repository, Permission)\
2393 q = Session().query(UserRepoToPerm, Repository, Permission)\
2393 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2394 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2394 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2395 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2395 .filter(UserRepoToPerm.user_id == user_id)
2396 .filter(UserRepoToPerm.user_id == user_id)
2396 if repo_id:
2397 if repo_id:
2397 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2398 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2398 return q.all()
2399 return q.all()
2399
2400
2400 @classmethod
2401 @classmethod
2401 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2402 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2402 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2403 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2403 .join(
2404 .join(
2404 Permission,
2405 Permission,
2405 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2406 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2406 .join(
2407 .join(
2407 Repository,
2408 Repository,
2408 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2409 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2409 .join(
2410 .join(
2410 UserGroup,
2411 UserGroup,
2411 UserGroupRepoToPerm.users_group_id ==
2412 UserGroupRepoToPerm.users_group_id ==
2412 UserGroup.users_group_id)\
2413 UserGroup.users_group_id)\
2413 .join(
2414 .join(
2414 UserGroupMember,
2415 UserGroupMember,
2415 UserGroupRepoToPerm.users_group_id ==
2416 UserGroupRepoToPerm.users_group_id ==
2416 UserGroupMember.users_group_id)\
2417 UserGroupMember.users_group_id)\
2417 .filter(
2418 .filter(
2418 UserGroupMember.user_id == user_id,
2419 UserGroupMember.user_id == user_id,
2419 UserGroup.users_group_active == true())
2420 UserGroup.users_group_active == true())
2420 if repo_id:
2421 if repo_id:
2421 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2422 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2422 return q.all()
2423 return q.all()
2423
2424
2424 @classmethod
2425 @classmethod
2425 def get_default_group_perms(cls, user_id, repo_group_id=None):
2426 def get_default_group_perms(cls, user_id, repo_group_id=None):
2426 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2427 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2427 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2428 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2428 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2429 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2429 .filter(UserRepoGroupToPerm.user_id == user_id)
2430 .filter(UserRepoGroupToPerm.user_id == user_id)
2430 if repo_group_id:
2431 if repo_group_id:
2431 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2432 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2432 return q.all()
2433 return q.all()
2433
2434
2434 @classmethod
2435 @classmethod
2435 def get_default_group_perms_from_user_group(
2436 def get_default_group_perms_from_user_group(
2436 cls, user_id, repo_group_id=None):
2437 cls, user_id, repo_group_id=None):
2437 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2438 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2438 .join(
2439 .join(
2439 Permission,
2440 Permission,
2440 UserGroupRepoGroupToPerm.permission_id ==
2441 UserGroupRepoGroupToPerm.permission_id ==
2441 Permission.permission_id)\
2442 Permission.permission_id)\
2442 .join(
2443 .join(
2443 RepoGroup,
2444 RepoGroup,
2444 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2445 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2445 .join(
2446 .join(
2446 UserGroup,
2447 UserGroup,
2447 UserGroupRepoGroupToPerm.users_group_id ==
2448 UserGroupRepoGroupToPerm.users_group_id ==
2448 UserGroup.users_group_id)\
2449 UserGroup.users_group_id)\
2449 .join(
2450 .join(
2450 UserGroupMember,
2451 UserGroupMember,
2451 UserGroupRepoGroupToPerm.users_group_id ==
2452 UserGroupRepoGroupToPerm.users_group_id ==
2452 UserGroupMember.users_group_id)\
2453 UserGroupMember.users_group_id)\
2453 .filter(
2454 .filter(
2454 UserGroupMember.user_id == user_id,
2455 UserGroupMember.user_id == user_id,
2455 UserGroup.users_group_active == true())
2456 UserGroup.users_group_active == true())
2456 if repo_group_id:
2457 if repo_group_id:
2457 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2458 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2458 return q.all()
2459 return q.all()
2459
2460
2460 @classmethod
2461 @classmethod
2461 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2462 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2462 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2463 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2463 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2464 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2464 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2465 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2465 .filter(UserUserGroupToPerm.user_id == user_id)
2466 .filter(UserUserGroupToPerm.user_id == user_id)
2466 if user_group_id:
2467 if user_group_id:
2467 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2468 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2468 return q.all()
2469 return q.all()
2469
2470
2470 @classmethod
2471 @classmethod
2471 def get_default_user_group_perms_from_user_group(
2472 def get_default_user_group_perms_from_user_group(
2472 cls, user_id, user_group_id=None):
2473 cls, user_id, user_group_id=None):
2473 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2474 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2474 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2475 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2475 .join(
2476 .join(
2476 Permission,
2477 Permission,
2477 UserGroupUserGroupToPerm.permission_id ==
2478 UserGroupUserGroupToPerm.permission_id ==
2478 Permission.permission_id)\
2479 Permission.permission_id)\
2479 .join(
2480 .join(
2480 TargetUserGroup,
2481 TargetUserGroup,
2481 UserGroupUserGroupToPerm.target_user_group_id ==
2482 UserGroupUserGroupToPerm.target_user_group_id ==
2482 TargetUserGroup.users_group_id)\
2483 TargetUserGroup.users_group_id)\
2483 .join(
2484 .join(
2484 UserGroup,
2485 UserGroup,
2485 UserGroupUserGroupToPerm.user_group_id ==
2486 UserGroupUserGroupToPerm.user_group_id ==
2486 UserGroup.users_group_id)\
2487 UserGroup.users_group_id)\
2487 .join(
2488 .join(
2488 UserGroupMember,
2489 UserGroupMember,
2489 UserGroupUserGroupToPerm.user_group_id ==
2490 UserGroupUserGroupToPerm.user_group_id ==
2490 UserGroupMember.users_group_id)\
2491 UserGroupMember.users_group_id)\
2491 .filter(
2492 .filter(
2492 UserGroupMember.user_id == user_id,
2493 UserGroupMember.user_id == user_id,
2493 UserGroup.users_group_active == true())
2494 UserGroup.users_group_active == true())
2494 if user_group_id:
2495 if user_group_id:
2495 q = q.filter(
2496 q = q.filter(
2496 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2497 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2497
2498
2498 return q.all()
2499 return q.all()
2499
2500
2500
2501
2501 class UserRepoToPerm(Base, BaseModel):
2502 class UserRepoToPerm(Base, BaseModel):
2502 __tablename__ = 'repo_to_perm'
2503 __tablename__ = 'repo_to_perm'
2503 __table_args__ = (
2504 __table_args__ = (
2504 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2505 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2506 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2507 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2507 )
2508 )
2508 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2509 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2510 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2510 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2511 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2511 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2512 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2512
2513
2513 user = relationship('User')
2514 user = relationship('User')
2514 repository = relationship('Repository')
2515 repository = relationship('Repository')
2515 permission = relationship('Permission')
2516 permission = relationship('Permission')
2516
2517
2517 @classmethod
2518 @classmethod
2518 def create(cls, user, repository, permission):
2519 def create(cls, user, repository, permission):
2519 n = cls()
2520 n = cls()
2520 n.user = user
2521 n.user = user
2521 n.repository = repository
2522 n.repository = repository
2522 n.permission = permission
2523 n.permission = permission
2523 Session().add(n)
2524 Session().add(n)
2524 return n
2525 return n
2525
2526
2526 def __unicode__(self):
2527 def __unicode__(self):
2527 return u'<%s => %s >' % (self.user, self.repository)
2528 return u'<%s => %s >' % (self.user, self.repository)
2528
2529
2529
2530
2530 class UserUserGroupToPerm(Base, BaseModel):
2531 class UserUserGroupToPerm(Base, BaseModel):
2531 __tablename__ = 'user_user_group_to_perm'
2532 __tablename__ = 'user_user_group_to_perm'
2532 __table_args__ = (
2533 __table_args__ = (
2533 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2534 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2535 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2536 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2536 )
2537 )
2537 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2538 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2538 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2539 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2540 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2540 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2541 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2541
2542
2542 user = relationship('User')
2543 user = relationship('User')
2543 user_group = relationship('UserGroup')
2544 user_group = relationship('UserGroup')
2544 permission = relationship('Permission')
2545 permission = relationship('Permission')
2545
2546
2546 @classmethod
2547 @classmethod
2547 def create(cls, user, user_group, permission):
2548 def create(cls, user, user_group, permission):
2548 n = cls()
2549 n = cls()
2549 n.user = user
2550 n.user = user
2550 n.user_group = user_group
2551 n.user_group = user_group
2551 n.permission = permission
2552 n.permission = permission
2552 Session().add(n)
2553 Session().add(n)
2553 return n
2554 return n
2554
2555
2555 def __unicode__(self):
2556 def __unicode__(self):
2556 return u'<%s => %s >' % (self.user, self.user_group)
2557 return u'<%s => %s >' % (self.user, self.user_group)
2557
2558
2558
2559
2559 class UserToPerm(Base, BaseModel):
2560 class UserToPerm(Base, BaseModel):
2560 __tablename__ = 'user_to_perm'
2561 __tablename__ = 'user_to_perm'
2561 __table_args__ = (
2562 __table_args__ = (
2562 UniqueConstraint('user_id', 'permission_id'),
2563 UniqueConstraint('user_id', 'permission_id'),
2563 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2564 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2564 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2565 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2565 )
2566 )
2566 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2567 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2568 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2568 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2569 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2569
2570
2570 user = relationship('User')
2571 user = relationship('User')
2571 permission = relationship('Permission', lazy='joined')
2572 permission = relationship('Permission', lazy='joined')
2572
2573
2573 def __unicode__(self):
2574 def __unicode__(self):
2574 return u'<%s => %s >' % (self.user, self.permission)
2575 return u'<%s => %s >' % (self.user, self.permission)
2575
2576
2576
2577
2577 class UserGroupRepoToPerm(Base, BaseModel):
2578 class UserGroupRepoToPerm(Base, BaseModel):
2578 __tablename__ = 'users_group_repo_to_perm'
2579 __tablename__ = 'users_group_repo_to_perm'
2579 __table_args__ = (
2580 __table_args__ = (
2580 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2581 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2582 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2583 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2583 )
2584 )
2584 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2585 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2586 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2587 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2587 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2588 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2588
2589
2589 users_group = relationship('UserGroup')
2590 users_group = relationship('UserGroup')
2590 permission = relationship('Permission')
2591 permission = relationship('Permission')
2591 repository = relationship('Repository')
2592 repository = relationship('Repository')
2592
2593
2593 @classmethod
2594 @classmethod
2594 def create(cls, users_group, repository, permission):
2595 def create(cls, users_group, repository, permission):
2595 n = cls()
2596 n = cls()
2596 n.users_group = users_group
2597 n.users_group = users_group
2597 n.repository = repository
2598 n.repository = repository
2598 n.permission = permission
2599 n.permission = permission
2599 Session().add(n)
2600 Session().add(n)
2600 return n
2601 return n
2601
2602
2602 def __unicode__(self):
2603 def __unicode__(self):
2603 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2604 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2604
2605
2605
2606
2606 class UserGroupUserGroupToPerm(Base, BaseModel):
2607 class UserGroupUserGroupToPerm(Base, BaseModel):
2607 __tablename__ = 'user_group_user_group_to_perm'
2608 __tablename__ = 'user_group_user_group_to_perm'
2608 __table_args__ = (
2609 __table_args__ = (
2609 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2610 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2610 CheckConstraint('target_user_group_id != user_group_id'),
2611 CheckConstraint('target_user_group_id != user_group_id'),
2611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2612 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2612 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2613 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2613 )
2614 )
2614 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)
2615 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)
2615 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2616 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2616 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2617 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2617 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2618 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2618
2619
2619 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2620 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2620 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2621 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2621 permission = relationship('Permission')
2622 permission = relationship('Permission')
2622
2623
2623 @classmethod
2624 @classmethod
2624 def create(cls, target_user_group, user_group, permission):
2625 def create(cls, target_user_group, user_group, permission):
2625 n = cls()
2626 n = cls()
2626 n.target_user_group = target_user_group
2627 n.target_user_group = target_user_group
2627 n.user_group = user_group
2628 n.user_group = user_group
2628 n.permission = permission
2629 n.permission = permission
2629 Session().add(n)
2630 Session().add(n)
2630 return n
2631 return n
2631
2632
2632 def __unicode__(self):
2633 def __unicode__(self):
2633 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2634 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2634
2635
2635
2636
2636 class UserGroupToPerm(Base, BaseModel):
2637 class UserGroupToPerm(Base, BaseModel):
2637 __tablename__ = 'users_group_to_perm'
2638 __tablename__ = 'users_group_to_perm'
2638 __table_args__ = (
2639 __table_args__ = (
2639 UniqueConstraint('users_group_id', 'permission_id',),
2640 UniqueConstraint('users_group_id', 'permission_id',),
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2641 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2642 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2642 )
2643 )
2643 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2644 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2644 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2645 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2646 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2646
2647
2647 users_group = relationship('UserGroup')
2648 users_group = relationship('UserGroup')
2648 permission = relationship('Permission')
2649 permission = relationship('Permission')
2649
2650
2650
2651
2651 class UserRepoGroupToPerm(Base, BaseModel):
2652 class UserRepoGroupToPerm(Base, BaseModel):
2652 __tablename__ = 'user_repo_group_to_perm'
2653 __tablename__ = 'user_repo_group_to_perm'
2653 __table_args__ = (
2654 __table_args__ = (
2654 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2655 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2656 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2656 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2657 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2657 )
2658 )
2658
2659
2659 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2660 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2660 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2661 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2662 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2662 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2663 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2663
2664
2664 user = relationship('User')
2665 user = relationship('User')
2665 group = relationship('RepoGroup')
2666 group = relationship('RepoGroup')
2666 permission = relationship('Permission')
2667 permission = relationship('Permission')
2667
2668
2668 @classmethod
2669 @classmethod
2669 def create(cls, user, repository_group, permission):
2670 def create(cls, user, repository_group, permission):
2670 n = cls()
2671 n = cls()
2671 n.user = user
2672 n.user = user
2672 n.group = repository_group
2673 n.group = repository_group
2673 n.permission = permission
2674 n.permission = permission
2674 Session().add(n)
2675 Session().add(n)
2675 return n
2676 return n
2676
2677
2677
2678
2678 class UserGroupRepoGroupToPerm(Base, BaseModel):
2679 class UserGroupRepoGroupToPerm(Base, BaseModel):
2679 __tablename__ = 'users_group_repo_group_to_perm'
2680 __tablename__ = 'users_group_repo_group_to_perm'
2680 __table_args__ = (
2681 __table_args__ = (
2681 UniqueConstraint('users_group_id', 'group_id'),
2682 UniqueConstraint('users_group_id', 'group_id'),
2682 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2683 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2683 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2684 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2684 )
2685 )
2685
2686
2686 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)
2687 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)
2687 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2688 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2688 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2689 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2689 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2690 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2690
2691
2691 users_group = relationship('UserGroup')
2692 users_group = relationship('UserGroup')
2692 permission = relationship('Permission')
2693 permission = relationship('Permission')
2693 group = relationship('RepoGroup')
2694 group = relationship('RepoGroup')
2694
2695
2695 @classmethod
2696 @classmethod
2696 def create(cls, user_group, repository_group, permission):
2697 def create(cls, user_group, repository_group, permission):
2697 n = cls()
2698 n = cls()
2698 n.users_group = user_group
2699 n.users_group = user_group
2699 n.group = repository_group
2700 n.group = repository_group
2700 n.permission = permission
2701 n.permission = permission
2701 Session().add(n)
2702 Session().add(n)
2702 return n
2703 return n
2703
2704
2704 def __unicode__(self):
2705 def __unicode__(self):
2705 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2706 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2706
2707
2707
2708
2708 class Statistics(Base, BaseModel):
2709 class Statistics(Base, BaseModel):
2709 __tablename__ = 'statistics'
2710 __tablename__ = 'statistics'
2710 __table_args__ = (
2711 __table_args__ = (
2711 UniqueConstraint('repository_id'),
2712 UniqueConstraint('repository_id'),
2712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2713 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2713 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2714 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2714 )
2715 )
2715 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2716 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2717 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2717 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2718 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2718 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2719 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2719 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2720 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2720 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2721 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2721
2722
2722 repository = relationship('Repository', single_parent=True)
2723 repository = relationship('Repository', single_parent=True)
2723
2724
2724
2725
2725 class UserFollowing(Base, BaseModel):
2726 class UserFollowing(Base, BaseModel):
2726 __tablename__ = 'user_followings'
2727 __tablename__ = 'user_followings'
2727 __table_args__ = (
2728 __table_args__ = (
2728 UniqueConstraint('user_id', 'follows_repository_id'),
2729 UniqueConstraint('user_id', 'follows_repository_id'),
2729 UniqueConstraint('user_id', 'follows_user_id'),
2730 UniqueConstraint('user_id', 'follows_user_id'),
2730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2731 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2731 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2732 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2732 )
2733 )
2733
2734
2734 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2735 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2735 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2736 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2736 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2737 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2737 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2738 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2738 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2739 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2739
2740
2740 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2741 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2741
2742
2742 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2743 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2743 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2744 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2744
2745
2745 @classmethod
2746 @classmethod
2746 def get_repo_followers(cls, repo_id):
2747 def get_repo_followers(cls, repo_id):
2747 return cls.query().filter(cls.follows_repo_id == repo_id)
2748 return cls.query().filter(cls.follows_repo_id == repo_id)
2748
2749
2749
2750
2750 class CacheKey(Base, BaseModel):
2751 class CacheKey(Base, BaseModel):
2751 __tablename__ = 'cache_invalidation'
2752 __tablename__ = 'cache_invalidation'
2752 __table_args__ = (
2753 __table_args__ = (
2753 UniqueConstraint('cache_key'),
2754 UniqueConstraint('cache_key'),
2754 Index('key_idx', 'cache_key'),
2755 Index('key_idx', 'cache_key'),
2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2756 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2757 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2757 )
2758 )
2758 CACHE_TYPE_ATOM = 'ATOM'
2759 CACHE_TYPE_ATOM = 'ATOM'
2759 CACHE_TYPE_RSS = 'RSS'
2760 CACHE_TYPE_RSS = 'RSS'
2760 CACHE_TYPE_README = 'README'
2761 CACHE_TYPE_README = 'README'
2761
2762
2762 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2763 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2763 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2764 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2764 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2765 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2766 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2766
2767
2767 def __init__(self, cache_key, cache_args=''):
2768 def __init__(self, cache_key, cache_args=''):
2768 self.cache_key = cache_key
2769 self.cache_key = cache_key
2769 self.cache_args = cache_args
2770 self.cache_args = cache_args
2770 self.cache_active = False
2771 self.cache_active = False
2771
2772
2772 def __unicode__(self):
2773 def __unicode__(self):
2773 return u"<%s('%s:%s[%s]')>" % (
2774 return u"<%s('%s:%s[%s]')>" % (
2774 self.__class__.__name__,
2775 self.__class__.__name__,
2775 self.cache_id, self.cache_key, self.cache_active)
2776 self.cache_id, self.cache_key, self.cache_active)
2776
2777
2777 def _cache_key_partition(self):
2778 def _cache_key_partition(self):
2778 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2779 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2779 return prefix, repo_name, suffix
2780 return prefix, repo_name, suffix
2780
2781
2781 def get_prefix(self):
2782 def get_prefix(self):
2782 """
2783 """
2783 Try to extract prefix from existing cache key. The key could consist
2784 Try to extract prefix from existing cache key. The key could consist
2784 of prefix, repo_name, suffix
2785 of prefix, repo_name, suffix
2785 """
2786 """
2786 # this returns prefix, repo_name, suffix
2787 # this returns prefix, repo_name, suffix
2787 return self._cache_key_partition()[0]
2788 return self._cache_key_partition()[0]
2788
2789
2789 def get_suffix(self):
2790 def get_suffix(self):
2790 """
2791 """
2791 get suffix that might have been used in _get_cache_key to
2792 get suffix that might have been used in _get_cache_key to
2792 generate self.cache_key. Only used for informational purposes
2793 generate self.cache_key. Only used for informational purposes
2793 in repo_edit.html.
2794 in repo_edit.html.
2794 """
2795 """
2795 # prefix, repo_name, suffix
2796 # prefix, repo_name, suffix
2796 return self._cache_key_partition()[2]
2797 return self._cache_key_partition()[2]
2797
2798
2798 @classmethod
2799 @classmethod
2799 def delete_all_cache(cls):
2800 def delete_all_cache(cls):
2800 """
2801 """
2801 Delete all cache keys from database.
2802 Delete all cache keys from database.
2802 Should only be run when all instances are down and all entries
2803 Should only be run when all instances are down and all entries
2803 thus stale.
2804 thus stale.
2804 """
2805 """
2805 cls.query().delete()
2806 cls.query().delete()
2806 Session().commit()
2807 Session().commit()
2807
2808
2808 @classmethod
2809 @classmethod
2809 def get_cache_key(cls, repo_name, cache_type):
2810 def get_cache_key(cls, repo_name, cache_type):
2810 """
2811 """
2811
2812
2812 Generate a cache key for this process of RhodeCode instance.
2813 Generate a cache key for this process of RhodeCode instance.
2813 Prefix most likely will be process id or maybe explicitly set
2814 Prefix most likely will be process id or maybe explicitly set
2814 instance_id from .ini file.
2815 instance_id from .ini file.
2815 """
2816 """
2816 import rhodecode
2817 import rhodecode
2817 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2818 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2818
2819
2819 repo_as_unicode = safe_unicode(repo_name)
2820 repo_as_unicode = safe_unicode(repo_name)
2820 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2821 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2821 if cache_type else repo_as_unicode
2822 if cache_type else repo_as_unicode
2822
2823
2823 return u'{}{}'.format(prefix, key)
2824 return u'{}{}'.format(prefix, key)
2824
2825
2825 @classmethod
2826 @classmethod
2826 def set_invalidate(cls, repo_name, delete=False):
2827 def set_invalidate(cls, repo_name, delete=False):
2827 """
2828 """
2828 Mark all caches of a repo as invalid in the database.
2829 Mark all caches of a repo as invalid in the database.
2829 """
2830 """
2830
2831
2831 try:
2832 try:
2832 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2833 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2833 if delete:
2834 if delete:
2834 log.debug('cache objects deleted for repo %s',
2835 log.debug('cache objects deleted for repo %s',
2835 safe_str(repo_name))
2836 safe_str(repo_name))
2836 qry.delete()
2837 qry.delete()
2837 else:
2838 else:
2838 log.debug('cache objects marked as invalid for repo %s',
2839 log.debug('cache objects marked as invalid for repo %s',
2839 safe_str(repo_name))
2840 safe_str(repo_name))
2840 qry.update({"cache_active": False})
2841 qry.update({"cache_active": False})
2841
2842
2842 Session().commit()
2843 Session().commit()
2843 except Exception:
2844 except Exception:
2844 log.exception(
2845 log.exception(
2845 'Cache key invalidation failed for repository %s',
2846 'Cache key invalidation failed for repository %s',
2846 safe_str(repo_name))
2847 safe_str(repo_name))
2847 Session().rollback()
2848 Session().rollback()
2848
2849
2849 @classmethod
2850 @classmethod
2850 def get_active_cache(cls, cache_key):
2851 def get_active_cache(cls, cache_key):
2851 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2852 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2852 if inv_obj:
2853 if inv_obj:
2853 return inv_obj
2854 return inv_obj
2854 return None
2855 return None
2855
2856
2856 @classmethod
2857 @classmethod
2857 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2858 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2858 """
2859 """
2859 @cache_region('long_term')
2860 @cache_region('long_term')
2860 def _heavy_calculation(cache_key):
2861 def _heavy_calculation(cache_key):
2861 return 'result'
2862 return 'result'
2862
2863
2863 cache_context = CacheKey.repo_context_cache(
2864 cache_context = CacheKey.repo_context_cache(
2864 _heavy_calculation, repo_name, cache_type)
2865 _heavy_calculation, repo_name, cache_type)
2865
2866
2866 with cache_context as context:
2867 with cache_context as context:
2867 context.invalidate()
2868 context.invalidate()
2868 computed = context.compute()
2869 computed = context.compute()
2869
2870
2870 assert computed == 'result'
2871 assert computed == 'result'
2871 """
2872 """
2872 from rhodecode.lib import caches
2873 from rhodecode.lib import caches
2873 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2874 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2874
2875
2875
2876
2876 class ChangesetComment(Base, BaseModel):
2877 class ChangesetComment(Base, BaseModel):
2877 __tablename__ = 'changeset_comments'
2878 __tablename__ = 'changeset_comments'
2878 __table_args__ = (
2879 __table_args__ = (
2879 Index('cc_revision_idx', 'revision'),
2880 Index('cc_revision_idx', 'revision'),
2880 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2881 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2882 )
2883 )
2883
2884
2884 COMMENT_OUTDATED = u'comment_outdated'
2885 COMMENT_OUTDATED = u'comment_outdated'
2885
2886
2886 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2887 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2887 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2888 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2888 revision = Column('revision', String(40), nullable=True)
2889 revision = Column('revision', String(40), nullable=True)
2889 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2890 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2890 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2891 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2891 line_no = Column('line_no', Unicode(10), nullable=True)
2892 line_no = Column('line_no', Unicode(10), nullable=True)
2892 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2893 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2893 f_path = Column('f_path', Unicode(1000), nullable=True)
2894 f_path = Column('f_path', Unicode(1000), nullable=True)
2894 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2895 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2895 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2896 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2896 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2897 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2898 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2898 renderer = Column('renderer', Unicode(64), nullable=True)
2899 renderer = Column('renderer', Unicode(64), nullable=True)
2899 display_state = Column('display_state', Unicode(128), nullable=True)
2900 display_state = Column('display_state', Unicode(128), nullable=True)
2900
2901
2901 author = relationship('User', lazy='joined')
2902 author = relationship('User', lazy='joined')
2902 repo = relationship('Repository')
2903 repo = relationship('Repository')
2903 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2904 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2904 pull_request = relationship('PullRequest', lazy='joined')
2905 pull_request = relationship('PullRequest', lazy='joined')
2905 pull_request_version = relationship('PullRequestVersion')
2906 pull_request_version = relationship('PullRequestVersion')
2906
2907
2907 @classmethod
2908 @classmethod
2908 def get_users(cls, revision=None, pull_request_id=None):
2909 def get_users(cls, revision=None, pull_request_id=None):
2909 """
2910 """
2910 Returns user associated with this ChangesetComment. ie those
2911 Returns user associated with this ChangesetComment. ie those
2911 who actually commented
2912 who actually commented
2912
2913
2913 :param cls:
2914 :param cls:
2914 :param revision:
2915 :param revision:
2915 """
2916 """
2916 q = Session().query(User)\
2917 q = Session().query(User)\
2917 .join(ChangesetComment.author)
2918 .join(ChangesetComment.author)
2918 if revision:
2919 if revision:
2919 q = q.filter(cls.revision == revision)
2920 q = q.filter(cls.revision == revision)
2920 elif pull_request_id:
2921 elif pull_request_id:
2921 q = q.filter(cls.pull_request_id == pull_request_id)
2922 q = q.filter(cls.pull_request_id == pull_request_id)
2922 return q.all()
2923 return q.all()
2923
2924
2924 def render(self, mentions=False):
2925 def render(self, mentions=False):
2925 from rhodecode.lib import helpers as h
2926 from rhodecode.lib import helpers as h
2926 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2927 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2927
2928
2928 def __repr__(self):
2929 def __repr__(self):
2929 if self.comment_id:
2930 if self.comment_id:
2930 return '<DB:ChangesetComment #%s>' % self.comment_id
2931 return '<DB:ChangesetComment #%s>' % self.comment_id
2931 else:
2932 else:
2932 return '<DB:ChangesetComment at %#x>' % id(self)
2933 return '<DB:ChangesetComment at %#x>' % id(self)
2933
2934
2934
2935
2935 class ChangesetStatus(Base, BaseModel):
2936 class ChangesetStatus(Base, BaseModel):
2936 __tablename__ = 'changeset_statuses'
2937 __tablename__ = 'changeset_statuses'
2937 __table_args__ = (
2938 __table_args__ = (
2938 Index('cs_revision_idx', 'revision'),
2939 Index('cs_revision_idx', 'revision'),
2939 Index('cs_version_idx', 'version'),
2940 Index('cs_version_idx', 'version'),
2940 UniqueConstraint('repo_id', 'revision', 'version'),
2941 UniqueConstraint('repo_id', 'revision', 'version'),
2941 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2942 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2943 )
2944 )
2944 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2945 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2945 STATUS_APPROVED = 'approved'
2946 STATUS_APPROVED = 'approved'
2946 STATUS_REJECTED = 'rejected'
2947 STATUS_REJECTED = 'rejected'
2947 STATUS_UNDER_REVIEW = 'under_review'
2948 STATUS_UNDER_REVIEW = 'under_review'
2948
2949
2949 STATUSES = [
2950 STATUSES = [
2950 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2951 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2951 (STATUS_APPROVED, _("Approved")),
2952 (STATUS_APPROVED, _("Approved")),
2952 (STATUS_REJECTED, _("Rejected")),
2953 (STATUS_REJECTED, _("Rejected")),
2953 (STATUS_UNDER_REVIEW, _("Under Review")),
2954 (STATUS_UNDER_REVIEW, _("Under Review")),
2954 ]
2955 ]
2955
2956
2956 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2957 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2957 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2958 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2958 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2959 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2959 revision = Column('revision', String(40), nullable=False)
2960 revision = Column('revision', String(40), nullable=False)
2960 status = Column('status', String(128), nullable=False, default=DEFAULT)
2961 status = Column('status', String(128), nullable=False, default=DEFAULT)
2961 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2962 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2962 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2963 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2963 version = Column('version', Integer(), nullable=False, default=0)
2964 version = Column('version', Integer(), nullable=False, default=0)
2964 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2965 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2965
2966
2966 author = relationship('User', lazy='joined')
2967 author = relationship('User', lazy='joined')
2967 repo = relationship('Repository')
2968 repo = relationship('Repository')
2968 comment = relationship('ChangesetComment', lazy='joined')
2969 comment = relationship('ChangesetComment', lazy='joined')
2969 pull_request = relationship('PullRequest', lazy='joined')
2970 pull_request = relationship('PullRequest', lazy='joined')
2970
2971
2971 def __unicode__(self):
2972 def __unicode__(self):
2972 return u"<%s('%s[%s]:%s')>" % (
2973 return u"<%s('%s[%s]:%s')>" % (
2973 self.__class__.__name__,
2974 self.__class__.__name__,
2974 self.status, self.version, self.author
2975 self.status, self.version, self.author
2975 )
2976 )
2976
2977
2977 @classmethod
2978 @classmethod
2978 def get_status_lbl(cls, value):
2979 def get_status_lbl(cls, value):
2979 return dict(cls.STATUSES).get(value)
2980 return dict(cls.STATUSES).get(value)
2980
2981
2981 @property
2982 @property
2982 def status_lbl(self):
2983 def status_lbl(self):
2983 return ChangesetStatus.get_status_lbl(self.status)
2984 return ChangesetStatus.get_status_lbl(self.status)
2984
2985
2985
2986
2986 class _PullRequestBase(BaseModel):
2987 class _PullRequestBase(BaseModel):
2987 """
2988 """
2988 Common attributes of pull request and version entries.
2989 Common attributes of pull request and version entries.
2989 """
2990 """
2990
2991
2991 # .status values
2992 # .status values
2992 STATUS_NEW = u'new'
2993 STATUS_NEW = u'new'
2993 STATUS_OPEN = u'open'
2994 STATUS_OPEN = u'open'
2994 STATUS_CLOSED = u'closed'
2995 STATUS_CLOSED = u'closed'
2995
2996
2996 title = Column('title', Unicode(255), nullable=True)
2997 title = Column('title', Unicode(255), nullable=True)
2997 description = Column(
2998 description = Column(
2998 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2999 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
2999 nullable=True)
3000 nullable=True)
3000 # new/open/closed status of pull request (not approve/reject/etc)
3001 # new/open/closed status of pull request (not approve/reject/etc)
3001 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3002 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3002 created_on = Column(
3003 created_on = Column(
3003 'created_on', DateTime(timezone=False), nullable=False,
3004 'created_on', DateTime(timezone=False), nullable=False,
3004 default=datetime.datetime.now)
3005 default=datetime.datetime.now)
3005 updated_on = Column(
3006 updated_on = Column(
3006 'updated_on', DateTime(timezone=False), nullable=False,
3007 'updated_on', DateTime(timezone=False), nullable=False,
3007 default=datetime.datetime.now)
3008 default=datetime.datetime.now)
3008
3009
3009 @declared_attr
3010 @declared_attr
3010 def user_id(cls):
3011 def user_id(cls):
3011 return Column(
3012 return Column(
3012 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3013 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3013 unique=None)
3014 unique=None)
3014
3015
3015 # 500 revisions max
3016 # 500 revisions max
3016 _revisions = Column(
3017 _revisions = Column(
3017 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3018 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3018
3019
3019 @declared_attr
3020 @declared_attr
3020 def source_repo_id(cls):
3021 def source_repo_id(cls):
3021 # TODO: dan: rename column to source_repo_id
3022 # TODO: dan: rename column to source_repo_id
3022 return Column(
3023 return Column(
3023 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3024 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3024 nullable=False)
3025 nullable=False)
3025
3026
3026 source_ref = Column('org_ref', Unicode(255), nullable=False)
3027 source_ref = Column('org_ref', Unicode(255), nullable=False)
3027
3028
3028 @declared_attr
3029 @declared_attr
3029 def target_repo_id(cls):
3030 def target_repo_id(cls):
3030 # TODO: dan: rename column to target_repo_id
3031 # TODO: dan: rename column to target_repo_id
3031 return Column(
3032 return Column(
3032 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3033 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3033 nullable=False)
3034 nullable=False)
3034
3035
3035 target_ref = Column('other_ref', Unicode(255), nullable=False)
3036 target_ref = Column('other_ref', Unicode(255), nullable=False)
3036
3037
3037 # TODO: dan: rename column to last_merge_source_rev
3038 # TODO: dan: rename column to last_merge_source_rev
3038 _last_merge_source_rev = Column(
3039 _last_merge_source_rev = Column(
3039 'last_merge_org_rev', String(40), nullable=True)
3040 'last_merge_org_rev', String(40), nullable=True)
3040 # TODO: dan: rename column to last_merge_target_rev
3041 # TODO: dan: rename column to last_merge_target_rev
3041 _last_merge_target_rev = Column(
3042 _last_merge_target_rev = Column(
3042 'last_merge_other_rev', String(40), nullable=True)
3043 'last_merge_other_rev', String(40), nullable=True)
3043 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3044 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3044 merge_rev = Column('merge_rev', String(40), nullable=True)
3045 merge_rev = Column('merge_rev', String(40), nullable=True)
3045
3046
3046 @hybrid_property
3047 @hybrid_property
3047 def revisions(self):
3048 def revisions(self):
3048 return self._revisions.split(':') if self._revisions else []
3049 return self._revisions.split(':') if self._revisions else []
3049
3050
3050 @revisions.setter
3051 @revisions.setter
3051 def revisions(self, val):
3052 def revisions(self, val):
3052 self._revisions = ':'.join(val)
3053 self._revisions = ':'.join(val)
3053
3054
3054 @declared_attr
3055 @declared_attr
3055 def author(cls):
3056 def author(cls):
3056 return relationship('User', lazy='joined')
3057 return relationship('User', lazy='joined')
3057
3058
3058 @declared_attr
3059 @declared_attr
3059 def source_repo(cls):
3060 def source_repo(cls):
3060 return relationship(
3061 return relationship(
3061 'Repository',
3062 'Repository',
3062 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3063 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3063
3064
3064 @property
3065 @property
3065 def source_ref_parts(self):
3066 def source_ref_parts(self):
3066 refs = self.source_ref.split(':')
3067 refs = self.source_ref.split(':')
3067 return Reference(refs[0], refs[1], refs[2])
3068 return Reference(refs[0], refs[1], refs[2])
3068
3069
3069 @declared_attr
3070 @declared_attr
3070 def target_repo(cls):
3071 def target_repo(cls):
3071 return relationship(
3072 return relationship(
3072 'Repository',
3073 'Repository',
3073 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3074 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3074
3075
3075 @property
3076 @property
3076 def target_ref_parts(self):
3077 def target_ref_parts(self):
3077 refs = self.target_ref.split(':')
3078 refs = self.target_ref.split(':')
3078 return Reference(refs[0], refs[1], refs[2])
3079 return Reference(refs[0], refs[1], refs[2])
3079
3080
3080
3081
3081 class PullRequest(Base, _PullRequestBase):
3082 class PullRequest(Base, _PullRequestBase):
3082 __tablename__ = 'pull_requests'
3083 __tablename__ = 'pull_requests'
3083 __table_args__ = (
3084 __table_args__ = (
3084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3085 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3086 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3086 )
3087 )
3087
3088
3088 pull_request_id = Column(
3089 pull_request_id = Column(
3089 'pull_request_id', Integer(), nullable=False, primary_key=True)
3090 'pull_request_id', Integer(), nullable=False, primary_key=True)
3090
3091
3091 def __repr__(self):
3092 def __repr__(self):
3092 if self.pull_request_id:
3093 if self.pull_request_id:
3093 return '<DB:PullRequest #%s>' % self.pull_request_id
3094 return '<DB:PullRequest #%s>' % self.pull_request_id
3094 else:
3095 else:
3095 return '<DB:PullRequest at %#x>' % id(self)
3096 return '<DB:PullRequest at %#x>' % id(self)
3096
3097
3097 reviewers = relationship('PullRequestReviewers',
3098 reviewers = relationship('PullRequestReviewers',
3098 cascade="all, delete, delete-orphan")
3099 cascade="all, delete, delete-orphan")
3099 statuses = relationship('ChangesetStatus')
3100 statuses = relationship('ChangesetStatus')
3100 comments = relationship('ChangesetComment',
3101 comments = relationship('ChangesetComment',
3101 cascade="all, delete, delete-orphan")
3102 cascade="all, delete, delete-orphan")
3102 versions = relationship('PullRequestVersion',
3103 versions = relationship('PullRequestVersion',
3103 cascade="all, delete, delete-orphan")
3104 cascade="all, delete, delete-orphan")
3104
3105
3105 def is_closed(self):
3106 def is_closed(self):
3106 return self.status == self.STATUS_CLOSED
3107 return self.status == self.STATUS_CLOSED
3107
3108
3108 def get_api_data(self):
3109 def get_api_data(self):
3109 from rhodecode.model.pull_request import PullRequestModel
3110 from rhodecode.model.pull_request import PullRequestModel
3110 pull_request = self
3111 pull_request = self
3111 merge_status = PullRequestModel().merge_status(pull_request)
3112 merge_status = PullRequestModel().merge_status(pull_request)
3112 data = {
3113 data = {
3113 'pull_request_id': pull_request.pull_request_id,
3114 'pull_request_id': pull_request.pull_request_id,
3114 'url': url('pullrequest_show',
3115 'url': url('pullrequest_show',
3115 repo_name=pull_request.target_repo.repo_name,
3116 repo_name=pull_request.target_repo.repo_name,
3116 pull_request_id=pull_request.pull_request_id,
3117 pull_request_id=pull_request.pull_request_id,
3117 qualified=True),
3118 qualified=True),
3118 'title': pull_request.title,
3119 'title': pull_request.title,
3119 'description': pull_request.description,
3120 'description': pull_request.description,
3120 'status': pull_request.status,
3121 'status': pull_request.status,
3121 'created_on': pull_request.created_on,
3122 'created_on': pull_request.created_on,
3122 'updated_on': pull_request.updated_on,
3123 'updated_on': pull_request.updated_on,
3123 'commit_ids': pull_request.revisions,
3124 'commit_ids': pull_request.revisions,
3124 'review_status': pull_request.calculated_review_status(),
3125 'review_status': pull_request.calculated_review_status(),
3125 'mergeable': {
3126 'mergeable': {
3126 'status': merge_status[0],
3127 'status': merge_status[0],
3127 'message': unicode(merge_status[1]),
3128 'message': unicode(merge_status[1]),
3128 },
3129 },
3129 'source': {
3130 'source': {
3130 'clone_url': pull_request.source_repo.clone_url(),
3131 'clone_url': pull_request.source_repo.clone_url(),
3131 'repository': pull_request.source_repo.repo_name,
3132 'repository': pull_request.source_repo.repo_name,
3132 'reference': {
3133 'reference': {
3133 'name': pull_request.source_ref_parts.name,
3134 'name': pull_request.source_ref_parts.name,
3134 'type': pull_request.source_ref_parts.type,
3135 'type': pull_request.source_ref_parts.type,
3135 'commit_id': pull_request.source_ref_parts.commit_id,
3136 'commit_id': pull_request.source_ref_parts.commit_id,
3136 },
3137 },
3137 },
3138 },
3138 'target': {
3139 'target': {
3139 'clone_url': pull_request.target_repo.clone_url(),
3140 'clone_url': pull_request.target_repo.clone_url(),
3140 'repository': pull_request.target_repo.repo_name,
3141 'repository': pull_request.target_repo.repo_name,
3141 'reference': {
3142 'reference': {
3142 'name': pull_request.target_ref_parts.name,
3143 'name': pull_request.target_ref_parts.name,
3143 'type': pull_request.target_ref_parts.type,
3144 'type': pull_request.target_ref_parts.type,
3144 'commit_id': pull_request.target_ref_parts.commit_id,
3145 'commit_id': pull_request.target_ref_parts.commit_id,
3145 },
3146 },
3146 },
3147 },
3147 'author': pull_request.author.get_api_data(include_secrets=False,
3148 'author': pull_request.author.get_api_data(include_secrets=False,
3148 details='basic'),
3149 details='basic'),
3149 'reviewers': [
3150 'reviewers': [
3150 {
3151 {
3151 'user': reviewer.get_api_data(include_secrets=False,
3152 'user': reviewer.get_api_data(include_secrets=False,
3152 details='basic'),
3153 details='basic'),
3153 'review_status': st[0][1].status if st else 'not_reviewed',
3154 'review_status': st[0][1].status if st else 'not_reviewed',
3154 }
3155 }
3155 for reviewer, st in pull_request.reviewers_statuses()
3156 for reviewer, st in pull_request.reviewers_statuses()
3156 ]
3157 ]
3157 }
3158 }
3158
3159
3159 return data
3160 return data
3160
3161
3161 def __json__(self):
3162 def __json__(self):
3162 return {
3163 return {
3163 'revisions': self.revisions,
3164 'revisions': self.revisions,
3164 }
3165 }
3165
3166
3166 def calculated_review_status(self):
3167 def calculated_review_status(self):
3167 # 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
3168 # because it's tricky on how to use ChangesetStatusModel from there
3169 # because it's tricky on how to use ChangesetStatusModel from there
3169 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3170 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3170 from rhodecode.model.changeset_status import ChangesetStatusModel
3171 from rhodecode.model.changeset_status import ChangesetStatusModel
3171 return ChangesetStatusModel().calculated_review_status(self)
3172 return ChangesetStatusModel().calculated_review_status(self)
3172
3173
3173 def reviewers_statuses(self):
3174 def reviewers_statuses(self):
3174 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3175 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3175 from rhodecode.model.changeset_status import ChangesetStatusModel
3176 from rhodecode.model.changeset_status import ChangesetStatusModel
3176 return ChangesetStatusModel().reviewers_statuses(self)
3177 return ChangesetStatusModel().reviewers_statuses(self)
3177
3178
3178
3179
3179 class PullRequestVersion(Base, _PullRequestBase):
3180 class PullRequestVersion(Base, _PullRequestBase):
3180 __tablename__ = 'pull_request_versions'
3181 __tablename__ = 'pull_request_versions'
3181 __table_args__ = (
3182 __table_args__ = (
3182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3184 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3184 )
3185 )
3185
3186
3186 pull_request_version_id = Column(
3187 pull_request_version_id = Column(
3187 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3188 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3188 pull_request_id = Column(
3189 pull_request_id = Column(
3189 'pull_request_id', Integer(),
3190 'pull_request_id', Integer(),
3190 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3191 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3191 pull_request = relationship('PullRequest')
3192 pull_request = relationship('PullRequest')
3192
3193
3193 def __repr__(self):
3194 def __repr__(self):
3194 if self.pull_request_version_id:
3195 if self.pull_request_version_id:
3195 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3196 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3196 else:
3197 else:
3197 return '<DB:PullRequestVersion at %#x>' % id(self)
3198 return '<DB:PullRequestVersion at %#x>' % id(self)
3198
3199
3199
3200
3200 class PullRequestReviewers(Base, BaseModel):
3201 class PullRequestReviewers(Base, BaseModel):
3201 __tablename__ = 'pull_request_reviewers'
3202 __tablename__ = 'pull_request_reviewers'
3202 __table_args__ = (
3203 __table_args__ = (
3203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3205 )
3206 )
3206
3207
3207 def __init__(self, user=None, pull_request=None):
3208 def __init__(self, user=None, pull_request=None):
3208 self.user = user
3209 self.user = user
3209 self.pull_request = pull_request
3210 self.pull_request = pull_request
3210
3211
3211 pull_requests_reviewers_id = Column(
3212 pull_requests_reviewers_id = Column(
3212 'pull_requests_reviewers_id', Integer(), nullable=False,
3213 'pull_requests_reviewers_id', Integer(), nullable=False,
3213 primary_key=True)
3214 primary_key=True)
3214 pull_request_id = Column(
3215 pull_request_id = Column(
3215 "pull_request_id", Integer(),
3216 "pull_request_id", Integer(),
3216 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3217 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3217 user_id = Column(
3218 user_id = Column(
3218 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3219 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3219
3220
3220 user = relationship('User')
3221 user = relationship('User')
3221 pull_request = relationship('PullRequest')
3222 pull_request = relationship('PullRequest')
3222
3223
3223
3224
3224 class Notification(Base, BaseModel):
3225 class Notification(Base, BaseModel):
3225 __tablename__ = 'notifications'
3226 __tablename__ = 'notifications'
3226 __table_args__ = (
3227 __table_args__ = (
3227 Index('notification_type_idx', 'type'),
3228 Index('notification_type_idx', 'type'),
3228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3229 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3230 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3230 )
3231 )
3231
3232
3232 TYPE_CHANGESET_COMMENT = u'cs_comment'
3233 TYPE_CHANGESET_COMMENT = u'cs_comment'
3233 TYPE_MESSAGE = u'message'
3234 TYPE_MESSAGE = u'message'
3234 TYPE_MENTION = u'mention'
3235 TYPE_MENTION = u'mention'
3235 TYPE_REGISTRATION = u'registration'
3236 TYPE_REGISTRATION = u'registration'
3236 TYPE_PULL_REQUEST = u'pull_request'
3237 TYPE_PULL_REQUEST = u'pull_request'
3237 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3238 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3238
3239
3239 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3240 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3240 subject = Column('subject', Unicode(512), nullable=True)
3241 subject = Column('subject', Unicode(512), nullable=True)
3241 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3242 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3242 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)
3243 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)
3244 type_ = Column('type', Unicode(255))
3245 type_ = Column('type', Unicode(255))
3245
3246
3246 created_by_user = relationship('User')
3247 created_by_user = relationship('User')
3247 notifications_to_users = relationship('UserNotification', lazy='joined',
3248 notifications_to_users = relationship('UserNotification', lazy='joined',
3248 cascade="all, delete, delete-orphan")
3249 cascade="all, delete, delete-orphan")
3249
3250
3250 @property
3251 @property
3251 def recipients(self):
3252 def recipients(self):
3252 return [x.user for x in UserNotification.query()\
3253 return [x.user for x in UserNotification.query()\
3253 .filter(UserNotification.notification == self)\
3254 .filter(UserNotification.notification == self)\
3254 .order_by(UserNotification.user_id.asc()).all()]
3255 .order_by(UserNotification.user_id.asc()).all()]
3255
3256
3256 @classmethod
3257 @classmethod
3257 def create(cls, created_by, subject, body, recipients, type_=None):
3258 def create(cls, created_by, subject, body, recipients, type_=None):
3258 if type_ is None:
3259 if type_ is None:
3259 type_ = Notification.TYPE_MESSAGE
3260 type_ = Notification.TYPE_MESSAGE
3260
3261
3261 notification = cls()
3262 notification = cls()
3262 notification.created_by_user = created_by
3263 notification.created_by_user = created_by
3263 notification.subject = subject
3264 notification.subject = subject
3264 notification.body = body
3265 notification.body = body
3265 notification.type_ = type_
3266 notification.type_ = type_
3266 notification.created_on = datetime.datetime.now()
3267 notification.created_on = datetime.datetime.now()
3267
3268
3268 for u in recipients:
3269 for u in recipients:
3269 assoc = UserNotification()
3270 assoc = UserNotification()
3270 assoc.notification = notification
3271 assoc.notification = notification
3271
3272
3272 # if created_by is inside recipients mark his notification
3273 # if created_by is inside recipients mark his notification
3273 # as read
3274 # as read
3274 if u.user_id == created_by.user_id:
3275 if u.user_id == created_by.user_id:
3275 assoc.read = True
3276 assoc.read = True
3276
3277
3277 u.notifications.append(assoc)
3278 u.notifications.append(assoc)
3278 Session().add(notification)
3279 Session().add(notification)
3279
3280
3280 return notification
3281 return notification
3281
3282
3282 @property
3283 @property
3283 def description(self):
3284 def description(self):
3284 from rhodecode.model.notification import NotificationModel
3285 from rhodecode.model.notification import NotificationModel
3285 return NotificationModel().make_description(self)
3286 return NotificationModel().make_description(self)
3286
3287
3287
3288
3288 class UserNotification(Base, BaseModel):
3289 class UserNotification(Base, BaseModel):
3289 __tablename__ = 'user_to_notification'
3290 __tablename__ = 'user_to_notification'
3290 __table_args__ = (
3291 __table_args__ = (
3291 UniqueConstraint('user_id', 'notification_id'),
3292 UniqueConstraint('user_id', 'notification_id'),
3292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3293 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3294 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3294 )
3295 )
3295 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)
3296 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)
3297 read = Column('read', Boolean, default=False)
3298 read = Column('read', Boolean, default=False)
3298 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)
3299
3300
3300 user = relationship('User', lazy="joined")
3301 user = relationship('User', lazy="joined")
3301 notification = relationship('Notification', lazy="joined",
3302 notification = relationship('Notification', lazy="joined",
3302 order_by=lambda: Notification.created_on.desc(),)
3303 order_by=lambda: Notification.created_on.desc(),)
3303
3304
3304 def mark_as_read(self):
3305 def mark_as_read(self):
3305 self.read = True
3306 self.read = True
3306 Session().add(self)
3307 Session().add(self)
3307
3308
3308
3309
3309 class Gist(Base, BaseModel):
3310 class Gist(Base, BaseModel):
3310 __tablename__ = 'gists'
3311 __tablename__ = 'gists'
3311 __table_args__ = (
3312 __table_args__ = (
3312 Index('g_gist_access_id_idx', 'gist_access_id'),
3313 Index('g_gist_access_id_idx', 'gist_access_id'),
3313 Index('g_created_on_idx', 'created_on'),
3314 Index('g_created_on_idx', 'created_on'),
3314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3315 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3316 )
3317 )
3317 GIST_PUBLIC = u'public'
3318 GIST_PUBLIC = u'public'
3318 GIST_PRIVATE = u'private'
3319 GIST_PRIVATE = u'private'
3319 DEFAULT_FILENAME = u'gistfile1.txt'
3320 DEFAULT_FILENAME = u'gistfile1.txt'
3320
3321
3321 ACL_LEVEL_PUBLIC = u'acl_public'
3322 ACL_LEVEL_PUBLIC = u'acl_public'
3322 ACL_LEVEL_PRIVATE = u'acl_private'
3323 ACL_LEVEL_PRIVATE = u'acl_private'
3323
3324
3324 gist_id = Column('gist_id', Integer(), primary_key=True)
3325 gist_id = Column('gist_id', Integer(), primary_key=True)
3325 gist_access_id = Column('gist_access_id', Unicode(250))
3326 gist_access_id = Column('gist_access_id', Unicode(250))
3326 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3327 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3327 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)
3328 gist_expires = Column('gist_expires', Float(53), nullable=False)
3329 gist_expires = Column('gist_expires', Float(53), nullable=False)
3329 gist_type = Column('gist_type', Unicode(128), nullable=False)
3330 gist_type = Column('gist_type', Unicode(128), nullable=False)
3330 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)
3331 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)
3332 acl_level = Column('acl_level', Unicode(128), nullable=True)
3333 acl_level = Column('acl_level', Unicode(128), nullable=True)
3333
3334
3334 owner = relationship('User')
3335 owner = relationship('User')
3335
3336
3336 def __repr__(self):
3337 def __repr__(self):
3337 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3338 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3338
3339
3339 @classmethod
3340 @classmethod
3340 def get_or_404(cls, id_):
3341 def get_or_404(cls, id_):
3341 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3342 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3342 if not res:
3343 if not res:
3343 raise HTTPNotFound
3344 raise HTTPNotFound
3344 return res
3345 return res
3345
3346
3346 @classmethod
3347 @classmethod
3347 def get_by_access_id(cls, gist_access_id):
3348 def get_by_access_id(cls, gist_access_id):
3348 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()
3349
3350
3350 def gist_url(self):
3351 def gist_url(self):
3351 import rhodecode
3352 import rhodecode
3352 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3353 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3353 if alias_url:
3354 if alias_url:
3354 return alias_url.replace('{gistid}', self.gist_access_id)
3355 return alias_url.replace('{gistid}', self.gist_access_id)
3355
3356
3356 return url('gist', gist_id=self.gist_access_id, qualified=True)
3357 return url('gist', gist_id=self.gist_access_id, qualified=True)
3357
3358
3358 @classmethod
3359 @classmethod
3359 def base_path(cls):
3360 def base_path(cls):
3360 """
3361 """
3361 Returns base path when all gists are stored
3362 Returns base path when all gists are stored
3362
3363
3363 :param cls:
3364 :param cls:
3364 """
3365 """
3365 from rhodecode.model.gist import GIST_STORE_LOC
3366 from rhodecode.model.gist import GIST_STORE_LOC
3366 q = Session().query(RhodeCodeUi)\
3367 q = Session().query(RhodeCodeUi)\
3367 .filter(RhodeCodeUi.ui_key == URL_SEP)
3368 .filter(RhodeCodeUi.ui_key == URL_SEP)
3368 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3369 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3369 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3370 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3370
3371
3371 def get_api_data(self):
3372 def get_api_data(self):
3372 """
3373 """
3373 Common function for generating gist related data for API
3374 Common function for generating gist related data for API
3374 """
3375 """
3375 gist = self
3376 gist = self
3376 data = {
3377 data = {
3377 'gist_id': gist.gist_id,
3378 'gist_id': gist.gist_id,
3378 'type': gist.gist_type,
3379 'type': gist.gist_type,
3379 'access_id': gist.gist_access_id,
3380 'access_id': gist.gist_access_id,
3380 'description': gist.gist_description,
3381 'description': gist.gist_description,
3381 'url': gist.gist_url(),
3382 'url': gist.gist_url(),
3382 'expires': gist.gist_expires,
3383 'expires': gist.gist_expires,
3383 'created_on': gist.created_on,
3384 'created_on': gist.created_on,
3384 'modified_at': gist.modified_at,
3385 'modified_at': gist.modified_at,
3385 'content': None,
3386 'content': None,
3386 'acl_level': gist.acl_level,
3387 'acl_level': gist.acl_level,
3387 }
3388 }
3388 return data
3389 return data
3389
3390
3390 def __json__(self):
3391 def __json__(self):
3391 data = dict(
3392 data = dict(
3392 )
3393 )
3393 data.update(self.get_api_data())
3394 data.update(self.get_api_data())
3394 return data
3395 return data
3395 # SCM functions
3396 # SCM functions
3396
3397
3397 def scm_instance(self, **kwargs):
3398 def scm_instance(self, **kwargs):
3398 from rhodecode.lib.vcs import get_repo
3399 from rhodecode.lib.vcs import get_repo
3399 base_path = self.base_path()
3400 base_path = self.base_path()
3400 return get_repo(os.path.join(*map(safe_str,
3401 return get_repo(os.path.join(*map(safe_str,
3401 [base_path, self.gist_access_id])))
3402 [base_path, self.gist_access_id])))
3402
3403
3403
3404
3404 class DbMigrateVersion(Base, BaseModel):
3405 class DbMigrateVersion(Base, BaseModel):
3405 __tablename__ = 'db_migrate_version'
3406 __tablename__ = 'db_migrate_version'
3406 __table_args__ = (
3407 __table_args__ = (
3407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3408 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3409 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3409 )
3410 )
3410 repository_id = Column('repository_id', String(250), primary_key=True)
3411 repository_id = Column('repository_id', String(250), primary_key=True)
3411 repository_path = Column('repository_path', Text)
3412 repository_path = Column('repository_path', Text)
3412 version = Column('version', Integer)
3413 version = Column('version', Integer)
3413
3414
3414
3415
3415 class ExternalIdentity(Base, BaseModel):
3416 class ExternalIdentity(Base, BaseModel):
3416 __tablename__ = 'external_identities'
3417 __tablename__ = 'external_identities'
3417 __table_args__ = (
3418 __table_args__ = (
3418 Index('local_user_id_idx', 'local_user_id'),
3419 Index('local_user_id_idx', 'local_user_id'),
3419 Index('external_id_idx', 'external_id'),
3420 Index('external_id_idx', 'external_id'),
3420 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3421 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3421 'mysql_charset': 'utf8'})
3422 'mysql_charset': 'utf8'})
3422
3423
3423 external_id = Column('external_id', Unicode(255), default=u'',
3424 external_id = Column('external_id', Unicode(255), default=u'',
3424 primary_key=True)
3425 primary_key=True)
3425 external_username = Column('external_username', Unicode(1024), default=u'')
3426 external_username = Column('external_username', Unicode(1024), default=u'')
3426 local_user_id = Column('local_user_id', Integer(),
3427 local_user_id = Column('local_user_id', Integer(),
3427 ForeignKey('users.user_id'), primary_key=True)
3428 ForeignKey('users.user_id'), primary_key=True)
3428 provider_name = Column('provider_name', Unicode(255), default=u'',
3429 provider_name = Column('provider_name', Unicode(255), default=u'',
3429 primary_key=True)
3430 primary_key=True)
3430 access_token = Column('access_token', String(1024), default=u'')
3431 access_token = Column('access_token', String(1024), default=u'')
3431 alt_token = Column('alt_token', String(1024), default=u'')
3432 alt_token = Column('alt_token', String(1024), default=u'')
3432 token_secret = Column('token_secret', String(1024), default=u'')
3433 token_secret = Column('token_secret', String(1024), default=u'')
3433
3434
3434 @classmethod
3435 @classmethod
3435 def by_external_id_and_provider(cls, external_id, provider_name,
3436 def by_external_id_and_provider(cls, external_id, provider_name,
3436 local_user_id=None):
3437 local_user_id=None):
3437 """
3438 """
3438 Returns ExternalIdentity instance based on search params
3439 Returns ExternalIdentity instance based on search params
3439
3440
3440 :param external_id:
3441 :param external_id:
3441 :param provider_name:
3442 :param provider_name:
3442 :return: ExternalIdentity
3443 :return: ExternalIdentity
3443 """
3444 """
3444 query = cls.query()
3445 query = cls.query()
3445 query = query.filter(cls.external_id == external_id)
3446 query = query.filter(cls.external_id == external_id)
3446 query = query.filter(cls.provider_name == provider_name)
3447 query = query.filter(cls.provider_name == provider_name)
3447 if local_user_id:
3448 if local_user_id:
3448 query = query.filter(cls.local_user_id == local_user_id)
3449 query = query.filter(cls.local_user_id == local_user_id)
3449 return query.first()
3450 return query.first()
3450
3451
3451 @classmethod
3452 @classmethod
3452 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):
3453 """
3454 """
3454 Returns User instance based on search params
3455 Returns User instance based on search params
3455
3456
3456 :param external_id:
3457 :param external_id:
3457 :param provider_name:
3458 :param provider_name:
3458 :return: User
3459 :return: User
3459 """
3460 """
3460 query = User.query()
3461 query = User.query()
3461 query = query.filter(cls.external_id == external_id)
3462 query = query.filter(cls.external_id == external_id)
3462 query = query.filter(cls.provider_name == provider_name)
3463 query = query.filter(cls.provider_name == provider_name)
3463 query = query.filter(User.user_id == cls.local_user_id)
3464 query = query.filter(User.user_id == cls.local_user_id)
3464 return query.first()
3465 return query.first()
3465
3466
3466 @classmethod
3467 @classmethod
3467 def by_local_user_id(cls, local_user_id):
3468 def by_local_user_id(cls, local_user_id):
3468 """
3469 """
3469 Returns all tokens for user
3470 Returns all tokens for user
3470
3471
3471 :param local_user_id:
3472 :param local_user_id:
3472 :return: ExternalIdentity
3473 :return: ExternalIdentity
3473 """
3474 """
3474 query = cls.query()
3475 query = cls.query()
3475 query = query.filter(cls.local_user_id == local_user_id)
3476 query = query.filter(cls.local_user_id == local_user_id)
3476 return query
3477 return query
General Comments 0
You need to be logged in to leave comments. Login now