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