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