##// END OF EJS Templates
db: reduce size of fingerprint column for mysql compat....
marcink -
r2220:c5a681cc stable
parent child Browse files
Show More
@@ -1,4172 +1,4172 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 user_ssh_keys = relationship('UserSshKeys', cascade='all')
548 user_ssh_keys = relationship('UserSshKeys', cascade='all')
549
549
550 # gists
550 # gists
551 user_gists = relationship('Gist', cascade='all')
551 user_gists = relationship('Gist', cascade='all')
552 # user pull requests
552 # user pull requests
553 user_pull_requests = relationship('PullRequest', cascade='all')
553 user_pull_requests = relationship('PullRequest', cascade='all')
554 # external identities
554 # external identities
555 extenal_identities = relationship(
555 extenal_identities = relationship(
556 'ExternalIdentity',
556 'ExternalIdentity',
557 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
557 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
558 cascade='all')
558 cascade='all')
559
559
560 def __unicode__(self):
560 def __unicode__(self):
561 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
561 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
562 self.user_id, self.username)
562 self.user_id, self.username)
563
563
564 @hybrid_property
564 @hybrid_property
565 def email(self):
565 def email(self):
566 return self._email
566 return self._email
567
567
568 @email.setter
568 @email.setter
569 def email(self, val):
569 def email(self, val):
570 self._email = val.lower() if val else None
570 self._email = val.lower() if val else None
571
571
572 @hybrid_property
572 @hybrid_property
573 def first_name(self):
573 def first_name(self):
574 from rhodecode.lib import helpers as h
574 from rhodecode.lib import helpers as h
575 if self.name:
575 if self.name:
576 return h.escape(self.name)
576 return h.escape(self.name)
577 return self.name
577 return self.name
578
578
579 @hybrid_property
579 @hybrid_property
580 def last_name(self):
580 def last_name(self):
581 from rhodecode.lib import helpers as h
581 from rhodecode.lib import helpers as h
582 if self.lastname:
582 if self.lastname:
583 return h.escape(self.lastname)
583 return h.escape(self.lastname)
584 return self.lastname
584 return self.lastname
585
585
586 @hybrid_property
586 @hybrid_property
587 def api_key(self):
587 def api_key(self):
588 """
588 """
589 Fetch if exist an auth-token with role ALL connected to this user
589 Fetch if exist an auth-token with role ALL connected to this user
590 """
590 """
591 user_auth_token = UserApiKeys.query()\
591 user_auth_token = UserApiKeys.query()\
592 .filter(UserApiKeys.user_id == self.user_id)\
592 .filter(UserApiKeys.user_id == self.user_id)\
593 .filter(or_(UserApiKeys.expires == -1,
593 .filter(or_(UserApiKeys.expires == -1,
594 UserApiKeys.expires >= time.time()))\
594 UserApiKeys.expires >= time.time()))\
595 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
595 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
596 if user_auth_token:
596 if user_auth_token:
597 user_auth_token = user_auth_token.api_key
597 user_auth_token = user_auth_token.api_key
598
598
599 return user_auth_token
599 return user_auth_token
600
600
601 @api_key.setter
601 @api_key.setter
602 def api_key(self, val):
602 def api_key(self, val):
603 # don't allow to set API key this is deprecated for now
603 # don't allow to set API key this is deprecated for now
604 self._api_key = None
604 self._api_key = None
605
605
606 @property
606 @property
607 def reviewer_pull_requests(self):
607 def reviewer_pull_requests(self):
608 return PullRequestReviewers.query() \
608 return PullRequestReviewers.query() \
609 .options(joinedload(PullRequestReviewers.pull_request)) \
609 .options(joinedload(PullRequestReviewers.pull_request)) \
610 .filter(PullRequestReviewers.user_id == self.user_id) \
610 .filter(PullRequestReviewers.user_id == self.user_id) \
611 .all()
611 .all()
612
612
613 @property
613 @property
614 def firstname(self):
614 def firstname(self):
615 # alias for future
615 # alias for future
616 return self.name
616 return self.name
617
617
618 @property
618 @property
619 def emails(self):
619 def emails(self):
620 other = UserEmailMap.query()\
620 other = UserEmailMap.query()\
621 .filter(UserEmailMap.user == self) \
621 .filter(UserEmailMap.user == self) \
622 .order_by(UserEmailMap.email_id.asc()) \
622 .order_by(UserEmailMap.email_id.asc()) \
623 .all()
623 .all()
624 return [self.email] + [x.email for x in other]
624 return [self.email] + [x.email for x in other]
625
625
626 @property
626 @property
627 def auth_tokens(self):
627 def auth_tokens(self):
628 auth_tokens = self.get_auth_tokens()
628 auth_tokens = self.get_auth_tokens()
629 return [x.api_key for x in auth_tokens]
629 return [x.api_key for x in auth_tokens]
630
630
631 def get_auth_tokens(self):
631 def get_auth_tokens(self):
632 return UserApiKeys.query()\
632 return UserApiKeys.query()\
633 .filter(UserApiKeys.user == self)\
633 .filter(UserApiKeys.user == self)\
634 .order_by(UserApiKeys.user_api_key_id.asc())\
634 .order_by(UserApiKeys.user_api_key_id.asc())\
635 .all()
635 .all()
636
636
637 @property
637 @property
638 def feed_token(self):
638 def feed_token(self):
639 return self.get_feed_token()
639 return self.get_feed_token()
640
640
641 def get_feed_token(self):
641 def get_feed_token(self):
642 feed_tokens = UserApiKeys.query()\
642 feed_tokens = UserApiKeys.query()\
643 .filter(UserApiKeys.user == self)\
643 .filter(UserApiKeys.user == self)\
644 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
644 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
645 .all()
645 .all()
646 if feed_tokens:
646 if feed_tokens:
647 return feed_tokens[0].api_key
647 return feed_tokens[0].api_key
648 return 'NO_FEED_TOKEN_AVAILABLE'
648 return 'NO_FEED_TOKEN_AVAILABLE'
649
649
650 @classmethod
650 @classmethod
651 def extra_valid_auth_tokens(cls, user, role=None):
651 def extra_valid_auth_tokens(cls, user, role=None):
652 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
652 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
653 .filter(or_(UserApiKeys.expires == -1,
653 .filter(or_(UserApiKeys.expires == -1,
654 UserApiKeys.expires >= time.time()))
654 UserApiKeys.expires >= time.time()))
655 if role:
655 if role:
656 tokens = tokens.filter(or_(UserApiKeys.role == role,
656 tokens = tokens.filter(or_(UserApiKeys.role == role,
657 UserApiKeys.role == UserApiKeys.ROLE_ALL))
657 UserApiKeys.role == UserApiKeys.ROLE_ALL))
658 return tokens.all()
658 return tokens.all()
659
659
660 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
660 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
661 from rhodecode.lib import auth
661 from rhodecode.lib import auth
662
662
663 log.debug('Trying to authenticate user: %s via auth-token, '
663 log.debug('Trying to authenticate user: %s via auth-token, '
664 'and roles: %s', self, roles)
664 'and roles: %s', self, roles)
665
665
666 if not auth_token:
666 if not auth_token:
667 return False
667 return False
668
668
669 crypto_backend = auth.crypto_backend()
669 crypto_backend = auth.crypto_backend()
670
670
671 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
671 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
672 tokens_q = UserApiKeys.query()\
672 tokens_q = UserApiKeys.query()\
673 .filter(UserApiKeys.user_id == self.user_id)\
673 .filter(UserApiKeys.user_id == self.user_id)\
674 .filter(or_(UserApiKeys.expires == -1,
674 .filter(or_(UserApiKeys.expires == -1,
675 UserApiKeys.expires >= time.time()))
675 UserApiKeys.expires >= time.time()))
676
676
677 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
677 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
678
678
679 plain_tokens = []
679 plain_tokens = []
680 hash_tokens = []
680 hash_tokens = []
681
681
682 for token in tokens_q.all():
682 for token in tokens_q.all():
683 # verify scope first
683 # verify scope first
684 if token.repo_id:
684 if token.repo_id:
685 # token has a scope, we need to verify it
685 # token has a scope, we need to verify it
686 if scope_repo_id != token.repo_id:
686 if scope_repo_id != token.repo_id:
687 log.debug(
687 log.debug(
688 'Scope mismatch: token has a set repo scope: %s, '
688 'Scope mismatch: token has a set repo scope: %s, '
689 'and calling scope is:%s, skipping further checks',
689 'and calling scope is:%s, skipping further checks',
690 token.repo, scope_repo_id)
690 token.repo, scope_repo_id)
691 # token has a scope, and it doesn't match, skip token
691 # token has a scope, and it doesn't match, skip token
692 continue
692 continue
693
693
694 if token.api_key.startswith(crypto_backend.ENC_PREF):
694 if token.api_key.startswith(crypto_backend.ENC_PREF):
695 hash_tokens.append(token.api_key)
695 hash_tokens.append(token.api_key)
696 else:
696 else:
697 plain_tokens.append(token.api_key)
697 plain_tokens.append(token.api_key)
698
698
699 is_plain_match = auth_token in plain_tokens
699 is_plain_match = auth_token in plain_tokens
700 if is_plain_match:
700 if is_plain_match:
701 return True
701 return True
702
702
703 for hashed in hash_tokens:
703 for hashed in hash_tokens:
704 # TODO(marcink): this is expensive to calculate, but most secure
704 # TODO(marcink): this is expensive to calculate, but most secure
705 match = crypto_backend.hash_check(auth_token, hashed)
705 match = crypto_backend.hash_check(auth_token, hashed)
706 if match:
706 if match:
707 return True
707 return True
708
708
709 return False
709 return False
710
710
711 @property
711 @property
712 def ip_addresses(self):
712 def ip_addresses(self):
713 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
713 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
714 return [x.ip_addr for x in ret]
714 return [x.ip_addr for x in ret]
715
715
716 @property
716 @property
717 def username_and_name(self):
717 def username_and_name(self):
718 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
718 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
719
719
720 @property
720 @property
721 def username_or_name_or_email(self):
721 def username_or_name_or_email(self):
722 full_name = self.full_name if self.full_name is not ' ' else None
722 full_name = self.full_name if self.full_name is not ' ' else None
723 return self.username or full_name or self.email
723 return self.username or full_name or self.email
724
724
725 @property
725 @property
726 def full_name(self):
726 def full_name(self):
727 return '%s %s' % (self.first_name, self.last_name)
727 return '%s %s' % (self.first_name, self.last_name)
728
728
729 @property
729 @property
730 def full_name_or_username(self):
730 def full_name_or_username(self):
731 return ('%s %s' % (self.first_name, self.last_name)
731 return ('%s %s' % (self.first_name, self.last_name)
732 if (self.first_name and self.last_name) else self.username)
732 if (self.first_name and self.last_name) else self.username)
733
733
734 @property
734 @property
735 def full_contact(self):
735 def full_contact(self):
736 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
736 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
737
737
738 @property
738 @property
739 def short_contact(self):
739 def short_contact(self):
740 return '%s %s' % (self.first_name, self.last_name)
740 return '%s %s' % (self.first_name, self.last_name)
741
741
742 @property
742 @property
743 def is_admin(self):
743 def is_admin(self):
744 return self.admin
744 return self.admin
745
745
746 @property
746 @property
747 def AuthUser(self):
747 def AuthUser(self):
748 """
748 """
749 Returns instance of AuthUser for this user
749 Returns instance of AuthUser for this user
750 """
750 """
751 from rhodecode.lib.auth import AuthUser
751 from rhodecode.lib.auth import AuthUser
752 return AuthUser(user_id=self.user_id, username=self.username)
752 return AuthUser(user_id=self.user_id, username=self.username)
753
753
754 @hybrid_property
754 @hybrid_property
755 def user_data(self):
755 def user_data(self):
756 if not self._user_data:
756 if not self._user_data:
757 return {}
757 return {}
758
758
759 try:
759 try:
760 return json.loads(self._user_data)
760 return json.loads(self._user_data)
761 except TypeError:
761 except TypeError:
762 return {}
762 return {}
763
763
764 @user_data.setter
764 @user_data.setter
765 def user_data(self, val):
765 def user_data(self, val):
766 if not isinstance(val, dict):
766 if not isinstance(val, dict):
767 raise Exception('user_data must be dict, got %s' % type(val))
767 raise Exception('user_data must be dict, got %s' % type(val))
768 try:
768 try:
769 self._user_data = json.dumps(val)
769 self._user_data = json.dumps(val)
770 except Exception:
770 except Exception:
771 log.error(traceback.format_exc())
771 log.error(traceback.format_exc())
772
772
773 @classmethod
773 @classmethod
774 def get_by_username(cls, username, case_insensitive=False,
774 def get_by_username(cls, username, case_insensitive=False,
775 cache=False, identity_cache=False):
775 cache=False, identity_cache=False):
776 session = Session()
776 session = Session()
777
777
778 if case_insensitive:
778 if case_insensitive:
779 q = cls.query().filter(
779 q = cls.query().filter(
780 func.lower(cls.username) == func.lower(username))
780 func.lower(cls.username) == func.lower(username))
781 else:
781 else:
782 q = cls.query().filter(cls.username == username)
782 q = cls.query().filter(cls.username == username)
783
783
784 if cache:
784 if cache:
785 if identity_cache:
785 if identity_cache:
786 val = cls.identity_cache(session, 'username', username)
786 val = cls.identity_cache(session, 'username', username)
787 if val:
787 if val:
788 return val
788 return val
789 else:
789 else:
790 cache_key = "get_user_by_name_%s" % _hash_key(username)
790 cache_key = "get_user_by_name_%s" % _hash_key(username)
791 q = q.options(
791 q = q.options(
792 FromCache("sql_cache_short", cache_key))
792 FromCache("sql_cache_short", cache_key))
793
793
794 return q.scalar()
794 return q.scalar()
795
795
796 @classmethod
796 @classmethod
797 def get_by_auth_token(cls, auth_token, cache=False):
797 def get_by_auth_token(cls, auth_token, cache=False):
798 q = UserApiKeys.query()\
798 q = UserApiKeys.query()\
799 .filter(UserApiKeys.api_key == auth_token)\
799 .filter(UserApiKeys.api_key == auth_token)\
800 .filter(or_(UserApiKeys.expires == -1,
800 .filter(or_(UserApiKeys.expires == -1,
801 UserApiKeys.expires >= time.time()))
801 UserApiKeys.expires >= time.time()))
802 if cache:
802 if cache:
803 q = q.options(
803 q = q.options(
804 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
804 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
805
805
806 match = q.first()
806 match = q.first()
807 if match:
807 if match:
808 return match.user
808 return match.user
809
809
810 @classmethod
810 @classmethod
811 def get_by_email(cls, email, case_insensitive=False, cache=False):
811 def get_by_email(cls, email, case_insensitive=False, cache=False):
812
812
813 if case_insensitive:
813 if case_insensitive:
814 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
814 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
815
815
816 else:
816 else:
817 q = cls.query().filter(cls.email == email)
817 q = cls.query().filter(cls.email == email)
818
818
819 email_key = _hash_key(email)
819 email_key = _hash_key(email)
820 if cache:
820 if cache:
821 q = q.options(
821 q = q.options(
822 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
822 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
823
823
824 ret = q.scalar()
824 ret = q.scalar()
825 if ret is None:
825 if ret is None:
826 q = UserEmailMap.query()
826 q = UserEmailMap.query()
827 # try fetching in alternate email map
827 # try fetching in alternate email map
828 if case_insensitive:
828 if case_insensitive:
829 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
829 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
830 else:
830 else:
831 q = q.filter(UserEmailMap.email == email)
831 q = q.filter(UserEmailMap.email == email)
832 q = q.options(joinedload(UserEmailMap.user))
832 q = q.options(joinedload(UserEmailMap.user))
833 if cache:
833 if cache:
834 q = q.options(
834 q = q.options(
835 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
835 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
836 ret = getattr(q.scalar(), 'user', None)
836 ret = getattr(q.scalar(), 'user', None)
837
837
838 return ret
838 return ret
839
839
840 @classmethod
840 @classmethod
841 def get_from_cs_author(cls, author):
841 def get_from_cs_author(cls, author):
842 """
842 """
843 Tries to get User objects out of commit author string
843 Tries to get User objects out of commit author string
844
844
845 :param author:
845 :param author:
846 """
846 """
847 from rhodecode.lib.helpers import email, author_name
847 from rhodecode.lib.helpers import email, author_name
848 # Valid email in the attribute passed, see if they're in the system
848 # Valid email in the attribute passed, see if they're in the system
849 _email = email(author)
849 _email = email(author)
850 if _email:
850 if _email:
851 user = cls.get_by_email(_email, case_insensitive=True)
851 user = cls.get_by_email(_email, case_insensitive=True)
852 if user:
852 if user:
853 return user
853 return user
854 # Maybe we can match by username?
854 # Maybe we can match by username?
855 _author = author_name(author)
855 _author = author_name(author)
856 user = cls.get_by_username(_author, case_insensitive=True)
856 user = cls.get_by_username(_author, case_insensitive=True)
857 if user:
857 if user:
858 return user
858 return user
859
859
860 def update_userdata(self, **kwargs):
860 def update_userdata(self, **kwargs):
861 usr = self
861 usr = self
862 old = usr.user_data
862 old = usr.user_data
863 old.update(**kwargs)
863 old.update(**kwargs)
864 usr.user_data = old
864 usr.user_data = old
865 Session().add(usr)
865 Session().add(usr)
866 log.debug('updated userdata with ', kwargs)
866 log.debug('updated userdata with ', kwargs)
867
867
868 def update_lastlogin(self):
868 def update_lastlogin(self):
869 """Update user lastlogin"""
869 """Update user lastlogin"""
870 self.last_login = datetime.datetime.now()
870 self.last_login = datetime.datetime.now()
871 Session().add(self)
871 Session().add(self)
872 log.debug('updated user %s lastlogin', self.username)
872 log.debug('updated user %s lastlogin', self.username)
873
873
874 def update_lastactivity(self):
874 def update_lastactivity(self):
875 """Update user lastactivity"""
875 """Update user lastactivity"""
876 self.last_activity = datetime.datetime.now()
876 self.last_activity = datetime.datetime.now()
877 Session().add(self)
877 Session().add(self)
878 log.debug('updated user %s lastactivity', self.username)
878 log.debug('updated user %s lastactivity', self.username)
879
879
880 def update_password(self, new_password):
880 def update_password(self, new_password):
881 from rhodecode.lib.auth import get_crypt_password
881 from rhodecode.lib.auth import get_crypt_password
882
882
883 self.password = get_crypt_password(new_password)
883 self.password = get_crypt_password(new_password)
884 Session().add(self)
884 Session().add(self)
885
885
886 @classmethod
886 @classmethod
887 def get_first_super_admin(cls):
887 def get_first_super_admin(cls):
888 user = User.query().filter(User.admin == true()).first()
888 user = User.query().filter(User.admin == true()).first()
889 if user is None:
889 if user is None:
890 raise Exception('FATAL: Missing administrative account!')
890 raise Exception('FATAL: Missing administrative account!')
891 return user
891 return user
892
892
893 @classmethod
893 @classmethod
894 def get_all_super_admins(cls):
894 def get_all_super_admins(cls):
895 """
895 """
896 Returns all admin accounts sorted by username
896 Returns all admin accounts sorted by username
897 """
897 """
898 return User.query().filter(User.admin == true())\
898 return User.query().filter(User.admin == true())\
899 .order_by(User.username.asc()).all()
899 .order_by(User.username.asc()).all()
900
900
901 @classmethod
901 @classmethod
902 def get_default_user(cls, cache=False, refresh=False):
902 def get_default_user(cls, cache=False, refresh=False):
903 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
903 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
904 if user is None:
904 if user is None:
905 raise Exception('FATAL: Missing default account!')
905 raise Exception('FATAL: Missing default account!')
906 if refresh:
906 if refresh:
907 # The default user might be based on outdated state which
907 # The default user might be based on outdated state which
908 # has been loaded from the cache.
908 # has been loaded from the cache.
909 # A call to refresh() ensures that the
909 # A call to refresh() ensures that the
910 # latest state from the database is used.
910 # latest state from the database is used.
911 Session().refresh(user)
911 Session().refresh(user)
912 return user
912 return user
913
913
914 def _get_default_perms(self, user, suffix=''):
914 def _get_default_perms(self, user, suffix=''):
915 from rhodecode.model.permission import PermissionModel
915 from rhodecode.model.permission import PermissionModel
916 return PermissionModel().get_default_perms(user.user_perms, suffix)
916 return PermissionModel().get_default_perms(user.user_perms, suffix)
917
917
918 def get_default_perms(self, suffix=''):
918 def get_default_perms(self, suffix=''):
919 return self._get_default_perms(self, suffix)
919 return self._get_default_perms(self, suffix)
920
920
921 def get_api_data(self, include_secrets=False, details='full'):
921 def get_api_data(self, include_secrets=False, details='full'):
922 """
922 """
923 Common function for generating user related data for API
923 Common function for generating user related data for API
924
924
925 :param include_secrets: By default secrets in the API data will be replaced
925 :param include_secrets: By default secrets in the API data will be replaced
926 by a placeholder value to prevent exposing this data by accident. In case
926 by a placeholder value to prevent exposing this data by accident. In case
927 this data shall be exposed, set this flag to ``True``.
927 this data shall be exposed, set this flag to ``True``.
928
928
929 :param details: details can be 'basic|full' basic gives only a subset of
929 :param details: details can be 'basic|full' basic gives only a subset of
930 the available user information that includes user_id, name and emails.
930 the available user information that includes user_id, name and emails.
931 """
931 """
932 user = self
932 user = self
933 user_data = self.user_data
933 user_data = self.user_data
934 data = {
934 data = {
935 'user_id': user.user_id,
935 'user_id': user.user_id,
936 'username': user.username,
936 'username': user.username,
937 'firstname': user.name,
937 'firstname': user.name,
938 'lastname': user.lastname,
938 'lastname': user.lastname,
939 'email': user.email,
939 'email': user.email,
940 'emails': user.emails,
940 'emails': user.emails,
941 }
941 }
942 if details == 'basic':
942 if details == 'basic':
943 return data
943 return data
944
944
945 auth_token_length = 40
945 auth_token_length = 40
946 auth_token_replacement = '*' * auth_token_length
946 auth_token_replacement = '*' * auth_token_length
947
947
948 extras = {
948 extras = {
949 'auth_tokens': [auth_token_replacement],
949 'auth_tokens': [auth_token_replacement],
950 'active': user.active,
950 'active': user.active,
951 'admin': user.admin,
951 'admin': user.admin,
952 'extern_type': user.extern_type,
952 'extern_type': user.extern_type,
953 'extern_name': user.extern_name,
953 'extern_name': user.extern_name,
954 'last_login': user.last_login,
954 'last_login': user.last_login,
955 'last_activity': user.last_activity,
955 'last_activity': user.last_activity,
956 'ip_addresses': user.ip_addresses,
956 'ip_addresses': user.ip_addresses,
957 'language': user_data.get('language')
957 'language': user_data.get('language')
958 }
958 }
959 data.update(extras)
959 data.update(extras)
960
960
961 if include_secrets:
961 if include_secrets:
962 data['auth_tokens'] = user.auth_tokens
962 data['auth_tokens'] = user.auth_tokens
963 return data
963 return data
964
964
965 def __json__(self):
965 def __json__(self):
966 data = {
966 data = {
967 'full_name': self.full_name,
967 'full_name': self.full_name,
968 'full_name_or_username': self.full_name_or_username,
968 'full_name_or_username': self.full_name_or_username,
969 'short_contact': self.short_contact,
969 'short_contact': self.short_contact,
970 'full_contact': self.full_contact,
970 'full_contact': self.full_contact,
971 }
971 }
972 data.update(self.get_api_data())
972 data.update(self.get_api_data())
973 return data
973 return data
974
974
975
975
976 class UserApiKeys(Base, BaseModel):
976 class UserApiKeys(Base, BaseModel):
977 __tablename__ = 'user_api_keys'
977 __tablename__ = 'user_api_keys'
978 __table_args__ = (
978 __table_args__ = (
979 Index('uak_api_key_idx', 'api_key', unique=True),
979 Index('uak_api_key_idx', 'api_key', unique=True),
980 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
980 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
982 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
982 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
983 )
983 )
984 __mapper_args__ = {}
984 __mapper_args__ = {}
985
985
986 # ApiKey role
986 # ApiKey role
987 ROLE_ALL = 'token_role_all'
987 ROLE_ALL = 'token_role_all'
988 ROLE_HTTP = 'token_role_http'
988 ROLE_HTTP = 'token_role_http'
989 ROLE_VCS = 'token_role_vcs'
989 ROLE_VCS = 'token_role_vcs'
990 ROLE_API = 'token_role_api'
990 ROLE_API = 'token_role_api'
991 ROLE_FEED = 'token_role_feed'
991 ROLE_FEED = 'token_role_feed'
992 ROLE_PASSWORD_RESET = 'token_password_reset'
992 ROLE_PASSWORD_RESET = 'token_password_reset'
993
993
994 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
994 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
995
995
996 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
998 api_key = Column("api_key", String(255), nullable=False, unique=True)
998 api_key = Column("api_key", String(255), nullable=False, unique=True)
999 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
999 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1000 expires = Column('expires', Float(53), nullable=False)
1000 expires = Column('expires', Float(53), nullable=False)
1001 role = Column('role', String(255), nullable=True)
1001 role = Column('role', String(255), nullable=True)
1002 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1002 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1003
1003
1004 # scope columns
1004 # scope columns
1005 repo_id = Column(
1005 repo_id = Column(
1006 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1006 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1007 nullable=True, unique=None, default=None)
1007 nullable=True, unique=None, default=None)
1008 repo = relationship('Repository', lazy='joined')
1008 repo = relationship('Repository', lazy='joined')
1009
1009
1010 repo_group_id = Column(
1010 repo_group_id = Column(
1011 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1011 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1012 nullable=True, unique=None, default=None)
1012 nullable=True, unique=None, default=None)
1013 repo_group = relationship('RepoGroup', lazy='joined')
1013 repo_group = relationship('RepoGroup', lazy='joined')
1014
1014
1015 user = relationship('User', lazy='joined')
1015 user = relationship('User', lazy='joined')
1016
1016
1017 def __unicode__(self):
1017 def __unicode__(self):
1018 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1018 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1019
1019
1020 def __json__(self):
1020 def __json__(self):
1021 data = {
1021 data = {
1022 'auth_token': self.api_key,
1022 'auth_token': self.api_key,
1023 'role': self.role,
1023 'role': self.role,
1024 'scope': self.scope_humanized,
1024 'scope': self.scope_humanized,
1025 'expired': self.expired
1025 'expired': self.expired
1026 }
1026 }
1027 return data
1027 return data
1028
1028
1029 def get_api_data(self, include_secrets=False):
1029 def get_api_data(self, include_secrets=False):
1030 data = self.__json__()
1030 data = self.__json__()
1031 if include_secrets:
1031 if include_secrets:
1032 return data
1032 return data
1033 else:
1033 else:
1034 data['auth_token'] = self.token_obfuscated
1034 data['auth_token'] = self.token_obfuscated
1035 return data
1035 return data
1036
1036
1037 @hybrid_property
1037 @hybrid_property
1038 def description_safe(self):
1038 def description_safe(self):
1039 from rhodecode.lib import helpers as h
1039 from rhodecode.lib import helpers as h
1040 return h.escape(self.description)
1040 return h.escape(self.description)
1041
1041
1042 @property
1042 @property
1043 def expired(self):
1043 def expired(self):
1044 if self.expires == -1:
1044 if self.expires == -1:
1045 return False
1045 return False
1046 return time.time() > self.expires
1046 return time.time() > self.expires
1047
1047
1048 @classmethod
1048 @classmethod
1049 def _get_role_name(cls, role):
1049 def _get_role_name(cls, role):
1050 return {
1050 return {
1051 cls.ROLE_ALL: _('all'),
1051 cls.ROLE_ALL: _('all'),
1052 cls.ROLE_HTTP: _('http/web interface'),
1052 cls.ROLE_HTTP: _('http/web interface'),
1053 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1053 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1054 cls.ROLE_API: _('api calls'),
1054 cls.ROLE_API: _('api calls'),
1055 cls.ROLE_FEED: _('feed access'),
1055 cls.ROLE_FEED: _('feed access'),
1056 }.get(role, role)
1056 }.get(role, role)
1057
1057
1058 @property
1058 @property
1059 def role_humanized(self):
1059 def role_humanized(self):
1060 return self._get_role_name(self.role)
1060 return self._get_role_name(self.role)
1061
1061
1062 def _get_scope(self):
1062 def _get_scope(self):
1063 if self.repo:
1063 if self.repo:
1064 return repr(self.repo)
1064 return repr(self.repo)
1065 if self.repo_group:
1065 if self.repo_group:
1066 return repr(self.repo_group) + ' (recursive)'
1066 return repr(self.repo_group) + ' (recursive)'
1067 return 'global'
1067 return 'global'
1068
1068
1069 @property
1069 @property
1070 def scope_humanized(self):
1070 def scope_humanized(self):
1071 return self._get_scope()
1071 return self._get_scope()
1072
1072
1073 @property
1073 @property
1074 def token_obfuscated(self):
1074 def token_obfuscated(self):
1075 if self.api_key:
1075 if self.api_key:
1076 return self.api_key[:4] + "****"
1076 return self.api_key[:4] + "****"
1077
1077
1078
1078
1079 class UserEmailMap(Base, BaseModel):
1079 class UserEmailMap(Base, BaseModel):
1080 __tablename__ = 'user_email_map'
1080 __tablename__ = 'user_email_map'
1081 __table_args__ = (
1081 __table_args__ = (
1082 Index('uem_email_idx', 'email'),
1082 Index('uem_email_idx', 'email'),
1083 UniqueConstraint('email'),
1083 UniqueConstraint('email'),
1084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1084 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1085 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1086 )
1086 )
1087 __mapper_args__ = {}
1087 __mapper_args__ = {}
1088
1088
1089 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1089 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1090 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1090 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1091 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1091 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1092 user = relationship('User', lazy='joined')
1092 user = relationship('User', lazy='joined')
1093
1093
1094 @validates('_email')
1094 @validates('_email')
1095 def validate_email(self, key, email):
1095 def validate_email(self, key, email):
1096 # check if this email is not main one
1096 # check if this email is not main one
1097 main_email = Session().query(User).filter(User.email == email).scalar()
1097 main_email = Session().query(User).filter(User.email == email).scalar()
1098 if main_email is not None:
1098 if main_email is not None:
1099 raise AttributeError('email %s is present is user table' % email)
1099 raise AttributeError('email %s is present is user table' % email)
1100 return email
1100 return email
1101
1101
1102 @hybrid_property
1102 @hybrid_property
1103 def email(self):
1103 def email(self):
1104 return self._email
1104 return self._email
1105
1105
1106 @email.setter
1106 @email.setter
1107 def email(self, val):
1107 def email(self, val):
1108 self._email = val.lower() if val else None
1108 self._email = val.lower() if val else None
1109
1109
1110
1110
1111 class UserIpMap(Base, BaseModel):
1111 class UserIpMap(Base, BaseModel):
1112 __tablename__ = 'user_ip_map'
1112 __tablename__ = 'user_ip_map'
1113 __table_args__ = (
1113 __table_args__ = (
1114 UniqueConstraint('user_id', 'ip_addr'),
1114 UniqueConstraint('user_id', 'ip_addr'),
1115 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1115 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1116 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1116 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1117 )
1117 )
1118 __mapper_args__ = {}
1118 __mapper_args__ = {}
1119
1119
1120 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1120 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1121 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1121 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1122 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1122 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1123 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1123 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1124 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1124 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1125 user = relationship('User', lazy='joined')
1125 user = relationship('User', lazy='joined')
1126
1126
1127 @hybrid_property
1127 @hybrid_property
1128 def description_safe(self):
1128 def description_safe(self):
1129 from rhodecode.lib import helpers as h
1129 from rhodecode.lib import helpers as h
1130 return h.escape(self.description)
1130 return h.escape(self.description)
1131
1131
1132 @classmethod
1132 @classmethod
1133 def _get_ip_range(cls, ip_addr):
1133 def _get_ip_range(cls, ip_addr):
1134 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1134 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1135 return [str(net.network_address), str(net.broadcast_address)]
1135 return [str(net.network_address), str(net.broadcast_address)]
1136
1136
1137 def __json__(self):
1137 def __json__(self):
1138 return {
1138 return {
1139 'ip_addr': self.ip_addr,
1139 'ip_addr': self.ip_addr,
1140 'ip_range': self._get_ip_range(self.ip_addr),
1140 'ip_range': self._get_ip_range(self.ip_addr),
1141 }
1141 }
1142
1142
1143 def __unicode__(self):
1143 def __unicode__(self):
1144 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1144 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1145 self.user_id, self.ip_addr)
1145 self.user_id, self.ip_addr)
1146
1146
1147
1147
1148 class UserSshKeys(Base, BaseModel):
1148 class UserSshKeys(Base, BaseModel):
1149 __tablename__ = 'user_ssh_keys'
1149 __tablename__ = 'user_ssh_keys'
1150 __table_args__ = (
1150 __table_args__ = (
1151 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1151 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1152 UniqueConstraint('ssh_key_fingerprint'),
1152 UniqueConstraint('ssh_key_fingerprint'),
1153
1153
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1155 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1155 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1156 )
1156 )
1157 __mapper_args__ = {}
1157 __mapper_args__ = {}
1158
1158
1159 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1159 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1160 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1160 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1161 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None)
1161 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1162
1162
1163 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1163 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1164
1164
1165 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1165 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1166 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1166 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1167 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1167 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1168
1168
1169 user = relationship('User', lazy='joined')
1169 user = relationship('User', lazy='joined')
1170
1170
1171 def __json__(self):
1171 def __json__(self):
1172 data = {
1172 data = {
1173 'ssh_fingerprint': self.ssh_key_fingerprint,
1173 'ssh_fingerprint': self.ssh_key_fingerprint,
1174 'description': self.description,
1174 'description': self.description,
1175 'created_on': self.created_on
1175 'created_on': self.created_on
1176 }
1176 }
1177 return data
1177 return data
1178
1178
1179 def get_api_data(self):
1179 def get_api_data(self):
1180 data = self.__json__()
1180 data = self.__json__()
1181 return data
1181 return data
1182
1182
1183
1183
1184 class UserLog(Base, BaseModel):
1184 class UserLog(Base, BaseModel):
1185 __tablename__ = 'user_logs'
1185 __tablename__ = 'user_logs'
1186 __table_args__ = (
1186 __table_args__ = (
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1189 )
1189 )
1190 VERSION_1 = 'v1'
1190 VERSION_1 = 'v1'
1191 VERSION_2 = 'v2'
1191 VERSION_2 = 'v2'
1192 VERSIONS = [VERSION_1, VERSION_2]
1192 VERSIONS = [VERSION_1, VERSION_2]
1193
1193
1194 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1194 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1195 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1195 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1196 username = Column("username", String(255), nullable=True, unique=None, default=None)
1196 username = Column("username", String(255), nullable=True, unique=None, default=None)
1197 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1197 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1198 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1198 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1199 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1199 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1200 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1200 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1201 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1201 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1202
1202
1203 version = Column("version", String(255), nullable=True, default=VERSION_1)
1203 version = Column("version", String(255), nullable=True, default=VERSION_1)
1204 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1204 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1205 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1205 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1206
1206
1207 def __unicode__(self):
1207 def __unicode__(self):
1208 return u"<%s('id:%s:%s')>" % (
1208 return u"<%s('id:%s:%s')>" % (
1209 self.__class__.__name__, self.repository_name, self.action)
1209 self.__class__.__name__, self.repository_name, self.action)
1210
1210
1211 def __json__(self):
1211 def __json__(self):
1212 return {
1212 return {
1213 'user_id': self.user_id,
1213 'user_id': self.user_id,
1214 'username': self.username,
1214 'username': self.username,
1215 'repository_id': self.repository_id,
1215 'repository_id': self.repository_id,
1216 'repository_name': self.repository_name,
1216 'repository_name': self.repository_name,
1217 'user_ip': self.user_ip,
1217 'user_ip': self.user_ip,
1218 'action_date': self.action_date,
1218 'action_date': self.action_date,
1219 'action': self.action,
1219 'action': self.action,
1220 }
1220 }
1221
1221
1222 @property
1222 @property
1223 def action_as_day(self):
1223 def action_as_day(self):
1224 return datetime.date(*self.action_date.timetuple()[:3])
1224 return datetime.date(*self.action_date.timetuple()[:3])
1225
1225
1226 user = relationship('User')
1226 user = relationship('User')
1227 repository = relationship('Repository', cascade='')
1227 repository = relationship('Repository', cascade='')
1228
1228
1229
1229
1230 class UserGroup(Base, BaseModel):
1230 class UserGroup(Base, BaseModel):
1231 __tablename__ = 'users_groups'
1231 __tablename__ = 'users_groups'
1232 __table_args__ = (
1232 __table_args__ = (
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1234 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1235 )
1235 )
1236
1236
1237 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1238 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1239 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1239 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1240 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1240 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1241 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1241 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1243 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1244 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1244 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1245
1245
1246 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1246 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1247 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1247 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1248 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1248 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1249 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1249 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1250 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1250 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1251 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1251 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1252
1252
1253 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1253 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1254
1254
1255 @classmethod
1255 @classmethod
1256 def _load_group_data(cls, column):
1256 def _load_group_data(cls, column):
1257 if not column:
1257 if not column:
1258 return {}
1258 return {}
1259
1259
1260 try:
1260 try:
1261 return json.loads(column) or {}
1261 return json.loads(column) or {}
1262 except TypeError:
1262 except TypeError:
1263 return {}
1263 return {}
1264
1264
1265 @hybrid_property
1265 @hybrid_property
1266 def description_safe(self):
1266 def description_safe(self):
1267 from rhodecode.lib import helpers as h
1267 from rhodecode.lib import helpers as h
1268 return h.escape(self.description)
1268 return h.escape(self.description)
1269
1269
1270 @hybrid_property
1270 @hybrid_property
1271 def group_data(self):
1271 def group_data(self):
1272 return self._load_group_data(self._group_data)
1272 return self._load_group_data(self._group_data)
1273
1273
1274 @group_data.expression
1274 @group_data.expression
1275 def group_data(self, **kwargs):
1275 def group_data(self, **kwargs):
1276 return self._group_data
1276 return self._group_data
1277
1277
1278 @group_data.setter
1278 @group_data.setter
1279 def group_data(self, val):
1279 def group_data(self, val):
1280 try:
1280 try:
1281 self._group_data = json.dumps(val)
1281 self._group_data = json.dumps(val)
1282 except Exception:
1282 except Exception:
1283 log.error(traceback.format_exc())
1283 log.error(traceback.format_exc())
1284
1284
1285 def __unicode__(self):
1285 def __unicode__(self):
1286 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1286 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1287 self.users_group_id,
1287 self.users_group_id,
1288 self.users_group_name)
1288 self.users_group_name)
1289
1289
1290 @classmethod
1290 @classmethod
1291 def get_by_group_name(cls, group_name, cache=False,
1291 def get_by_group_name(cls, group_name, cache=False,
1292 case_insensitive=False):
1292 case_insensitive=False):
1293 if case_insensitive:
1293 if case_insensitive:
1294 q = cls.query().filter(func.lower(cls.users_group_name) ==
1294 q = cls.query().filter(func.lower(cls.users_group_name) ==
1295 func.lower(group_name))
1295 func.lower(group_name))
1296
1296
1297 else:
1297 else:
1298 q = cls.query().filter(cls.users_group_name == group_name)
1298 q = cls.query().filter(cls.users_group_name == group_name)
1299 if cache:
1299 if cache:
1300 q = q.options(
1300 q = q.options(
1301 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1301 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1302 return q.scalar()
1302 return q.scalar()
1303
1303
1304 @classmethod
1304 @classmethod
1305 def get(cls, user_group_id, cache=False):
1305 def get(cls, user_group_id, cache=False):
1306 user_group = cls.query()
1306 user_group = cls.query()
1307 if cache:
1307 if cache:
1308 user_group = user_group.options(
1308 user_group = user_group.options(
1309 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1309 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1310 return user_group.get(user_group_id)
1310 return user_group.get(user_group_id)
1311
1311
1312 def permissions(self, with_admins=True, with_owner=True):
1312 def permissions(self, with_admins=True, with_owner=True):
1313 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1313 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1314 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1314 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1315 joinedload(UserUserGroupToPerm.user),
1315 joinedload(UserUserGroupToPerm.user),
1316 joinedload(UserUserGroupToPerm.permission),)
1316 joinedload(UserUserGroupToPerm.permission),)
1317
1317
1318 # get owners and admins and permissions. We do a trick of re-writing
1318 # get owners and admins and permissions. We do a trick of re-writing
1319 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1319 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1320 # has a global reference and changing one object propagates to all
1320 # has a global reference and changing one object propagates to all
1321 # others. This means if admin is also an owner admin_row that change
1321 # others. This means if admin is also an owner admin_row that change
1322 # would propagate to both objects
1322 # would propagate to both objects
1323 perm_rows = []
1323 perm_rows = []
1324 for _usr in q.all():
1324 for _usr in q.all():
1325 usr = AttributeDict(_usr.user.get_dict())
1325 usr = AttributeDict(_usr.user.get_dict())
1326 usr.permission = _usr.permission.permission_name
1326 usr.permission = _usr.permission.permission_name
1327 perm_rows.append(usr)
1327 perm_rows.append(usr)
1328
1328
1329 # filter the perm rows by 'default' first and then sort them by
1329 # filter the perm rows by 'default' first and then sort them by
1330 # admin,write,read,none permissions sorted again alphabetically in
1330 # admin,write,read,none permissions sorted again alphabetically in
1331 # each group
1331 # each group
1332 perm_rows = sorted(perm_rows, key=display_sort)
1332 perm_rows = sorted(perm_rows, key=display_sort)
1333
1333
1334 _admin_perm = 'usergroup.admin'
1334 _admin_perm = 'usergroup.admin'
1335 owner_row = []
1335 owner_row = []
1336 if with_owner:
1336 if with_owner:
1337 usr = AttributeDict(self.user.get_dict())
1337 usr = AttributeDict(self.user.get_dict())
1338 usr.owner_row = True
1338 usr.owner_row = True
1339 usr.permission = _admin_perm
1339 usr.permission = _admin_perm
1340 owner_row.append(usr)
1340 owner_row.append(usr)
1341
1341
1342 super_admin_rows = []
1342 super_admin_rows = []
1343 if with_admins:
1343 if with_admins:
1344 for usr in User.get_all_super_admins():
1344 for usr in User.get_all_super_admins():
1345 # if this admin is also owner, don't double the record
1345 # if this admin is also owner, don't double the record
1346 if usr.user_id == owner_row[0].user_id:
1346 if usr.user_id == owner_row[0].user_id:
1347 owner_row[0].admin_row = True
1347 owner_row[0].admin_row = True
1348 else:
1348 else:
1349 usr = AttributeDict(usr.get_dict())
1349 usr = AttributeDict(usr.get_dict())
1350 usr.admin_row = True
1350 usr.admin_row = True
1351 usr.permission = _admin_perm
1351 usr.permission = _admin_perm
1352 super_admin_rows.append(usr)
1352 super_admin_rows.append(usr)
1353
1353
1354 return super_admin_rows + owner_row + perm_rows
1354 return super_admin_rows + owner_row + perm_rows
1355
1355
1356 def permission_user_groups(self):
1356 def permission_user_groups(self):
1357 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1357 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1358 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1358 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1359 joinedload(UserGroupUserGroupToPerm.target_user_group),
1359 joinedload(UserGroupUserGroupToPerm.target_user_group),
1360 joinedload(UserGroupUserGroupToPerm.permission),)
1360 joinedload(UserGroupUserGroupToPerm.permission),)
1361
1361
1362 perm_rows = []
1362 perm_rows = []
1363 for _user_group in q.all():
1363 for _user_group in q.all():
1364 usr = AttributeDict(_user_group.user_group.get_dict())
1364 usr = AttributeDict(_user_group.user_group.get_dict())
1365 usr.permission = _user_group.permission.permission_name
1365 usr.permission = _user_group.permission.permission_name
1366 perm_rows.append(usr)
1366 perm_rows.append(usr)
1367
1367
1368 return perm_rows
1368 return perm_rows
1369
1369
1370 def _get_default_perms(self, user_group, suffix=''):
1370 def _get_default_perms(self, user_group, suffix=''):
1371 from rhodecode.model.permission import PermissionModel
1371 from rhodecode.model.permission import PermissionModel
1372 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1372 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1373
1373
1374 def get_default_perms(self, suffix=''):
1374 def get_default_perms(self, suffix=''):
1375 return self._get_default_perms(self, suffix)
1375 return self._get_default_perms(self, suffix)
1376
1376
1377 def get_api_data(self, with_group_members=True, include_secrets=False):
1377 def get_api_data(self, with_group_members=True, include_secrets=False):
1378 """
1378 """
1379 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1379 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1380 basically forwarded.
1380 basically forwarded.
1381
1381
1382 """
1382 """
1383 user_group = self
1383 user_group = self
1384 data = {
1384 data = {
1385 'users_group_id': user_group.users_group_id,
1385 'users_group_id': user_group.users_group_id,
1386 'group_name': user_group.users_group_name,
1386 'group_name': user_group.users_group_name,
1387 'group_description': user_group.user_group_description,
1387 'group_description': user_group.user_group_description,
1388 'active': user_group.users_group_active,
1388 'active': user_group.users_group_active,
1389 'owner': user_group.user.username,
1389 'owner': user_group.user.username,
1390 'owner_email': user_group.user.email,
1390 'owner_email': user_group.user.email,
1391 }
1391 }
1392
1392
1393 if with_group_members:
1393 if with_group_members:
1394 users = []
1394 users = []
1395 for user in user_group.members:
1395 for user in user_group.members:
1396 user = user.user
1396 user = user.user
1397 users.append(user.get_api_data(include_secrets=include_secrets))
1397 users.append(user.get_api_data(include_secrets=include_secrets))
1398 data['users'] = users
1398 data['users'] = users
1399
1399
1400 return data
1400 return data
1401
1401
1402
1402
1403 class UserGroupMember(Base, BaseModel):
1403 class UserGroupMember(Base, BaseModel):
1404 __tablename__ = 'users_groups_members'
1404 __tablename__ = 'users_groups_members'
1405 __table_args__ = (
1405 __table_args__ = (
1406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1407 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1407 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1408 )
1408 )
1409
1409
1410 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1410 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1411 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1411 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1413
1413
1414 user = relationship('User', lazy='joined')
1414 user = relationship('User', lazy='joined')
1415 users_group = relationship('UserGroup')
1415 users_group = relationship('UserGroup')
1416
1416
1417 def __init__(self, gr_id='', u_id=''):
1417 def __init__(self, gr_id='', u_id=''):
1418 self.users_group_id = gr_id
1418 self.users_group_id = gr_id
1419 self.user_id = u_id
1419 self.user_id = u_id
1420
1420
1421
1421
1422 class RepositoryField(Base, BaseModel):
1422 class RepositoryField(Base, BaseModel):
1423 __tablename__ = 'repositories_fields'
1423 __tablename__ = 'repositories_fields'
1424 __table_args__ = (
1424 __table_args__ = (
1425 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1425 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1426 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1426 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1427 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1427 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1428 )
1428 )
1429 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1429 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1430
1430
1431 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1431 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1432 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1432 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1433 field_key = Column("field_key", String(250))
1433 field_key = Column("field_key", String(250))
1434 field_label = Column("field_label", String(1024), nullable=False)
1434 field_label = Column("field_label", String(1024), nullable=False)
1435 field_value = Column("field_value", String(10000), nullable=False)
1435 field_value = Column("field_value", String(10000), nullable=False)
1436 field_desc = Column("field_desc", String(1024), nullable=False)
1436 field_desc = Column("field_desc", String(1024), nullable=False)
1437 field_type = Column("field_type", String(255), nullable=False, unique=None)
1437 field_type = Column("field_type", String(255), nullable=False, unique=None)
1438 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1438 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1439
1439
1440 repository = relationship('Repository')
1440 repository = relationship('Repository')
1441
1441
1442 @property
1442 @property
1443 def field_key_prefixed(self):
1443 def field_key_prefixed(self):
1444 return 'ex_%s' % self.field_key
1444 return 'ex_%s' % self.field_key
1445
1445
1446 @classmethod
1446 @classmethod
1447 def un_prefix_key(cls, key):
1447 def un_prefix_key(cls, key):
1448 if key.startswith(cls.PREFIX):
1448 if key.startswith(cls.PREFIX):
1449 return key[len(cls.PREFIX):]
1449 return key[len(cls.PREFIX):]
1450 return key
1450 return key
1451
1451
1452 @classmethod
1452 @classmethod
1453 def get_by_key_name(cls, key, repo):
1453 def get_by_key_name(cls, key, repo):
1454 row = cls.query()\
1454 row = cls.query()\
1455 .filter(cls.repository == repo)\
1455 .filter(cls.repository == repo)\
1456 .filter(cls.field_key == key).scalar()
1456 .filter(cls.field_key == key).scalar()
1457 return row
1457 return row
1458
1458
1459
1459
1460 class Repository(Base, BaseModel):
1460 class Repository(Base, BaseModel):
1461 __tablename__ = 'repositories'
1461 __tablename__ = 'repositories'
1462 __table_args__ = (
1462 __table_args__ = (
1463 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1463 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1466 )
1466 )
1467 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1467 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1468 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1468 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1469
1469
1470 STATE_CREATED = 'repo_state_created'
1470 STATE_CREATED = 'repo_state_created'
1471 STATE_PENDING = 'repo_state_pending'
1471 STATE_PENDING = 'repo_state_pending'
1472 STATE_ERROR = 'repo_state_error'
1472 STATE_ERROR = 'repo_state_error'
1473
1473
1474 LOCK_AUTOMATIC = 'lock_auto'
1474 LOCK_AUTOMATIC = 'lock_auto'
1475 LOCK_API = 'lock_api'
1475 LOCK_API = 'lock_api'
1476 LOCK_WEB = 'lock_web'
1476 LOCK_WEB = 'lock_web'
1477 LOCK_PULL = 'lock_pull'
1477 LOCK_PULL = 'lock_pull'
1478
1478
1479 NAME_SEP = URL_SEP
1479 NAME_SEP = URL_SEP
1480
1480
1481 repo_id = Column(
1481 repo_id = Column(
1482 "repo_id", Integer(), nullable=False, unique=True, default=None,
1482 "repo_id", Integer(), nullable=False, unique=True, default=None,
1483 primary_key=True)
1483 primary_key=True)
1484 _repo_name = Column(
1484 _repo_name = Column(
1485 "repo_name", Text(), nullable=False, default=None)
1485 "repo_name", Text(), nullable=False, default=None)
1486 _repo_name_hash = Column(
1486 _repo_name_hash = Column(
1487 "repo_name_hash", String(255), nullable=False, unique=True)
1487 "repo_name_hash", String(255), nullable=False, unique=True)
1488 repo_state = Column("repo_state", String(255), nullable=True)
1488 repo_state = Column("repo_state", String(255), nullable=True)
1489
1489
1490 clone_uri = Column(
1490 clone_uri = Column(
1491 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1491 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1492 default=None)
1492 default=None)
1493 repo_type = Column(
1493 repo_type = Column(
1494 "repo_type", String(255), nullable=False, unique=False, default=None)
1494 "repo_type", String(255), nullable=False, unique=False, default=None)
1495 user_id = Column(
1495 user_id = Column(
1496 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1496 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1497 unique=False, default=None)
1497 unique=False, default=None)
1498 private = Column(
1498 private = Column(
1499 "private", Boolean(), nullable=True, unique=None, default=None)
1499 "private", Boolean(), nullable=True, unique=None, default=None)
1500 enable_statistics = Column(
1500 enable_statistics = Column(
1501 "statistics", Boolean(), nullable=True, unique=None, default=True)
1501 "statistics", Boolean(), nullable=True, unique=None, default=True)
1502 enable_downloads = Column(
1502 enable_downloads = Column(
1503 "downloads", Boolean(), nullable=True, unique=None, default=True)
1503 "downloads", Boolean(), nullable=True, unique=None, default=True)
1504 description = Column(
1504 description = Column(
1505 "description", String(10000), nullable=True, unique=None, default=None)
1505 "description", String(10000), nullable=True, unique=None, default=None)
1506 created_on = Column(
1506 created_on = Column(
1507 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1507 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1508 default=datetime.datetime.now)
1508 default=datetime.datetime.now)
1509 updated_on = Column(
1509 updated_on = Column(
1510 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1510 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1511 default=datetime.datetime.now)
1511 default=datetime.datetime.now)
1512 _landing_revision = Column(
1512 _landing_revision = Column(
1513 "landing_revision", String(255), nullable=False, unique=False,
1513 "landing_revision", String(255), nullable=False, unique=False,
1514 default=None)
1514 default=None)
1515 enable_locking = Column(
1515 enable_locking = Column(
1516 "enable_locking", Boolean(), nullable=False, unique=None,
1516 "enable_locking", Boolean(), nullable=False, unique=None,
1517 default=False)
1517 default=False)
1518 _locked = Column(
1518 _locked = Column(
1519 "locked", String(255), nullable=True, unique=False, default=None)
1519 "locked", String(255), nullable=True, unique=False, default=None)
1520 _changeset_cache = Column(
1520 _changeset_cache = Column(
1521 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1521 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1522
1522
1523 fork_id = Column(
1523 fork_id = Column(
1524 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1524 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1525 nullable=True, unique=False, default=None)
1525 nullable=True, unique=False, default=None)
1526 group_id = Column(
1526 group_id = Column(
1527 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1527 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1528 unique=False, default=None)
1528 unique=False, default=None)
1529
1529
1530 user = relationship('User', lazy='joined')
1530 user = relationship('User', lazy='joined')
1531 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1531 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1532 group = relationship('RepoGroup', lazy='joined')
1532 group = relationship('RepoGroup', lazy='joined')
1533 repo_to_perm = relationship(
1533 repo_to_perm = relationship(
1534 'UserRepoToPerm', cascade='all',
1534 'UserRepoToPerm', cascade='all',
1535 order_by='UserRepoToPerm.repo_to_perm_id')
1535 order_by='UserRepoToPerm.repo_to_perm_id')
1536 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1536 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1537 stats = relationship('Statistics', cascade='all', uselist=False)
1537 stats = relationship('Statistics', cascade='all', uselist=False)
1538
1538
1539 followers = relationship(
1539 followers = relationship(
1540 'UserFollowing',
1540 'UserFollowing',
1541 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1541 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1542 cascade='all')
1542 cascade='all')
1543 extra_fields = relationship(
1543 extra_fields = relationship(
1544 'RepositoryField', cascade="all, delete, delete-orphan")
1544 'RepositoryField', cascade="all, delete, delete-orphan")
1545 logs = relationship('UserLog')
1545 logs = relationship('UserLog')
1546 comments = relationship(
1546 comments = relationship(
1547 'ChangesetComment', cascade="all, delete, delete-orphan")
1547 'ChangesetComment', cascade="all, delete, delete-orphan")
1548 pull_requests_source = relationship(
1548 pull_requests_source = relationship(
1549 'PullRequest',
1549 'PullRequest',
1550 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1550 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1551 cascade="all, delete, delete-orphan")
1551 cascade="all, delete, delete-orphan")
1552 pull_requests_target = relationship(
1552 pull_requests_target = relationship(
1553 'PullRequest',
1553 'PullRequest',
1554 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1554 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1555 cascade="all, delete, delete-orphan")
1555 cascade="all, delete, delete-orphan")
1556 ui = relationship('RepoRhodeCodeUi', cascade="all")
1556 ui = relationship('RepoRhodeCodeUi', cascade="all")
1557 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1557 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1558 integrations = relationship('Integration',
1558 integrations = relationship('Integration',
1559 cascade="all, delete, delete-orphan")
1559 cascade="all, delete, delete-orphan")
1560
1560
1561 def __unicode__(self):
1561 def __unicode__(self):
1562 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1562 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1563 safe_unicode(self.repo_name))
1563 safe_unicode(self.repo_name))
1564
1564
1565 @hybrid_property
1565 @hybrid_property
1566 def description_safe(self):
1566 def description_safe(self):
1567 from rhodecode.lib import helpers as h
1567 from rhodecode.lib import helpers as h
1568 return h.escape(self.description)
1568 return h.escape(self.description)
1569
1569
1570 @hybrid_property
1570 @hybrid_property
1571 def landing_rev(self):
1571 def landing_rev(self):
1572 # always should return [rev_type, rev]
1572 # always should return [rev_type, rev]
1573 if self._landing_revision:
1573 if self._landing_revision:
1574 _rev_info = self._landing_revision.split(':')
1574 _rev_info = self._landing_revision.split(':')
1575 if len(_rev_info) < 2:
1575 if len(_rev_info) < 2:
1576 _rev_info.insert(0, 'rev')
1576 _rev_info.insert(0, 'rev')
1577 return [_rev_info[0], _rev_info[1]]
1577 return [_rev_info[0], _rev_info[1]]
1578 return [None, None]
1578 return [None, None]
1579
1579
1580 @landing_rev.setter
1580 @landing_rev.setter
1581 def landing_rev(self, val):
1581 def landing_rev(self, val):
1582 if ':' not in val:
1582 if ':' not in val:
1583 raise ValueError('value must be delimited with `:` and consist '
1583 raise ValueError('value must be delimited with `:` and consist '
1584 'of <rev_type>:<rev>, got %s instead' % val)
1584 'of <rev_type>:<rev>, got %s instead' % val)
1585 self._landing_revision = val
1585 self._landing_revision = val
1586
1586
1587 @hybrid_property
1587 @hybrid_property
1588 def locked(self):
1588 def locked(self):
1589 if self._locked:
1589 if self._locked:
1590 user_id, timelocked, reason = self._locked.split(':')
1590 user_id, timelocked, reason = self._locked.split(':')
1591 lock_values = int(user_id), timelocked, reason
1591 lock_values = int(user_id), timelocked, reason
1592 else:
1592 else:
1593 lock_values = [None, None, None]
1593 lock_values = [None, None, None]
1594 return lock_values
1594 return lock_values
1595
1595
1596 @locked.setter
1596 @locked.setter
1597 def locked(self, val):
1597 def locked(self, val):
1598 if val and isinstance(val, (list, tuple)):
1598 if val and isinstance(val, (list, tuple)):
1599 self._locked = ':'.join(map(str, val))
1599 self._locked = ':'.join(map(str, val))
1600 else:
1600 else:
1601 self._locked = None
1601 self._locked = None
1602
1602
1603 @hybrid_property
1603 @hybrid_property
1604 def changeset_cache(self):
1604 def changeset_cache(self):
1605 from rhodecode.lib.vcs.backends.base import EmptyCommit
1605 from rhodecode.lib.vcs.backends.base import EmptyCommit
1606 dummy = EmptyCommit().__json__()
1606 dummy = EmptyCommit().__json__()
1607 if not self._changeset_cache:
1607 if not self._changeset_cache:
1608 return dummy
1608 return dummy
1609 try:
1609 try:
1610 return json.loads(self._changeset_cache)
1610 return json.loads(self._changeset_cache)
1611 except TypeError:
1611 except TypeError:
1612 return dummy
1612 return dummy
1613 except Exception:
1613 except Exception:
1614 log.error(traceback.format_exc())
1614 log.error(traceback.format_exc())
1615 return dummy
1615 return dummy
1616
1616
1617 @changeset_cache.setter
1617 @changeset_cache.setter
1618 def changeset_cache(self, val):
1618 def changeset_cache(self, val):
1619 try:
1619 try:
1620 self._changeset_cache = json.dumps(val)
1620 self._changeset_cache = json.dumps(val)
1621 except Exception:
1621 except Exception:
1622 log.error(traceback.format_exc())
1622 log.error(traceback.format_exc())
1623
1623
1624 @hybrid_property
1624 @hybrid_property
1625 def repo_name(self):
1625 def repo_name(self):
1626 return self._repo_name
1626 return self._repo_name
1627
1627
1628 @repo_name.setter
1628 @repo_name.setter
1629 def repo_name(self, value):
1629 def repo_name(self, value):
1630 self._repo_name = value
1630 self._repo_name = value
1631 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1631 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1632
1632
1633 @classmethod
1633 @classmethod
1634 def normalize_repo_name(cls, repo_name):
1634 def normalize_repo_name(cls, repo_name):
1635 """
1635 """
1636 Normalizes os specific repo_name to the format internally stored inside
1636 Normalizes os specific repo_name to the format internally stored inside
1637 database using URL_SEP
1637 database using URL_SEP
1638
1638
1639 :param cls:
1639 :param cls:
1640 :param repo_name:
1640 :param repo_name:
1641 """
1641 """
1642 return cls.NAME_SEP.join(repo_name.split(os.sep))
1642 return cls.NAME_SEP.join(repo_name.split(os.sep))
1643
1643
1644 @classmethod
1644 @classmethod
1645 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1645 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1646 session = Session()
1646 session = Session()
1647 q = session.query(cls).filter(cls.repo_name == repo_name)
1647 q = session.query(cls).filter(cls.repo_name == repo_name)
1648
1648
1649 if cache:
1649 if cache:
1650 if identity_cache:
1650 if identity_cache:
1651 val = cls.identity_cache(session, 'repo_name', repo_name)
1651 val = cls.identity_cache(session, 'repo_name', repo_name)
1652 if val:
1652 if val:
1653 return val
1653 return val
1654 else:
1654 else:
1655 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1655 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1656 q = q.options(
1656 q = q.options(
1657 FromCache("sql_cache_short", cache_key))
1657 FromCache("sql_cache_short", cache_key))
1658
1658
1659 return q.scalar()
1659 return q.scalar()
1660
1660
1661 @classmethod
1661 @classmethod
1662 def get_by_full_path(cls, repo_full_path):
1662 def get_by_full_path(cls, repo_full_path):
1663 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1663 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1664 repo_name = cls.normalize_repo_name(repo_name)
1664 repo_name = cls.normalize_repo_name(repo_name)
1665 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1665 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1666
1666
1667 @classmethod
1667 @classmethod
1668 def get_repo_forks(cls, repo_id):
1668 def get_repo_forks(cls, repo_id):
1669 return cls.query().filter(Repository.fork_id == repo_id)
1669 return cls.query().filter(Repository.fork_id == repo_id)
1670
1670
1671 @classmethod
1671 @classmethod
1672 def base_path(cls):
1672 def base_path(cls):
1673 """
1673 """
1674 Returns base path when all repos are stored
1674 Returns base path when all repos are stored
1675
1675
1676 :param cls:
1676 :param cls:
1677 """
1677 """
1678 q = Session().query(RhodeCodeUi)\
1678 q = Session().query(RhodeCodeUi)\
1679 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1679 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1680 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1680 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1681 return q.one().ui_value
1681 return q.one().ui_value
1682
1682
1683 @classmethod
1683 @classmethod
1684 def is_valid(cls, repo_name):
1684 def is_valid(cls, repo_name):
1685 """
1685 """
1686 returns True if given repo name is a valid filesystem repository
1686 returns True if given repo name is a valid filesystem repository
1687
1687
1688 :param cls:
1688 :param cls:
1689 :param repo_name:
1689 :param repo_name:
1690 """
1690 """
1691 from rhodecode.lib.utils import is_valid_repo
1691 from rhodecode.lib.utils import is_valid_repo
1692
1692
1693 return is_valid_repo(repo_name, cls.base_path())
1693 return is_valid_repo(repo_name, cls.base_path())
1694
1694
1695 @classmethod
1695 @classmethod
1696 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1696 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1697 case_insensitive=True):
1697 case_insensitive=True):
1698 q = Repository.query()
1698 q = Repository.query()
1699
1699
1700 if not isinstance(user_id, Optional):
1700 if not isinstance(user_id, Optional):
1701 q = q.filter(Repository.user_id == user_id)
1701 q = q.filter(Repository.user_id == user_id)
1702
1702
1703 if not isinstance(group_id, Optional):
1703 if not isinstance(group_id, Optional):
1704 q = q.filter(Repository.group_id == group_id)
1704 q = q.filter(Repository.group_id == group_id)
1705
1705
1706 if case_insensitive:
1706 if case_insensitive:
1707 q = q.order_by(func.lower(Repository.repo_name))
1707 q = q.order_by(func.lower(Repository.repo_name))
1708 else:
1708 else:
1709 q = q.order_by(Repository.repo_name)
1709 q = q.order_by(Repository.repo_name)
1710 return q.all()
1710 return q.all()
1711
1711
1712 @property
1712 @property
1713 def forks(self):
1713 def forks(self):
1714 """
1714 """
1715 Return forks of this repo
1715 Return forks of this repo
1716 """
1716 """
1717 return Repository.get_repo_forks(self.repo_id)
1717 return Repository.get_repo_forks(self.repo_id)
1718
1718
1719 @property
1719 @property
1720 def parent(self):
1720 def parent(self):
1721 """
1721 """
1722 Returns fork parent
1722 Returns fork parent
1723 """
1723 """
1724 return self.fork
1724 return self.fork
1725
1725
1726 @property
1726 @property
1727 def just_name(self):
1727 def just_name(self):
1728 return self.repo_name.split(self.NAME_SEP)[-1]
1728 return self.repo_name.split(self.NAME_SEP)[-1]
1729
1729
1730 @property
1730 @property
1731 def groups_with_parents(self):
1731 def groups_with_parents(self):
1732 groups = []
1732 groups = []
1733 if self.group is None:
1733 if self.group is None:
1734 return groups
1734 return groups
1735
1735
1736 cur_gr = self.group
1736 cur_gr = self.group
1737 groups.insert(0, cur_gr)
1737 groups.insert(0, cur_gr)
1738 while 1:
1738 while 1:
1739 gr = getattr(cur_gr, 'parent_group', None)
1739 gr = getattr(cur_gr, 'parent_group', None)
1740 cur_gr = cur_gr.parent_group
1740 cur_gr = cur_gr.parent_group
1741 if gr is None:
1741 if gr is None:
1742 break
1742 break
1743 groups.insert(0, gr)
1743 groups.insert(0, gr)
1744
1744
1745 return groups
1745 return groups
1746
1746
1747 @property
1747 @property
1748 def groups_and_repo(self):
1748 def groups_and_repo(self):
1749 return self.groups_with_parents, self
1749 return self.groups_with_parents, self
1750
1750
1751 @LazyProperty
1751 @LazyProperty
1752 def repo_path(self):
1752 def repo_path(self):
1753 """
1753 """
1754 Returns base full path for that repository means where it actually
1754 Returns base full path for that repository means where it actually
1755 exists on a filesystem
1755 exists on a filesystem
1756 """
1756 """
1757 q = Session().query(RhodeCodeUi).filter(
1757 q = Session().query(RhodeCodeUi).filter(
1758 RhodeCodeUi.ui_key == self.NAME_SEP)
1758 RhodeCodeUi.ui_key == self.NAME_SEP)
1759 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1759 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1760 return q.one().ui_value
1760 return q.one().ui_value
1761
1761
1762 @property
1762 @property
1763 def repo_full_path(self):
1763 def repo_full_path(self):
1764 p = [self.repo_path]
1764 p = [self.repo_path]
1765 # we need to split the name by / since this is how we store the
1765 # we need to split the name by / since this is how we store the
1766 # names in the database, but that eventually needs to be converted
1766 # names in the database, but that eventually needs to be converted
1767 # into a valid system path
1767 # into a valid system path
1768 p += self.repo_name.split(self.NAME_SEP)
1768 p += self.repo_name.split(self.NAME_SEP)
1769 return os.path.join(*map(safe_unicode, p))
1769 return os.path.join(*map(safe_unicode, p))
1770
1770
1771 @property
1771 @property
1772 def cache_keys(self):
1772 def cache_keys(self):
1773 """
1773 """
1774 Returns associated cache keys for that repo
1774 Returns associated cache keys for that repo
1775 """
1775 """
1776 return CacheKey.query()\
1776 return CacheKey.query()\
1777 .filter(CacheKey.cache_args == self.repo_name)\
1777 .filter(CacheKey.cache_args == self.repo_name)\
1778 .order_by(CacheKey.cache_key)\
1778 .order_by(CacheKey.cache_key)\
1779 .all()
1779 .all()
1780
1780
1781 def get_new_name(self, repo_name):
1781 def get_new_name(self, repo_name):
1782 """
1782 """
1783 returns new full repository name based on assigned group and new new
1783 returns new full repository name based on assigned group and new new
1784
1784
1785 :param group_name:
1785 :param group_name:
1786 """
1786 """
1787 path_prefix = self.group.full_path_splitted if self.group else []
1787 path_prefix = self.group.full_path_splitted if self.group else []
1788 return self.NAME_SEP.join(path_prefix + [repo_name])
1788 return self.NAME_SEP.join(path_prefix + [repo_name])
1789
1789
1790 @property
1790 @property
1791 def _config(self):
1791 def _config(self):
1792 """
1792 """
1793 Returns db based config object.
1793 Returns db based config object.
1794 """
1794 """
1795 from rhodecode.lib.utils import make_db_config
1795 from rhodecode.lib.utils import make_db_config
1796 return make_db_config(clear_session=False, repo=self)
1796 return make_db_config(clear_session=False, repo=self)
1797
1797
1798 def permissions(self, with_admins=True, with_owner=True):
1798 def permissions(self, with_admins=True, with_owner=True):
1799 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1799 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1800 q = q.options(joinedload(UserRepoToPerm.repository),
1800 q = q.options(joinedload(UserRepoToPerm.repository),
1801 joinedload(UserRepoToPerm.user),
1801 joinedload(UserRepoToPerm.user),
1802 joinedload(UserRepoToPerm.permission),)
1802 joinedload(UserRepoToPerm.permission),)
1803
1803
1804 # get owners and admins and permissions. We do a trick of re-writing
1804 # get owners and admins and permissions. We do a trick of re-writing
1805 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1805 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1806 # has a global reference and changing one object propagates to all
1806 # has a global reference and changing one object propagates to all
1807 # others. This means if admin is also an owner admin_row that change
1807 # others. This means if admin is also an owner admin_row that change
1808 # would propagate to both objects
1808 # would propagate to both objects
1809 perm_rows = []
1809 perm_rows = []
1810 for _usr in q.all():
1810 for _usr in q.all():
1811 usr = AttributeDict(_usr.user.get_dict())
1811 usr = AttributeDict(_usr.user.get_dict())
1812 usr.permission = _usr.permission.permission_name
1812 usr.permission = _usr.permission.permission_name
1813 perm_rows.append(usr)
1813 perm_rows.append(usr)
1814
1814
1815 # filter the perm rows by 'default' first and then sort them by
1815 # filter the perm rows by 'default' first and then sort them by
1816 # admin,write,read,none permissions sorted again alphabetically in
1816 # admin,write,read,none permissions sorted again alphabetically in
1817 # each group
1817 # each group
1818 perm_rows = sorted(perm_rows, key=display_sort)
1818 perm_rows = sorted(perm_rows, key=display_sort)
1819
1819
1820 _admin_perm = 'repository.admin'
1820 _admin_perm = 'repository.admin'
1821 owner_row = []
1821 owner_row = []
1822 if with_owner:
1822 if with_owner:
1823 usr = AttributeDict(self.user.get_dict())
1823 usr = AttributeDict(self.user.get_dict())
1824 usr.owner_row = True
1824 usr.owner_row = True
1825 usr.permission = _admin_perm
1825 usr.permission = _admin_perm
1826 owner_row.append(usr)
1826 owner_row.append(usr)
1827
1827
1828 super_admin_rows = []
1828 super_admin_rows = []
1829 if with_admins:
1829 if with_admins:
1830 for usr in User.get_all_super_admins():
1830 for usr in User.get_all_super_admins():
1831 # if this admin is also owner, don't double the record
1831 # if this admin is also owner, don't double the record
1832 if usr.user_id == owner_row[0].user_id:
1832 if usr.user_id == owner_row[0].user_id:
1833 owner_row[0].admin_row = True
1833 owner_row[0].admin_row = True
1834 else:
1834 else:
1835 usr = AttributeDict(usr.get_dict())
1835 usr = AttributeDict(usr.get_dict())
1836 usr.admin_row = True
1836 usr.admin_row = True
1837 usr.permission = _admin_perm
1837 usr.permission = _admin_perm
1838 super_admin_rows.append(usr)
1838 super_admin_rows.append(usr)
1839
1839
1840 return super_admin_rows + owner_row + perm_rows
1840 return super_admin_rows + owner_row + perm_rows
1841
1841
1842 def permission_user_groups(self):
1842 def permission_user_groups(self):
1843 q = UserGroupRepoToPerm.query().filter(
1843 q = UserGroupRepoToPerm.query().filter(
1844 UserGroupRepoToPerm.repository == self)
1844 UserGroupRepoToPerm.repository == self)
1845 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1845 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1846 joinedload(UserGroupRepoToPerm.users_group),
1846 joinedload(UserGroupRepoToPerm.users_group),
1847 joinedload(UserGroupRepoToPerm.permission),)
1847 joinedload(UserGroupRepoToPerm.permission),)
1848
1848
1849 perm_rows = []
1849 perm_rows = []
1850 for _user_group in q.all():
1850 for _user_group in q.all():
1851 usr = AttributeDict(_user_group.users_group.get_dict())
1851 usr = AttributeDict(_user_group.users_group.get_dict())
1852 usr.permission = _user_group.permission.permission_name
1852 usr.permission = _user_group.permission.permission_name
1853 perm_rows.append(usr)
1853 perm_rows.append(usr)
1854
1854
1855 return perm_rows
1855 return perm_rows
1856
1856
1857 def get_api_data(self, include_secrets=False):
1857 def get_api_data(self, include_secrets=False):
1858 """
1858 """
1859 Common function for generating repo api data
1859 Common function for generating repo api data
1860
1860
1861 :param include_secrets: See :meth:`User.get_api_data`.
1861 :param include_secrets: See :meth:`User.get_api_data`.
1862
1862
1863 """
1863 """
1864 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1864 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1865 # move this methods on models level.
1865 # move this methods on models level.
1866 from rhodecode.model.settings import SettingsModel
1866 from rhodecode.model.settings import SettingsModel
1867 from rhodecode.model.repo import RepoModel
1867 from rhodecode.model.repo import RepoModel
1868
1868
1869 repo = self
1869 repo = self
1870 _user_id, _time, _reason = self.locked
1870 _user_id, _time, _reason = self.locked
1871
1871
1872 data = {
1872 data = {
1873 'repo_id': repo.repo_id,
1873 'repo_id': repo.repo_id,
1874 'repo_name': repo.repo_name,
1874 'repo_name': repo.repo_name,
1875 'repo_type': repo.repo_type,
1875 'repo_type': repo.repo_type,
1876 'clone_uri': repo.clone_uri or '',
1876 'clone_uri': repo.clone_uri or '',
1877 'url': RepoModel().get_url(self),
1877 'url': RepoModel().get_url(self),
1878 'private': repo.private,
1878 'private': repo.private,
1879 'created_on': repo.created_on,
1879 'created_on': repo.created_on,
1880 'description': repo.description_safe,
1880 'description': repo.description_safe,
1881 'landing_rev': repo.landing_rev,
1881 'landing_rev': repo.landing_rev,
1882 'owner': repo.user.username,
1882 'owner': repo.user.username,
1883 'fork_of': repo.fork.repo_name if repo.fork else None,
1883 'fork_of': repo.fork.repo_name if repo.fork else None,
1884 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1884 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1885 'enable_statistics': repo.enable_statistics,
1885 'enable_statistics': repo.enable_statistics,
1886 'enable_locking': repo.enable_locking,
1886 'enable_locking': repo.enable_locking,
1887 'enable_downloads': repo.enable_downloads,
1887 'enable_downloads': repo.enable_downloads,
1888 'last_changeset': repo.changeset_cache,
1888 'last_changeset': repo.changeset_cache,
1889 'locked_by': User.get(_user_id).get_api_data(
1889 'locked_by': User.get(_user_id).get_api_data(
1890 include_secrets=include_secrets) if _user_id else None,
1890 include_secrets=include_secrets) if _user_id else None,
1891 'locked_date': time_to_datetime(_time) if _time else None,
1891 'locked_date': time_to_datetime(_time) if _time else None,
1892 'lock_reason': _reason if _reason else None,
1892 'lock_reason': _reason if _reason else None,
1893 }
1893 }
1894
1894
1895 # TODO: mikhail: should be per-repo settings here
1895 # TODO: mikhail: should be per-repo settings here
1896 rc_config = SettingsModel().get_all_settings()
1896 rc_config = SettingsModel().get_all_settings()
1897 repository_fields = str2bool(
1897 repository_fields = str2bool(
1898 rc_config.get('rhodecode_repository_fields'))
1898 rc_config.get('rhodecode_repository_fields'))
1899 if repository_fields:
1899 if repository_fields:
1900 for f in self.extra_fields:
1900 for f in self.extra_fields:
1901 data[f.field_key_prefixed] = f.field_value
1901 data[f.field_key_prefixed] = f.field_value
1902
1902
1903 return data
1903 return data
1904
1904
1905 @classmethod
1905 @classmethod
1906 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1906 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1907 if not lock_time:
1907 if not lock_time:
1908 lock_time = time.time()
1908 lock_time = time.time()
1909 if not lock_reason:
1909 if not lock_reason:
1910 lock_reason = cls.LOCK_AUTOMATIC
1910 lock_reason = cls.LOCK_AUTOMATIC
1911 repo.locked = [user_id, lock_time, lock_reason]
1911 repo.locked = [user_id, lock_time, lock_reason]
1912 Session().add(repo)
1912 Session().add(repo)
1913 Session().commit()
1913 Session().commit()
1914
1914
1915 @classmethod
1915 @classmethod
1916 def unlock(cls, repo):
1916 def unlock(cls, repo):
1917 repo.locked = None
1917 repo.locked = None
1918 Session().add(repo)
1918 Session().add(repo)
1919 Session().commit()
1919 Session().commit()
1920
1920
1921 @classmethod
1921 @classmethod
1922 def getlock(cls, repo):
1922 def getlock(cls, repo):
1923 return repo.locked
1923 return repo.locked
1924
1924
1925 def is_user_lock(self, user_id):
1925 def is_user_lock(self, user_id):
1926 if self.lock[0]:
1926 if self.lock[0]:
1927 lock_user_id = safe_int(self.lock[0])
1927 lock_user_id = safe_int(self.lock[0])
1928 user_id = safe_int(user_id)
1928 user_id = safe_int(user_id)
1929 # both are ints, and they are equal
1929 # both are ints, and they are equal
1930 return all([lock_user_id, user_id]) and lock_user_id == user_id
1930 return all([lock_user_id, user_id]) and lock_user_id == user_id
1931
1931
1932 return False
1932 return False
1933
1933
1934 def get_locking_state(self, action, user_id, only_when_enabled=True):
1934 def get_locking_state(self, action, user_id, only_when_enabled=True):
1935 """
1935 """
1936 Checks locking on this repository, if locking is enabled and lock is
1936 Checks locking on this repository, if locking is enabled and lock is
1937 present returns a tuple of make_lock, locked, locked_by.
1937 present returns a tuple of make_lock, locked, locked_by.
1938 make_lock can have 3 states None (do nothing) True, make lock
1938 make_lock can have 3 states None (do nothing) True, make lock
1939 False release lock, This value is later propagated to hooks, which
1939 False release lock, This value is later propagated to hooks, which
1940 do the locking. Think about this as signals passed to hooks what to do.
1940 do the locking. Think about this as signals passed to hooks what to do.
1941
1941
1942 """
1942 """
1943 # TODO: johbo: This is part of the business logic and should be moved
1943 # TODO: johbo: This is part of the business logic and should be moved
1944 # into the RepositoryModel.
1944 # into the RepositoryModel.
1945
1945
1946 if action not in ('push', 'pull'):
1946 if action not in ('push', 'pull'):
1947 raise ValueError("Invalid action value: %s" % repr(action))
1947 raise ValueError("Invalid action value: %s" % repr(action))
1948
1948
1949 # defines if locked error should be thrown to user
1949 # defines if locked error should be thrown to user
1950 currently_locked = False
1950 currently_locked = False
1951 # defines if new lock should be made, tri-state
1951 # defines if new lock should be made, tri-state
1952 make_lock = None
1952 make_lock = None
1953 repo = self
1953 repo = self
1954 user = User.get(user_id)
1954 user = User.get(user_id)
1955
1955
1956 lock_info = repo.locked
1956 lock_info = repo.locked
1957
1957
1958 if repo and (repo.enable_locking or not only_when_enabled):
1958 if repo and (repo.enable_locking or not only_when_enabled):
1959 if action == 'push':
1959 if action == 'push':
1960 # check if it's already locked !, if it is compare users
1960 # check if it's already locked !, if it is compare users
1961 locked_by_user_id = lock_info[0]
1961 locked_by_user_id = lock_info[0]
1962 if user.user_id == locked_by_user_id:
1962 if user.user_id == locked_by_user_id:
1963 log.debug(
1963 log.debug(
1964 'Got `push` action from user %s, now unlocking', user)
1964 'Got `push` action from user %s, now unlocking', user)
1965 # unlock if we have push from user who locked
1965 # unlock if we have push from user who locked
1966 make_lock = False
1966 make_lock = False
1967 else:
1967 else:
1968 # we're not the same user who locked, ban with
1968 # we're not the same user who locked, ban with
1969 # code defined in settings (default is 423 HTTP Locked) !
1969 # code defined in settings (default is 423 HTTP Locked) !
1970 log.debug('Repo %s is currently locked by %s', repo, user)
1970 log.debug('Repo %s is currently locked by %s', repo, user)
1971 currently_locked = True
1971 currently_locked = True
1972 elif action == 'pull':
1972 elif action == 'pull':
1973 # [0] user [1] date
1973 # [0] user [1] date
1974 if lock_info[0] and lock_info[1]:
1974 if lock_info[0] and lock_info[1]:
1975 log.debug('Repo %s is currently locked by %s', repo, user)
1975 log.debug('Repo %s is currently locked by %s', repo, user)
1976 currently_locked = True
1976 currently_locked = True
1977 else:
1977 else:
1978 log.debug('Setting lock on repo %s by %s', repo, user)
1978 log.debug('Setting lock on repo %s by %s', repo, user)
1979 make_lock = True
1979 make_lock = True
1980
1980
1981 else:
1981 else:
1982 log.debug('Repository %s do not have locking enabled', repo)
1982 log.debug('Repository %s do not have locking enabled', repo)
1983
1983
1984 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1984 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1985 make_lock, currently_locked, lock_info)
1985 make_lock, currently_locked, lock_info)
1986
1986
1987 from rhodecode.lib.auth import HasRepoPermissionAny
1987 from rhodecode.lib.auth import HasRepoPermissionAny
1988 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1988 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1989 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1989 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1990 # if we don't have at least write permission we cannot make a lock
1990 # if we don't have at least write permission we cannot make a lock
1991 log.debug('lock state reset back to FALSE due to lack '
1991 log.debug('lock state reset back to FALSE due to lack '
1992 'of at least read permission')
1992 'of at least read permission')
1993 make_lock = False
1993 make_lock = False
1994
1994
1995 return make_lock, currently_locked, lock_info
1995 return make_lock, currently_locked, lock_info
1996
1996
1997 @property
1997 @property
1998 def last_db_change(self):
1998 def last_db_change(self):
1999 return self.updated_on
1999 return self.updated_on
2000
2000
2001 @property
2001 @property
2002 def clone_uri_hidden(self):
2002 def clone_uri_hidden(self):
2003 clone_uri = self.clone_uri
2003 clone_uri = self.clone_uri
2004 if clone_uri:
2004 if clone_uri:
2005 import urlobject
2005 import urlobject
2006 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2006 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2007 if url_obj.password:
2007 if url_obj.password:
2008 clone_uri = url_obj.with_password('*****')
2008 clone_uri = url_obj.with_password('*****')
2009 return clone_uri
2009 return clone_uri
2010
2010
2011 def clone_url(self, **override):
2011 def clone_url(self, **override):
2012 from rhodecode.model.settings import SettingsModel
2012 from rhodecode.model.settings import SettingsModel
2013
2013
2014 uri_tmpl = None
2014 uri_tmpl = None
2015 if 'with_id' in override:
2015 if 'with_id' in override:
2016 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2016 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2017 del override['with_id']
2017 del override['with_id']
2018
2018
2019 if 'uri_tmpl' in override:
2019 if 'uri_tmpl' in override:
2020 uri_tmpl = override['uri_tmpl']
2020 uri_tmpl = override['uri_tmpl']
2021 del override['uri_tmpl']
2021 del override['uri_tmpl']
2022
2022
2023 # we didn't override our tmpl from **overrides
2023 # we didn't override our tmpl from **overrides
2024 if not uri_tmpl:
2024 if not uri_tmpl:
2025 rc_config = SettingsModel().get_all_settings(cache=True)
2025 rc_config = SettingsModel().get_all_settings(cache=True)
2026 uri_tmpl = rc_config.get(
2026 uri_tmpl = rc_config.get(
2027 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2027 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2028
2028
2029 request = get_current_request()
2029 request = get_current_request()
2030 return get_clone_url(request=request,
2030 return get_clone_url(request=request,
2031 uri_tmpl=uri_tmpl,
2031 uri_tmpl=uri_tmpl,
2032 repo_name=self.repo_name,
2032 repo_name=self.repo_name,
2033 repo_id=self.repo_id, **override)
2033 repo_id=self.repo_id, **override)
2034
2034
2035 def set_state(self, state):
2035 def set_state(self, state):
2036 self.repo_state = state
2036 self.repo_state = state
2037 Session().add(self)
2037 Session().add(self)
2038 #==========================================================================
2038 #==========================================================================
2039 # SCM PROPERTIES
2039 # SCM PROPERTIES
2040 #==========================================================================
2040 #==========================================================================
2041
2041
2042 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2042 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2043 return get_commit_safe(
2043 return get_commit_safe(
2044 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2044 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2045
2045
2046 def get_changeset(self, rev=None, pre_load=None):
2046 def get_changeset(self, rev=None, pre_load=None):
2047 warnings.warn("Use get_commit", DeprecationWarning)
2047 warnings.warn("Use get_commit", DeprecationWarning)
2048 commit_id = None
2048 commit_id = None
2049 commit_idx = None
2049 commit_idx = None
2050 if isinstance(rev, basestring):
2050 if isinstance(rev, basestring):
2051 commit_id = rev
2051 commit_id = rev
2052 else:
2052 else:
2053 commit_idx = rev
2053 commit_idx = rev
2054 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2054 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2055 pre_load=pre_load)
2055 pre_load=pre_load)
2056
2056
2057 def get_landing_commit(self):
2057 def get_landing_commit(self):
2058 """
2058 """
2059 Returns landing commit, or if that doesn't exist returns the tip
2059 Returns landing commit, or if that doesn't exist returns the tip
2060 """
2060 """
2061 _rev_type, _rev = self.landing_rev
2061 _rev_type, _rev = self.landing_rev
2062 commit = self.get_commit(_rev)
2062 commit = self.get_commit(_rev)
2063 if isinstance(commit, EmptyCommit):
2063 if isinstance(commit, EmptyCommit):
2064 return self.get_commit()
2064 return self.get_commit()
2065 return commit
2065 return commit
2066
2066
2067 def update_commit_cache(self, cs_cache=None, config=None):
2067 def update_commit_cache(self, cs_cache=None, config=None):
2068 """
2068 """
2069 Update cache of last changeset for repository, keys should be::
2069 Update cache of last changeset for repository, keys should be::
2070
2070
2071 short_id
2071 short_id
2072 raw_id
2072 raw_id
2073 revision
2073 revision
2074 parents
2074 parents
2075 message
2075 message
2076 date
2076 date
2077 author
2077 author
2078
2078
2079 :param cs_cache:
2079 :param cs_cache:
2080 """
2080 """
2081 from rhodecode.lib.vcs.backends.base import BaseChangeset
2081 from rhodecode.lib.vcs.backends.base import BaseChangeset
2082 if cs_cache is None:
2082 if cs_cache is None:
2083 # use no-cache version here
2083 # use no-cache version here
2084 scm_repo = self.scm_instance(cache=False, config=config)
2084 scm_repo = self.scm_instance(cache=False, config=config)
2085 if scm_repo:
2085 if scm_repo:
2086 cs_cache = scm_repo.get_commit(
2086 cs_cache = scm_repo.get_commit(
2087 pre_load=["author", "date", "message", "parents"])
2087 pre_load=["author", "date", "message", "parents"])
2088 else:
2088 else:
2089 cs_cache = EmptyCommit()
2089 cs_cache = EmptyCommit()
2090
2090
2091 if isinstance(cs_cache, BaseChangeset):
2091 if isinstance(cs_cache, BaseChangeset):
2092 cs_cache = cs_cache.__json__()
2092 cs_cache = cs_cache.__json__()
2093
2093
2094 def is_outdated(new_cs_cache):
2094 def is_outdated(new_cs_cache):
2095 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2095 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2096 new_cs_cache['revision'] != self.changeset_cache['revision']):
2096 new_cs_cache['revision'] != self.changeset_cache['revision']):
2097 return True
2097 return True
2098 return False
2098 return False
2099
2099
2100 # check if we have maybe already latest cached revision
2100 # check if we have maybe already latest cached revision
2101 if is_outdated(cs_cache) or not self.changeset_cache:
2101 if is_outdated(cs_cache) or not self.changeset_cache:
2102 _default = datetime.datetime.fromtimestamp(0)
2102 _default = datetime.datetime.fromtimestamp(0)
2103 last_change = cs_cache.get('date') or _default
2103 last_change = cs_cache.get('date') or _default
2104 log.debug('updated repo %s with new cs cache %s',
2104 log.debug('updated repo %s with new cs cache %s',
2105 self.repo_name, cs_cache)
2105 self.repo_name, cs_cache)
2106 self.updated_on = last_change
2106 self.updated_on = last_change
2107 self.changeset_cache = cs_cache
2107 self.changeset_cache = cs_cache
2108 Session().add(self)
2108 Session().add(self)
2109 Session().commit()
2109 Session().commit()
2110 else:
2110 else:
2111 log.debug('Skipping update_commit_cache for repo:`%s` '
2111 log.debug('Skipping update_commit_cache for repo:`%s` '
2112 'commit already with latest changes', self.repo_name)
2112 'commit already with latest changes', self.repo_name)
2113
2113
2114 @property
2114 @property
2115 def tip(self):
2115 def tip(self):
2116 return self.get_commit('tip')
2116 return self.get_commit('tip')
2117
2117
2118 @property
2118 @property
2119 def author(self):
2119 def author(self):
2120 return self.tip.author
2120 return self.tip.author
2121
2121
2122 @property
2122 @property
2123 def last_change(self):
2123 def last_change(self):
2124 return self.scm_instance().last_change
2124 return self.scm_instance().last_change
2125
2125
2126 def get_comments(self, revisions=None):
2126 def get_comments(self, revisions=None):
2127 """
2127 """
2128 Returns comments for this repository grouped by revisions
2128 Returns comments for this repository grouped by revisions
2129
2129
2130 :param revisions: filter query by revisions only
2130 :param revisions: filter query by revisions only
2131 """
2131 """
2132 cmts = ChangesetComment.query()\
2132 cmts = ChangesetComment.query()\
2133 .filter(ChangesetComment.repo == self)
2133 .filter(ChangesetComment.repo == self)
2134 if revisions:
2134 if revisions:
2135 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2135 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2136 grouped = collections.defaultdict(list)
2136 grouped = collections.defaultdict(list)
2137 for cmt in cmts.all():
2137 for cmt in cmts.all():
2138 grouped[cmt.revision].append(cmt)
2138 grouped[cmt.revision].append(cmt)
2139 return grouped
2139 return grouped
2140
2140
2141 def statuses(self, revisions=None):
2141 def statuses(self, revisions=None):
2142 """
2142 """
2143 Returns statuses for this repository
2143 Returns statuses for this repository
2144
2144
2145 :param revisions: list of revisions to get statuses for
2145 :param revisions: list of revisions to get statuses for
2146 """
2146 """
2147 statuses = ChangesetStatus.query()\
2147 statuses = ChangesetStatus.query()\
2148 .filter(ChangesetStatus.repo == self)\
2148 .filter(ChangesetStatus.repo == self)\
2149 .filter(ChangesetStatus.version == 0)
2149 .filter(ChangesetStatus.version == 0)
2150
2150
2151 if revisions:
2151 if revisions:
2152 # Try doing the filtering in chunks to avoid hitting limits
2152 # Try doing the filtering in chunks to avoid hitting limits
2153 size = 500
2153 size = 500
2154 status_results = []
2154 status_results = []
2155 for chunk in xrange(0, len(revisions), size):
2155 for chunk in xrange(0, len(revisions), size):
2156 status_results += statuses.filter(
2156 status_results += statuses.filter(
2157 ChangesetStatus.revision.in_(
2157 ChangesetStatus.revision.in_(
2158 revisions[chunk: chunk+size])
2158 revisions[chunk: chunk+size])
2159 ).all()
2159 ).all()
2160 else:
2160 else:
2161 status_results = statuses.all()
2161 status_results = statuses.all()
2162
2162
2163 grouped = {}
2163 grouped = {}
2164
2164
2165 # maybe we have open new pullrequest without a status?
2165 # maybe we have open new pullrequest without a status?
2166 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2166 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2167 status_lbl = ChangesetStatus.get_status_lbl(stat)
2167 status_lbl = ChangesetStatus.get_status_lbl(stat)
2168 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2168 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2169 for rev in pr.revisions:
2169 for rev in pr.revisions:
2170 pr_id = pr.pull_request_id
2170 pr_id = pr.pull_request_id
2171 pr_repo = pr.target_repo.repo_name
2171 pr_repo = pr.target_repo.repo_name
2172 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2172 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2173
2173
2174 for stat in status_results:
2174 for stat in status_results:
2175 pr_id = pr_repo = None
2175 pr_id = pr_repo = None
2176 if stat.pull_request:
2176 if stat.pull_request:
2177 pr_id = stat.pull_request.pull_request_id
2177 pr_id = stat.pull_request.pull_request_id
2178 pr_repo = stat.pull_request.target_repo.repo_name
2178 pr_repo = stat.pull_request.target_repo.repo_name
2179 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2179 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2180 pr_id, pr_repo]
2180 pr_id, pr_repo]
2181 return grouped
2181 return grouped
2182
2182
2183 # ==========================================================================
2183 # ==========================================================================
2184 # SCM CACHE INSTANCE
2184 # SCM CACHE INSTANCE
2185 # ==========================================================================
2185 # ==========================================================================
2186
2186
2187 def scm_instance(self, **kwargs):
2187 def scm_instance(self, **kwargs):
2188 import rhodecode
2188 import rhodecode
2189
2189
2190 # Passing a config will not hit the cache currently only used
2190 # Passing a config will not hit the cache currently only used
2191 # for repo2dbmapper
2191 # for repo2dbmapper
2192 config = kwargs.pop('config', None)
2192 config = kwargs.pop('config', None)
2193 cache = kwargs.pop('cache', None)
2193 cache = kwargs.pop('cache', None)
2194 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2194 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2195 # if cache is NOT defined use default global, else we have a full
2195 # if cache is NOT defined use default global, else we have a full
2196 # control over cache behaviour
2196 # control over cache behaviour
2197 if cache is None and full_cache and not config:
2197 if cache is None and full_cache and not config:
2198 return self._get_instance_cached()
2198 return self._get_instance_cached()
2199 return self._get_instance(cache=bool(cache), config=config)
2199 return self._get_instance(cache=bool(cache), config=config)
2200
2200
2201 def _get_instance_cached(self):
2201 def _get_instance_cached(self):
2202 @cache_region('long_term')
2202 @cache_region('long_term')
2203 def _get_repo(cache_key):
2203 def _get_repo(cache_key):
2204 return self._get_instance()
2204 return self._get_instance()
2205
2205
2206 invalidator_context = CacheKey.repo_context_cache(
2206 invalidator_context = CacheKey.repo_context_cache(
2207 _get_repo, self.repo_name, None, thread_scoped=True)
2207 _get_repo, self.repo_name, None, thread_scoped=True)
2208
2208
2209 with invalidator_context as context:
2209 with invalidator_context as context:
2210 context.invalidate()
2210 context.invalidate()
2211 repo = context.compute()
2211 repo = context.compute()
2212
2212
2213 return repo
2213 return repo
2214
2214
2215 def _get_instance(self, cache=True, config=None):
2215 def _get_instance(self, cache=True, config=None):
2216 config = config or self._config
2216 config = config or self._config
2217 custom_wire = {
2217 custom_wire = {
2218 'cache': cache # controls the vcs.remote cache
2218 'cache': cache # controls the vcs.remote cache
2219 }
2219 }
2220 repo = get_vcs_instance(
2220 repo = get_vcs_instance(
2221 repo_path=safe_str(self.repo_full_path),
2221 repo_path=safe_str(self.repo_full_path),
2222 config=config,
2222 config=config,
2223 with_wire=custom_wire,
2223 with_wire=custom_wire,
2224 create=False,
2224 create=False,
2225 _vcs_alias=self.repo_type)
2225 _vcs_alias=self.repo_type)
2226
2226
2227 return repo
2227 return repo
2228
2228
2229 def __json__(self):
2229 def __json__(self):
2230 return {'landing_rev': self.landing_rev}
2230 return {'landing_rev': self.landing_rev}
2231
2231
2232 def get_dict(self):
2232 def get_dict(self):
2233
2233
2234 # Since we transformed `repo_name` to a hybrid property, we need to
2234 # Since we transformed `repo_name` to a hybrid property, we need to
2235 # keep compatibility with the code which uses `repo_name` field.
2235 # keep compatibility with the code which uses `repo_name` field.
2236
2236
2237 result = super(Repository, self).get_dict()
2237 result = super(Repository, self).get_dict()
2238 result['repo_name'] = result.pop('_repo_name', None)
2238 result['repo_name'] = result.pop('_repo_name', None)
2239 return result
2239 return result
2240
2240
2241
2241
2242 class RepoGroup(Base, BaseModel):
2242 class RepoGroup(Base, BaseModel):
2243 __tablename__ = 'groups'
2243 __tablename__ = 'groups'
2244 __table_args__ = (
2244 __table_args__ = (
2245 UniqueConstraint('group_name', 'group_parent_id'),
2245 UniqueConstraint('group_name', 'group_parent_id'),
2246 CheckConstraint('group_id != group_parent_id'),
2246 CheckConstraint('group_id != group_parent_id'),
2247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2248 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2248 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2249 )
2249 )
2250 __mapper_args__ = {'order_by': 'group_name'}
2250 __mapper_args__ = {'order_by': 'group_name'}
2251
2251
2252 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2252 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2253
2253
2254 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2254 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2255 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2255 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2256 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2256 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2257 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2257 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2258 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2258 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2260 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2261 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2261 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2262 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2262 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2263
2263
2264 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2264 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2265 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2265 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2266 parent_group = relationship('RepoGroup', remote_side=group_id)
2266 parent_group = relationship('RepoGroup', remote_side=group_id)
2267 user = relationship('User')
2267 user = relationship('User')
2268 integrations = relationship('Integration',
2268 integrations = relationship('Integration',
2269 cascade="all, delete, delete-orphan")
2269 cascade="all, delete, delete-orphan")
2270
2270
2271 def __init__(self, group_name='', parent_group=None):
2271 def __init__(self, group_name='', parent_group=None):
2272 self.group_name = group_name
2272 self.group_name = group_name
2273 self.parent_group = parent_group
2273 self.parent_group = parent_group
2274
2274
2275 def __unicode__(self):
2275 def __unicode__(self):
2276 return u"<%s('id:%s:%s')>" % (
2276 return u"<%s('id:%s:%s')>" % (
2277 self.__class__.__name__, self.group_id, self.group_name)
2277 self.__class__.__name__, self.group_id, self.group_name)
2278
2278
2279 @hybrid_property
2279 @hybrid_property
2280 def description_safe(self):
2280 def description_safe(self):
2281 from rhodecode.lib import helpers as h
2281 from rhodecode.lib import helpers as h
2282 return h.escape(self.group_description)
2282 return h.escape(self.group_description)
2283
2283
2284 @classmethod
2284 @classmethod
2285 def _generate_choice(cls, repo_group):
2285 def _generate_choice(cls, repo_group):
2286 from webhelpers.html import literal as _literal
2286 from webhelpers.html import literal as _literal
2287 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2287 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2288 return repo_group.group_id, _name(repo_group.full_path_splitted)
2288 return repo_group.group_id, _name(repo_group.full_path_splitted)
2289
2289
2290 @classmethod
2290 @classmethod
2291 def groups_choices(cls, groups=None, show_empty_group=True):
2291 def groups_choices(cls, groups=None, show_empty_group=True):
2292 if not groups:
2292 if not groups:
2293 groups = cls.query().all()
2293 groups = cls.query().all()
2294
2294
2295 repo_groups = []
2295 repo_groups = []
2296 if show_empty_group:
2296 if show_empty_group:
2297 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2297 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2298
2298
2299 repo_groups.extend([cls._generate_choice(x) for x in groups])
2299 repo_groups.extend([cls._generate_choice(x) for x in groups])
2300
2300
2301 repo_groups = sorted(
2301 repo_groups = sorted(
2302 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2302 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2303 return repo_groups
2303 return repo_groups
2304
2304
2305 @classmethod
2305 @classmethod
2306 def url_sep(cls):
2306 def url_sep(cls):
2307 return URL_SEP
2307 return URL_SEP
2308
2308
2309 @classmethod
2309 @classmethod
2310 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2310 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2311 if case_insensitive:
2311 if case_insensitive:
2312 gr = cls.query().filter(func.lower(cls.group_name)
2312 gr = cls.query().filter(func.lower(cls.group_name)
2313 == func.lower(group_name))
2313 == func.lower(group_name))
2314 else:
2314 else:
2315 gr = cls.query().filter(cls.group_name == group_name)
2315 gr = cls.query().filter(cls.group_name == group_name)
2316 if cache:
2316 if cache:
2317 name_key = _hash_key(group_name)
2317 name_key = _hash_key(group_name)
2318 gr = gr.options(
2318 gr = gr.options(
2319 FromCache("sql_cache_short", "get_group_%s" % name_key))
2319 FromCache("sql_cache_short", "get_group_%s" % name_key))
2320 return gr.scalar()
2320 return gr.scalar()
2321
2321
2322 @classmethod
2322 @classmethod
2323 def get_user_personal_repo_group(cls, user_id):
2323 def get_user_personal_repo_group(cls, user_id):
2324 user = User.get(user_id)
2324 user = User.get(user_id)
2325 if user.username == User.DEFAULT_USER:
2325 if user.username == User.DEFAULT_USER:
2326 return None
2326 return None
2327
2327
2328 return cls.query()\
2328 return cls.query()\
2329 .filter(cls.personal == true()) \
2329 .filter(cls.personal == true()) \
2330 .filter(cls.user == user).scalar()
2330 .filter(cls.user == user).scalar()
2331
2331
2332 @classmethod
2332 @classmethod
2333 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2333 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2334 case_insensitive=True):
2334 case_insensitive=True):
2335 q = RepoGroup.query()
2335 q = RepoGroup.query()
2336
2336
2337 if not isinstance(user_id, Optional):
2337 if not isinstance(user_id, Optional):
2338 q = q.filter(RepoGroup.user_id == user_id)
2338 q = q.filter(RepoGroup.user_id == user_id)
2339
2339
2340 if not isinstance(group_id, Optional):
2340 if not isinstance(group_id, Optional):
2341 q = q.filter(RepoGroup.group_parent_id == group_id)
2341 q = q.filter(RepoGroup.group_parent_id == group_id)
2342
2342
2343 if case_insensitive:
2343 if case_insensitive:
2344 q = q.order_by(func.lower(RepoGroup.group_name))
2344 q = q.order_by(func.lower(RepoGroup.group_name))
2345 else:
2345 else:
2346 q = q.order_by(RepoGroup.group_name)
2346 q = q.order_by(RepoGroup.group_name)
2347 return q.all()
2347 return q.all()
2348
2348
2349 @property
2349 @property
2350 def parents(self):
2350 def parents(self):
2351 parents_recursion_limit = 10
2351 parents_recursion_limit = 10
2352 groups = []
2352 groups = []
2353 if self.parent_group is None:
2353 if self.parent_group is None:
2354 return groups
2354 return groups
2355 cur_gr = self.parent_group
2355 cur_gr = self.parent_group
2356 groups.insert(0, cur_gr)
2356 groups.insert(0, cur_gr)
2357 cnt = 0
2357 cnt = 0
2358 while 1:
2358 while 1:
2359 cnt += 1
2359 cnt += 1
2360 gr = getattr(cur_gr, 'parent_group', None)
2360 gr = getattr(cur_gr, 'parent_group', None)
2361 cur_gr = cur_gr.parent_group
2361 cur_gr = cur_gr.parent_group
2362 if gr is None:
2362 if gr is None:
2363 break
2363 break
2364 if cnt == parents_recursion_limit:
2364 if cnt == parents_recursion_limit:
2365 # this will prevent accidental infinit loops
2365 # this will prevent accidental infinit loops
2366 log.error(('more than %s parents found for group %s, stopping '
2366 log.error(('more than %s parents found for group %s, stopping '
2367 'recursive parent fetching' % (parents_recursion_limit, self)))
2367 'recursive parent fetching' % (parents_recursion_limit, self)))
2368 break
2368 break
2369
2369
2370 groups.insert(0, gr)
2370 groups.insert(0, gr)
2371 return groups
2371 return groups
2372
2372
2373 @property
2373 @property
2374 def last_db_change(self):
2374 def last_db_change(self):
2375 return self.updated_on
2375 return self.updated_on
2376
2376
2377 @property
2377 @property
2378 def children(self):
2378 def children(self):
2379 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2379 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2380
2380
2381 @property
2381 @property
2382 def name(self):
2382 def name(self):
2383 return self.group_name.split(RepoGroup.url_sep())[-1]
2383 return self.group_name.split(RepoGroup.url_sep())[-1]
2384
2384
2385 @property
2385 @property
2386 def full_path(self):
2386 def full_path(self):
2387 return self.group_name
2387 return self.group_name
2388
2388
2389 @property
2389 @property
2390 def full_path_splitted(self):
2390 def full_path_splitted(self):
2391 return self.group_name.split(RepoGroup.url_sep())
2391 return self.group_name.split(RepoGroup.url_sep())
2392
2392
2393 @property
2393 @property
2394 def repositories(self):
2394 def repositories(self):
2395 return Repository.query()\
2395 return Repository.query()\
2396 .filter(Repository.group == self)\
2396 .filter(Repository.group == self)\
2397 .order_by(Repository.repo_name)
2397 .order_by(Repository.repo_name)
2398
2398
2399 @property
2399 @property
2400 def repositories_recursive_count(self):
2400 def repositories_recursive_count(self):
2401 cnt = self.repositories.count()
2401 cnt = self.repositories.count()
2402
2402
2403 def children_count(group):
2403 def children_count(group):
2404 cnt = 0
2404 cnt = 0
2405 for child in group.children:
2405 for child in group.children:
2406 cnt += child.repositories.count()
2406 cnt += child.repositories.count()
2407 cnt += children_count(child)
2407 cnt += children_count(child)
2408 return cnt
2408 return cnt
2409
2409
2410 return cnt + children_count(self)
2410 return cnt + children_count(self)
2411
2411
2412 def _recursive_objects(self, include_repos=True):
2412 def _recursive_objects(self, include_repos=True):
2413 all_ = []
2413 all_ = []
2414
2414
2415 def _get_members(root_gr):
2415 def _get_members(root_gr):
2416 if include_repos:
2416 if include_repos:
2417 for r in root_gr.repositories:
2417 for r in root_gr.repositories:
2418 all_.append(r)
2418 all_.append(r)
2419 childs = root_gr.children.all()
2419 childs = root_gr.children.all()
2420 if childs:
2420 if childs:
2421 for gr in childs:
2421 for gr in childs:
2422 all_.append(gr)
2422 all_.append(gr)
2423 _get_members(gr)
2423 _get_members(gr)
2424
2424
2425 _get_members(self)
2425 _get_members(self)
2426 return [self] + all_
2426 return [self] + all_
2427
2427
2428 def recursive_groups_and_repos(self):
2428 def recursive_groups_and_repos(self):
2429 """
2429 """
2430 Recursive return all groups, with repositories in those groups
2430 Recursive return all groups, with repositories in those groups
2431 """
2431 """
2432 return self._recursive_objects()
2432 return self._recursive_objects()
2433
2433
2434 def recursive_groups(self):
2434 def recursive_groups(self):
2435 """
2435 """
2436 Returns all children groups for this group including children of children
2436 Returns all children groups for this group including children of children
2437 """
2437 """
2438 return self._recursive_objects(include_repos=False)
2438 return self._recursive_objects(include_repos=False)
2439
2439
2440 def get_new_name(self, group_name):
2440 def get_new_name(self, group_name):
2441 """
2441 """
2442 returns new full group name based on parent and new name
2442 returns new full group name based on parent and new name
2443
2443
2444 :param group_name:
2444 :param group_name:
2445 """
2445 """
2446 path_prefix = (self.parent_group.full_path_splitted if
2446 path_prefix = (self.parent_group.full_path_splitted if
2447 self.parent_group else [])
2447 self.parent_group else [])
2448 return RepoGroup.url_sep().join(path_prefix + [group_name])
2448 return RepoGroup.url_sep().join(path_prefix + [group_name])
2449
2449
2450 def permissions(self, with_admins=True, with_owner=True):
2450 def permissions(self, with_admins=True, with_owner=True):
2451 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2451 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2452 q = q.options(joinedload(UserRepoGroupToPerm.group),
2452 q = q.options(joinedload(UserRepoGroupToPerm.group),
2453 joinedload(UserRepoGroupToPerm.user),
2453 joinedload(UserRepoGroupToPerm.user),
2454 joinedload(UserRepoGroupToPerm.permission),)
2454 joinedload(UserRepoGroupToPerm.permission),)
2455
2455
2456 # get owners and admins and permissions. We do a trick of re-writing
2456 # get owners and admins and permissions. We do a trick of re-writing
2457 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2457 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2458 # has a global reference and changing one object propagates to all
2458 # has a global reference and changing one object propagates to all
2459 # others. This means if admin is also an owner admin_row that change
2459 # others. This means if admin is also an owner admin_row that change
2460 # would propagate to both objects
2460 # would propagate to both objects
2461 perm_rows = []
2461 perm_rows = []
2462 for _usr in q.all():
2462 for _usr in q.all():
2463 usr = AttributeDict(_usr.user.get_dict())
2463 usr = AttributeDict(_usr.user.get_dict())
2464 usr.permission = _usr.permission.permission_name
2464 usr.permission = _usr.permission.permission_name
2465 perm_rows.append(usr)
2465 perm_rows.append(usr)
2466
2466
2467 # filter the perm rows by 'default' first and then sort them by
2467 # filter the perm rows by 'default' first and then sort them by
2468 # admin,write,read,none permissions sorted again alphabetically in
2468 # admin,write,read,none permissions sorted again alphabetically in
2469 # each group
2469 # each group
2470 perm_rows = sorted(perm_rows, key=display_sort)
2470 perm_rows = sorted(perm_rows, key=display_sort)
2471
2471
2472 _admin_perm = 'group.admin'
2472 _admin_perm = 'group.admin'
2473 owner_row = []
2473 owner_row = []
2474 if with_owner:
2474 if with_owner:
2475 usr = AttributeDict(self.user.get_dict())
2475 usr = AttributeDict(self.user.get_dict())
2476 usr.owner_row = True
2476 usr.owner_row = True
2477 usr.permission = _admin_perm
2477 usr.permission = _admin_perm
2478 owner_row.append(usr)
2478 owner_row.append(usr)
2479
2479
2480 super_admin_rows = []
2480 super_admin_rows = []
2481 if with_admins:
2481 if with_admins:
2482 for usr in User.get_all_super_admins():
2482 for usr in User.get_all_super_admins():
2483 # if this admin is also owner, don't double the record
2483 # if this admin is also owner, don't double the record
2484 if usr.user_id == owner_row[0].user_id:
2484 if usr.user_id == owner_row[0].user_id:
2485 owner_row[0].admin_row = True
2485 owner_row[0].admin_row = True
2486 else:
2486 else:
2487 usr = AttributeDict(usr.get_dict())
2487 usr = AttributeDict(usr.get_dict())
2488 usr.admin_row = True
2488 usr.admin_row = True
2489 usr.permission = _admin_perm
2489 usr.permission = _admin_perm
2490 super_admin_rows.append(usr)
2490 super_admin_rows.append(usr)
2491
2491
2492 return super_admin_rows + owner_row + perm_rows
2492 return super_admin_rows + owner_row + perm_rows
2493
2493
2494 def permission_user_groups(self):
2494 def permission_user_groups(self):
2495 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2495 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2496 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2496 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2497 joinedload(UserGroupRepoGroupToPerm.users_group),
2497 joinedload(UserGroupRepoGroupToPerm.users_group),
2498 joinedload(UserGroupRepoGroupToPerm.permission),)
2498 joinedload(UserGroupRepoGroupToPerm.permission),)
2499
2499
2500 perm_rows = []
2500 perm_rows = []
2501 for _user_group in q.all():
2501 for _user_group in q.all():
2502 usr = AttributeDict(_user_group.users_group.get_dict())
2502 usr = AttributeDict(_user_group.users_group.get_dict())
2503 usr.permission = _user_group.permission.permission_name
2503 usr.permission = _user_group.permission.permission_name
2504 perm_rows.append(usr)
2504 perm_rows.append(usr)
2505
2505
2506 return perm_rows
2506 return perm_rows
2507
2507
2508 def get_api_data(self):
2508 def get_api_data(self):
2509 """
2509 """
2510 Common function for generating api data
2510 Common function for generating api data
2511
2511
2512 """
2512 """
2513 group = self
2513 group = self
2514 data = {
2514 data = {
2515 'group_id': group.group_id,
2515 'group_id': group.group_id,
2516 'group_name': group.group_name,
2516 'group_name': group.group_name,
2517 'group_description': group.description_safe,
2517 'group_description': group.description_safe,
2518 'parent_group': group.parent_group.group_name if group.parent_group else None,
2518 'parent_group': group.parent_group.group_name if group.parent_group else None,
2519 'repositories': [x.repo_name for x in group.repositories],
2519 'repositories': [x.repo_name for x in group.repositories],
2520 'owner': group.user.username,
2520 'owner': group.user.username,
2521 }
2521 }
2522 return data
2522 return data
2523
2523
2524
2524
2525 class Permission(Base, BaseModel):
2525 class Permission(Base, BaseModel):
2526 __tablename__ = 'permissions'
2526 __tablename__ = 'permissions'
2527 __table_args__ = (
2527 __table_args__ = (
2528 Index('p_perm_name_idx', 'permission_name'),
2528 Index('p_perm_name_idx', 'permission_name'),
2529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2530 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2530 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2531 )
2531 )
2532 PERMS = [
2532 PERMS = [
2533 ('hg.admin', _('RhodeCode Super Administrator')),
2533 ('hg.admin', _('RhodeCode Super Administrator')),
2534
2534
2535 ('repository.none', _('Repository no access')),
2535 ('repository.none', _('Repository no access')),
2536 ('repository.read', _('Repository read access')),
2536 ('repository.read', _('Repository read access')),
2537 ('repository.write', _('Repository write access')),
2537 ('repository.write', _('Repository write access')),
2538 ('repository.admin', _('Repository admin access')),
2538 ('repository.admin', _('Repository admin access')),
2539
2539
2540 ('group.none', _('Repository group no access')),
2540 ('group.none', _('Repository group no access')),
2541 ('group.read', _('Repository group read access')),
2541 ('group.read', _('Repository group read access')),
2542 ('group.write', _('Repository group write access')),
2542 ('group.write', _('Repository group write access')),
2543 ('group.admin', _('Repository group admin access')),
2543 ('group.admin', _('Repository group admin access')),
2544
2544
2545 ('usergroup.none', _('User group no access')),
2545 ('usergroup.none', _('User group no access')),
2546 ('usergroup.read', _('User group read access')),
2546 ('usergroup.read', _('User group read access')),
2547 ('usergroup.write', _('User group write access')),
2547 ('usergroup.write', _('User group write access')),
2548 ('usergroup.admin', _('User group admin access')),
2548 ('usergroup.admin', _('User group admin access')),
2549
2549
2550 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2550 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2551 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2551 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2552
2552
2553 ('hg.usergroup.create.false', _('User Group creation disabled')),
2553 ('hg.usergroup.create.false', _('User Group creation disabled')),
2554 ('hg.usergroup.create.true', _('User Group creation enabled')),
2554 ('hg.usergroup.create.true', _('User Group creation enabled')),
2555
2555
2556 ('hg.create.none', _('Repository creation disabled')),
2556 ('hg.create.none', _('Repository creation disabled')),
2557 ('hg.create.repository', _('Repository creation enabled')),
2557 ('hg.create.repository', _('Repository creation enabled')),
2558 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2558 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2559 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2559 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2560
2560
2561 ('hg.fork.none', _('Repository forking disabled')),
2561 ('hg.fork.none', _('Repository forking disabled')),
2562 ('hg.fork.repository', _('Repository forking enabled')),
2562 ('hg.fork.repository', _('Repository forking enabled')),
2563
2563
2564 ('hg.register.none', _('Registration disabled')),
2564 ('hg.register.none', _('Registration disabled')),
2565 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2565 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2566 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2566 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2567
2567
2568 ('hg.password_reset.enabled', _('Password reset enabled')),
2568 ('hg.password_reset.enabled', _('Password reset enabled')),
2569 ('hg.password_reset.hidden', _('Password reset hidden')),
2569 ('hg.password_reset.hidden', _('Password reset hidden')),
2570 ('hg.password_reset.disabled', _('Password reset disabled')),
2570 ('hg.password_reset.disabled', _('Password reset disabled')),
2571
2571
2572 ('hg.extern_activate.manual', _('Manual activation of external account')),
2572 ('hg.extern_activate.manual', _('Manual activation of external account')),
2573 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2573 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2574
2574
2575 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2575 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2576 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2576 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2577 ]
2577 ]
2578
2578
2579 # definition of system default permissions for DEFAULT user
2579 # definition of system default permissions for DEFAULT user
2580 DEFAULT_USER_PERMISSIONS = [
2580 DEFAULT_USER_PERMISSIONS = [
2581 'repository.read',
2581 'repository.read',
2582 'group.read',
2582 'group.read',
2583 'usergroup.read',
2583 'usergroup.read',
2584 'hg.create.repository',
2584 'hg.create.repository',
2585 'hg.repogroup.create.false',
2585 'hg.repogroup.create.false',
2586 'hg.usergroup.create.false',
2586 'hg.usergroup.create.false',
2587 'hg.create.write_on_repogroup.true',
2587 'hg.create.write_on_repogroup.true',
2588 'hg.fork.repository',
2588 'hg.fork.repository',
2589 'hg.register.manual_activate',
2589 'hg.register.manual_activate',
2590 'hg.password_reset.enabled',
2590 'hg.password_reset.enabled',
2591 'hg.extern_activate.auto',
2591 'hg.extern_activate.auto',
2592 'hg.inherit_default_perms.true',
2592 'hg.inherit_default_perms.true',
2593 ]
2593 ]
2594
2594
2595 # defines which permissions are more important higher the more important
2595 # defines which permissions are more important higher the more important
2596 # Weight defines which permissions are more important.
2596 # Weight defines which permissions are more important.
2597 # The higher number the more important.
2597 # The higher number the more important.
2598 PERM_WEIGHTS = {
2598 PERM_WEIGHTS = {
2599 'repository.none': 0,
2599 'repository.none': 0,
2600 'repository.read': 1,
2600 'repository.read': 1,
2601 'repository.write': 3,
2601 'repository.write': 3,
2602 'repository.admin': 4,
2602 'repository.admin': 4,
2603
2603
2604 'group.none': 0,
2604 'group.none': 0,
2605 'group.read': 1,
2605 'group.read': 1,
2606 'group.write': 3,
2606 'group.write': 3,
2607 'group.admin': 4,
2607 'group.admin': 4,
2608
2608
2609 'usergroup.none': 0,
2609 'usergroup.none': 0,
2610 'usergroup.read': 1,
2610 'usergroup.read': 1,
2611 'usergroup.write': 3,
2611 'usergroup.write': 3,
2612 'usergroup.admin': 4,
2612 'usergroup.admin': 4,
2613
2613
2614 'hg.repogroup.create.false': 0,
2614 'hg.repogroup.create.false': 0,
2615 'hg.repogroup.create.true': 1,
2615 'hg.repogroup.create.true': 1,
2616
2616
2617 'hg.usergroup.create.false': 0,
2617 'hg.usergroup.create.false': 0,
2618 'hg.usergroup.create.true': 1,
2618 'hg.usergroup.create.true': 1,
2619
2619
2620 'hg.fork.none': 0,
2620 'hg.fork.none': 0,
2621 'hg.fork.repository': 1,
2621 'hg.fork.repository': 1,
2622 'hg.create.none': 0,
2622 'hg.create.none': 0,
2623 'hg.create.repository': 1
2623 'hg.create.repository': 1
2624 }
2624 }
2625
2625
2626 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2626 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2627 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2627 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2628 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2628 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2629
2629
2630 def __unicode__(self):
2630 def __unicode__(self):
2631 return u"<%s('%s:%s')>" % (
2631 return u"<%s('%s:%s')>" % (
2632 self.__class__.__name__, self.permission_id, self.permission_name
2632 self.__class__.__name__, self.permission_id, self.permission_name
2633 )
2633 )
2634
2634
2635 @classmethod
2635 @classmethod
2636 def get_by_key(cls, key):
2636 def get_by_key(cls, key):
2637 return cls.query().filter(cls.permission_name == key).scalar()
2637 return cls.query().filter(cls.permission_name == key).scalar()
2638
2638
2639 @classmethod
2639 @classmethod
2640 def get_default_repo_perms(cls, user_id, repo_id=None):
2640 def get_default_repo_perms(cls, user_id, repo_id=None):
2641 q = Session().query(UserRepoToPerm, Repository, Permission)\
2641 q = Session().query(UserRepoToPerm, Repository, Permission)\
2642 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2642 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2643 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2643 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2644 .filter(UserRepoToPerm.user_id == user_id)
2644 .filter(UserRepoToPerm.user_id == user_id)
2645 if repo_id:
2645 if repo_id:
2646 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2646 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2647 return q.all()
2647 return q.all()
2648
2648
2649 @classmethod
2649 @classmethod
2650 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2650 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2651 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2651 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2652 .join(
2652 .join(
2653 Permission,
2653 Permission,
2654 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2654 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2655 .join(
2655 .join(
2656 Repository,
2656 Repository,
2657 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2657 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2658 .join(
2658 .join(
2659 UserGroup,
2659 UserGroup,
2660 UserGroupRepoToPerm.users_group_id ==
2660 UserGroupRepoToPerm.users_group_id ==
2661 UserGroup.users_group_id)\
2661 UserGroup.users_group_id)\
2662 .join(
2662 .join(
2663 UserGroupMember,
2663 UserGroupMember,
2664 UserGroupRepoToPerm.users_group_id ==
2664 UserGroupRepoToPerm.users_group_id ==
2665 UserGroupMember.users_group_id)\
2665 UserGroupMember.users_group_id)\
2666 .filter(
2666 .filter(
2667 UserGroupMember.user_id == user_id,
2667 UserGroupMember.user_id == user_id,
2668 UserGroup.users_group_active == true())
2668 UserGroup.users_group_active == true())
2669 if repo_id:
2669 if repo_id:
2670 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2670 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2671 return q.all()
2671 return q.all()
2672
2672
2673 @classmethod
2673 @classmethod
2674 def get_default_group_perms(cls, user_id, repo_group_id=None):
2674 def get_default_group_perms(cls, user_id, repo_group_id=None):
2675 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2675 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2676 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2676 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2677 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2677 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2678 .filter(UserRepoGroupToPerm.user_id == user_id)
2678 .filter(UserRepoGroupToPerm.user_id == user_id)
2679 if repo_group_id:
2679 if repo_group_id:
2680 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2680 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2681 return q.all()
2681 return q.all()
2682
2682
2683 @classmethod
2683 @classmethod
2684 def get_default_group_perms_from_user_group(
2684 def get_default_group_perms_from_user_group(
2685 cls, user_id, repo_group_id=None):
2685 cls, user_id, repo_group_id=None):
2686 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2686 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2687 .join(
2687 .join(
2688 Permission,
2688 Permission,
2689 UserGroupRepoGroupToPerm.permission_id ==
2689 UserGroupRepoGroupToPerm.permission_id ==
2690 Permission.permission_id)\
2690 Permission.permission_id)\
2691 .join(
2691 .join(
2692 RepoGroup,
2692 RepoGroup,
2693 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2693 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2694 .join(
2694 .join(
2695 UserGroup,
2695 UserGroup,
2696 UserGroupRepoGroupToPerm.users_group_id ==
2696 UserGroupRepoGroupToPerm.users_group_id ==
2697 UserGroup.users_group_id)\
2697 UserGroup.users_group_id)\
2698 .join(
2698 .join(
2699 UserGroupMember,
2699 UserGroupMember,
2700 UserGroupRepoGroupToPerm.users_group_id ==
2700 UserGroupRepoGroupToPerm.users_group_id ==
2701 UserGroupMember.users_group_id)\
2701 UserGroupMember.users_group_id)\
2702 .filter(
2702 .filter(
2703 UserGroupMember.user_id == user_id,
2703 UserGroupMember.user_id == user_id,
2704 UserGroup.users_group_active == true())
2704 UserGroup.users_group_active == true())
2705 if repo_group_id:
2705 if repo_group_id:
2706 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2706 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2707 return q.all()
2707 return q.all()
2708
2708
2709 @classmethod
2709 @classmethod
2710 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2710 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2711 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2711 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2712 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2712 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2713 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2713 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2714 .filter(UserUserGroupToPerm.user_id == user_id)
2714 .filter(UserUserGroupToPerm.user_id == user_id)
2715 if user_group_id:
2715 if user_group_id:
2716 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2716 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2717 return q.all()
2717 return q.all()
2718
2718
2719 @classmethod
2719 @classmethod
2720 def get_default_user_group_perms_from_user_group(
2720 def get_default_user_group_perms_from_user_group(
2721 cls, user_id, user_group_id=None):
2721 cls, user_id, user_group_id=None):
2722 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2722 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2723 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2723 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2724 .join(
2724 .join(
2725 Permission,
2725 Permission,
2726 UserGroupUserGroupToPerm.permission_id ==
2726 UserGroupUserGroupToPerm.permission_id ==
2727 Permission.permission_id)\
2727 Permission.permission_id)\
2728 .join(
2728 .join(
2729 TargetUserGroup,
2729 TargetUserGroup,
2730 UserGroupUserGroupToPerm.target_user_group_id ==
2730 UserGroupUserGroupToPerm.target_user_group_id ==
2731 TargetUserGroup.users_group_id)\
2731 TargetUserGroup.users_group_id)\
2732 .join(
2732 .join(
2733 UserGroup,
2733 UserGroup,
2734 UserGroupUserGroupToPerm.user_group_id ==
2734 UserGroupUserGroupToPerm.user_group_id ==
2735 UserGroup.users_group_id)\
2735 UserGroup.users_group_id)\
2736 .join(
2736 .join(
2737 UserGroupMember,
2737 UserGroupMember,
2738 UserGroupUserGroupToPerm.user_group_id ==
2738 UserGroupUserGroupToPerm.user_group_id ==
2739 UserGroupMember.users_group_id)\
2739 UserGroupMember.users_group_id)\
2740 .filter(
2740 .filter(
2741 UserGroupMember.user_id == user_id,
2741 UserGroupMember.user_id == user_id,
2742 UserGroup.users_group_active == true())
2742 UserGroup.users_group_active == true())
2743 if user_group_id:
2743 if user_group_id:
2744 q = q.filter(
2744 q = q.filter(
2745 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2745 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2746
2746
2747 return q.all()
2747 return q.all()
2748
2748
2749
2749
2750 class UserRepoToPerm(Base, BaseModel):
2750 class UserRepoToPerm(Base, BaseModel):
2751 __tablename__ = 'repo_to_perm'
2751 __tablename__ = 'repo_to_perm'
2752 __table_args__ = (
2752 __table_args__ = (
2753 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2753 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2755 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2755 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2756 )
2756 )
2757 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2757 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2758 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2758 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2759 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2759 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2760 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2760 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2761
2761
2762 user = relationship('User')
2762 user = relationship('User')
2763 repository = relationship('Repository')
2763 repository = relationship('Repository')
2764 permission = relationship('Permission')
2764 permission = relationship('Permission')
2765
2765
2766 @classmethod
2766 @classmethod
2767 def create(cls, user, repository, permission):
2767 def create(cls, user, repository, permission):
2768 n = cls()
2768 n = cls()
2769 n.user = user
2769 n.user = user
2770 n.repository = repository
2770 n.repository = repository
2771 n.permission = permission
2771 n.permission = permission
2772 Session().add(n)
2772 Session().add(n)
2773 return n
2773 return n
2774
2774
2775 def __unicode__(self):
2775 def __unicode__(self):
2776 return u'<%s => %s >' % (self.user, self.repository)
2776 return u'<%s => %s >' % (self.user, self.repository)
2777
2777
2778
2778
2779 class UserUserGroupToPerm(Base, BaseModel):
2779 class UserUserGroupToPerm(Base, BaseModel):
2780 __tablename__ = 'user_user_group_to_perm'
2780 __tablename__ = 'user_user_group_to_perm'
2781 __table_args__ = (
2781 __table_args__ = (
2782 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2782 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2784 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2784 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2785 )
2785 )
2786 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2786 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2787 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2787 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2788 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2788 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2789 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2789 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2790
2790
2791 user = relationship('User')
2791 user = relationship('User')
2792 user_group = relationship('UserGroup')
2792 user_group = relationship('UserGroup')
2793 permission = relationship('Permission')
2793 permission = relationship('Permission')
2794
2794
2795 @classmethod
2795 @classmethod
2796 def create(cls, user, user_group, permission):
2796 def create(cls, user, user_group, permission):
2797 n = cls()
2797 n = cls()
2798 n.user = user
2798 n.user = user
2799 n.user_group = user_group
2799 n.user_group = user_group
2800 n.permission = permission
2800 n.permission = permission
2801 Session().add(n)
2801 Session().add(n)
2802 return n
2802 return n
2803
2803
2804 def __unicode__(self):
2804 def __unicode__(self):
2805 return u'<%s => %s >' % (self.user, self.user_group)
2805 return u'<%s => %s >' % (self.user, self.user_group)
2806
2806
2807
2807
2808 class UserToPerm(Base, BaseModel):
2808 class UserToPerm(Base, BaseModel):
2809 __tablename__ = 'user_to_perm'
2809 __tablename__ = 'user_to_perm'
2810 __table_args__ = (
2810 __table_args__ = (
2811 UniqueConstraint('user_id', 'permission_id'),
2811 UniqueConstraint('user_id', 'permission_id'),
2812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2814 )
2814 )
2815 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2815 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2816 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2816 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2817 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2817 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2818
2818
2819 user = relationship('User')
2819 user = relationship('User')
2820 permission = relationship('Permission', lazy='joined')
2820 permission = relationship('Permission', lazy='joined')
2821
2821
2822 def __unicode__(self):
2822 def __unicode__(self):
2823 return u'<%s => %s >' % (self.user, self.permission)
2823 return u'<%s => %s >' % (self.user, self.permission)
2824
2824
2825
2825
2826 class UserGroupRepoToPerm(Base, BaseModel):
2826 class UserGroupRepoToPerm(Base, BaseModel):
2827 __tablename__ = 'users_group_repo_to_perm'
2827 __tablename__ = 'users_group_repo_to_perm'
2828 __table_args__ = (
2828 __table_args__ = (
2829 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2829 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2830 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2830 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2831 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2831 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2832 )
2832 )
2833 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2833 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2834 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2834 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2835 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2835 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2836 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2836 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2837
2837
2838 users_group = relationship('UserGroup')
2838 users_group = relationship('UserGroup')
2839 permission = relationship('Permission')
2839 permission = relationship('Permission')
2840 repository = relationship('Repository')
2840 repository = relationship('Repository')
2841
2841
2842 @classmethod
2842 @classmethod
2843 def create(cls, users_group, repository, permission):
2843 def create(cls, users_group, repository, permission):
2844 n = cls()
2844 n = cls()
2845 n.users_group = users_group
2845 n.users_group = users_group
2846 n.repository = repository
2846 n.repository = repository
2847 n.permission = permission
2847 n.permission = permission
2848 Session().add(n)
2848 Session().add(n)
2849 return n
2849 return n
2850
2850
2851 def __unicode__(self):
2851 def __unicode__(self):
2852 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2852 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2853
2853
2854
2854
2855 class UserGroupUserGroupToPerm(Base, BaseModel):
2855 class UserGroupUserGroupToPerm(Base, BaseModel):
2856 __tablename__ = 'user_group_user_group_to_perm'
2856 __tablename__ = 'user_group_user_group_to_perm'
2857 __table_args__ = (
2857 __table_args__ = (
2858 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2858 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2859 CheckConstraint('target_user_group_id != user_group_id'),
2859 CheckConstraint('target_user_group_id != user_group_id'),
2860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2860 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2861 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2862 )
2862 )
2863 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)
2863 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)
2864 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2864 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2865 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2865 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2866 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2866 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2867
2867
2868 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2868 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2869 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2869 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2870 permission = relationship('Permission')
2870 permission = relationship('Permission')
2871
2871
2872 @classmethod
2872 @classmethod
2873 def create(cls, target_user_group, user_group, permission):
2873 def create(cls, target_user_group, user_group, permission):
2874 n = cls()
2874 n = cls()
2875 n.target_user_group = target_user_group
2875 n.target_user_group = target_user_group
2876 n.user_group = user_group
2876 n.user_group = user_group
2877 n.permission = permission
2877 n.permission = permission
2878 Session().add(n)
2878 Session().add(n)
2879 return n
2879 return n
2880
2880
2881 def __unicode__(self):
2881 def __unicode__(self):
2882 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2882 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2883
2883
2884
2884
2885 class UserGroupToPerm(Base, BaseModel):
2885 class UserGroupToPerm(Base, BaseModel):
2886 __tablename__ = 'users_group_to_perm'
2886 __tablename__ = 'users_group_to_perm'
2887 __table_args__ = (
2887 __table_args__ = (
2888 UniqueConstraint('users_group_id', 'permission_id',),
2888 UniqueConstraint('users_group_id', 'permission_id',),
2889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2890 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2890 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2891 )
2891 )
2892 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2892 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2893 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2893 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_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)
2894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2895
2895
2896 users_group = relationship('UserGroup')
2896 users_group = relationship('UserGroup')
2897 permission = relationship('Permission')
2897 permission = relationship('Permission')
2898
2898
2899
2899
2900 class UserRepoGroupToPerm(Base, BaseModel):
2900 class UserRepoGroupToPerm(Base, BaseModel):
2901 __tablename__ = 'user_repo_group_to_perm'
2901 __tablename__ = 'user_repo_group_to_perm'
2902 __table_args__ = (
2902 __table_args__ = (
2903 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2903 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2904 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2904 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2905 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2905 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2906 )
2906 )
2907
2907
2908 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2908 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2909 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2909 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2910 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2910 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2911 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2911 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2912
2912
2913 user = relationship('User')
2913 user = relationship('User')
2914 group = relationship('RepoGroup')
2914 group = relationship('RepoGroup')
2915 permission = relationship('Permission')
2915 permission = relationship('Permission')
2916
2916
2917 @classmethod
2917 @classmethod
2918 def create(cls, user, repository_group, permission):
2918 def create(cls, user, repository_group, permission):
2919 n = cls()
2919 n = cls()
2920 n.user = user
2920 n.user = user
2921 n.group = repository_group
2921 n.group = repository_group
2922 n.permission = permission
2922 n.permission = permission
2923 Session().add(n)
2923 Session().add(n)
2924 return n
2924 return n
2925
2925
2926
2926
2927 class UserGroupRepoGroupToPerm(Base, BaseModel):
2927 class UserGroupRepoGroupToPerm(Base, BaseModel):
2928 __tablename__ = 'users_group_repo_group_to_perm'
2928 __tablename__ = 'users_group_repo_group_to_perm'
2929 __table_args__ = (
2929 __table_args__ = (
2930 UniqueConstraint('users_group_id', 'group_id'),
2930 UniqueConstraint('users_group_id', 'group_id'),
2931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2932 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2932 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2933 )
2933 )
2934
2934
2935 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)
2935 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)
2936 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2936 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2937 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2937 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2938 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2939
2939
2940 users_group = relationship('UserGroup')
2940 users_group = relationship('UserGroup')
2941 permission = relationship('Permission')
2941 permission = relationship('Permission')
2942 group = relationship('RepoGroup')
2942 group = relationship('RepoGroup')
2943
2943
2944 @classmethod
2944 @classmethod
2945 def create(cls, user_group, repository_group, permission):
2945 def create(cls, user_group, repository_group, permission):
2946 n = cls()
2946 n = cls()
2947 n.users_group = user_group
2947 n.users_group = user_group
2948 n.group = repository_group
2948 n.group = repository_group
2949 n.permission = permission
2949 n.permission = permission
2950 Session().add(n)
2950 Session().add(n)
2951 return n
2951 return n
2952
2952
2953 def __unicode__(self):
2953 def __unicode__(self):
2954 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2954 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2955
2955
2956
2956
2957 class Statistics(Base, BaseModel):
2957 class Statistics(Base, BaseModel):
2958 __tablename__ = 'statistics'
2958 __tablename__ = 'statistics'
2959 __table_args__ = (
2959 __table_args__ = (
2960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2961 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2961 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2962 )
2962 )
2963 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2963 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2964 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2964 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2965 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2965 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2966 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2966 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2967 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2967 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2968 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2968 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2969
2969
2970 repository = relationship('Repository', single_parent=True)
2970 repository = relationship('Repository', single_parent=True)
2971
2971
2972
2972
2973 class UserFollowing(Base, BaseModel):
2973 class UserFollowing(Base, BaseModel):
2974 __tablename__ = 'user_followings'
2974 __tablename__ = 'user_followings'
2975 __table_args__ = (
2975 __table_args__ = (
2976 UniqueConstraint('user_id', 'follows_repository_id'),
2976 UniqueConstraint('user_id', 'follows_repository_id'),
2977 UniqueConstraint('user_id', 'follows_user_id'),
2977 UniqueConstraint('user_id', 'follows_user_id'),
2978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2979 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2979 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2980 )
2980 )
2981
2981
2982 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2982 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2983 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2983 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2984 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2984 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2985 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2985 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2986 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2986 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2987
2987
2988 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2988 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2989
2989
2990 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2990 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2991 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2991 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2992
2992
2993 @classmethod
2993 @classmethod
2994 def get_repo_followers(cls, repo_id):
2994 def get_repo_followers(cls, repo_id):
2995 return cls.query().filter(cls.follows_repo_id == repo_id)
2995 return cls.query().filter(cls.follows_repo_id == repo_id)
2996
2996
2997
2997
2998 class CacheKey(Base, BaseModel):
2998 class CacheKey(Base, BaseModel):
2999 __tablename__ = 'cache_invalidation'
2999 __tablename__ = 'cache_invalidation'
3000 __table_args__ = (
3000 __table_args__ = (
3001 UniqueConstraint('cache_key'),
3001 UniqueConstraint('cache_key'),
3002 Index('key_idx', 'cache_key'),
3002 Index('key_idx', 'cache_key'),
3003 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3003 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3004 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3004 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3005 )
3005 )
3006 CACHE_TYPE_ATOM = 'ATOM'
3006 CACHE_TYPE_ATOM = 'ATOM'
3007 CACHE_TYPE_RSS = 'RSS'
3007 CACHE_TYPE_RSS = 'RSS'
3008 CACHE_TYPE_README = 'README'
3008 CACHE_TYPE_README = 'README'
3009
3009
3010 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3010 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3011 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3011 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3012 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3012 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3013 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3013 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3014
3014
3015 def __init__(self, cache_key, cache_args=''):
3015 def __init__(self, cache_key, cache_args=''):
3016 self.cache_key = cache_key
3016 self.cache_key = cache_key
3017 self.cache_args = cache_args
3017 self.cache_args = cache_args
3018 self.cache_active = False
3018 self.cache_active = False
3019
3019
3020 def __unicode__(self):
3020 def __unicode__(self):
3021 return u"<%s('%s:%s[%s]')>" % (
3021 return u"<%s('%s:%s[%s]')>" % (
3022 self.__class__.__name__,
3022 self.__class__.__name__,
3023 self.cache_id, self.cache_key, self.cache_active)
3023 self.cache_id, self.cache_key, self.cache_active)
3024
3024
3025 def _cache_key_partition(self):
3025 def _cache_key_partition(self):
3026 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3026 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3027 return prefix, repo_name, suffix
3027 return prefix, repo_name, suffix
3028
3028
3029 def get_prefix(self):
3029 def get_prefix(self):
3030 """
3030 """
3031 Try to extract prefix from existing cache key. The key could consist
3031 Try to extract prefix from existing cache key. The key could consist
3032 of prefix, repo_name, suffix
3032 of prefix, repo_name, suffix
3033 """
3033 """
3034 # this returns prefix, repo_name, suffix
3034 # this returns prefix, repo_name, suffix
3035 return self._cache_key_partition()[0]
3035 return self._cache_key_partition()[0]
3036
3036
3037 def get_suffix(self):
3037 def get_suffix(self):
3038 """
3038 """
3039 get suffix that might have been used in _get_cache_key to
3039 get suffix that might have been used in _get_cache_key to
3040 generate self.cache_key. Only used for informational purposes
3040 generate self.cache_key. Only used for informational purposes
3041 in repo_edit.mako.
3041 in repo_edit.mako.
3042 """
3042 """
3043 # prefix, repo_name, suffix
3043 # prefix, repo_name, suffix
3044 return self._cache_key_partition()[2]
3044 return self._cache_key_partition()[2]
3045
3045
3046 @classmethod
3046 @classmethod
3047 def delete_all_cache(cls):
3047 def delete_all_cache(cls):
3048 """
3048 """
3049 Delete all cache keys from database.
3049 Delete all cache keys from database.
3050 Should only be run when all instances are down and all entries
3050 Should only be run when all instances are down and all entries
3051 thus stale.
3051 thus stale.
3052 """
3052 """
3053 cls.query().delete()
3053 cls.query().delete()
3054 Session().commit()
3054 Session().commit()
3055
3055
3056 @classmethod
3056 @classmethod
3057 def get_cache_key(cls, repo_name, cache_type):
3057 def get_cache_key(cls, repo_name, cache_type):
3058 """
3058 """
3059
3059
3060 Generate a cache key for this process of RhodeCode instance.
3060 Generate a cache key for this process of RhodeCode instance.
3061 Prefix most likely will be process id or maybe explicitly set
3061 Prefix most likely will be process id or maybe explicitly set
3062 instance_id from .ini file.
3062 instance_id from .ini file.
3063 """
3063 """
3064 import rhodecode
3064 import rhodecode
3065 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3065 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3066
3066
3067 repo_as_unicode = safe_unicode(repo_name)
3067 repo_as_unicode = safe_unicode(repo_name)
3068 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3068 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3069 if cache_type else repo_as_unicode
3069 if cache_type else repo_as_unicode
3070
3070
3071 return u'{}{}'.format(prefix, key)
3071 return u'{}{}'.format(prefix, key)
3072
3072
3073 @classmethod
3073 @classmethod
3074 def set_invalidate(cls, repo_name, delete=False):
3074 def set_invalidate(cls, repo_name, delete=False):
3075 """
3075 """
3076 Mark all caches of a repo as invalid in the database.
3076 Mark all caches of a repo as invalid in the database.
3077 """
3077 """
3078
3078
3079 try:
3079 try:
3080 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3080 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3081 if delete:
3081 if delete:
3082 log.debug('cache objects deleted for repo %s',
3082 log.debug('cache objects deleted for repo %s',
3083 safe_str(repo_name))
3083 safe_str(repo_name))
3084 qry.delete()
3084 qry.delete()
3085 else:
3085 else:
3086 log.debug('cache objects marked as invalid for repo %s',
3086 log.debug('cache objects marked as invalid for repo %s',
3087 safe_str(repo_name))
3087 safe_str(repo_name))
3088 qry.update({"cache_active": False})
3088 qry.update({"cache_active": False})
3089
3089
3090 Session().commit()
3090 Session().commit()
3091 except Exception:
3091 except Exception:
3092 log.exception(
3092 log.exception(
3093 'Cache key invalidation failed for repository %s',
3093 'Cache key invalidation failed for repository %s',
3094 safe_str(repo_name))
3094 safe_str(repo_name))
3095 Session().rollback()
3095 Session().rollback()
3096
3096
3097 @classmethod
3097 @classmethod
3098 def get_active_cache(cls, cache_key):
3098 def get_active_cache(cls, cache_key):
3099 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3099 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3100 if inv_obj:
3100 if inv_obj:
3101 return inv_obj
3101 return inv_obj
3102 return None
3102 return None
3103
3103
3104 @classmethod
3104 @classmethod
3105 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3105 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3106 thread_scoped=False):
3106 thread_scoped=False):
3107 """
3107 """
3108 @cache_region('long_term')
3108 @cache_region('long_term')
3109 def _heavy_calculation(cache_key):
3109 def _heavy_calculation(cache_key):
3110 return 'result'
3110 return 'result'
3111
3111
3112 cache_context = CacheKey.repo_context_cache(
3112 cache_context = CacheKey.repo_context_cache(
3113 _heavy_calculation, repo_name, cache_type)
3113 _heavy_calculation, repo_name, cache_type)
3114
3114
3115 with cache_context as context:
3115 with cache_context as context:
3116 context.invalidate()
3116 context.invalidate()
3117 computed = context.compute()
3117 computed = context.compute()
3118
3118
3119 assert computed == 'result'
3119 assert computed == 'result'
3120 """
3120 """
3121 from rhodecode.lib import caches
3121 from rhodecode.lib import caches
3122 return caches.InvalidationContext(
3122 return caches.InvalidationContext(
3123 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3123 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3124
3124
3125
3125
3126 class ChangesetComment(Base, BaseModel):
3126 class ChangesetComment(Base, BaseModel):
3127 __tablename__ = 'changeset_comments'
3127 __tablename__ = 'changeset_comments'
3128 __table_args__ = (
3128 __table_args__ = (
3129 Index('cc_revision_idx', 'revision'),
3129 Index('cc_revision_idx', 'revision'),
3130 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3130 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3131 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3131 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3132 )
3132 )
3133
3133
3134 COMMENT_OUTDATED = u'comment_outdated'
3134 COMMENT_OUTDATED = u'comment_outdated'
3135 COMMENT_TYPE_NOTE = u'note'
3135 COMMENT_TYPE_NOTE = u'note'
3136 COMMENT_TYPE_TODO = u'todo'
3136 COMMENT_TYPE_TODO = u'todo'
3137 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3137 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3138
3138
3139 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3139 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3140 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3140 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3141 revision = Column('revision', String(40), nullable=True)
3141 revision = Column('revision', String(40), nullable=True)
3142 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3142 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3143 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3143 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3144 line_no = Column('line_no', Unicode(10), nullable=True)
3144 line_no = Column('line_no', Unicode(10), nullable=True)
3145 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3145 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3146 f_path = Column('f_path', Unicode(1000), nullable=True)
3146 f_path = Column('f_path', Unicode(1000), nullable=True)
3147 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3147 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3148 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3148 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3149 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3149 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3150 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3150 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3151 renderer = Column('renderer', Unicode(64), nullable=True)
3151 renderer = Column('renderer', Unicode(64), nullable=True)
3152 display_state = Column('display_state', Unicode(128), nullable=True)
3152 display_state = Column('display_state', Unicode(128), nullable=True)
3153
3153
3154 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3154 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3155 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3155 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3156 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3156 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3157 author = relationship('User', lazy='joined')
3157 author = relationship('User', lazy='joined')
3158 repo = relationship('Repository')
3158 repo = relationship('Repository')
3159 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3159 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3160 pull_request = relationship('PullRequest', lazy='joined')
3160 pull_request = relationship('PullRequest', lazy='joined')
3161 pull_request_version = relationship('PullRequestVersion')
3161 pull_request_version = relationship('PullRequestVersion')
3162
3162
3163 @classmethod
3163 @classmethod
3164 def get_users(cls, revision=None, pull_request_id=None):
3164 def get_users(cls, revision=None, pull_request_id=None):
3165 """
3165 """
3166 Returns user associated with this ChangesetComment. ie those
3166 Returns user associated with this ChangesetComment. ie those
3167 who actually commented
3167 who actually commented
3168
3168
3169 :param cls:
3169 :param cls:
3170 :param revision:
3170 :param revision:
3171 """
3171 """
3172 q = Session().query(User)\
3172 q = Session().query(User)\
3173 .join(ChangesetComment.author)
3173 .join(ChangesetComment.author)
3174 if revision:
3174 if revision:
3175 q = q.filter(cls.revision == revision)
3175 q = q.filter(cls.revision == revision)
3176 elif pull_request_id:
3176 elif pull_request_id:
3177 q = q.filter(cls.pull_request_id == pull_request_id)
3177 q = q.filter(cls.pull_request_id == pull_request_id)
3178 return q.all()
3178 return q.all()
3179
3179
3180 @classmethod
3180 @classmethod
3181 def get_index_from_version(cls, pr_version, versions):
3181 def get_index_from_version(cls, pr_version, versions):
3182 num_versions = [x.pull_request_version_id for x in versions]
3182 num_versions = [x.pull_request_version_id for x in versions]
3183 try:
3183 try:
3184 return num_versions.index(pr_version) +1
3184 return num_versions.index(pr_version) +1
3185 except (IndexError, ValueError):
3185 except (IndexError, ValueError):
3186 return
3186 return
3187
3187
3188 @property
3188 @property
3189 def outdated(self):
3189 def outdated(self):
3190 return self.display_state == self.COMMENT_OUTDATED
3190 return self.display_state == self.COMMENT_OUTDATED
3191
3191
3192 def outdated_at_version(self, version):
3192 def outdated_at_version(self, version):
3193 """
3193 """
3194 Checks if comment is outdated for given pull request version
3194 Checks if comment is outdated for given pull request version
3195 """
3195 """
3196 return self.outdated and self.pull_request_version_id != version
3196 return self.outdated and self.pull_request_version_id != version
3197
3197
3198 def older_than_version(self, version):
3198 def older_than_version(self, version):
3199 """
3199 """
3200 Checks if comment is made from previous version than given
3200 Checks if comment is made from previous version than given
3201 """
3201 """
3202 if version is None:
3202 if version is None:
3203 return self.pull_request_version_id is not None
3203 return self.pull_request_version_id is not None
3204
3204
3205 return self.pull_request_version_id < version
3205 return self.pull_request_version_id < version
3206
3206
3207 @property
3207 @property
3208 def resolved(self):
3208 def resolved(self):
3209 return self.resolved_by[0] if self.resolved_by else None
3209 return self.resolved_by[0] if self.resolved_by else None
3210
3210
3211 @property
3211 @property
3212 def is_todo(self):
3212 def is_todo(self):
3213 return self.comment_type == self.COMMENT_TYPE_TODO
3213 return self.comment_type == self.COMMENT_TYPE_TODO
3214
3214
3215 @property
3215 @property
3216 def is_inline(self):
3216 def is_inline(self):
3217 return self.line_no and self.f_path
3217 return self.line_no and self.f_path
3218
3218
3219 def get_index_version(self, versions):
3219 def get_index_version(self, versions):
3220 return self.get_index_from_version(
3220 return self.get_index_from_version(
3221 self.pull_request_version_id, versions)
3221 self.pull_request_version_id, versions)
3222
3222
3223 def __repr__(self):
3223 def __repr__(self):
3224 if self.comment_id:
3224 if self.comment_id:
3225 return '<DB:Comment #%s>' % self.comment_id
3225 return '<DB:Comment #%s>' % self.comment_id
3226 else:
3226 else:
3227 return '<DB:Comment at %#x>' % id(self)
3227 return '<DB:Comment at %#x>' % id(self)
3228
3228
3229 def get_api_data(self):
3229 def get_api_data(self):
3230 comment = self
3230 comment = self
3231 data = {
3231 data = {
3232 'comment_id': comment.comment_id,
3232 'comment_id': comment.comment_id,
3233 'comment_type': comment.comment_type,
3233 'comment_type': comment.comment_type,
3234 'comment_text': comment.text,
3234 'comment_text': comment.text,
3235 'comment_status': comment.status_change,
3235 'comment_status': comment.status_change,
3236 'comment_f_path': comment.f_path,
3236 'comment_f_path': comment.f_path,
3237 'comment_lineno': comment.line_no,
3237 'comment_lineno': comment.line_no,
3238 'comment_author': comment.author,
3238 'comment_author': comment.author,
3239 'comment_created_on': comment.created_on
3239 'comment_created_on': comment.created_on
3240 }
3240 }
3241 return data
3241 return data
3242
3242
3243 def __json__(self):
3243 def __json__(self):
3244 data = dict()
3244 data = dict()
3245 data.update(self.get_api_data())
3245 data.update(self.get_api_data())
3246 return data
3246 return data
3247
3247
3248
3248
3249 class ChangesetStatus(Base, BaseModel):
3249 class ChangesetStatus(Base, BaseModel):
3250 __tablename__ = 'changeset_statuses'
3250 __tablename__ = 'changeset_statuses'
3251 __table_args__ = (
3251 __table_args__ = (
3252 Index('cs_revision_idx', 'revision'),
3252 Index('cs_revision_idx', 'revision'),
3253 Index('cs_version_idx', 'version'),
3253 Index('cs_version_idx', 'version'),
3254 UniqueConstraint('repo_id', 'revision', 'version'),
3254 UniqueConstraint('repo_id', 'revision', 'version'),
3255 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3255 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3256 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3256 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3257 )
3257 )
3258 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3258 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3259 STATUS_APPROVED = 'approved'
3259 STATUS_APPROVED = 'approved'
3260 STATUS_REJECTED = 'rejected'
3260 STATUS_REJECTED = 'rejected'
3261 STATUS_UNDER_REVIEW = 'under_review'
3261 STATUS_UNDER_REVIEW = 'under_review'
3262
3262
3263 STATUSES = [
3263 STATUSES = [
3264 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3264 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3265 (STATUS_APPROVED, _("Approved")),
3265 (STATUS_APPROVED, _("Approved")),
3266 (STATUS_REJECTED, _("Rejected")),
3266 (STATUS_REJECTED, _("Rejected")),
3267 (STATUS_UNDER_REVIEW, _("Under Review")),
3267 (STATUS_UNDER_REVIEW, _("Under Review")),
3268 ]
3268 ]
3269
3269
3270 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3270 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3271 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3271 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3272 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3272 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3273 revision = Column('revision', String(40), nullable=False)
3273 revision = Column('revision', String(40), nullable=False)
3274 status = Column('status', String(128), nullable=False, default=DEFAULT)
3274 status = Column('status', String(128), nullable=False, default=DEFAULT)
3275 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3275 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3276 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3276 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3277 version = Column('version', Integer(), nullable=False, default=0)
3277 version = Column('version', Integer(), nullable=False, default=0)
3278 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3278 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3279
3279
3280 author = relationship('User', lazy='joined')
3280 author = relationship('User', lazy='joined')
3281 repo = relationship('Repository')
3281 repo = relationship('Repository')
3282 comment = relationship('ChangesetComment', lazy='joined')
3282 comment = relationship('ChangesetComment', lazy='joined')
3283 pull_request = relationship('PullRequest', lazy='joined')
3283 pull_request = relationship('PullRequest', lazy='joined')
3284
3284
3285 def __unicode__(self):
3285 def __unicode__(self):
3286 return u"<%s('%s[v%s]:%s')>" % (
3286 return u"<%s('%s[v%s]:%s')>" % (
3287 self.__class__.__name__,
3287 self.__class__.__name__,
3288 self.status, self.version, self.author
3288 self.status, self.version, self.author
3289 )
3289 )
3290
3290
3291 @classmethod
3291 @classmethod
3292 def get_status_lbl(cls, value):
3292 def get_status_lbl(cls, value):
3293 return dict(cls.STATUSES).get(value)
3293 return dict(cls.STATUSES).get(value)
3294
3294
3295 @property
3295 @property
3296 def status_lbl(self):
3296 def status_lbl(self):
3297 return ChangesetStatus.get_status_lbl(self.status)
3297 return ChangesetStatus.get_status_lbl(self.status)
3298
3298
3299 def get_api_data(self):
3299 def get_api_data(self):
3300 status = self
3300 status = self
3301 data = {
3301 data = {
3302 'status_id': status.changeset_status_id,
3302 'status_id': status.changeset_status_id,
3303 'status': status.status,
3303 'status': status.status,
3304 }
3304 }
3305 return data
3305 return data
3306
3306
3307 def __json__(self):
3307 def __json__(self):
3308 data = dict()
3308 data = dict()
3309 data.update(self.get_api_data())
3309 data.update(self.get_api_data())
3310 return data
3310 return data
3311
3311
3312
3312
3313 class _PullRequestBase(BaseModel):
3313 class _PullRequestBase(BaseModel):
3314 """
3314 """
3315 Common attributes of pull request and version entries.
3315 Common attributes of pull request and version entries.
3316 """
3316 """
3317
3317
3318 # .status values
3318 # .status values
3319 STATUS_NEW = u'new'
3319 STATUS_NEW = u'new'
3320 STATUS_OPEN = u'open'
3320 STATUS_OPEN = u'open'
3321 STATUS_CLOSED = u'closed'
3321 STATUS_CLOSED = u'closed'
3322
3322
3323 title = Column('title', Unicode(255), nullable=True)
3323 title = Column('title', Unicode(255), nullable=True)
3324 description = Column(
3324 description = Column(
3325 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3325 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3326 nullable=True)
3326 nullable=True)
3327 # new/open/closed status of pull request (not approve/reject/etc)
3327 # new/open/closed status of pull request (not approve/reject/etc)
3328 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3328 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3329 created_on = Column(
3329 created_on = Column(
3330 'created_on', DateTime(timezone=False), nullable=False,
3330 'created_on', DateTime(timezone=False), nullable=False,
3331 default=datetime.datetime.now)
3331 default=datetime.datetime.now)
3332 updated_on = Column(
3332 updated_on = Column(
3333 'updated_on', DateTime(timezone=False), nullable=False,
3333 'updated_on', DateTime(timezone=False), nullable=False,
3334 default=datetime.datetime.now)
3334 default=datetime.datetime.now)
3335
3335
3336 @declared_attr
3336 @declared_attr
3337 def user_id(cls):
3337 def user_id(cls):
3338 return Column(
3338 return Column(
3339 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3339 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3340 unique=None)
3340 unique=None)
3341
3341
3342 # 500 revisions max
3342 # 500 revisions max
3343 _revisions = Column(
3343 _revisions = Column(
3344 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3344 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3345
3345
3346 @declared_attr
3346 @declared_attr
3347 def source_repo_id(cls):
3347 def source_repo_id(cls):
3348 # TODO: dan: rename column to source_repo_id
3348 # TODO: dan: rename column to source_repo_id
3349 return Column(
3349 return Column(
3350 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3350 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3351 nullable=False)
3351 nullable=False)
3352
3352
3353 source_ref = Column('org_ref', Unicode(255), nullable=False)
3353 source_ref = Column('org_ref', Unicode(255), nullable=False)
3354
3354
3355 @declared_attr
3355 @declared_attr
3356 def target_repo_id(cls):
3356 def target_repo_id(cls):
3357 # TODO: dan: rename column to target_repo_id
3357 # TODO: dan: rename column to target_repo_id
3358 return Column(
3358 return Column(
3359 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3359 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3360 nullable=False)
3360 nullable=False)
3361
3361
3362 target_ref = Column('other_ref', Unicode(255), nullable=False)
3362 target_ref = Column('other_ref', Unicode(255), nullable=False)
3363 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3363 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3364
3364
3365 # TODO: dan: rename column to last_merge_source_rev
3365 # TODO: dan: rename column to last_merge_source_rev
3366 _last_merge_source_rev = Column(
3366 _last_merge_source_rev = Column(
3367 'last_merge_org_rev', String(40), nullable=True)
3367 'last_merge_org_rev', String(40), nullable=True)
3368 # TODO: dan: rename column to last_merge_target_rev
3368 # TODO: dan: rename column to last_merge_target_rev
3369 _last_merge_target_rev = Column(
3369 _last_merge_target_rev = Column(
3370 'last_merge_other_rev', String(40), nullable=True)
3370 'last_merge_other_rev', String(40), nullable=True)
3371 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3371 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3372 merge_rev = Column('merge_rev', String(40), nullable=True)
3372 merge_rev = Column('merge_rev', String(40), nullable=True)
3373
3373
3374 reviewer_data = Column(
3374 reviewer_data = Column(
3375 'reviewer_data_json', MutationObj.as_mutable(
3375 'reviewer_data_json', MutationObj.as_mutable(
3376 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3376 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3377
3377
3378 @property
3378 @property
3379 def reviewer_data_json(self):
3379 def reviewer_data_json(self):
3380 return json.dumps(self.reviewer_data)
3380 return json.dumps(self.reviewer_data)
3381
3381
3382 @hybrid_property
3382 @hybrid_property
3383 def description_safe(self):
3383 def description_safe(self):
3384 from rhodecode.lib import helpers as h
3384 from rhodecode.lib import helpers as h
3385 return h.escape(self.description)
3385 return h.escape(self.description)
3386
3386
3387 @hybrid_property
3387 @hybrid_property
3388 def revisions(self):
3388 def revisions(self):
3389 return self._revisions.split(':') if self._revisions else []
3389 return self._revisions.split(':') if self._revisions else []
3390
3390
3391 @revisions.setter
3391 @revisions.setter
3392 def revisions(self, val):
3392 def revisions(self, val):
3393 self._revisions = ':'.join(val)
3393 self._revisions = ':'.join(val)
3394
3394
3395 @hybrid_property
3395 @hybrid_property
3396 def last_merge_status(self):
3396 def last_merge_status(self):
3397 return safe_int(self._last_merge_status)
3397 return safe_int(self._last_merge_status)
3398
3398
3399 @last_merge_status.setter
3399 @last_merge_status.setter
3400 def last_merge_status(self, val):
3400 def last_merge_status(self, val):
3401 self._last_merge_status = val
3401 self._last_merge_status = val
3402
3402
3403 @declared_attr
3403 @declared_attr
3404 def author(cls):
3404 def author(cls):
3405 return relationship('User', lazy='joined')
3405 return relationship('User', lazy='joined')
3406
3406
3407 @declared_attr
3407 @declared_attr
3408 def source_repo(cls):
3408 def source_repo(cls):
3409 return relationship(
3409 return relationship(
3410 'Repository',
3410 'Repository',
3411 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3411 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3412
3412
3413 @property
3413 @property
3414 def source_ref_parts(self):
3414 def source_ref_parts(self):
3415 return self.unicode_to_reference(self.source_ref)
3415 return self.unicode_to_reference(self.source_ref)
3416
3416
3417 @declared_attr
3417 @declared_attr
3418 def target_repo(cls):
3418 def target_repo(cls):
3419 return relationship(
3419 return relationship(
3420 'Repository',
3420 'Repository',
3421 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3421 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3422
3422
3423 @property
3423 @property
3424 def target_ref_parts(self):
3424 def target_ref_parts(self):
3425 return self.unicode_to_reference(self.target_ref)
3425 return self.unicode_to_reference(self.target_ref)
3426
3426
3427 @property
3427 @property
3428 def shadow_merge_ref(self):
3428 def shadow_merge_ref(self):
3429 return self.unicode_to_reference(self._shadow_merge_ref)
3429 return self.unicode_to_reference(self._shadow_merge_ref)
3430
3430
3431 @shadow_merge_ref.setter
3431 @shadow_merge_ref.setter
3432 def shadow_merge_ref(self, ref):
3432 def shadow_merge_ref(self, ref):
3433 self._shadow_merge_ref = self.reference_to_unicode(ref)
3433 self._shadow_merge_ref = self.reference_to_unicode(ref)
3434
3434
3435 def unicode_to_reference(self, raw):
3435 def unicode_to_reference(self, raw):
3436 """
3436 """
3437 Convert a unicode (or string) to a reference object.
3437 Convert a unicode (or string) to a reference object.
3438 If unicode evaluates to False it returns None.
3438 If unicode evaluates to False it returns None.
3439 """
3439 """
3440 if raw:
3440 if raw:
3441 refs = raw.split(':')
3441 refs = raw.split(':')
3442 return Reference(*refs)
3442 return Reference(*refs)
3443 else:
3443 else:
3444 return None
3444 return None
3445
3445
3446 def reference_to_unicode(self, ref):
3446 def reference_to_unicode(self, ref):
3447 """
3447 """
3448 Convert a reference object to unicode.
3448 Convert a reference object to unicode.
3449 If reference is None it returns None.
3449 If reference is None it returns None.
3450 """
3450 """
3451 if ref:
3451 if ref:
3452 return u':'.join(ref)
3452 return u':'.join(ref)
3453 else:
3453 else:
3454 return None
3454 return None
3455
3455
3456 def get_api_data(self, with_merge_state=True):
3456 def get_api_data(self, with_merge_state=True):
3457 from rhodecode.model.pull_request import PullRequestModel
3457 from rhodecode.model.pull_request import PullRequestModel
3458
3458
3459 pull_request = self
3459 pull_request = self
3460 if with_merge_state:
3460 if with_merge_state:
3461 merge_status = PullRequestModel().merge_status(pull_request)
3461 merge_status = PullRequestModel().merge_status(pull_request)
3462 merge_state = {
3462 merge_state = {
3463 'status': merge_status[0],
3463 'status': merge_status[0],
3464 'message': safe_unicode(merge_status[1]),
3464 'message': safe_unicode(merge_status[1]),
3465 }
3465 }
3466 else:
3466 else:
3467 merge_state = {'status': 'not_available',
3467 merge_state = {'status': 'not_available',
3468 'message': 'not_available'}
3468 'message': 'not_available'}
3469
3469
3470 merge_data = {
3470 merge_data = {
3471 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3471 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3472 'reference': (
3472 'reference': (
3473 pull_request.shadow_merge_ref._asdict()
3473 pull_request.shadow_merge_ref._asdict()
3474 if pull_request.shadow_merge_ref else None),
3474 if pull_request.shadow_merge_ref else None),
3475 }
3475 }
3476
3476
3477 data = {
3477 data = {
3478 'pull_request_id': pull_request.pull_request_id,
3478 'pull_request_id': pull_request.pull_request_id,
3479 'url': PullRequestModel().get_url(pull_request),
3479 'url': PullRequestModel().get_url(pull_request),
3480 'title': pull_request.title,
3480 'title': pull_request.title,
3481 'description': pull_request.description,
3481 'description': pull_request.description,
3482 'status': pull_request.status,
3482 'status': pull_request.status,
3483 'created_on': pull_request.created_on,
3483 'created_on': pull_request.created_on,
3484 'updated_on': pull_request.updated_on,
3484 'updated_on': pull_request.updated_on,
3485 'commit_ids': pull_request.revisions,
3485 'commit_ids': pull_request.revisions,
3486 'review_status': pull_request.calculated_review_status(),
3486 'review_status': pull_request.calculated_review_status(),
3487 'mergeable': merge_state,
3487 'mergeable': merge_state,
3488 'source': {
3488 'source': {
3489 'clone_url': pull_request.source_repo.clone_url(),
3489 'clone_url': pull_request.source_repo.clone_url(),
3490 'repository': pull_request.source_repo.repo_name,
3490 'repository': pull_request.source_repo.repo_name,
3491 'reference': {
3491 'reference': {
3492 'name': pull_request.source_ref_parts.name,
3492 'name': pull_request.source_ref_parts.name,
3493 'type': pull_request.source_ref_parts.type,
3493 'type': pull_request.source_ref_parts.type,
3494 'commit_id': pull_request.source_ref_parts.commit_id,
3494 'commit_id': pull_request.source_ref_parts.commit_id,
3495 },
3495 },
3496 },
3496 },
3497 'target': {
3497 'target': {
3498 'clone_url': pull_request.target_repo.clone_url(),
3498 'clone_url': pull_request.target_repo.clone_url(),
3499 'repository': pull_request.target_repo.repo_name,
3499 'repository': pull_request.target_repo.repo_name,
3500 'reference': {
3500 'reference': {
3501 'name': pull_request.target_ref_parts.name,
3501 'name': pull_request.target_ref_parts.name,
3502 'type': pull_request.target_ref_parts.type,
3502 'type': pull_request.target_ref_parts.type,
3503 'commit_id': pull_request.target_ref_parts.commit_id,
3503 'commit_id': pull_request.target_ref_parts.commit_id,
3504 },
3504 },
3505 },
3505 },
3506 'merge': merge_data,
3506 'merge': merge_data,
3507 'author': pull_request.author.get_api_data(include_secrets=False,
3507 'author': pull_request.author.get_api_data(include_secrets=False,
3508 details='basic'),
3508 details='basic'),
3509 'reviewers': [
3509 'reviewers': [
3510 {
3510 {
3511 'user': reviewer.get_api_data(include_secrets=False,
3511 'user': reviewer.get_api_data(include_secrets=False,
3512 details='basic'),
3512 details='basic'),
3513 'reasons': reasons,
3513 'reasons': reasons,
3514 'review_status': st[0][1].status if st else 'not_reviewed',
3514 'review_status': st[0][1].status if st else 'not_reviewed',
3515 }
3515 }
3516 for reviewer, reasons, mandatory, st in
3516 for reviewer, reasons, mandatory, st in
3517 pull_request.reviewers_statuses()
3517 pull_request.reviewers_statuses()
3518 ]
3518 ]
3519 }
3519 }
3520
3520
3521 return data
3521 return data
3522
3522
3523
3523
3524 class PullRequest(Base, _PullRequestBase):
3524 class PullRequest(Base, _PullRequestBase):
3525 __tablename__ = 'pull_requests'
3525 __tablename__ = 'pull_requests'
3526 __table_args__ = (
3526 __table_args__ = (
3527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3528 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3528 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3529 )
3529 )
3530
3530
3531 pull_request_id = Column(
3531 pull_request_id = Column(
3532 'pull_request_id', Integer(), nullable=False, primary_key=True)
3532 'pull_request_id', Integer(), nullable=False, primary_key=True)
3533
3533
3534 def __repr__(self):
3534 def __repr__(self):
3535 if self.pull_request_id:
3535 if self.pull_request_id:
3536 return '<DB:PullRequest #%s>' % self.pull_request_id
3536 return '<DB:PullRequest #%s>' % self.pull_request_id
3537 else:
3537 else:
3538 return '<DB:PullRequest at %#x>' % id(self)
3538 return '<DB:PullRequest at %#x>' % id(self)
3539
3539
3540 reviewers = relationship('PullRequestReviewers',
3540 reviewers = relationship('PullRequestReviewers',
3541 cascade="all, delete, delete-orphan")
3541 cascade="all, delete, delete-orphan")
3542 statuses = relationship('ChangesetStatus',
3542 statuses = relationship('ChangesetStatus',
3543 cascade="all, delete, delete-orphan")
3543 cascade="all, delete, delete-orphan")
3544 comments = relationship('ChangesetComment',
3544 comments = relationship('ChangesetComment',
3545 cascade="all, delete, delete-orphan")
3545 cascade="all, delete, delete-orphan")
3546 versions = relationship('PullRequestVersion',
3546 versions = relationship('PullRequestVersion',
3547 cascade="all, delete, delete-orphan",
3547 cascade="all, delete, delete-orphan",
3548 lazy='dynamic')
3548 lazy='dynamic')
3549
3549
3550 @classmethod
3550 @classmethod
3551 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3551 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3552 internal_methods=None):
3552 internal_methods=None):
3553
3553
3554 class PullRequestDisplay(object):
3554 class PullRequestDisplay(object):
3555 """
3555 """
3556 Special object wrapper for showing PullRequest data via Versions
3556 Special object wrapper for showing PullRequest data via Versions
3557 It mimics PR object as close as possible. This is read only object
3557 It mimics PR object as close as possible. This is read only object
3558 just for display
3558 just for display
3559 """
3559 """
3560
3560
3561 def __init__(self, attrs, internal=None):
3561 def __init__(self, attrs, internal=None):
3562 self.attrs = attrs
3562 self.attrs = attrs
3563 # internal have priority over the given ones via attrs
3563 # internal have priority over the given ones via attrs
3564 self.internal = internal or ['versions']
3564 self.internal = internal or ['versions']
3565
3565
3566 def __getattr__(self, item):
3566 def __getattr__(self, item):
3567 if item in self.internal:
3567 if item in self.internal:
3568 return getattr(self, item)
3568 return getattr(self, item)
3569 try:
3569 try:
3570 return self.attrs[item]
3570 return self.attrs[item]
3571 except KeyError:
3571 except KeyError:
3572 raise AttributeError(
3572 raise AttributeError(
3573 '%s object has no attribute %s' % (self, item))
3573 '%s object has no attribute %s' % (self, item))
3574
3574
3575 def __repr__(self):
3575 def __repr__(self):
3576 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3576 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3577
3577
3578 def versions(self):
3578 def versions(self):
3579 return pull_request_obj.versions.order_by(
3579 return pull_request_obj.versions.order_by(
3580 PullRequestVersion.pull_request_version_id).all()
3580 PullRequestVersion.pull_request_version_id).all()
3581
3581
3582 def is_closed(self):
3582 def is_closed(self):
3583 return pull_request_obj.is_closed()
3583 return pull_request_obj.is_closed()
3584
3584
3585 @property
3585 @property
3586 def pull_request_version_id(self):
3586 def pull_request_version_id(self):
3587 return getattr(pull_request_obj, 'pull_request_version_id', None)
3587 return getattr(pull_request_obj, 'pull_request_version_id', None)
3588
3588
3589 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3589 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3590
3590
3591 attrs.author = StrictAttributeDict(
3591 attrs.author = StrictAttributeDict(
3592 pull_request_obj.author.get_api_data())
3592 pull_request_obj.author.get_api_data())
3593 if pull_request_obj.target_repo:
3593 if pull_request_obj.target_repo:
3594 attrs.target_repo = StrictAttributeDict(
3594 attrs.target_repo = StrictAttributeDict(
3595 pull_request_obj.target_repo.get_api_data())
3595 pull_request_obj.target_repo.get_api_data())
3596 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3596 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3597
3597
3598 if pull_request_obj.source_repo:
3598 if pull_request_obj.source_repo:
3599 attrs.source_repo = StrictAttributeDict(
3599 attrs.source_repo = StrictAttributeDict(
3600 pull_request_obj.source_repo.get_api_data())
3600 pull_request_obj.source_repo.get_api_data())
3601 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3601 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3602
3602
3603 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3603 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3604 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3604 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3605 attrs.revisions = pull_request_obj.revisions
3605 attrs.revisions = pull_request_obj.revisions
3606
3606
3607 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3607 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3608 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3608 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3609 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3609 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3610
3610
3611 return PullRequestDisplay(attrs, internal=internal_methods)
3611 return PullRequestDisplay(attrs, internal=internal_methods)
3612
3612
3613 def is_closed(self):
3613 def is_closed(self):
3614 return self.status == self.STATUS_CLOSED
3614 return self.status == self.STATUS_CLOSED
3615
3615
3616 def __json__(self):
3616 def __json__(self):
3617 return {
3617 return {
3618 'revisions': self.revisions,
3618 'revisions': self.revisions,
3619 }
3619 }
3620
3620
3621 def calculated_review_status(self):
3621 def calculated_review_status(self):
3622 from rhodecode.model.changeset_status import ChangesetStatusModel
3622 from rhodecode.model.changeset_status import ChangesetStatusModel
3623 return ChangesetStatusModel().calculated_review_status(self)
3623 return ChangesetStatusModel().calculated_review_status(self)
3624
3624
3625 def reviewers_statuses(self):
3625 def reviewers_statuses(self):
3626 from rhodecode.model.changeset_status import ChangesetStatusModel
3626 from rhodecode.model.changeset_status import ChangesetStatusModel
3627 return ChangesetStatusModel().reviewers_statuses(self)
3627 return ChangesetStatusModel().reviewers_statuses(self)
3628
3628
3629 @property
3629 @property
3630 def workspace_id(self):
3630 def workspace_id(self):
3631 from rhodecode.model.pull_request import PullRequestModel
3631 from rhodecode.model.pull_request import PullRequestModel
3632 return PullRequestModel()._workspace_id(self)
3632 return PullRequestModel()._workspace_id(self)
3633
3633
3634 def get_shadow_repo(self):
3634 def get_shadow_repo(self):
3635 workspace_id = self.workspace_id
3635 workspace_id = self.workspace_id
3636 vcs_obj = self.target_repo.scm_instance()
3636 vcs_obj = self.target_repo.scm_instance()
3637 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3637 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3638 workspace_id)
3638 workspace_id)
3639 return vcs_obj._get_shadow_instance(shadow_repository_path)
3639 return vcs_obj._get_shadow_instance(shadow_repository_path)
3640
3640
3641
3641
3642 class PullRequestVersion(Base, _PullRequestBase):
3642 class PullRequestVersion(Base, _PullRequestBase):
3643 __tablename__ = 'pull_request_versions'
3643 __tablename__ = 'pull_request_versions'
3644 __table_args__ = (
3644 __table_args__ = (
3645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3646 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3646 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3647 )
3647 )
3648
3648
3649 pull_request_version_id = Column(
3649 pull_request_version_id = Column(
3650 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3650 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3651 pull_request_id = Column(
3651 pull_request_id = Column(
3652 'pull_request_id', Integer(),
3652 'pull_request_id', Integer(),
3653 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3653 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3654 pull_request = relationship('PullRequest')
3654 pull_request = relationship('PullRequest')
3655
3655
3656 def __repr__(self):
3656 def __repr__(self):
3657 if self.pull_request_version_id:
3657 if self.pull_request_version_id:
3658 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3658 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3659 else:
3659 else:
3660 return '<DB:PullRequestVersion at %#x>' % id(self)
3660 return '<DB:PullRequestVersion at %#x>' % id(self)
3661
3661
3662 @property
3662 @property
3663 def reviewers(self):
3663 def reviewers(self):
3664 return self.pull_request.reviewers
3664 return self.pull_request.reviewers
3665
3665
3666 @property
3666 @property
3667 def versions(self):
3667 def versions(self):
3668 return self.pull_request.versions
3668 return self.pull_request.versions
3669
3669
3670 def is_closed(self):
3670 def is_closed(self):
3671 # calculate from original
3671 # calculate from original
3672 return self.pull_request.status == self.STATUS_CLOSED
3672 return self.pull_request.status == self.STATUS_CLOSED
3673
3673
3674 def calculated_review_status(self):
3674 def calculated_review_status(self):
3675 return self.pull_request.calculated_review_status()
3675 return self.pull_request.calculated_review_status()
3676
3676
3677 def reviewers_statuses(self):
3677 def reviewers_statuses(self):
3678 return self.pull_request.reviewers_statuses()
3678 return self.pull_request.reviewers_statuses()
3679
3679
3680
3680
3681 class PullRequestReviewers(Base, BaseModel):
3681 class PullRequestReviewers(Base, BaseModel):
3682 __tablename__ = 'pull_request_reviewers'
3682 __tablename__ = 'pull_request_reviewers'
3683 __table_args__ = (
3683 __table_args__ = (
3684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3685 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3685 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3686 )
3686 )
3687
3687
3688 @hybrid_property
3688 @hybrid_property
3689 def reasons(self):
3689 def reasons(self):
3690 if not self._reasons:
3690 if not self._reasons:
3691 return []
3691 return []
3692 return self._reasons
3692 return self._reasons
3693
3693
3694 @reasons.setter
3694 @reasons.setter
3695 def reasons(self, val):
3695 def reasons(self, val):
3696 val = val or []
3696 val = val or []
3697 if any(not isinstance(x, basestring) for x in val):
3697 if any(not isinstance(x, basestring) for x in val):
3698 raise Exception('invalid reasons type, must be list of strings')
3698 raise Exception('invalid reasons type, must be list of strings')
3699 self._reasons = val
3699 self._reasons = val
3700
3700
3701 pull_requests_reviewers_id = Column(
3701 pull_requests_reviewers_id = Column(
3702 'pull_requests_reviewers_id', Integer(), nullable=False,
3702 'pull_requests_reviewers_id', Integer(), nullable=False,
3703 primary_key=True)
3703 primary_key=True)
3704 pull_request_id = Column(
3704 pull_request_id = Column(
3705 "pull_request_id", Integer(),
3705 "pull_request_id", Integer(),
3706 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3706 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3707 user_id = Column(
3707 user_id = Column(
3708 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3708 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3709 _reasons = Column(
3709 _reasons = Column(
3710 'reason', MutationList.as_mutable(
3710 'reason', MutationList.as_mutable(
3711 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3711 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3712 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3712 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3713 user = relationship('User')
3713 user = relationship('User')
3714 pull_request = relationship('PullRequest')
3714 pull_request = relationship('PullRequest')
3715
3715
3716
3716
3717 class Notification(Base, BaseModel):
3717 class Notification(Base, BaseModel):
3718 __tablename__ = 'notifications'
3718 __tablename__ = 'notifications'
3719 __table_args__ = (
3719 __table_args__ = (
3720 Index('notification_type_idx', 'type'),
3720 Index('notification_type_idx', 'type'),
3721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3721 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3722 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3723 )
3723 )
3724
3724
3725 TYPE_CHANGESET_COMMENT = u'cs_comment'
3725 TYPE_CHANGESET_COMMENT = u'cs_comment'
3726 TYPE_MESSAGE = u'message'
3726 TYPE_MESSAGE = u'message'
3727 TYPE_MENTION = u'mention'
3727 TYPE_MENTION = u'mention'
3728 TYPE_REGISTRATION = u'registration'
3728 TYPE_REGISTRATION = u'registration'
3729 TYPE_PULL_REQUEST = u'pull_request'
3729 TYPE_PULL_REQUEST = u'pull_request'
3730 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3730 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3731
3731
3732 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3732 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3733 subject = Column('subject', Unicode(512), nullable=True)
3733 subject = Column('subject', Unicode(512), nullable=True)
3734 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3734 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3735 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3735 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3736 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3736 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3737 type_ = Column('type', Unicode(255))
3737 type_ = Column('type', Unicode(255))
3738
3738
3739 created_by_user = relationship('User')
3739 created_by_user = relationship('User')
3740 notifications_to_users = relationship('UserNotification', lazy='joined',
3740 notifications_to_users = relationship('UserNotification', lazy='joined',
3741 cascade="all, delete, delete-orphan")
3741 cascade="all, delete, delete-orphan")
3742
3742
3743 @property
3743 @property
3744 def recipients(self):
3744 def recipients(self):
3745 return [x.user for x in UserNotification.query()\
3745 return [x.user for x in UserNotification.query()\
3746 .filter(UserNotification.notification == self)\
3746 .filter(UserNotification.notification == self)\
3747 .order_by(UserNotification.user_id.asc()).all()]
3747 .order_by(UserNotification.user_id.asc()).all()]
3748
3748
3749 @classmethod
3749 @classmethod
3750 def create(cls, created_by, subject, body, recipients, type_=None):
3750 def create(cls, created_by, subject, body, recipients, type_=None):
3751 if type_ is None:
3751 if type_ is None:
3752 type_ = Notification.TYPE_MESSAGE
3752 type_ = Notification.TYPE_MESSAGE
3753
3753
3754 notification = cls()
3754 notification = cls()
3755 notification.created_by_user = created_by
3755 notification.created_by_user = created_by
3756 notification.subject = subject
3756 notification.subject = subject
3757 notification.body = body
3757 notification.body = body
3758 notification.type_ = type_
3758 notification.type_ = type_
3759 notification.created_on = datetime.datetime.now()
3759 notification.created_on = datetime.datetime.now()
3760
3760
3761 for u in recipients:
3761 for u in recipients:
3762 assoc = UserNotification()
3762 assoc = UserNotification()
3763 assoc.notification = notification
3763 assoc.notification = notification
3764
3764
3765 # if created_by is inside recipients mark his notification
3765 # if created_by is inside recipients mark his notification
3766 # as read
3766 # as read
3767 if u.user_id == created_by.user_id:
3767 if u.user_id == created_by.user_id:
3768 assoc.read = True
3768 assoc.read = True
3769
3769
3770 u.notifications.append(assoc)
3770 u.notifications.append(assoc)
3771 Session().add(notification)
3771 Session().add(notification)
3772
3772
3773 return notification
3773 return notification
3774
3774
3775
3775
3776 class UserNotification(Base, BaseModel):
3776 class UserNotification(Base, BaseModel):
3777 __tablename__ = 'user_to_notification'
3777 __tablename__ = 'user_to_notification'
3778 __table_args__ = (
3778 __table_args__ = (
3779 UniqueConstraint('user_id', 'notification_id'),
3779 UniqueConstraint('user_id', 'notification_id'),
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3782 )
3782 )
3783 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3783 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3784 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3784 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3785 read = Column('read', Boolean, default=False)
3785 read = Column('read', Boolean, default=False)
3786 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3786 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3787
3787
3788 user = relationship('User', lazy="joined")
3788 user = relationship('User', lazy="joined")
3789 notification = relationship('Notification', lazy="joined",
3789 notification = relationship('Notification', lazy="joined",
3790 order_by=lambda: Notification.created_on.desc(),)
3790 order_by=lambda: Notification.created_on.desc(),)
3791
3791
3792 def mark_as_read(self):
3792 def mark_as_read(self):
3793 self.read = True
3793 self.read = True
3794 Session().add(self)
3794 Session().add(self)
3795
3795
3796
3796
3797 class Gist(Base, BaseModel):
3797 class Gist(Base, BaseModel):
3798 __tablename__ = 'gists'
3798 __tablename__ = 'gists'
3799 __table_args__ = (
3799 __table_args__ = (
3800 Index('g_gist_access_id_idx', 'gist_access_id'),
3800 Index('g_gist_access_id_idx', 'gist_access_id'),
3801 Index('g_created_on_idx', 'created_on'),
3801 Index('g_created_on_idx', 'created_on'),
3802 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3802 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3803 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3803 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3804 )
3804 )
3805 GIST_PUBLIC = u'public'
3805 GIST_PUBLIC = u'public'
3806 GIST_PRIVATE = u'private'
3806 GIST_PRIVATE = u'private'
3807 DEFAULT_FILENAME = u'gistfile1.txt'
3807 DEFAULT_FILENAME = u'gistfile1.txt'
3808
3808
3809 ACL_LEVEL_PUBLIC = u'acl_public'
3809 ACL_LEVEL_PUBLIC = u'acl_public'
3810 ACL_LEVEL_PRIVATE = u'acl_private'
3810 ACL_LEVEL_PRIVATE = u'acl_private'
3811
3811
3812 gist_id = Column('gist_id', Integer(), primary_key=True)
3812 gist_id = Column('gist_id', Integer(), primary_key=True)
3813 gist_access_id = Column('gist_access_id', Unicode(250))
3813 gist_access_id = Column('gist_access_id', Unicode(250))
3814 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3814 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3815 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3815 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3816 gist_expires = Column('gist_expires', Float(53), nullable=False)
3816 gist_expires = Column('gist_expires', Float(53), nullable=False)
3817 gist_type = Column('gist_type', Unicode(128), nullable=False)
3817 gist_type = Column('gist_type', Unicode(128), nullable=False)
3818 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3818 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3819 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3819 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3820 acl_level = Column('acl_level', Unicode(128), nullable=True)
3820 acl_level = Column('acl_level', Unicode(128), nullable=True)
3821
3821
3822 owner = relationship('User')
3822 owner = relationship('User')
3823
3823
3824 def __repr__(self):
3824 def __repr__(self):
3825 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3825 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3826
3826
3827 @hybrid_property
3827 @hybrid_property
3828 def description_safe(self):
3828 def description_safe(self):
3829 from rhodecode.lib import helpers as h
3829 from rhodecode.lib import helpers as h
3830 return h.escape(self.gist_description)
3830 return h.escape(self.gist_description)
3831
3831
3832 @classmethod
3832 @classmethod
3833 def get_or_404(cls, id_):
3833 def get_or_404(cls, id_):
3834 from pyramid.httpexceptions import HTTPNotFound
3834 from pyramid.httpexceptions import HTTPNotFound
3835
3835
3836 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3836 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3837 if not res:
3837 if not res:
3838 raise HTTPNotFound()
3838 raise HTTPNotFound()
3839 return res
3839 return res
3840
3840
3841 @classmethod
3841 @classmethod
3842 def get_by_access_id(cls, gist_access_id):
3842 def get_by_access_id(cls, gist_access_id):
3843 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3843 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3844
3844
3845 def gist_url(self):
3845 def gist_url(self):
3846 from rhodecode.model.gist import GistModel
3846 from rhodecode.model.gist import GistModel
3847 return GistModel().get_url(self)
3847 return GistModel().get_url(self)
3848
3848
3849 @classmethod
3849 @classmethod
3850 def base_path(cls):
3850 def base_path(cls):
3851 """
3851 """
3852 Returns base path when all gists are stored
3852 Returns base path when all gists are stored
3853
3853
3854 :param cls:
3854 :param cls:
3855 """
3855 """
3856 from rhodecode.model.gist import GIST_STORE_LOC
3856 from rhodecode.model.gist import GIST_STORE_LOC
3857 q = Session().query(RhodeCodeUi)\
3857 q = Session().query(RhodeCodeUi)\
3858 .filter(RhodeCodeUi.ui_key == URL_SEP)
3858 .filter(RhodeCodeUi.ui_key == URL_SEP)
3859 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3859 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3860 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3860 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3861
3861
3862 def get_api_data(self):
3862 def get_api_data(self):
3863 """
3863 """
3864 Common function for generating gist related data for API
3864 Common function for generating gist related data for API
3865 """
3865 """
3866 gist = self
3866 gist = self
3867 data = {
3867 data = {
3868 'gist_id': gist.gist_id,
3868 'gist_id': gist.gist_id,
3869 'type': gist.gist_type,
3869 'type': gist.gist_type,
3870 'access_id': gist.gist_access_id,
3870 'access_id': gist.gist_access_id,
3871 'description': gist.gist_description,
3871 'description': gist.gist_description,
3872 'url': gist.gist_url(),
3872 'url': gist.gist_url(),
3873 'expires': gist.gist_expires,
3873 'expires': gist.gist_expires,
3874 'created_on': gist.created_on,
3874 'created_on': gist.created_on,
3875 'modified_at': gist.modified_at,
3875 'modified_at': gist.modified_at,
3876 'content': None,
3876 'content': None,
3877 'acl_level': gist.acl_level,
3877 'acl_level': gist.acl_level,
3878 }
3878 }
3879 return data
3879 return data
3880
3880
3881 def __json__(self):
3881 def __json__(self):
3882 data = dict(
3882 data = dict(
3883 )
3883 )
3884 data.update(self.get_api_data())
3884 data.update(self.get_api_data())
3885 return data
3885 return data
3886 # SCM functions
3886 # SCM functions
3887
3887
3888 def scm_instance(self, **kwargs):
3888 def scm_instance(self, **kwargs):
3889 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3889 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3890 return get_vcs_instance(
3890 return get_vcs_instance(
3891 repo_path=safe_str(full_repo_path), create=False)
3891 repo_path=safe_str(full_repo_path), create=False)
3892
3892
3893
3893
3894 class ExternalIdentity(Base, BaseModel):
3894 class ExternalIdentity(Base, BaseModel):
3895 __tablename__ = 'external_identities'
3895 __tablename__ = 'external_identities'
3896 __table_args__ = (
3896 __table_args__ = (
3897 Index('local_user_id_idx', 'local_user_id'),
3897 Index('local_user_id_idx', 'local_user_id'),
3898 Index('external_id_idx', 'external_id'),
3898 Index('external_id_idx', 'external_id'),
3899 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3899 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3900 'mysql_charset': 'utf8'})
3900 'mysql_charset': 'utf8'})
3901
3901
3902 external_id = Column('external_id', Unicode(255), default=u'',
3902 external_id = Column('external_id', Unicode(255), default=u'',
3903 primary_key=True)
3903 primary_key=True)
3904 external_username = Column('external_username', Unicode(1024), default=u'')
3904 external_username = Column('external_username', Unicode(1024), default=u'')
3905 local_user_id = Column('local_user_id', Integer(),
3905 local_user_id = Column('local_user_id', Integer(),
3906 ForeignKey('users.user_id'), primary_key=True)
3906 ForeignKey('users.user_id'), primary_key=True)
3907 provider_name = Column('provider_name', Unicode(255), default=u'',
3907 provider_name = Column('provider_name', Unicode(255), default=u'',
3908 primary_key=True)
3908 primary_key=True)
3909 access_token = Column('access_token', String(1024), default=u'')
3909 access_token = Column('access_token', String(1024), default=u'')
3910 alt_token = Column('alt_token', String(1024), default=u'')
3910 alt_token = Column('alt_token', String(1024), default=u'')
3911 token_secret = Column('token_secret', String(1024), default=u'')
3911 token_secret = Column('token_secret', String(1024), default=u'')
3912
3912
3913 @classmethod
3913 @classmethod
3914 def by_external_id_and_provider(cls, external_id, provider_name,
3914 def by_external_id_and_provider(cls, external_id, provider_name,
3915 local_user_id=None):
3915 local_user_id=None):
3916 """
3916 """
3917 Returns ExternalIdentity instance based on search params
3917 Returns ExternalIdentity instance based on search params
3918
3918
3919 :param external_id:
3919 :param external_id:
3920 :param provider_name:
3920 :param provider_name:
3921 :return: ExternalIdentity
3921 :return: ExternalIdentity
3922 """
3922 """
3923 query = cls.query()
3923 query = cls.query()
3924 query = query.filter(cls.external_id == external_id)
3924 query = query.filter(cls.external_id == external_id)
3925 query = query.filter(cls.provider_name == provider_name)
3925 query = query.filter(cls.provider_name == provider_name)
3926 if local_user_id:
3926 if local_user_id:
3927 query = query.filter(cls.local_user_id == local_user_id)
3927 query = query.filter(cls.local_user_id == local_user_id)
3928 return query.first()
3928 return query.first()
3929
3929
3930 @classmethod
3930 @classmethod
3931 def user_by_external_id_and_provider(cls, external_id, provider_name):
3931 def user_by_external_id_and_provider(cls, external_id, provider_name):
3932 """
3932 """
3933 Returns User instance based on search params
3933 Returns User instance based on search params
3934
3934
3935 :param external_id:
3935 :param external_id:
3936 :param provider_name:
3936 :param provider_name:
3937 :return: User
3937 :return: User
3938 """
3938 """
3939 query = User.query()
3939 query = User.query()
3940 query = query.filter(cls.external_id == external_id)
3940 query = query.filter(cls.external_id == external_id)
3941 query = query.filter(cls.provider_name == provider_name)
3941 query = query.filter(cls.provider_name == provider_name)
3942 query = query.filter(User.user_id == cls.local_user_id)
3942 query = query.filter(User.user_id == cls.local_user_id)
3943 return query.first()
3943 return query.first()
3944
3944
3945 @classmethod
3945 @classmethod
3946 def by_local_user_id(cls, local_user_id):
3946 def by_local_user_id(cls, local_user_id):
3947 """
3947 """
3948 Returns all tokens for user
3948 Returns all tokens for user
3949
3949
3950 :param local_user_id:
3950 :param local_user_id:
3951 :return: ExternalIdentity
3951 :return: ExternalIdentity
3952 """
3952 """
3953 query = cls.query()
3953 query = cls.query()
3954 query = query.filter(cls.local_user_id == local_user_id)
3954 query = query.filter(cls.local_user_id == local_user_id)
3955 return query
3955 return query
3956
3956
3957
3957
3958 class Integration(Base, BaseModel):
3958 class Integration(Base, BaseModel):
3959 __tablename__ = 'integrations'
3959 __tablename__ = 'integrations'
3960 __table_args__ = (
3960 __table_args__ = (
3961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3962 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3963 )
3963 )
3964
3964
3965 integration_id = Column('integration_id', Integer(), primary_key=True)
3965 integration_id = Column('integration_id', Integer(), primary_key=True)
3966 integration_type = Column('integration_type', String(255))
3966 integration_type = Column('integration_type', String(255))
3967 enabled = Column('enabled', Boolean(), nullable=False)
3967 enabled = Column('enabled', Boolean(), nullable=False)
3968 name = Column('name', String(255), nullable=False)
3968 name = Column('name', String(255), nullable=False)
3969 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3969 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3970 default=False)
3970 default=False)
3971
3971
3972 settings = Column(
3972 settings = Column(
3973 'settings_json', MutationObj.as_mutable(
3973 'settings_json', MutationObj.as_mutable(
3974 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3974 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3975 repo_id = Column(
3975 repo_id = Column(
3976 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3976 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3977 nullable=True, unique=None, default=None)
3977 nullable=True, unique=None, default=None)
3978 repo = relationship('Repository', lazy='joined')
3978 repo = relationship('Repository', lazy='joined')
3979
3979
3980 repo_group_id = Column(
3980 repo_group_id = Column(
3981 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3981 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3982 nullable=True, unique=None, default=None)
3982 nullable=True, unique=None, default=None)
3983 repo_group = relationship('RepoGroup', lazy='joined')
3983 repo_group = relationship('RepoGroup', lazy='joined')
3984
3984
3985 @property
3985 @property
3986 def scope(self):
3986 def scope(self):
3987 if self.repo:
3987 if self.repo:
3988 return repr(self.repo)
3988 return repr(self.repo)
3989 if self.repo_group:
3989 if self.repo_group:
3990 if self.child_repos_only:
3990 if self.child_repos_only:
3991 return repr(self.repo_group) + ' (child repos only)'
3991 return repr(self.repo_group) + ' (child repos only)'
3992 else:
3992 else:
3993 return repr(self.repo_group) + ' (recursive)'
3993 return repr(self.repo_group) + ' (recursive)'
3994 if self.child_repos_only:
3994 if self.child_repos_only:
3995 return 'root_repos'
3995 return 'root_repos'
3996 return 'global'
3996 return 'global'
3997
3997
3998 def __repr__(self):
3998 def __repr__(self):
3999 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3999 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4000
4000
4001
4001
4002 class RepoReviewRuleUser(Base, BaseModel):
4002 class RepoReviewRuleUser(Base, BaseModel):
4003 __tablename__ = 'repo_review_rules_users'
4003 __tablename__ = 'repo_review_rules_users'
4004 __table_args__ = (
4004 __table_args__ = (
4005 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4005 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4006 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4006 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4007 )
4007 )
4008 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4008 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4009 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4009 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4010 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4010 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4011 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4011 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4012 user = relationship('User')
4012 user = relationship('User')
4013
4013
4014 def rule_data(self):
4014 def rule_data(self):
4015 return {
4015 return {
4016 'mandatory': self.mandatory
4016 'mandatory': self.mandatory
4017 }
4017 }
4018
4018
4019
4019
4020 class RepoReviewRuleUserGroup(Base, BaseModel):
4020 class RepoReviewRuleUserGroup(Base, BaseModel):
4021 __tablename__ = 'repo_review_rules_users_groups'
4021 __tablename__ = 'repo_review_rules_users_groups'
4022 __table_args__ = (
4022 __table_args__ = (
4023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4025 )
4025 )
4026 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4026 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4027 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4027 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4028 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4028 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4029 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4029 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4030 users_group = relationship('UserGroup')
4030 users_group = relationship('UserGroup')
4031
4031
4032 def rule_data(self):
4032 def rule_data(self):
4033 return {
4033 return {
4034 'mandatory': self.mandatory
4034 'mandatory': self.mandatory
4035 }
4035 }
4036
4036
4037
4037
4038 class RepoReviewRule(Base, BaseModel):
4038 class RepoReviewRule(Base, BaseModel):
4039 __tablename__ = 'repo_review_rules'
4039 __tablename__ = 'repo_review_rules'
4040 __table_args__ = (
4040 __table_args__ = (
4041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4042 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4042 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4043 )
4043 )
4044
4044
4045 repo_review_rule_id = Column(
4045 repo_review_rule_id = Column(
4046 'repo_review_rule_id', Integer(), primary_key=True)
4046 'repo_review_rule_id', Integer(), primary_key=True)
4047 repo_id = Column(
4047 repo_id = Column(
4048 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4048 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4049 repo = relationship('Repository', backref='review_rules')
4049 repo = relationship('Repository', backref='review_rules')
4050
4050
4051 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4051 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4052 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4052 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4053
4053
4054 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4054 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4055 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4055 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4056 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4056 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4057 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4057 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4058
4058
4059 rule_users = relationship('RepoReviewRuleUser')
4059 rule_users = relationship('RepoReviewRuleUser')
4060 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4060 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4061
4061
4062 @hybrid_property
4062 @hybrid_property
4063 def branch_pattern(self):
4063 def branch_pattern(self):
4064 return self._branch_pattern or '*'
4064 return self._branch_pattern or '*'
4065
4065
4066 def _validate_glob(self, value):
4066 def _validate_glob(self, value):
4067 re.compile('^' + glob2re(value) + '$')
4067 re.compile('^' + glob2re(value) + '$')
4068
4068
4069 @branch_pattern.setter
4069 @branch_pattern.setter
4070 def branch_pattern(self, value):
4070 def branch_pattern(self, value):
4071 self._validate_glob(value)
4071 self._validate_glob(value)
4072 self._branch_pattern = value or '*'
4072 self._branch_pattern = value or '*'
4073
4073
4074 @hybrid_property
4074 @hybrid_property
4075 def file_pattern(self):
4075 def file_pattern(self):
4076 return self._file_pattern or '*'
4076 return self._file_pattern or '*'
4077
4077
4078 @file_pattern.setter
4078 @file_pattern.setter
4079 def file_pattern(self, value):
4079 def file_pattern(self, value):
4080 self._validate_glob(value)
4080 self._validate_glob(value)
4081 self._file_pattern = value or '*'
4081 self._file_pattern = value or '*'
4082
4082
4083 def matches(self, branch, files_changed):
4083 def matches(self, branch, files_changed):
4084 """
4084 """
4085 Check if this review rule matches a branch/files in a pull request
4085 Check if this review rule matches a branch/files in a pull request
4086
4086
4087 :param branch: branch name for the commit
4087 :param branch: branch name for the commit
4088 :param files_changed: list of file paths changed in the pull request
4088 :param files_changed: list of file paths changed in the pull request
4089 """
4089 """
4090
4090
4091 branch = branch or ''
4091 branch = branch or ''
4092 files_changed = files_changed or []
4092 files_changed = files_changed or []
4093
4093
4094 branch_matches = True
4094 branch_matches = True
4095 if branch:
4095 if branch:
4096 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4096 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4097 branch_matches = bool(branch_regex.search(branch))
4097 branch_matches = bool(branch_regex.search(branch))
4098
4098
4099 files_matches = True
4099 files_matches = True
4100 if self.file_pattern != '*':
4100 if self.file_pattern != '*':
4101 files_matches = False
4101 files_matches = False
4102 file_regex = re.compile(glob2re(self.file_pattern))
4102 file_regex = re.compile(glob2re(self.file_pattern))
4103 for filename in files_changed:
4103 for filename in files_changed:
4104 if file_regex.search(filename):
4104 if file_regex.search(filename):
4105 files_matches = True
4105 files_matches = True
4106 break
4106 break
4107
4107
4108 return branch_matches and files_matches
4108 return branch_matches and files_matches
4109
4109
4110 @property
4110 @property
4111 def review_users(self):
4111 def review_users(self):
4112 """ Returns the users which this rule applies to """
4112 """ Returns the users which this rule applies to """
4113
4113
4114 users = collections.OrderedDict()
4114 users = collections.OrderedDict()
4115
4115
4116 for rule_user in self.rule_users:
4116 for rule_user in self.rule_users:
4117 if rule_user.user.active:
4117 if rule_user.user.active:
4118 if rule_user.user not in users:
4118 if rule_user.user not in users:
4119 users[rule_user.user.username] = {
4119 users[rule_user.user.username] = {
4120 'user': rule_user.user,
4120 'user': rule_user.user,
4121 'source': 'user',
4121 'source': 'user',
4122 'source_data': {},
4122 'source_data': {},
4123 'data': rule_user.rule_data()
4123 'data': rule_user.rule_data()
4124 }
4124 }
4125
4125
4126 for rule_user_group in self.rule_user_groups:
4126 for rule_user_group in self.rule_user_groups:
4127 source_data = {
4127 source_data = {
4128 'name': rule_user_group.users_group.users_group_name,
4128 'name': rule_user_group.users_group.users_group_name,
4129 'members': len(rule_user_group.users_group.members)
4129 'members': len(rule_user_group.users_group.members)
4130 }
4130 }
4131 for member in rule_user_group.users_group.members:
4131 for member in rule_user_group.users_group.members:
4132 if member.user.active:
4132 if member.user.active:
4133 users[member.user.username] = {
4133 users[member.user.username] = {
4134 'user': member.user,
4134 'user': member.user,
4135 'source': 'user_group',
4135 'source': 'user_group',
4136 'source_data': source_data,
4136 'source_data': source_data,
4137 'data': rule_user_group.rule_data()
4137 'data': rule_user_group.rule_data()
4138 }
4138 }
4139
4139
4140 return users
4140 return users
4141
4141
4142 def __repr__(self):
4142 def __repr__(self):
4143 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4143 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4144 self.repo_review_rule_id, self.repo)
4144 self.repo_review_rule_id, self.repo)
4145
4145
4146
4146
4147 class DbMigrateVersion(Base, BaseModel):
4147 class DbMigrateVersion(Base, BaseModel):
4148 __tablename__ = 'db_migrate_version'
4148 __tablename__ = 'db_migrate_version'
4149 __table_args__ = (
4149 __table_args__ = (
4150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4151 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4151 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4152 )
4152 )
4153 repository_id = Column('repository_id', String(250), primary_key=True)
4153 repository_id = Column('repository_id', String(250), primary_key=True)
4154 repository_path = Column('repository_path', Text)
4154 repository_path = Column('repository_path', Text)
4155 version = Column('version', Integer)
4155 version = Column('version', Integer)
4156
4156
4157
4157
4158 class DbSession(Base, BaseModel):
4158 class DbSession(Base, BaseModel):
4159 __tablename__ = 'db_session'
4159 __tablename__ = 'db_session'
4160 __table_args__ = (
4160 __table_args__ = (
4161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4162 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4162 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4163 )
4163 )
4164
4164
4165 def __repr__(self):
4165 def __repr__(self):
4166 return '<DB:DbSession({})>'.format(self.id)
4166 return '<DB:DbSession({})>'.format(self.id)
4167
4167
4168 id = Column('id', Integer())
4168 id = Column('id', Integer())
4169 namespace = Column('namespace', String(255), primary_key=True)
4169 namespace = Column('namespace', String(255), primary_key=True)
4170 accessed = Column('accessed', DateTime, nullable=False)
4170 accessed = Column('accessed', DateTime, nullable=False)
4171 created = Column('created', DateTime, nullable=False)
4171 created = Column('created', DateTime, nullable=False)
4172 data = Column('data', PickleType, nullable=False)
4172 data = Column('data', PickleType, nullable=False)
@@ -1,4232 +1,4232 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 sqlalchemy.exc import IntegrityError # noqa
45 from sqlalchemy.exc import IntegrityError # noqa
46 from sqlalchemy.dialects.mysql import LONGTEXT
46 from sqlalchemy.dialects.mysql import LONGTEXT
47 from beaker.cache import cache_region
47 from beaker.cache import cache_region
48 from zope.cachedescriptors.property import Lazy as LazyProperty
48 from zope.cachedescriptors.property import Lazy as LazyProperty
49
49
50 from pyramid.threadlocal import get_current_request
50 from pyramid.threadlocal import get_current_request
51
51
52 from rhodecode.translation import _
52 from rhodecode.translation import _
53 from rhodecode.lib.vcs import get_vcs_instance
53 from rhodecode.lib.vcs import get_vcs_instance
54 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
54 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
56 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
57 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
57 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
58 glob2re, StrictAttributeDict, cleaned_uri)
58 glob2re, StrictAttributeDict, cleaned_uri)
59 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
59 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
60 from rhodecode.lib.ext_json import json
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
62 from rhodecode.lib.encrypt import AESCipher
63
63
64 from rhodecode.model.meta import Base, Session
64 from rhodecode.model.meta import Base, Session
65
65
66 URL_SEP = '/'
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
68
68
69 # =============================================================================
69 # =============================================================================
70 # BASE CLASSES
70 # BASE CLASSES
71 # =============================================================================
71 # =============================================================================
72
72
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
74 # beaker.session.secret if first is not set.
75 # and initialized at environment.py
75 # and initialized at environment.py
76 ENCRYPTION_KEY = None
76 ENCRYPTION_KEY = None
77
77
78 # used to sort permissions by types, '#' used here is not allowed to be in
78 # used to sort permissions by types, '#' used here is not allowed to be in
79 # usernames, and it's very early in sorted string.printable table.
79 # usernames, and it's very early in sorted string.printable table.
80 PERMISSION_TYPE_SORT = {
80 PERMISSION_TYPE_SORT = {
81 'admin': '####',
81 'admin': '####',
82 'write': '###',
82 'write': '###',
83 'read': '##',
83 'read': '##',
84 'none': '#',
84 'none': '#',
85 }
85 }
86
86
87
87
88 def display_user_sort(obj):
88 def display_user_sort(obj):
89 """
89 """
90 Sort function used to sort permissions in .permissions() function of
90 Sort function used to sort permissions in .permissions() function of
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
92 of all other resources
92 of all other resources
93 """
93 """
94
94
95 if obj.username == User.DEFAULT_USER:
95 if obj.username == User.DEFAULT_USER:
96 return '#####'
96 return '#####'
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
98 return prefix + obj.username
98 return prefix + obj.username
99
99
100
100
101 def display_user_group_sort(obj):
101 def display_user_group_sort(obj):
102 """
102 """
103 Sort function used to sort permissions in .permissions() function of
103 Sort function used to sort permissions in .permissions() function of
104 Repository, RepoGroup, UserGroup. Also it put the default user in front
104 Repository, RepoGroup, UserGroup. Also it put the default user in front
105 of all other resources
105 of all other resources
106 """
106 """
107
107
108 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
108 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
109 return prefix + obj.users_group_name
109 return prefix + obj.users_group_name
110
110
111
111
112 def _hash_key(k):
112 def _hash_key(k):
113 return md5_safe(k)
113 return md5_safe(k)
114
114
115
115
116 def in_filter_generator(qry, items, limit=500):
116 def in_filter_generator(qry, items, limit=500):
117 """
117 """
118 Splits IN() into multiple with OR
118 Splits IN() into multiple with OR
119 e.g.::
119 e.g.::
120 cnt = Repository.query().filter(
120 cnt = Repository.query().filter(
121 or_(
121 or_(
122 *in_filter_generator(Repository.repo_id, range(100000))
122 *in_filter_generator(Repository.repo_id, range(100000))
123 )).count()
123 )).count()
124 """
124 """
125 if not items:
125 if not items:
126 # empty list will cause empty query which might cause security issues
126 # empty list will cause empty query which might cause security issues
127 # this can lead to hidden unpleasant results
127 # this can lead to hidden unpleasant results
128 items = [-1]
128 items = [-1]
129
129
130 parts = []
130 parts = []
131 for chunk in xrange(0, len(items), limit):
131 for chunk in xrange(0, len(items), limit):
132 parts.append(
132 parts.append(
133 qry.in_(items[chunk: chunk + limit])
133 qry.in_(items[chunk: chunk + limit])
134 )
134 )
135
135
136 return parts
136 return parts
137
137
138
138
139 class EncryptedTextValue(TypeDecorator):
139 class EncryptedTextValue(TypeDecorator):
140 """
140 """
141 Special column for encrypted long text data, use like::
141 Special column for encrypted long text data, use like::
142
142
143 value = Column("encrypted_value", EncryptedValue(), nullable=False)
143 value = Column("encrypted_value", EncryptedValue(), nullable=False)
144
144
145 This column is intelligent so if value is in unencrypted form it return
145 This column is intelligent so if value is in unencrypted form it return
146 unencrypted form, but on save it always encrypts
146 unencrypted form, but on save it always encrypts
147 """
147 """
148 impl = Text
148 impl = Text
149
149
150 def process_bind_param(self, value, dialect):
150 def process_bind_param(self, value, dialect):
151 if not value:
151 if not value:
152 return value
152 return value
153 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
153 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
154 # protect against double encrypting if someone manually starts
154 # protect against double encrypting if someone manually starts
155 # doing
155 # doing
156 raise ValueError('value needs to be in unencrypted format, ie. '
156 raise ValueError('value needs to be in unencrypted format, ie. '
157 'not starting with enc$aes')
157 'not starting with enc$aes')
158 return 'enc$aes_hmac$%s' % AESCipher(
158 return 'enc$aes_hmac$%s' % AESCipher(
159 ENCRYPTION_KEY, hmac=True).encrypt(value)
159 ENCRYPTION_KEY, hmac=True).encrypt(value)
160
160
161 def process_result_value(self, value, dialect):
161 def process_result_value(self, value, dialect):
162 import rhodecode
162 import rhodecode
163
163
164 if not value:
164 if not value:
165 return value
165 return value
166
166
167 parts = value.split('$', 3)
167 parts = value.split('$', 3)
168 if not len(parts) == 3:
168 if not len(parts) == 3:
169 # probably not encrypted values
169 # probably not encrypted values
170 return value
170 return value
171 else:
171 else:
172 if parts[0] != 'enc':
172 if parts[0] != 'enc':
173 # parts ok but without our header ?
173 # parts ok but without our header ?
174 return value
174 return value
175 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
175 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
176 'rhodecode.encrypted_values.strict') or True)
176 'rhodecode.encrypted_values.strict') or True)
177 # at that stage we know it's our encryption
177 # at that stage we know it's our encryption
178 if parts[1] == 'aes':
178 if parts[1] == 'aes':
179 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
179 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
180 elif parts[1] == 'aes_hmac':
180 elif parts[1] == 'aes_hmac':
181 decrypted_data = AESCipher(
181 decrypted_data = AESCipher(
182 ENCRYPTION_KEY, hmac=True,
182 ENCRYPTION_KEY, hmac=True,
183 strict_verification=enc_strict_mode).decrypt(parts[2])
183 strict_verification=enc_strict_mode).decrypt(parts[2])
184 else:
184 else:
185 raise ValueError(
185 raise ValueError(
186 'Encryption type part is wrong, must be `aes` '
186 'Encryption type part is wrong, must be `aes` '
187 'or `aes_hmac`, got `%s` instead' % (parts[1]))
187 'or `aes_hmac`, got `%s` instead' % (parts[1]))
188 return decrypted_data
188 return decrypted_data
189
189
190
190
191 class BaseModel(object):
191 class BaseModel(object):
192 """
192 """
193 Base Model for all classes
193 Base Model for all classes
194 """
194 """
195
195
196 @classmethod
196 @classmethod
197 def _get_keys(cls):
197 def _get_keys(cls):
198 """return column names for this model """
198 """return column names for this model """
199 return class_mapper(cls).c.keys()
199 return class_mapper(cls).c.keys()
200
200
201 def get_dict(self):
201 def get_dict(self):
202 """
202 """
203 return dict with keys and values corresponding
203 return dict with keys and values corresponding
204 to this model data """
204 to this model data """
205
205
206 d = {}
206 d = {}
207 for k in self._get_keys():
207 for k in self._get_keys():
208 d[k] = getattr(self, k)
208 d[k] = getattr(self, k)
209
209
210 # also use __json__() if present to get additional fields
210 # also use __json__() if present to get additional fields
211 _json_attr = getattr(self, '__json__', None)
211 _json_attr = getattr(self, '__json__', None)
212 if _json_attr:
212 if _json_attr:
213 # update with attributes from __json__
213 # update with attributes from __json__
214 if callable(_json_attr):
214 if callable(_json_attr):
215 _json_attr = _json_attr()
215 _json_attr = _json_attr()
216 for k, val in _json_attr.iteritems():
216 for k, val in _json_attr.iteritems():
217 d[k] = val
217 d[k] = val
218 return d
218 return d
219
219
220 def get_appstruct(self):
220 def get_appstruct(self):
221 """return list with keys and values tuples corresponding
221 """return list with keys and values tuples corresponding
222 to this model data """
222 to this model data """
223
223
224 l = []
224 l = []
225 for k in self._get_keys():
225 for k in self._get_keys():
226 l.append((k, getattr(self, k),))
226 l.append((k, getattr(self, k),))
227 return l
227 return l
228
228
229 def populate_obj(self, populate_dict):
229 def populate_obj(self, populate_dict):
230 """populate model with data from given populate_dict"""
230 """populate model with data from given populate_dict"""
231
231
232 for k in self._get_keys():
232 for k in self._get_keys():
233 if k in populate_dict:
233 if k in populate_dict:
234 setattr(self, k, populate_dict[k])
234 setattr(self, k, populate_dict[k])
235
235
236 @classmethod
236 @classmethod
237 def query(cls):
237 def query(cls):
238 return Session().query(cls)
238 return Session().query(cls)
239
239
240 @classmethod
240 @classmethod
241 def get(cls, id_):
241 def get(cls, id_):
242 if id_:
242 if id_:
243 return cls.query().get(id_)
243 return cls.query().get(id_)
244
244
245 @classmethod
245 @classmethod
246 def get_or_404(cls, id_):
246 def get_or_404(cls, id_):
247 from pyramid.httpexceptions import HTTPNotFound
247 from pyramid.httpexceptions import HTTPNotFound
248
248
249 try:
249 try:
250 id_ = int(id_)
250 id_ = int(id_)
251 except (TypeError, ValueError):
251 except (TypeError, ValueError):
252 raise HTTPNotFound()
252 raise HTTPNotFound()
253
253
254 res = cls.query().get(id_)
254 res = cls.query().get(id_)
255 if not res:
255 if not res:
256 raise HTTPNotFound()
256 raise HTTPNotFound()
257 return res
257 return res
258
258
259 @classmethod
259 @classmethod
260 def getAll(cls):
260 def getAll(cls):
261 # deprecated and left for backward compatibility
261 # deprecated and left for backward compatibility
262 return cls.get_all()
262 return cls.get_all()
263
263
264 @classmethod
264 @classmethod
265 def get_all(cls):
265 def get_all(cls):
266 return cls.query().all()
266 return cls.query().all()
267
267
268 @classmethod
268 @classmethod
269 def delete(cls, id_):
269 def delete(cls, id_):
270 obj = cls.query().get(id_)
270 obj = cls.query().get(id_)
271 Session().delete(obj)
271 Session().delete(obj)
272
272
273 @classmethod
273 @classmethod
274 def identity_cache(cls, session, attr_name, value):
274 def identity_cache(cls, session, attr_name, value):
275 exist_in_session = []
275 exist_in_session = []
276 for (item_cls, pkey), instance in session.identity_map.items():
276 for (item_cls, pkey), instance in session.identity_map.items():
277 if cls == item_cls and getattr(instance, attr_name) == value:
277 if cls == item_cls and getattr(instance, attr_name) == value:
278 exist_in_session.append(instance)
278 exist_in_session.append(instance)
279 if exist_in_session:
279 if exist_in_session:
280 if len(exist_in_session) == 1:
280 if len(exist_in_session) == 1:
281 return exist_in_session[0]
281 return exist_in_session[0]
282 log.exception(
282 log.exception(
283 'multiple objects with attr %s and '
283 'multiple objects with attr %s and '
284 'value %s found with same name: %r',
284 'value %s found with same name: %r',
285 attr_name, value, exist_in_session)
285 attr_name, value, exist_in_session)
286
286
287 def __repr__(self):
287 def __repr__(self):
288 if hasattr(self, '__unicode__'):
288 if hasattr(self, '__unicode__'):
289 # python repr needs to return str
289 # python repr needs to return str
290 try:
290 try:
291 return safe_str(self.__unicode__())
291 return safe_str(self.__unicode__())
292 except UnicodeDecodeError:
292 except UnicodeDecodeError:
293 pass
293 pass
294 return '<DB:%s>' % (self.__class__.__name__)
294 return '<DB:%s>' % (self.__class__.__name__)
295
295
296
296
297 class RhodeCodeSetting(Base, BaseModel):
297 class RhodeCodeSetting(Base, BaseModel):
298 __tablename__ = 'rhodecode_settings'
298 __tablename__ = 'rhodecode_settings'
299 __table_args__ = (
299 __table_args__ = (
300 UniqueConstraint('app_settings_name'),
300 UniqueConstraint('app_settings_name'),
301 {'extend_existing': True, 'mysql_engine': 'InnoDB',
301 {'extend_existing': True, 'mysql_engine': 'InnoDB',
302 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
302 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
303 )
303 )
304
304
305 SETTINGS_TYPES = {
305 SETTINGS_TYPES = {
306 'str': safe_str,
306 'str': safe_str,
307 'int': safe_int,
307 'int': safe_int,
308 'unicode': safe_unicode,
308 'unicode': safe_unicode,
309 'bool': str2bool,
309 'bool': str2bool,
310 'list': functools.partial(aslist, sep=',')
310 'list': functools.partial(aslist, sep=',')
311 }
311 }
312 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
312 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
313 GLOBAL_CONF_KEY = 'app_settings'
313 GLOBAL_CONF_KEY = 'app_settings'
314
314
315 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
315 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
316 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
316 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
317 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
317 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
318 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
318 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
319
319
320 def __init__(self, key='', val='', type='unicode'):
320 def __init__(self, key='', val='', type='unicode'):
321 self.app_settings_name = key
321 self.app_settings_name = key
322 self.app_settings_type = type
322 self.app_settings_type = type
323 self.app_settings_value = val
323 self.app_settings_value = val
324
324
325 @validates('_app_settings_value')
325 @validates('_app_settings_value')
326 def validate_settings_value(self, key, val):
326 def validate_settings_value(self, key, val):
327 assert type(val) == unicode
327 assert type(val) == unicode
328 return val
328 return val
329
329
330 @hybrid_property
330 @hybrid_property
331 def app_settings_value(self):
331 def app_settings_value(self):
332 v = self._app_settings_value
332 v = self._app_settings_value
333 _type = self.app_settings_type
333 _type = self.app_settings_type
334 if _type:
334 if _type:
335 _type = self.app_settings_type.split('.')[0]
335 _type = self.app_settings_type.split('.')[0]
336 # decode the encrypted value
336 # decode the encrypted value
337 if 'encrypted' in self.app_settings_type:
337 if 'encrypted' in self.app_settings_type:
338 cipher = EncryptedTextValue()
338 cipher = EncryptedTextValue()
339 v = safe_unicode(cipher.process_result_value(v, None))
339 v = safe_unicode(cipher.process_result_value(v, None))
340
340
341 converter = self.SETTINGS_TYPES.get(_type) or \
341 converter = self.SETTINGS_TYPES.get(_type) or \
342 self.SETTINGS_TYPES['unicode']
342 self.SETTINGS_TYPES['unicode']
343 return converter(v)
343 return converter(v)
344
344
345 @app_settings_value.setter
345 @app_settings_value.setter
346 def app_settings_value(self, val):
346 def app_settings_value(self, val):
347 """
347 """
348 Setter that will always make sure we use unicode in app_settings_value
348 Setter that will always make sure we use unicode in app_settings_value
349
349
350 :param val:
350 :param val:
351 """
351 """
352 val = safe_unicode(val)
352 val = safe_unicode(val)
353 # encode the encrypted value
353 # encode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 val = safe_unicode(cipher.process_bind_param(val, None))
356 val = safe_unicode(cipher.process_bind_param(val, None))
357 self._app_settings_value = val
357 self._app_settings_value = val
358
358
359 @hybrid_property
359 @hybrid_property
360 def app_settings_type(self):
360 def app_settings_type(self):
361 return self._app_settings_type
361 return self._app_settings_type
362
362
363 @app_settings_type.setter
363 @app_settings_type.setter
364 def app_settings_type(self, val):
364 def app_settings_type(self, val):
365 if val.split('.')[0] not in self.SETTINGS_TYPES:
365 if val.split('.')[0] not in self.SETTINGS_TYPES:
366 raise Exception('type must be one of %s got %s'
366 raise Exception('type must be one of %s got %s'
367 % (self.SETTINGS_TYPES.keys(), val))
367 % (self.SETTINGS_TYPES.keys(), val))
368 self._app_settings_type = val
368 self._app_settings_type = val
369
369
370 def __unicode__(self):
370 def __unicode__(self):
371 return u"<%s('%s:%s[%s]')>" % (
371 return u"<%s('%s:%s[%s]')>" % (
372 self.__class__.__name__,
372 self.__class__.__name__,
373 self.app_settings_name, self.app_settings_value,
373 self.app_settings_name, self.app_settings_value,
374 self.app_settings_type
374 self.app_settings_type
375 )
375 )
376
376
377
377
378 class RhodeCodeUi(Base, BaseModel):
378 class RhodeCodeUi(Base, BaseModel):
379 __tablename__ = 'rhodecode_ui'
379 __tablename__ = 'rhodecode_ui'
380 __table_args__ = (
380 __table_args__ = (
381 UniqueConstraint('ui_key'),
381 UniqueConstraint('ui_key'),
382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
383 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
383 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
384 )
384 )
385
385
386 HOOK_REPO_SIZE = 'changegroup.repo_size'
386 HOOK_REPO_SIZE = 'changegroup.repo_size'
387 # HG
387 # HG
388 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
388 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
389 HOOK_PULL = 'outgoing.pull_logger'
389 HOOK_PULL = 'outgoing.pull_logger'
390 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
390 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
391 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
391 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
392 HOOK_PUSH = 'changegroup.push_logger'
392 HOOK_PUSH = 'changegroup.push_logger'
393 HOOK_PUSH_KEY = 'pushkey.key_push'
393 HOOK_PUSH_KEY = 'pushkey.key_push'
394
394
395 # TODO: johbo: Unify way how hooks are configured for git and hg,
395 # TODO: johbo: Unify way how hooks are configured for git and hg,
396 # git part is currently hardcoded.
396 # git part is currently hardcoded.
397
397
398 # SVN PATTERNS
398 # SVN PATTERNS
399 SVN_BRANCH_ID = 'vcs_svn_branch'
399 SVN_BRANCH_ID = 'vcs_svn_branch'
400 SVN_TAG_ID = 'vcs_svn_tag'
400 SVN_TAG_ID = 'vcs_svn_tag'
401
401
402 ui_id = Column(
402 ui_id = Column(
403 "ui_id", Integer(), nullable=False, unique=True, default=None,
403 "ui_id", Integer(), nullable=False, unique=True, default=None,
404 primary_key=True)
404 primary_key=True)
405 ui_section = Column(
405 ui_section = Column(
406 "ui_section", String(255), nullable=True, unique=None, default=None)
406 "ui_section", String(255), nullable=True, unique=None, default=None)
407 ui_key = Column(
407 ui_key = Column(
408 "ui_key", String(255), nullable=True, unique=None, default=None)
408 "ui_key", String(255), nullable=True, unique=None, default=None)
409 ui_value = Column(
409 ui_value = Column(
410 "ui_value", String(255), nullable=True, unique=None, default=None)
410 "ui_value", String(255), nullable=True, unique=None, default=None)
411 ui_active = Column(
411 ui_active = Column(
412 "ui_active", Boolean(), nullable=True, unique=None, default=True)
412 "ui_active", Boolean(), nullable=True, unique=None, default=True)
413
413
414 def __repr__(self):
414 def __repr__(self):
415 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
415 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
416 self.ui_key, self.ui_value)
416 self.ui_key, self.ui_value)
417
417
418
418
419 class RepoRhodeCodeSetting(Base, BaseModel):
419 class RepoRhodeCodeSetting(Base, BaseModel):
420 __tablename__ = 'repo_rhodecode_settings'
420 __tablename__ = 'repo_rhodecode_settings'
421 __table_args__ = (
421 __table_args__ = (
422 UniqueConstraint(
422 UniqueConstraint(
423 'app_settings_name', 'repository_id',
423 'app_settings_name', 'repository_id',
424 name='uq_repo_rhodecode_setting_name_repo_id'),
424 name='uq_repo_rhodecode_setting_name_repo_id'),
425 {'extend_existing': True, 'mysql_engine': 'InnoDB',
425 {'extend_existing': True, 'mysql_engine': 'InnoDB',
426 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
426 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
427 )
427 )
428
428
429 repository_id = Column(
429 repository_id = Column(
430 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
430 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
431 nullable=False)
431 nullable=False)
432 app_settings_id = Column(
432 app_settings_id = Column(
433 "app_settings_id", Integer(), nullable=False, unique=True,
433 "app_settings_id", Integer(), nullable=False, unique=True,
434 default=None, primary_key=True)
434 default=None, primary_key=True)
435 app_settings_name = Column(
435 app_settings_name = Column(
436 "app_settings_name", String(255), nullable=True, unique=None,
436 "app_settings_name", String(255), nullable=True, unique=None,
437 default=None)
437 default=None)
438 _app_settings_value = Column(
438 _app_settings_value = Column(
439 "app_settings_value", String(4096), nullable=True, unique=None,
439 "app_settings_value", String(4096), nullable=True, unique=None,
440 default=None)
440 default=None)
441 _app_settings_type = Column(
441 _app_settings_type = Column(
442 "app_settings_type", String(255), nullable=True, unique=None,
442 "app_settings_type", String(255), nullable=True, unique=None,
443 default=None)
443 default=None)
444
444
445 repository = relationship('Repository')
445 repository = relationship('Repository')
446
446
447 def __init__(self, repository_id, key='', val='', type='unicode'):
447 def __init__(self, repository_id, key='', val='', type='unicode'):
448 self.repository_id = repository_id
448 self.repository_id = repository_id
449 self.app_settings_name = key
449 self.app_settings_name = key
450 self.app_settings_type = type
450 self.app_settings_type = type
451 self.app_settings_value = val
451 self.app_settings_value = val
452
452
453 @validates('_app_settings_value')
453 @validates('_app_settings_value')
454 def validate_settings_value(self, key, val):
454 def validate_settings_value(self, key, val):
455 assert type(val) == unicode
455 assert type(val) == unicode
456 return val
456 return val
457
457
458 @hybrid_property
458 @hybrid_property
459 def app_settings_value(self):
459 def app_settings_value(self):
460 v = self._app_settings_value
460 v = self._app_settings_value
461 type_ = self.app_settings_type
461 type_ = self.app_settings_type
462 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
462 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
463 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
463 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
464 return converter(v)
464 return converter(v)
465
465
466 @app_settings_value.setter
466 @app_settings_value.setter
467 def app_settings_value(self, val):
467 def app_settings_value(self, val):
468 """
468 """
469 Setter that will always make sure we use unicode in app_settings_value
469 Setter that will always make sure we use unicode in app_settings_value
470
470
471 :param val:
471 :param val:
472 """
472 """
473 self._app_settings_value = safe_unicode(val)
473 self._app_settings_value = safe_unicode(val)
474
474
475 @hybrid_property
475 @hybrid_property
476 def app_settings_type(self):
476 def app_settings_type(self):
477 return self._app_settings_type
477 return self._app_settings_type
478
478
479 @app_settings_type.setter
479 @app_settings_type.setter
480 def app_settings_type(self, val):
480 def app_settings_type(self, val):
481 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
481 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
482 if val not in SETTINGS_TYPES:
482 if val not in SETTINGS_TYPES:
483 raise Exception('type must be one of %s got %s'
483 raise Exception('type must be one of %s got %s'
484 % (SETTINGS_TYPES.keys(), val))
484 % (SETTINGS_TYPES.keys(), val))
485 self._app_settings_type = val
485 self._app_settings_type = val
486
486
487 def __unicode__(self):
487 def __unicode__(self):
488 return u"<%s('%s:%s:%s[%s]')>" % (
488 return u"<%s('%s:%s:%s[%s]')>" % (
489 self.__class__.__name__, self.repository.repo_name,
489 self.__class__.__name__, self.repository.repo_name,
490 self.app_settings_name, self.app_settings_value,
490 self.app_settings_name, self.app_settings_value,
491 self.app_settings_type
491 self.app_settings_type
492 )
492 )
493
493
494
494
495 class RepoRhodeCodeUi(Base, BaseModel):
495 class RepoRhodeCodeUi(Base, BaseModel):
496 __tablename__ = 'repo_rhodecode_ui'
496 __tablename__ = 'repo_rhodecode_ui'
497 __table_args__ = (
497 __table_args__ = (
498 UniqueConstraint(
498 UniqueConstraint(
499 'repository_id', 'ui_section', 'ui_key',
499 'repository_id', 'ui_section', 'ui_key',
500 name='uq_repo_rhodecode_ui_repository_id_section_key'),
500 name='uq_repo_rhodecode_ui_repository_id_section_key'),
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 )
503 )
504
504
505 repository_id = Column(
505 repository_id = Column(
506 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
506 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
507 nullable=False)
507 nullable=False)
508 ui_id = Column(
508 ui_id = Column(
509 "ui_id", Integer(), nullable=False, unique=True, default=None,
509 "ui_id", Integer(), nullable=False, unique=True, default=None,
510 primary_key=True)
510 primary_key=True)
511 ui_section = Column(
511 ui_section = Column(
512 "ui_section", String(255), nullable=True, unique=None, default=None)
512 "ui_section", String(255), nullable=True, unique=None, default=None)
513 ui_key = Column(
513 ui_key = Column(
514 "ui_key", String(255), nullable=True, unique=None, default=None)
514 "ui_key", String(255), nullable=True, unique=None, default=None)
515 ui_value = Column(
515 ui_value = Column(
516 "ui_value", String(255), nullable=True, unique=None, default=None)
516 "ui_value", String(255), nullable=True, unique=None, default=None)
517 ui_active = Column(
517 ui_active = Column(
518 "ui_active", Boolean(), nullable=True, unique=None, default=True)
518 "ui_active", Boolean(), nullable=True, unique=None, default=True)
519
519
520 repository = relationship('Repository')
520 repository = relationship('Repository')
521
521
522 def __repr__(self):
522 def __repr__(self):
523 return '<%s[%s:%s]%s=>%s]>' % (
523 return '<%s[%s:%s]%s=>%s]>' % (
524 self.__class__.__name__, self.repository.repo_name,
524 self.__class__.__name__, self.repository.repo_name,
525 self.ui_section, self.ui_key, self.ui_value)
525 self.ui_section, self.ui_key, self.ui_value)
526
526
527
527
528 class User(Base, BaseModel):
528 class User(Base, BaseModel):
529 __tablename__ = 'users'
529 __tablename__ = 'users'
530 __table_args__ = (
530 __table_args__ = (
531 UniqueConstraint('username'), UniqueConstraint('email'),
531 UniqueConstraint('username'), UniqueConstraint('email'),
532 Index('u_username_idx', 'username'),
532 Index('u_username_idx', 'username'),
533 Index('u_email_idx', 'email'),
533 Index('u_email_idx', 'email'),
534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
536 )
536 )
537 DEFAULT_USER = 'default'
537 DEFAULT_USER = 'default'
538 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
538 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
539 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
539 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
540
540
541 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
541 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
542 username = Column("username", String(255), nullable=True, unique=None, default=None)
542 username = Column("username", String(255), nullable=True, unique=None, default=None)
543 password = Column("password", String(255), nullable=True, unique=None, default=None)
543 password = Column("password", String(255), nullable=True, unique=None, default=None)
544 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
544 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
545 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
545 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
546 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
546 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
547 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
547 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
548 _email = Column("email", String(255), nullable=True, unique=None, default=None)
548 _email = Column("email", String(255), nullable=True, unique=None, default=None)
549 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
549 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
550 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
550 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
551
551
552 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
552 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
553 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
553 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
554 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
554 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
555 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
555 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
556 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
557 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
557 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
558
558
559 user_log = relationship('UserLog')
559 user_log = relationship('UserLog')
560 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
560 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
561
561
562 repositories = relationship('Repository')
562 repositories = relationship('Repository')
563 repository_groups = relationship('RepoGroup')
563 repository_groups = relationship('RepoGroup')
564 user_groups = relationship('UserGroup')
564 user_groups = relationship('UserGroup')
565
565
566 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
566 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
567 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
567 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
568
568
569 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
569 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
570 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
570 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
571 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
571 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
572
572
573 group_member = relationship('UserGroupMember', cascade='all')
573 group_member = relationship('UserGroupMember', cascade='all')
574
574
575 notifications = relationship('UserNotification', cascade='all')
575 notifications = relationship('UserNotification', cascade='all')
576 # notifications assigned to this user
576 # notifications assigned to this user
577 user_created_notifications = relationship('Notification', cascade='all')
577 user_created_notifications = relationship('Notification', cascade='all')
578 # comments created by this user
578 # comments created by this user
579 user_comments = relationship('ChangesetComment', cascade='all')
579 user_comments = relationship('ChangesetComment', cascade='all')
580 # user profile extra info
580 # user profile extra info
581 user_emails = relationship('UserEmailMap', cascade='all')
581 user_emails = relationship('UserEmailMap', cascade='all')
582 user_ip_map = relationship('UserIpMap', cascade='all')
582 user_ip_map = relationship('UserIpMap', cascade='all')
583 user_auth_tokens = relationship('UserApiKeys', cascade='all')
583 user_auth_tokens = relationship('UserApiKeys', cascade='all')
584 user_ssh_keys = relationship('UserSshKeys', cascade='all')
584 user_ssh_keys = relationship('UserSshKeys', cascade='all')
585
585
586 # gists
586 # gists
587 user_gists = relationship('Gist', cascade='all')
587 user_gists = relationship('Gist', cascade='all')
588 # user pull requests
588 # user pull requests
589 user_pull_requests = relationship('PullRequest', cascade='all')
589 user_pull_requests = relationship('PullRequest', cascade='all')
590 # external identities
590 # external identities
591 extenal_identities = relationship(
591 extenal_identities = relationship(
592 'ExternalIdentity',
592 'ExternalIdentity',
593 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
593 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
594 cascade='all')
594 cascade='all')
595 # review rules
595 # review rules
596 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
596 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
597
597
598 def __unicode__(self):
598 def __unicode__(self):
599 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
599 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
600 self.user_id, self.username)
600 self.user_id, self.username)
601
601
602 @hybrid_property
602 @hybrid_property
603 def email(self):
603 def email(self):
604 return self._email
604 return self._email
605
605
606 @email.setter
606 @email.setter
607 def email(self, val):
607 def email(self, val):
608 self._email = val.lower() if val else None
608 self._email = val.lower() if val else None
609
609
610 @hybrid_property
610 @hybrid_property
611 def first_name(self):
611 def first_name(self):
612 from rhodecode.lib import helpers as h
612 from rhodecode.lib import helpers as h
613 if self.name:
613 if self.name:
614 return h.escape(self.name)
614 return h.escape(self.name)
615 return self.name
615 return self.name
616
616
617 @hybrid_property
617 @hybrid_property
618 def last_name(self):
618 def last_name(self):
619 from rhodecode.lib import helpers as h
619 from rhodecode.lib import helpers as h
620 if self.lastname:
620 if self.lastname:
621 return h.escape(self.lastname)
621 return h.escape(self.lastname)
622 return self.lastname
622 return self.lastname
623
623
624 @hybrid_property
624 @hybrid_property
625 def api_key(self):
625 def api_key(self):
626 """
626 """
627 Fetch if exist an auth-token with role ALL connected to this user
627 Fetch if exist an auth-token with role ALL connected to this user
628 """
628 """
629 user_auth_token = UserApiKeys.query()\
629 user_auth_token = UserApiKeys.query()\
630 .filter(UserApiKeys.user_id == self.user_id)\
630 .filter(UserApiKeys.user_id == self.user_id)\
631 .filter(or_(UserApiKeys.expires == -1,
631 .filter(or_(UserApiKeys.expires == -1,
632 UserApiKeys.expires >= time.time()))\
632 UserApiKeys.expires >= time.time()))\
633 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
633 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
634 if user_auth_token:
634 if user_auth_token:
635 user_auth_token = user_auth_token.api_key
635 user_auth_token = user_auth_token.api_key
636
636
637 return user_auth_token
637 return user_auth_token
638
638
639 @api_key.setter
639 @api_key.setter
640 def api_key(self, val):
640 def api_key(self, val):
641 # don't allow to set API key this is deprecated for now
641 # don't allow to set API key this is deprecated for now
642 self._api_key = None
642 self._api_key = None
643
643
644 @property
644 @property
645 def reviewer_pull_requests(self):
645 def reviewer_pull_requests(self):
646 return PullRequestReviewers.query() \
646 return PullRequestReviewers.query() \
647 .options(joinedload(PullRequestReviewers.pull_request)) \
647 .options(joinedload(PullRequestReviewers.pull_request)) \
648 .filter(PullRequestReviewers.user_id == self.user_id) \
648 .filter(PullRequestReviewers.user_id == self.user_id) \
649 .all()
649 .all()
650
650
651 @property
651 @property
652 def firstname(self):
652 def firstname(self):
653 # alias for future
653 # alias for future
654 return self.name
654 return self.name
655
655
656 @property
656 @property
657 def emails(self):
657 def emails(self):
658 other = UserEmailMap.query()\
658 other = UserEmailMap.query()\
659 .filter(UserEmailMap.user == self) \
659 .filter(UserEmailMap.user == self) \
660 .order_by(UserEmailMap.email_id.asc()) \
660 .order_by(UserEmailMap.email_id.asc()) \
661 .all()
661 .all()
662 return [self.email] + [x.email for x in other]
662 return [self.email] + [x.email for x in other]
663
663
664 @property
664 @property
665 def auth_tokens(self):
665 def auth_tokens(self):
666 auth_tokens = self.get_auth_tokens()
666 auth_tokens = self.get_auth_tokens()
667 return [x.api_key for x in auth_tokens]
667 return [x.api_key for x in auth_tokens]
668
668
669 def get_auth_tokens(self):
669 def get_auth_tokens(self):
670 return UserApiKeys.query()\
670 return UserApiKeys.query()\
671 .filter(UserApiKeys.user == self)\
671 .filter(UserApiKeys.user == self)\
672 .order_by(UserApiKeys.user_api_key_id.asc())\
672 .order_by(UserApiKeys.user_api_key_id.asc())\
673 .all()
673 .all()
674
674
675 @property
675 @property
676 def feed_token(self):
676 def feed_token(self):
677 return self.get_feed_token()
677 return self.get_feed_token()
678
678
679 def get_feed_token(self):
679 def get_feed_token(self):
680 feed_tokens = UserApiKeys.query()\
680 feed_tokens = UserApiKeys.query()\
681 .filter(UserApiKeys.user == self)\
681 .filter(UserApiKeys.user == self)\
682 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
682 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
683 .all()
683 .all()
684 if feed_tokens:
684 if feed_tokens:
685 return feed_tokens[0].api_key
685 return feed_tokens[0].api_key
686 return 'NO_FEED_TOKEN_AVAILABLE'
686 return 'NO_FEED_TOKEN_AVAILABLE'
687
687
688 @classmethod
688 @classmethod
689 def get(cls, user_id, cache=False):
689 def get(cls, user_id, cache=False):
690 if not user_id:
690 if not user_id:
691 return
691 return
692
692
693 user = cls.query()
693 user = cls.query()
694 if cache:
694 if cache:
695 user = user.options(
695 user = user.options(
696 FromCache("sql_cache_short", "get_users_%s" % user_id))
696 FromCache("sql_cache_short", "get_users_%s" % user_id))
697 return user.get(user_id)
697 return user.get(user_id)
698
698
699 @classmethod
699 @classmethod
700 def extra_valid_auth_tokens(cls, user, role=None):
700 def extra_valid_auth_tokens(cls, user, role=None):
701 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
701 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
702 .filter(or_(UserApiKeys.expires == -1,
702 .filter(or_(UserApiKeys.expires == -1,
703 UserApiKeys.expires >= time.time()))
703 UserApiKeys.expires >= time.time()))
704 if role:
704 if role:
705 tokens = tokens.filter(or_(UserApiKeys.role == role,
705 tokens = tokens.filter(or_(UserApiKeys.role == role,
706 UserApiKeys.role == UserApiKeys.ROLE_ALL))
706 UserApiKeys.role == UserApiKeys.ROLE_ALL))
707 return tokens.all()
707 return tokens.all()
708
708
709 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
709 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
710 from rhodecode.lib import auth
710 from rhodecode.lib import auth
711
711
712 log.debug('Trying to authenticate user: %s via auth-token, '
712 log.debug('Trying to authenticate user: %s via auth-token, '
713 'and roles: %s', self, roles)
713 'and roles: %s', self, roles)
714
714
715 if not auth_token:
715 if not auth_token:
716 return False
716 return False
717
717
718 crypto_backend = auth.crypto_backend()
718 crypto_backend = auth.crypto_backend()
719
719
720 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
720 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
721 tokens_q = UserApiKeys.query()\
721 tokens_q = UserApiKeys.query()\
722 .filter(UserApiKeys.user_id == self.user_id)\
722 .filter(UserApiKeys.user_id == self.user_id)\
723 .filter(or_(UserApiKeys.expires == -1,
723 .filter(or_(UserApiKeys.expires == -1,
724 UserApiKeys.expires >= time.time()))
724 UserApiKeys.expires >= time.time()))
725
725
726 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
726 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
727
727
728 plain_tokens = []
728 plain_tokens = []
729 hash_tokens = []
729 hash_tokens = []
730
730
731 for token in tokens_q.all():
731 for token in tokens_q.all():
732 # verify scope first
732 # verify scope first
733 if token.repo_id:
733 if token.repo_id:
734 # token has a scope, we need to verify it
734 # token has a scope, we need to verify it
735 if scope_repo_id != token.repo_id:
735 if scope_repo_id != token.repo_id:
736 log.debug(
736 log.debug(
737 'Scope mismatch: token has a set repo scope: %s, '
737 'Scope mismatch: token has a set repo scope: %s, '
738 'and calling scope is:%s, skipping further checks',
738 'and calling scope is:%s, skipping further checks',
739 token.repo, scope_repo_id)
739 token.repo, scope_repo_id)
740 # token has a scope, and it doesn't match, skip token
740 # token has a scope, and it doesn't match, skip token
741 continue
741 continue
742
742
743 if token.api_key.startswith(crypto_backend.ENC_PREF):
743 if token.api_key.startswith(crypto_backend.ENC_PREF):
744 hash_tokens.append(token.api_key)
744 hash_tokens.append(token.api_key)
745 else:
745 else:
746 plain_tokens.append(token.api_key)
746 plain_tokens.append(token.api_key)
747
747
748 is_plain_match = auth_token in plain_tokens
748 is_plain_match = auth_token in plain_tokens
749 if is_plain_match:
749 if is_plain_match:
750 return True
750 return True
751
751
752 for hashed in hash_tokens:
752 for hashed in hash_tokens:
753 # TODO(marcink): this is expensive to calculate, but most secure
753 # TODO(marcink): this is expensive to calculate, but most secure
754 match = crypto_backend.hash_check(auth_token, hashed)
754 match = crypto_backend.hash_check(auth_token, hashed)
755 if match:
755 if match:
756 return True
756 return True
757
757
758 return False
758 return False
759
759
760 @property
760 @property
761 def ip_addresses(self):
761 def ip_addresses(self):
762 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
762 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
763 return [x.ip_addr for x in ret]
763 return [x.ip_addr for x in ret]
764
764
765 @property
765 @property
766 def username_and_name(self):
766 def username_and_name(self):
767 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
767 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
768
768
769 @property
769 @property
770 def username_or_name_or_email(self):
770 def username_or_name_or_email(self):
771 full_name = self.full_name if self.full_name is not ' ' else None
771 full_name = self.full_name if self.full_name is not ' ' else None
772 return self.username or full_name or self.email
772 return self.username or full_name or self.email
773
773
774 @property
774 @property
775 def full_name(self):
775 def full_name(self):
776 return '%s %s' % (self.first_name, self.last_name)
776 return '%s %s' % (self.first_name, self.last_name)
777
777
778 @property
778 @property
779 def full_name_or_username(self):
779 def full_name_or_username(self):
780 return ('%s %s' % (self.first_name, self.last_name)
780 return ('%s %s' % (self.first_name, self.last_name)
781 if (self.first_name and self.last_name) else self.username)
781 if (self.first_name and self.last_name) else self.username)
782
782
783 @property
783 @property
784 def full_contact(self):
784 def full_contact(self):
785 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
785 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
786
786
787 @property
787 @property
788 def short_contact(self):
788 def short_contact(self):
789 return '%s %s' % (self.first_name, self.last_name)
789 return '%s %s' % (self.first_name, self.last_name)
790
790
791 @property
791 @property
792 def is_admin(self):
792 def is_admin(self):
793 return self.admin
793 return self.admin
794
794
795 def AuthUser(self, **kwargs):
795 def AuthUser(self, **kwargs):
796 """
796 """
797 Returns instance of AuthUser for this user
797 Returns instance of AuthUser for this user
798 """
798 """
799 from rhodecode.lib.auth import AuthUser
799 from rhodecode.lib.auth import AuthUser
800 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
800 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
801
801
802 @hybrid_property
802 @hybrid_property
803 def user_data(self):
803 def user_data(self):
804 if not self._user_data:
804 if not self._user_data:
805 return {}
805 return {}
806
806
807 try:
807 try:
808 return json.loads(self._user_data)
808 return json.loads(self._user_data)
809 except TypeError:
809 except TypeError:
810 return {}
810 return {}
811
811
812 @user_data.setter
812 @user_data.setter
813 def user_data(self, val):
813 def user_data(self, val):
814 if not isinstance(val, dict):
814 if not isinstance(val, dict):
815 raise Exception('user_data must be dict, got %s' % type(val))
815 raise Exception('user_data must be dict, got %s' % type(val))
816 try:
816 try:
817 self._user_data = json.dumps(val)
817 self._user_data = json.dumps(val)
818 except Exception:
818 except Exception:
819 log.error(traceback.format_exc())
819 log.error(traceback.format_exc())
820
820
821 @classmethod
821 @classmethod
822 def get_by_username(cls, username, case_insensitive=False,
822 def get_by_username(cls, username, case_insensitive=False,
823 cache=False, identity_cache=False):
823 cache=False, identity_cache=False):
824 session = Session()
824 session = Session()
825
825
826 if case_insensitive:
826 if case_insensitive:
827 q = cls.query().filter(
827 q = cls.query().filter(
828 func.lower(cls.username) == func.lower(username))
828 func.lower(cls.username) == func.lower(username))
829 else:
829 else:
830 q = cls.query().filter(cls.username == username)
830 q = cls.query().filter(cls.username == username)
831
831
832 if cache:
832 if cache:
833 if identity_cache:
833 if identity_cache:
834 val = cls.identity_cache(session, 'username', username)
834 val = cls.identity_cache(session, 'username', username)
835 if val:
835 if val:
836 return val
836 return val
837 else:
837 else:
838 cache_key = "get_user_by_name_%s" % _hash_key(username)
838 cache_key = "get_user_by_name_%s" % _hash_key(username)
839 q = q.options(
839 q = q.options(
840 FromCache("sql_cache_short", cache_key))
840 FromCache("sql_cache_short", cache_key))
841
841
842 return q.scalar()
842 return q.scalar()
843
843
844 @classmethod
844 @classmethod
845 def get_by_auth_token(cls, auth_token, cache=False):
845 def get_by_auth_token(cls, auth_token, cache=False):
846 q = UserApiKeys.query()\
846 q = UserApiKeys.query()\
847 .filter(UserApiKeys.api_key == auth_token)\
847 .filter(UserApiKeys.api_key == auth_token)\
848 .filter(or_(UserApiKeys.expires == -1,
848 .filter(or_(UserApiKeys.expires == -1,
849 UserApiKeys.expires >= time.time()))
849 UserApiKeys.expires >= time.time()))
850 if cache:
850 if cache:
851 q = q.options(
851 q = q.options(
852 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
852 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
853
853
854 match = q.first()
854 match = q.first()
855 if match:
855 if match:
856 return match.user
856 return match.user
857
857
858 @classmethod
858 @classmethod
859 def get_by_email(cls, email, case_insensitive=False, cache=False):
859 def get_by_email(cls, email, case_insensitive=False, cache=False):
860
860
861 if case_insensitive:
861 if case_insensitive:
862 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
862 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
863
863
864 else:
864 else:
865 q = cls.query().filter(cls.email == email)
865 q = cls.query().filter(cls.email == email)
866
866
867 email_key = _hash_key(email)
867 email_key = _hash_key(email)
868 if cache:
868 if cache:
869 q = q.options(
869 q = q.options(
870 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
870 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
871
871
872 ret = q.scalar()
872 ret = q.scalar()
873 if ret is None:
873 if ret is None:
874 q = UserEmailMap.query()
874 q = UserEmailMap.query()
875 # try fetching in alternate email map
875 # try fetching in alternate email map
876 if case_insensitive:
876 if case_insensitive:
877 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
877 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
878 else:
878 else:
879 q = q.filter(UserEmailMap.email == email)
879 q = q.filter(UserEmailMap.email == email)
880 q = q.options(joinedload(UserEmailMap.user))
880 q = q.options(joinedload(UserEmailMap.user))
881 if cache:
881 if cache:
882 q = q.options(
882 q = q.options(
883 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
883 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
884 ret = getattr(q.scalar(), 'user', None)
884 ret = getattr(q.scalar(), 'user', None)
885
885
886 return ret
886 return ret
887
887
888 @classmethod
888 @classmethod
889 def get_from_cs_author(cls, author):
889 def get_from_cs_author(cls, author):
890 """
890 """
891 Tries to get User objects out of commit author string
891 Tries to get User objects out of commit author string
892
892
893 :param author:
893 :param author:
894 """
894 """
895 from rhodecode.lib.helpers import email, author_name
895 from rhodecode.lib.helpers import email, author_name
896 # Valid email in the attribute passed, see if they're in the system
896 # Valid email in the attribute passed, see if they're in the system
897 _email = email(author)
897 _email = email(author)
898 if _email:
898 if _email:
899 user = cls.get_by_email(_email, case_insensitive=True)
899 user = cls.get_by_email(_email, case_insensitive=True)
900 if user:
900 if user:
901 return user
901 return user
902 # Maybe we can match by username?
902 # Maybe we can match by username?
903 _author = author_name(author)
903 _author = author_name(author)
904 user = cls.get_by_username(_author, case_insensitive=True)
904 user = cls.get_by_username(_author, case_insensitive=True)
905 if user:
905 if user:
906 return user
906 return user
907
907
908 def update_userdata(self, **kwargs):
908 def update_userdata(self, **kwargs):
909 usr = self
909 usr = self
910 old = usr.user_data
910 old = usr.user_data
911 old.update(**kwargs)
911 old.update(**kwargs)
912 usr.user_data = old
912 usr.user_data = old
913 Session().add(usr)
913 Session().add(usr)
914 log.debug('updated userdata with ', kwargs)
914 log.debug('updated userdata with ', kwargs)
915
915
916 def update_lastlogin(self):
916 def update_lastlogin(self):
917 """Update user lastlogin"""
917 """Update user lastlogin"""
918 self.last_login = datetime.datetime.now()
918 self.last_login = datetime.datetime.now()
919 Session().add(self)
919 Session().add(self)
920 log.debug('updated user %s lastlogin', self.username)
920 log.debug('updated user %s lastlogin', self.username)
921
921
922 def update_lastactivity(self):
922 def update_lastactivity(self):
923 """Update user lastactivity"""
923 """Update user lastactivity"""
924 self.last_activity = datetime.datetime.now()
924 self.last_activity = datetime.datetime.now()
925 Session().add(self)
925 Session().add(self)
926 log.debug('updated user `%s` last activity', self.username)
926 log.debug('updated user `%s` last activity', self.username)
927
927
928 def update_password(self, new_password):
928 def update_password(self, new_password):
929 from rhodecode.lib.auth import get_crypt_password
929 from rhodecode.lib.auth import get_crypt_password
930
930
931 self.password = get_crypt_password(new_password)
931 self.password = get_crypt_password(new_password)
932 Session().add(self)
932 Session().add(self)
933
933
934 @classmethod
934 @classmethod
935 def get_first_super_admin(cls):
935 def get_first_super_admin(cls):
936 user = User.query().filter(User.admin == true()).first()
936 user = User.query().filter(User.admin == true()).first()
937 if user is None:
937 if user is None:
938 raise Exception('FATAL: Missing administrative account!')
938 raise Exception('FATAL: Missing administrative account!')
939 return user
939 return user
940
940
941 @classmethod
941 @classmethod
942 def get_all_super_admins(cls):
942 def get_all_super_admins(cls):
943 """
943 """
944 Returns all admin accounts sorted by username
944 Returns all admin accounts sorted by username
945 """
945 """
946 return User.query().filter(User.admin == true())\
946 return User.query().filter(User.admin == true())\
947 .order_by(User.username.asc()).all()
947 .order_by(User.username.asc()).all()
948
948
949 @classmethod
949 @classmethod
950 def get_default_user(cls, cache=False, refresh=False):
950 def get_default_user(cls, cache=False, refresh=False):
951 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
951 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
952 if user is None:
952 if user is None:
953 raise Exception('FATAL: Missing default account!')
953 raise Exception('FATAL: Missing default account!')
954 if refresh:
954 if refresh:
955 # The default user might be based on outdated state which
955 # The default user might be based on outdated state which
956 # has been loaded from the cache.
956 # has been loaded from the cache.
957 # A call to refresh() ensures that the
957 # A call to refresh() ensures that the
958 # latest state from the database is used.
958 # latest state from the database is used.
959 Session().refresh(user)
959 Session().refresh(user)
960 return user
960 return user
961
961
962 def _get_default_perms(self, user, suffix=''):
962 def _get_default_perms(self, user, suffix=''):
963 from rhodecode.model.permission import PermissionModel
963 from rhodecode.model.permission import PermissionModel
964 return PermissionModel().get_default_perms(user.user_perms, suffix)
964 return PermissionModel().get_default_perms(user.user_perms, suffix)
965
965
966 def get_default_perms(self, suffix=''):
966 def get_default_perms(self, suffix=''):
967 return self._get_default_perms(self, suffix)
967 return self._get_default_perms(self, suffix)
968
968
969 def get_api_data(self, include_secrets=False, details='full'):
969 def get_api_data(self, include_secrets=False, details='full'):
970 """
970 """
971 Common function for generating user related data for API
971 Common function for generating user related data for API
972
972
973 :param include_secrets: By default secrets in the API data will be replaced
973 :param include_secrets: By default secrets in the API data will be replaced
974 by a placeholder value to prevent exposing this data by accident. In case
974 by a placeholder value to prevent exposing this data by accident. In case
975 this data shall be exposed, set this flag to ``True``.
975 this data shall be exposed, set this flag to ``True``.
976
976
977 :param details: details can be 'basic|full' basic gives only a subset of
977 :param details: details can be 'basic|full' basic gives only a subset of
978 the available user information that includes user_id, name and emails.
978 the available user information that includes user_id, name and emails.
979 """
979 """
980 user = self
980 user = self
981 user_data = self.user_data
981 user_data = self.user_data
982 data = {
982 data = {
983 'user_id': user.user_id,
983 'user_id': user.user_id,
984 'username': user.username,
984 'username': user.username,
985 'firstname': user.name,
985 'firstname': user.name,
986 'lastname': user.lastname,
986 'lastname': user.lastname,
987 'email': user.email,
987 'email': user.email,
988 'emails': user.emails,
988 'emails': user.emails,
989 }
989 }
990 if details == 'basic':
990 if details == 'basic':
991 return data
991 return data
992
992
993 auth_token_length = 40
993 auth_token_length = 40
994 auth_token_replacement = '*' * auth_token_length
994 auth_token_replacement = '*' * auth_token_length
995
995
996 extras = {
996 extras = {
997 'auth_tokens': [auth_token_replacement],
997 'auth_tokens': [auth_token_replacement],
998 'active': user.active,
998 'active': user.active,
999 'admin': user.admin,
999 'admin': user.admin,
1000 'extern_type': user.extern_type,
1000 'extern_type': user.extern_type,
1001 'extern_name': user.extern_name,
1001 'extern_name': user.extern_name,
1002 'last_login': user.last_login,
1002 'last_login': user.last_login,
1003 'last_activity': user.last_activity,
1003 'last_activity': user.last_activity,
1004 'ip_addresses': user.ip_addresses,
1004 'ip_addresses': user.ip_addresses,
1005 'language': user_data.get('language')
1005 'language': user_data.get('language')
1006 }
1006 }
1007 data.update(extras)
1007 data.update(extras)
1008
1008
1009 if include_secrets:
1009 if include_secrets:
1010 data['auth_tokens'] = user.auth_tokens
1010 data['auth_tokens'] = user.auth_tokens
1011 return data
1011 return data
1012
1012
1013 def __json__(self):
1013 def __json__(self):
1014 data = {
1014 data = {
1015 'full_name': self.full_name,
1015 'full_name': self.full_name,
1016 'full_name_or_username': self.full_name_or_username,
1016 'full_name_or_username': self.full_name_or_username,
1017 'short_contact': self.short_contact,
1017 'short_contact': self.short_contact,
1018 'full_contact': self.full_contact,
1018 'full_contact': self.full_contact,
1019 }
1019 }
1020 data.update(self.get_api_data())
1020 data.update(self.get_api_data())
1021 return data
1021 return data
1022
1022
1023
1023
1024 class UserApiKeys(Base, BaseModel):
1024 class UserApiKeys(Base, BaseModel):
1025 __tablename__ = 'user_api_keys'
1025 __tablename__ = 'user_api_keys'
1026 __table_args__ = (
1026 __table_args__ = (
1027 Index('uak_api_key_idx', 'api_key', unique=True),
1027 Index('uak_api_key_idx', 'api_key', unique=True),
1028 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1028 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1029 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1029 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1030 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1030 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1031 )
1031 )
1032 __mapper_args__ = {}
1032 __mapper_args__ = {}
1033
1033
1034 # ApiKey role
1034 # ApiKey role
1035 ROLE_ALL = 'token_role_all'
1035 ROLE_ALL = 'token_role_all'
1036 ROLE_HTTP = 'token_role_http'
1036 ROLE_HTTP = 'token_role_http'
1037 ROLE_VCS = 'token_role_vcs'
1037 ROLE_VCS = 'token_role_vcs'
1038 ROLE_API = 'token_role_api'
1038 ROLE_API = 'token_role_api'
1039 ROLE_FEED = 'token_role_feed'
1039 ROLE_FEED = 'token_role_feed'
1040 ROLE_PASSWORD_RESET = 'token_password_reset'
1040 ROLE_PASSWORD_RESET = 'token_password_reset'
1041
1041
1042 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1042 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1043
1043
1044 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1046 api_key = Column("api_key", String(255), nullable=False, unique=True)
1046 api_key = Column("api_key", String(255), nullable=False, unique=True)
1047 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1047 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1048 expires = Column('expires', Float(53), nullable=False)
1048 expires = Column('expires', Float(53), nullable=False)
1049 role = Column('role', String(255), nullable=True)
1049 role = Column('role', String(255), nullable=True)
1050 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1050 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1051
1051
1052 # scope columns
1052 # scope columns
1053 repo_id = Column(
1053 repo_id = Column(
1054 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1054 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1055 nullable=True, unique=None, default=None)
1055 nullable=True, unique=None, default=None)
1056 repo = relationship('Repository', lazy='joined')
1056 repo = relationship('Repository', lazy='joined')
1057
1057
1058 repo_group_id = Column(
1058 repo_group_id = Column(
1059 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1059 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1060 nullable=True, unique=None, default=None)
1060 nullable=True, unique=None, default=None)
1061 repo_group = relationship('RepoGroup', lazy='joined')
1061 repo_group = relationship('RepoGroup', lazy='joined')
1062
1062
1063 user = relationship('User', lazy='joined')
1063 user = relationship('User', lazy='joined')
1064
1064
1065 def __unicode__(self):
1065 def __unicode__(self):
1066 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1066 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1067
1067
1068 def __json__(self):
1068 def __json__(self):
1069 data = {
1069 data = {
1070 'auth_token': self.api_key,
1070 'auth_token': self.api_key,
1071 'role': self.role,
1071 'role': self.role,
1072 'scope': self.scope_humanized,
1072 'scope': self.scope_humanized,
1073 'expired': self.expired
1073 'expired': self.expired
1074 }
1074 }
1075 return data
1075 return data
1076
1076
1077 def get_api_data(self, include_secrets=False):
1077 def get_api_data(self, include_secrets=False):
1078 data = self.__json__()
1078 data = self.__json__()
1079 if include_secrets:
1079 if include_secrets:
1080 return data
1080 return data
1081 else:
1081 else:
1082 data['auth_token'] = self.token_obfuscated
1082 data['auth_token'] = self.token_obfuscated
1083 return data
1083 return data
1084
1084
1085 @hybrid_property
1085 @hybrid_property
1086 def description_safe(self):
1086 def description_safe(self):
1087 from rhodecode.lib import helpers as h
1087 from rhodecode.lib import helpers as h
1088 return h.escape(self.description)
1088 return h.escape(self.description)
1089
1089
1090 @property
1090 @property
1091 def expired(self):
1091 def expired(self):
1092 if self.expires == -1:
1092 if self.expires == -1:
1093 return False
1093 return False
1094 return time.time() > self.expires
1094 return time.time() > self.expires
1095
1095
1096 @classmethod
1096 @classmethod
1097 def _get_role_name(cls, role):
1097 def _get_role_name(cls, role):
1098 return {
1098 return {
1099 cls.ROLE_ALL: _('all'),
1099 cls.ROLE_ALL: _('all'),
1100 cls.ROLE_HTTP: _('http/web interface'),
1100 cls.ROLE_HTTP: _('http/web interface'),
1101 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1101 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1102 cls.ROLE_API: _('api calls'),
1102 cls.ROLE_API: _('api calls'),
1103 cls.ROLE_FEED: _('feed access'),
1103 cls.ROLE_FEED: _('feed access'),
1104 }.get(role, role)
1104 }.get(role, role)
1105
1105
1106 @property
1106 @property
1107 def role_humanized(self):
1107 def role_humanized(self):
1108 return self._get_role_name(self.role)
1108 return self._get_role_name(self.role)
1109
1109
1110 def _get_scope(self):
1110 def _get_scope(self):
1111 if self.repo:
1111 if self.repo:
1112 return repr(self.repo)
1112 return repr(self.repo)
1113 if self.repo_group:
1113 if self.repo_group:
1114 return repr(self.repo_group) + ' (recursive)'
1114 return repr(self.repo_group) + ' (recursive)'
1115 return 'global'
1115 return 'global'
1116
1116
1117 @property
1117 @property
1118 def scope_humanized(self):
1118 def scope_humanized(self):
1119 return self._get_scope()
1119 return self._get_scope()
1120
1120
1121 @property
1121 @property
1122 def token_obfuscated(self):
1122 def token_obfuscated(self):
1123 if self.api_key:
1123 if self.api_key:
1124 return self.api_key[:4] + "****"
1124 return self.api_key[:4] + "****"
1125
1125
1126
1126
1127 class UserEmailMap(Base, BaseModel):
1127 class UserEmailMap(Base, BaseModel):
1128 __tablename__ = 'user_email_map'
1128 __tablename__ = 'user_email_map'
1129 __table_args__ = (
1129 __table_args__ = (
1130 Index('uem_email_idx', 'email'),
1130 Index('uem_email_idx', 'email'),
1131 UniqueConstraint('email'),
1131 UniqueConstraint('email'),
1132 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1132 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1133 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1133 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1134 )
1134 )
1135 __mapper_args__ = {}
1135 __mapper_args__ = {}
1136
1136
1137 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1137 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1139 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1139 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1140 user = relationship('User', lazy='joined')
1140 user = relationship('User', lazy='joined')
1141
1141
1142 @validates('_email')
1142 @validates('_email')
1143 def validate_email(self, key, email):
1143 def validate_email(self, key, email):
1144 # check if this email is not main one
1144 # check if this email is not main one
1145 main_email = Session().query(User).filter(User.email == email).scalar()
1145 main_email = Session().query(User).filter(User.email == email).scalar()
1146 if main_email is not None:
1146 if main_email is not None:
1147 raise AttributeError('email %s is present is user table' % email)
1147 raise AttributeError('email %s is present is user table' % email)
1148 return email
1148 return email
1149
1149
1150 @hybrid_property
1150 @hybrid_property
1151 def email(self):
1151 def email(self):
1152 return self._email
1152 return self._email
1153
1153
1154 @email.setter
1154 @email.setter
1155 def email(self, val):
1155 def email(self, val):
1156 self._email = val.lower() if val else None
1156 self._email = val.lower() if val else None
1157
1157
1158
1158
1159 class UserIpMap(Base, BaseModel):
1159 class UserIpMap(Base, BaseModel):
1160 __tablename__ = 'user_ip_map'
1160 __tablename__ = 'user_ip_map'
1161 __table_args__ = (
1161 __table_args__ = (
1162 UniqueConstraint('user_id', 'ip_addr'),
1162 UniqueConstraint('user_id', 'ip_addr'),
1163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1165 )
1165 )
1166 __mapper_args__ = {}
1166 __mapper_args__ = {}
1167
1167
1168 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1168 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1169 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1169 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1170 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1170 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1171 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1171 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1172 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1172 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1173 user = relationship('User', lazy='joined')
1173 user = relationship('User', lazy='joined')
1174
1174
1175 @hybrid_property
1175 @hybrid_property
1176 def description_safe(self):
1176 def description_safe(self):
1177 from rhodecode.lib import helpers as h
1177 from rhodecode.lib import helpers as h
1178 return h.escape(self.description)
1178 return h.escape(self.description)
1179
1179
1180 @classmethod
1180 @classmethod
1181 def _get_ip_range(cls, ip_addr):
1181 def _get_ip_range(cls, ip_addr):
1182 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1182 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1183 return [str(net.network_address), str(net.broadcast_address)]
1183 return [str(net.network_address), str(net.broadcast_address)]
1184
1184
1185 def __json__(self):
1185 def __json__(self):
1186 return {
1186 return {
1187 'ip_addr': self.ip_addr,
1187 'ip_addr': self.ip_addr,
1188 'ip_range': self._get_ip_range(self.ip_addr),
1188 'ip_range': self._get_ip_range(self.ip_addr),
1189 }
1189 }
1190
1190
1191 def __unicode__(self):
1191 def __unicode__(self):
1192 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1192 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1193 self.user_id, self.ip_addr)
1193 self.user_id, self.ip_addr)
1194
1194
1195
1195
1196 class UserSshKeys(Base, BaseModel):
1196 class UserSshKeys(Base, BaseModel):
1197 __tablename__ = 'user_ssh_keys'
1197 __tablename__ = 'user_ssh_keys'
1198 __table_args__ = (
1198 __table_args__ = (
1199 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1199 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1200
1200
1201 UniqueConstraint('ssh_key_fingerprint'),
1201 UniqueConstraint('ssh_key_fingerprint'),
1202
1202
1203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1204 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1205 )
1205 )
1206 __mapper_args__ = {}
1206 __mapper_args__ = {}
1207
1207
1208 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1208 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1209 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1209 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1210 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None)
1210 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1211
1211
1212 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1212 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1213
1213
1214 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1214 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1215 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1215 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1216 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1216 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1217
1217
1218 user = relationship('User', lazy='joined')
1218 user = relationship('User', lazy='joined')
1219
1219
1220 def __json__(self):
1220 def __json__(self):
1221 data = {
1221 data = {
1222 'ssh_fingerprint': self.ssh_key_fingerprint,
1222 'ssh_fingerprint': self.ssh_key_fingerprint,
1223 'description': self.description,
1223 'description': self.description,
1224 'created_on': self.created_on
1224 'created_on': self.created_on
1225 }
1225 }
1226 return data
1226 return data
1227
1227
1228 def get_api_data(self):
1228 def get_api_data(self):
1229 data = self.__json__()
1229 data = self.__json__()
1230 return data
1230 return data
1231
1231
1232
1232
1233 class UserLog(Base, BaseModel):
1233 class UserLog(Base, BaseModel):
1234 __tablename__ = 'user_logs'
1234 __tablename__ = 'user_logs'
1235 __table_args__ = (
1235 __table_args__ = (
1236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1237 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1238 )
1238 )
1239 VERSION_1 = 'v1'
1239 VERSION_1 = 'v1'
1240 VERSION_2 = 'v2'
1240 VERSION_2 = 'v2'
1241 VERSIONS = [VERSION_1, VERSION_2]
1241 VERSIONS = [VERSION_1, VERSION_2]
1242
1242
1243 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1243 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1245 username = Column("username", String(255), nullable=True, unique=None, default=None)
1245 username = Column("username", String(255), nullable=True, unique=None, default=None)
1246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1247 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1247 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1248 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1248 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1249 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1249 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1250 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1250 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1251
1251
1252 version = Column("version", String(255), nullable=True, default=VERSION_1)
1252 version = Column("version", String(255), nullable=True, default=VERSION_1)
1253 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1253 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1254 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1254 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1255
1255
1256 def __unicode__(self):
1256 def __unicode__(self):
1257 return u"<%s('id:%s:%s')>" % (
1257 return u"<%s('id:%s:%s')>" % (
1258 self.__class__.__name__, self.repository_name, self.action)
1258 self.__class__.__name__, self.repository_name, self.action)
1259
1259
1260 def __json__(self):
1260 def __json__(self):
1261 return {
1261 return {
1262 'user_id': self.user_id,
1262 'user_id': self.user_id,
1263 'username': self.username,
1263 'username': self.username,
1264 'repository_id': self.repository_id,
1264 'repository_id': self.repository_id,
1265 'repository_name': self.repository_name,
1265 'repository_name': self.repository_name,
1266 'user_ip': self.user_ip,
1266 'user_ip': self.user_ip,
1267 'action_date': self.action_date,
1267 'action_date': self.action_date,
1268 'action': self.action,
1268 'action': self.action,
1269 }
1269 }
1270
1270
1271 @hybrid_property
1271 @hybrid_property
1272 def entry_id(self):
1272 def entry_id(self):
1273 return self.user_log_id
1273 return self.user_log_id
1274
1274
1275 @property
1275 @property
1276 def action_as_day(self):
1276 def action_as_day(self):
1277 return datetime.date(*self.action_date.timetuple()[:3])
1277 return datetime.date(*self.action_date.timetuple()[:3])
1278
1278
1279 user = relationship('User')
1279 user = relationship('User')
1280 repository = relationship('Repository', cascade='')
1280 repository = relationship('Repository', cascade='')
1281
1281
1282
1282
1283 class UserGroup(Base, BaseModel):
1283 class UserGroup(Base, BaseModel):
1284 __tablename__ = 'users_groups'
1284 __tablename__ = 'users_groups'
1285 __table_args__ = (
1285 __table_args__ = (
1286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1287 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1288 )
1288 )
1289
1289
1290 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1290 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1291 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1291 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1292 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1292 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1293 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1293 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1294 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1294 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1295 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1295 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1296 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1296 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1297 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1297 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1298
1298
1299 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1299 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1300 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1300 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1301 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1301 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1302 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1302 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1303 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1303 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1304 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1304 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1305
1305
1306 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1306 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1307 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1307 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1308
1308
1309 @classmethod
1309 @classmethod
1310 def _load_group_data(cls, column):
1310 def _load_group_data(cls, column):
1311 if not column:
1311 if not column:
1312 return {}
1312 return {}
1313
1313
1314 try:
1314 try:
1315 return json.loads(column) or {}
1315 return json.loads(column) or {}
1316 except TypeError:
1316 except TypeError:
1317 return {}
1317 return {}
1318
1318
1319 @hybrid_property
1319 @hybrid_property
1320 def description_safe(self):
1320 def description_safe(self):
1321 from rhodecode.lib import helpers as h
1321 from rhodecode.lib import helpers as h
1322 return h.escape(self.description)
1322 return h.escape(self.description)
1323
1323
1324 @hybrid_property
1324 @hybrid_property
1325 def group_data(self):
1325 def group_data(self):
1326 return self._load_group_data(self._group_data)
1326 return self._load_group_data(self._group_data)
1327
1327
1328 @group_data.expression
1328 @group_data.expression
1329 def group_data(self, **kwargs):
1329 def group_data(self, **kwargs):
1330 return self._group_data
1330 return self._group_data
1331
1331
1332 @group_data.setter
1332 @group_data.setter
1333 def group_data(self, val):
1333 def group_data(self, val):
1334 try:
1334 try:
1335 self._group_data = json.dumps(val)
1335 self._group_data = json.dumps(val)
1336 except Exception:
1336 except Exception:
1337 log.error(traceback.format_exc())
1337 log.error(traceback.format_exc())
1338
1338
1339 def __unicode__(self):
1339 def __unicode__(self):
1340 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1340 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1341 self.users_group_id,
1341 self.users_group_id,
1342 self.users_group_name)
1342 self.users_group_name)
1343
1343
1344 @classmethod
1344 @classmethod
1345 def get_by_group_name(cls, group_name, cache=False,
1345 def get_by_group_name(cls, group_name, cache=False,
1346 case_insensitive=False):
1346 case_insensitive=False):
1347 if case_insensitive:
1347 if case_insensitive:
1348 q = cls.query().filter(func.lower(cls.users_group_name) ==
1348 q = cls.query().filter(func.lower(cls.users_group_name) ==
1349 func.lower(group_name))
1349 func.lower(group_name))
1350
1350
1351 else:
1351 else:
1352 q = cls.query().filter(cls.users_group_name == group_name)
1352 q = cls.query().filter(cls.users_group_name == group_name)
1353 if cache:
1353 if cache:
1354 q = q.options(
1354 q = q.options(
1355 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1355 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1356 return q.scalar()
1356 return q.scalar()
1357
1357
1358 @classmethod
1358 @classmethod
1359 def get(cls, user_group_id, cache=False):
1359 def get(cls, user_group_id, cache=False):
1360 if not user_group_id:
1360 if not user_group_id:
1361 return
1361 return
1362
1362
1363 user_group = cls.query()
1363 user_group = cls.query()
1364 if cache:
1364 if cache:
1365 user_group = user_group.options(
1365 user_group = user_group.options(
1366 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1366 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1367 return user_group.get(user_group_id)
1367 return user_group.get(user_group_id)
1368
1368
1369 def permissions(self, with_admins=True, with_owner=True):
1369 def permissions(self, with_admins=True, with_owner=True):
1370 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1370 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1371 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1371 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1372 joinedload(UserUserGroupToPerm.user),
1372 joinedload(UserUserGroupToPerm.user),
1373 joinedload(UserUserGroupToPerm.permission),)
1373 joinedload(UserUserGroupToPerm.permission),)
1374
1374
1375 # get owners and admins and permissions. We do a trick of re-writing
1375 # get owners and admins and permissions. We do a trick of re-writing
1376 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1376 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1377 # has a global reference and changing one object propagates to all
1377 # has a global reference and changing one object propagates to all
1378 # others. This means if admin is also an owner admin_row that change
1378 # others. This means if admin is also an owner admin_row that change
1379 # would propagate to both objects
1379 # would propagate to both objects
1380 perm_rows = []
1380 perm_rows = []
1381 for _usr in q.all():
1381 for _usr in q.all():
1382 usr = AttributeDict(_usr.user.get_dict())
1382 usr = AttributeDict(_usr.user.get_dict())
1383 usr.permission = _usr.permission.permission_name
1383 usr.permission = _usr.permission.permission_name
1384 perm_rows.append(usr)
1384 perm_rows.append(usr)
1385
1385
1386 # filter the perm rows by 'default' first and then sort them by
1386 # filter the perm rows by 'default' first and then sort them by
1387 # admin,write,read,none permissions sorted again alphabetically in
1387 # admin,write,read,none permissions sorted again alphabetically in
1388 # each group
1388 # each group
1389 perm_rows = sorted(perm_rows, key=display_user_sort)
1389 perm_rows = sorted(perm_rows, key=display_user_sort)
1390
1390
1391 _admin_perm = 'usergroup.admin'
1391 _admin_perm = 'usergroup.admin'
1392 owner_row = []
1392 owner_row = []
1393 if with_owner:
1393 if with_owner:
1394 usr = AttributeDict(self.user.get_dict())
1394 usr = AttributeDict(self.user.get_dict())
1395 usr.owner_row = True
1395 usr.owner_row = True
1396 usr.permission = _admin_perm
1396 usr.permission = _admin_perm
1397 owner_row.append(usr)
1397 owner_row.append(usr)
1398
1398
1399 super_admin_rows = []
1399 super_admin_rows = []
1400 if with_admins:
1400 if with_admins:
1401 for usr in User.get_all_super_admins():
1401 for usr in User.get_all_super_admins():
1402 # if this admin is also owner, don't double the record
1402 # if this admin is also owner, don't double the record
1403 if usr.user_id == owner_row[0].user_id:
1403 if usr.user_id == owner_row[0].user_id:
1404 owner_row[0].admin_row = True
1404 owner_row[0].admin_row = True
1405 else:
1405 else:
1406 usr = AttributeDict(usr.get_dict())
1406 usr = AttributeDict(usr.get_dict())
1407 usr.admin_row = True
1407 usr.admin_row = True
1408 usr.permission = _admin_perm
1408 usr.permission = _admin_perm
1409 super_admin_rows.append(usr)
1409 super_admin_rows.append(usr)
1410
1410
1411 return super_admin_rows + owner_row + perm_rows
1411 return super_admin_rows + owner_row + perm_rows
1412
1412
1413 def permission_user_groups(self):
1413 def permission_user_groups(self):
1414 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1414 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1415 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1415 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1416 joinedload(UserGroupUserGroupToPerm.target_user_group),
1416 joinedload(UserGroupUserGroupToPerm.target_user_group),
1417 joinedload(UserGroupUserGroupToPerm.permission),)
1417 joinedload(UserGroupUserGroupToPerm.permission),)
1418
1418
1419 perm_rows = []
1419 perm_rows = []
1420 for _user_group in q.all():
1420 for _user_group in q.all():
1421 usr = AttributeDict(_user_group.user_group.get_dict())
1421 usr = AttributeDict(_user_group.user_group.get_dict())
1422 usr.permission = _user_group.permission.permission_name
1422 usr.permission = _user_group.permission.permission_name
1423 perm_rows.append(usr)
1423 perm_rows.append(usr)
1424
1424
1425 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1425 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1426 return perm_rows
1426 return perm_rows
1427
1427
1428 def _get_default_perms(self, user_group, suffix=''):
1428 def _get_default_perms(self, user_group, suffix=''):
1429 from rhodecode.model.permission import PermissionModel
1429 from rhodecode.model.permission import PermissionModel
1430 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1430 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1431
1431
1432 def get_default_perms(self, suffix=''):
1432 def get_default_perms(self, suffix=''):
1433 return self._get_default_perms(self, suffix)
1433 return self._get_default_perms(self, suffix)
1434
1434
1435 def get_api_data(self, with_group_members=True, include_secrets=False):
1435 def get_api_data(self, with_group_members=True, include_secrets=False):
1436 """
1436 """
1437 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1437 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1438 basically forwarded.
1438 basically forwarded.
1439
1439
1440 """
1440 """
1441 user_group = self
1441 user_group = self
1442 data = {
1442 data = {
1443 'users_group_id': user_group.users_group_id,
1443 'users_group_id': user_group.users_group_id,
1444 'group_name': user_group.users_group_name,
1444 'group_name': user_group.users_group_name,
1445 'group_description': user_group.user_group_description,
1445 'group_description': user_group.user_group_description,
1446 'active': user_group.users_group_active,
1446 'active': user_group.users_group_active,
1447 'owner': user_group.user.username,
1447 'owner': user_group.user.username,
1448 'owner_email': user_group.user.email,
1448 'owner_email': user_group.user.email,
1449 }
1449 }
1450
1450
1451 if with_group_members:
1451 if with_group_members:
1452 users = []
1452 users = []
1453 for user in user_group.members:
1453 for user in user_group.members:
1454 user = user.user
1454 user = user.user
1455 users.append(user.get_api_data(include_secrets=include_secrets))
1455 users.append(user.get_api_data(include_secrets=include_secrets))
1456 data['users'] = users
1456 data['users'] = users
1457
1457
1458 return data
1458 return data
1459
1459
1460
1460
1461 class UserGroupMember(Base, BaseModel):
1461 class UserGroupMember(Base, BaseModel):
1462 __tablename__ = 'users_groups_members'
1462 __tablename__ = 'users_groups_members'
1463 __table_args__ = (
1463 __table_args__ = (
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1465 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1466 )
1466 )
1467
1467
1468 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1468 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1471
1471
1472 user = relationship('User', lazy='joined')
1472 user = relationship('User', lazy='joined')
1473 users_group = relationship('UserGroup')
1473 users_group = relationship('UserGroup')
1474
1474
1475 def __init__(self, gr_id='', u_id=''):
1475 def __init__(self, gr_id='', u_id=''):
1476 self.users_group_id = gr_id
1476 self.users_group_id = gr_id
1477 self.user_id = u_id
1477 self.user_id = u_id
1478
1478
1479
1479
1480 class RepositoryField(Base, BaseModel):
1480 class RepositoryField(Base, BaseModel):
1481 __tablename__ = 'repositories_fields'
1481 __tablename__ = 'repositories_fields'
1482 __table_args__ = (
1482 __table_args__ = (
1483 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1483 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1484 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1484 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1485 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1485 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1486 )
1486 )
1487 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1487 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1488
1488
1489 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1489 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1490 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1490 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1491 field_key = Column("field_key", String(250))
1491 field_key = Column("field_key", String(250))
1492 field_label = Column("field_label", String(1024), nullable=False)
1492 field_label = Column("field_label", String(1024), nullable=False)
1493 field_value = Column("field_value", String(10000), nullable=False)
1493 field_value = Column("field_value", String(10000), nullable=False)
1494 field_desc = Column("field_desc", String(1024), nullable=False)
1494 field_desc = Column("field_desc", String(1024), nullable=False)
1495 field_type = Column("field_type", String(255), nullable=False, unique=None)
1495 field_type = Column("field_type", String(255), nullable=False, unique=None)
1496 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1496 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1497
1497
1498 repository = relationship('Repository')
1498 repository = relationship('Repository')
1499
1499
1500 @property
1500 @property
1501 def field_key_prefixed(self):
1501 def field_key_prefixed(self):
1502 return 'ex_%s' % self.field_key
1502 return 'ex_%s' % self.field_key
1503
1503
1504 @classmethod
1504 @classmethod
1505 def un_prefix_key(cls, key):
1505 def un_prefix_key(cls, key):
1506 if key.startswith(cls.PREFIX):
1506 if key.startswith(cls.PREFIX):
1507 return key[len(cls.PREFIX):]
1507 return key[len(cls.PREFIX):]
1508 return key
1508 return key
1509
1509
1510 @classmethod
1510 @classmethod
1511 def get_by_key_name(cls, key, repo):
1511 def get_by_key_name(cls, key, repo):
1512 row = cls.query()\
1512 row = cls.query()\
1513 .filter(cls.repository == repo)\
1513 .filter(cls.repository == repo)\
1514 .filter(cls.field_key == key).scalar()
1514 .filter(cls.field_key == key).scalar()
1515 return row
1515 return row
1516
1516
1517
1517
1518 class Repository(Base, BaseModel):
1518 class Repository(Base, BaseModel):
1519 __tablename__ = 'repositories'
1519 __tablename__ = 'repositories'
1520 __table_args__ = (
1520 __table_args__ = (
1521 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1521 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1522 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1522 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1523 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1523 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1524 )
1524 )
1525 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1525 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1526 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1526 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1527
1527
1528 STATE_CREATED = 'repo_state_created'
1528 STATE_CREATED = 'repo_state_created'
1529 STATE_PENDING = 'repo_state_pending'
1529 STATE_PENDING = 'repo_state_pending'
1530 STATE_ERROR = 'repo_state_error'
1530 STATE_ERROR = 'repo_state_error'
1531
1531
1532 LOCK_AUTOMATIC = 'lock_auto'
1532 LOCK_AUTOMATIC = 'lock_auto'
1533 LOCK_API = 'lock_api'
1533 LOCK_API = 'lock_api'
1534 LOCK_WEB = 'lock_web'
1534 LOCK_WEB = 'lock_web'
1535 LOCK_PULL = 'lock_pull'
1535 LOCK_PULL = 'lock_pull'
1536
1536
1537 NAME_SEP = URL_SEP
1537 NAME_SEP = URL_SEP
1538
1538
1539 repo_id = Column(
1539 repo_id = Column(
1540 "repo_id", Integer(), nullable=False, unique=True, default=None,
1540 "repo_id", Integer(), nullable=False, unique=True, default=None,
1541 primary_key=True)
1541 primary_key=True)
1542 _repo_name = Column(
1542 _repo_name = Column(
1543 "repo_name", Text(), nullable=False, default=None)
1543 "repo_name", Text(), nullable=False, default=None)
1544 _repo_name_hash = Column(
1544 _repo_name_hash = Column(
1545 "repo_name_hash", String(255), nullable=False, unique=True)
1545 "repo_name_hash", String(255), nullable=False, unique=True)
1546 repo_state = Column("repo_state", String(255), nullable=True)
1546 repo_state = Column("repo_state", String(255), nullable=True)
1547
1547
1548 clone_uri = Column(
1548 clone_uri = Column(
1549 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1549 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1550 default=None)
1550 default=None)
1551 repo_type = Column(
1551 repo_type = Column(
1552 "repo_type", String(255), nullable=False, unique=False, default=None)
1552 "repo_type", String(255), nullable=False, unique=False, default=None)
1553 user_id = Column(
1553 user_id = Column(
1554 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1554 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1555 unique=False, default=None)
1555 unique=False, default=None)
1556 private = Column(
1556 private = Column(
1557 "private", Boolean(), nullable=True, unique=None, default=None)
1557 "private", Boolean(), nullable=True, unique=None, default=None)
1558 enable_statistics = Column(
1558 enable_statistics = Column(
1559 "statistics", Boolean(), nullable=True, unique=None, default=True)
1559 "statistics", Boolean(), nullable=True, unique=None, default=True)
1560 enable_downloads = Column(
1560 enable_downloads = Column(
1561 "downloads", Boolean(), nullable=True, unique=None, default=True)
1561 "downloads", Boolean(), nullable=True, unique=None, default=True)
1562 description = Column(
1562 description = Column(
1563 "description", String(10000), nullable=True, unique=None, default=None)
1563 "description", String(10000), nullable=True, unique=None, default=None)
1564 created_on = Column(
1564 created_on = Column(
1565 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1565 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1566 default=datetime.datetime.now)
1566 default=datetime.datetime.now)
1567 updated_on = Column(
1567 updated_on = Column(
1568 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1568 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1569 default=datetime.datetime.now)
1569 default=datetime.datetime.now)
1570 _landing_revision = Column(
1570 _landing_revision = Column(
1571 "landing_revision", String(255), nullable=False, unique=False,
1571 "landing_revision", String(255), nullable=False, unique=False,
1572 default=None)
1572 default=None)
1573 enable_locking = Column(
1573 enable_locking = Column(
1574 "enable_locking", Boolean(), nullable=False, unique=None,
1574 "enable_locking", Boolean(), nullable=False, unique=None,
1575 default=False)
1575 default=False)
1576 _locked = Column(
1576 _locked = Column(
1577 "locked", String(255), nullable=True, unique=False, default=None)
1577 "locked", String(255), nullable=True, unique=False, default=None)
1578 _changeset_cache = Column(
1578 _changeset_cache = Column(
1579 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1579 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1580
1580
1581 fork_id = Column(
1581 fork_id = Column(
1582 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1582 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1583 nullable=True, unique=False, default=None)
1583 nullable=True, unique=False, default=None)
1584 group_id = Column(
1584 group_id = Column(
1585 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1585 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1586 unique=False, default=None)
1586 unique=False, default=None)
1587
1587
1588 user = relationship('User', lazy='joined')
1588 user = relationship('User', lazy='joined')
1589 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1589 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1590 group = relationship('RepoGroup', lazy='joined')
1590 group = relationship('RepoGroup', lazy='joined')
1591 repo_to_perm = relationship(
1591 repo_to_perm = relationship(
1592 'UserRepoToPerm', cascade='all',
1592 'UserRepoToPerm', cascade='all',
1593 order_by='UserRepoToPerm.repo_to_perm_id')
1593 order_by='UserRepoToPerm.repo_to_perm_id')
1594 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1594 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1595 stats = relationship('Statistics', cascade='all', uselist=False)
1595 stats = relationship('Statistics', cascade='all', uselist=False)
1596
1596
1597 followers = relationship(
1597 followers = relationship(
1598 'UserFollowing',
1598 'UserFollowing',
1599 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1599 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1600 cascade='all')
1600 cascade='all')
1601 extra_fields = relationship(
1601 extra_fields = relationship(
1602 'RepositoryField', cascade="all, delete, delete-orphan")
1602 'RepositoryField', cascade="all, delete, delete-orphan")
1603 logs = relationship('UserLog')
1603 logs = relationship('UserLog')
1604 comments = relationship(
1604 comments = relationship(
1605 'ChangesetComment', cascade="all, delete, delete-orphan")
1605 'ChangesetComment', cascade="all, delete, delete-orphan")
1606 pull_requests_source = relationship(
1606 pull_requests_source = relationship(
1607 'PullRequest',
1607 'PullRequest',
1608 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1608 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1609 cascade="all, delete, delete-orphan")
1609 cascade="all, delete, delete-orphan")
1610 pull_requests_target = relationship(
1610 pull_requests_target = relationship(
1611 'PullRequest',
1611 'PullRequest',
1612 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1612 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1613 cascade="all, delete, delete-orphan")
1613 cascade="all, delete, delete-orphan")
1614 ui = relationship('RepoRhodeCodeUi', cascade="all")
1614 ui = relationship('RepoRhodeCodeUi', cascade="all")
1615 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1615 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1616 integrations = relationship('Integration',
1616 integrations = relationship('Integration',
1617 cascade="all, delete, delete-orphan")
1617 cascade="all, delete, delete-orphan")
1618
1618
1619 def __unicode__(self):
1619 def __unicode__(self):
1620 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1620 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1621 safe_unicode(self.repo_name))
1621 safe_unicode(self.repo_name))
1622
1622
1623 @hybrid_property
1623 @hybrid_property
1624 def description_safe(self):
1624 def description_safe(self):
1625 from rhodecode.lib import helpers as h
1625 from rhodecode.lib import helpers as h
1626 return h.escape(self.description)
1626 return h.escape(self.description)
1627
1627
1628 @hybrid_property
1628 @hybrid_property
1629 def landing_rev(self):
1629 def landing_rev(self):
1630 # always should return [rev_type, rev]
1630 # always should return [rev_type, rev]
1631 if self._landing_revision:
1631 if self._landing_revision:
1632 _rev_info = self._landing_revision.split(':')
1632 _rev_info = self._landing_revision.split(':')
1633 if len(_rev_info) < 2:
1633 if len(_rev_info) < 2:
1634 _rev_info.insert(0, 'rev')
1634 _rev_info.insert(0, 'rev')
1635 return [_rev_info[0], _rev_info[1]]
1635 return [_rev_info[0], _rev_info[1]]
1636 return [None, None]
1636 return [None, None]
1637
1637
1638 @landing_rev.setter
1638 @landing_rev.setter
1639 def landing_rev(self, val):
1639 def landing_rev(self, val):
1640 if ':' not in val:
1640 if ':' not in val:
1641 raise ValueError('value must be delimited with `:` and consist '
1641 raise ValueError('value must be delimited with `:` and consist '
1642 'of <rev_type>:<rev>, got %s instead' % val)
1642 'of <rev_type>:<rev>, got %s instead' % val)
1643 self._landing_revision = val
1643 self._landing_revision = val
1644
1644
1645 @hybrid_property
1645 @hybrid_property
1646 def locked(self):
1646 def locked(self):
1647 if self._locked:
1647 if self._locked:
1648 user_id, timelocked, reason = self._locked.split(':')
1648 user_id, timelocked, reason = self._locked.split(':')
1649 lock_values = int(user_id), timelocked, reason
1649 lock_values = int(user_id), timelocked, reason
1650 else:
1650 else:
1651 lock_values = [None, None, None]
1651 lock_values = [None, None, None]
1652 return lock_values
1652 return lock_values
1653
1653
1654 @locked.setter
1654 @locked.setter
1655 def locked(self, val):
1655 def locked(self, val):
1656 if val and isinstance(val, (list, tuple)):
1656 if val and isinstance(val, (list, tuple)):
1657 self._locked = ':'.join(map(str, val))
1657 self._locked = ':'.join(map(str, val))
1658 else:
1658 else:
1659 self._locked = None
1659 self._locked = None
1660
1660
1661 @hybrid_property
1661 @hybrid_property
1662 def changeset_cache(self):
1662 def changeset_cache(self):
1663 from rhodecode.lib.vcs.backends.base import EmptyCommit
1663 from rhodecode.lib.vcs.backends.base import EmptyCommit
1664 dummy = EmptyCommit().__json__()
1664 dummy = EmptyCommit().__json__()
1665 if not self._changeset_cache:
1665 if not self._changeset_cache:
1666 return dummy
1666 return dummy
1667 try:
1667 try:
1668 return json.loads(self._changeset_cache)
1668 return json.loads(self._changeset_cache)
1669 except TypeError:
1669 except TypeError:
1670 return dummy
1670 return dummy
1671 except Exception:
1671 except Exception:
1672 log.error(traceback.format_exc())
1672 log.error(traceback.format_exc())
1673 return dummy
1673 return dummy
1674
1674
1675 @changeset_cache.setter
1675 @changeset_cache.setter
1676 def changeset_cache(self, val):
1676 def changeset_cache(self, val):
1677 try:
1677 try:
1678 self._changeset_cache = json.dumps(val)
1678 self._changeset_cache = json.dumps(val)
1679 except Exception:
1679 except Exception:
1680 log.error(traceback.format_exc())
1680 log.error(traceback.format_exc())
1681
1681
1682 @hybrid_property
1682 @hybrid_property
1683 def repo_name(self):
1683 def repo_name(self):
1684 return self._repo_name
1684 return self._repo_name
1685
1685
1686 @repo_name.setter
1686 @repo_name.setter
1687 def repo_name(self, value):
1687 def repo_name(self, value):
1688 self._repo_name = value
1688 self._repo_name = value
1689 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1689 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1690
1690
1691 @classmethod
1691 @classmethod
1692 def normalize_repo_name(cls, repo_name):
1692 def normalize_repo_name(cls, repo_name):
1693 """
1693 """
1694 Normalizes os specific repo_name to the format internally stored inside
1694 Normalizes os specific repo_name to the format internally stored inside
1695 database using URL_SEP
1695 database using URL_SEP
1696
1696
1697 :param cls:
1697 :param cls:
1698 :param repo_name:
1698 :param repo_name:
1699 """
1699 """
1700 return cls.NAME_SEP.join(repo_name.split(os.sep))
1700 return cls.NAME_SEP.join(repo_name.split(os.sep))
1701
1701
1702 @classmethod
1702 @classmethod
1703 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1703 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1704 session = Session()
1704 session = Session()
1705 q = session.query(cls).filter(cls.repo_name == repo_name)
1705 q = session.query(cls).filter(cls.repo_name == repo_name)
1706
1706
1707 if cache:
1707 if cache:
1708 if identity_cache:
1708 if identity_cache:
1709 val = cls.identity_cache(session, 'repo_name', repo_name)
1709 val = cls.identity_cache(session, 'repo_name', repo_name)
1710 if val:
1710 if val:
1711 return val
1711 return val
1712 else:
1712 else:
1713 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1713 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1714 q = q.options(
1714 q = q.options(
1715 FromCache("sql_cache_short", cache_key))
1715 FromCache("sql_cache_short", cache_key))
1716
1716
1717 return q.scalar()
1717 return q.scalar()
1718
1718
1719 @classmethod
1719 @classmethod
1720 def get_by_full_path(cls, repo_full_path):
1720 def get_by_full_path(cls, repo_full_path):
1721 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1721 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1722 repo_name = cls.normalize_repo_name(repo_name)
1722 repo_name = cls.normalize_repo_name(repo_name)
1723 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1723 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1724
1724
1725 @classmethod
1725 @classmethod
1726 def get_repo_forks(cls, repo_id):
1726 def get_repo_forks(cls, repo_id):
1727 return cls.query().filter(Repository.fork_id == repo_id)
1727 return cls.query().filter(Repository.fork_id == repo_id)
1728
1728
1729 @classmethod
1729 @classmethod
1730 def base_path(cls):
1730 def base_path(cls):
1731 """
1731 """
1732 Returns base path when all repos are stored
1732 Returns base path when all repos are stored
1733
1733
1734 :param cls:
1734 :param cls:
1735 """
1735 """
1736 q = Session().query(RhodeCodeUi)\
1736 q = Session().query(RhodeCodeUi)\
1737 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1737 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1738 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1738 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1739 return q.one().ui_value
1739 return q.one().ui_value
1740
1740
1741 @classmethod
1741 @classmethod
1742 def is_valid(cls, repo_name):
1742 def is_valid(cls, repo_name):
1743 """
1743 """
1744 returns True if given repo name is a valid filesystem repository
1744 returns True if given repo name is a valid filesystem repository
1745
1745
1746 :param cls:
1746 :param cls:
1747 :param repo_name:
1747 :param repo_name:
1748 """
1748 """
1749 from rhodecode.lib.utils import is_valid_repo
1749 from rhodecode.lib.utils import is_valid_repo
1750
1750
1751 return is_valid_repo(repo_name, cls.base_path())
1751 return is_valid_repo(repo_name, cls.base_path())
1752
1752
1753 @classmethod
1753 @classmethod
1754 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1754 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1755 case_insensitive=True):
1755 case_insensitive=True):
1756 q = Repository.query()
1756 q = Repository.query()
1757
1757
1758 if not isinstance(user_id, Optional):
1758 if not isinstance(user_id, Optional):
1759 q = q.filter(Repository.user_id == user_id)
1759 q = q.filter(Repository.user_id == user_id)
1760
1760
1761 if not isinstance(group_id, Optional):
1761 if not isinstance(group_id, Optional):
1762 q = q.filter(Repository.group_id == group_id)
1762 q = q.filter(Repository.group_id == group_id)
1763
1763
1764 if case_insensitive:
1764 if case_insensitive:
1765 q = q.order_by(func.lower(Repository.repo_name))
1765 q = q.order_by(func.lower(Repository.repo_name))
1766 else:
1766 else:
1767 q = q.order_by(Repository.repo_name)
1767 q = q.order_by(Repository.repo_name)
1768 return q.all()
1768 return q.all()
1769
1769
1770 @property
1770 @property
1771 def forks(self):
1771 def forks(self):
1772 """
1772 """
1773 Return forks of this repo
1773 Return forks of this repo
1774 """
1774 """
1775 return Repository.get_repo_forks(self.repo_id)
1775 return Repository.get_repo_forks(self.repo_id)
1776
1776
1777 @property
1777 @property
1778 def parent(self):
1778 def parent(self):
1779 """
1779 """
1780 Returns fork parent
1780 Returns fork parent
1781 """
1781 """
1782 return self.fork
1782 return self.fork
1783
1783
1784 @property
1784 @property
1785 def just_name(self):
1785 def just_name(self):
1786 return self.repo_name.split(self.NAME_SEP)[-1]
1786 return self.repo_name.split(self.NAME_SEP)[-1]
1787
1787
1788 @property
1788 @property
1789 def groups_with_parents(self):
1789 def groups_with_parents(self):
1790 groups = []
1790 groups = []
1791 if self.group is None:
1791 if self.group is None:
1792 return groups
1792 return groups
1793
1793
1794 cur_gr = self.group
1794 cur_gr = self.group
1795 groups.insert(0, cur_gr)
1795 groups.insert(0, cur_gr)
1796 while 1:
1796 while 1:
1797 gr = getattr(cur_gr, 'parent_group', None)
1797 gr = getattr(cur_gr, 'parent_group', None)
1798 cur_gr = cur_gr.parent_group
1798 cur_gr = cur_gr.parent_group
1799 if gr is None:
1799 if gr is None:
1800 break
1800 break
1801 groups.insert(0, gr)
1801 groups.insert(0, gr)
1802
1802
1803 return groups
1803 return groups
1804
1804
1805 @property
1805 @property
1806 def groups_and_repo(self):
1806 def groups_and_repo(self):
1807 return self.groups_with_parents, self
1807 return self.groups_with_parents, self
1808
1808
1809 @LazyProperty
1809 @LazyProperty
1810 def repo_path(self):
1810 def repo_path(self):
1811 """
1811 """
1812 Returns base full path for that repository means where it actually
1812 Returns base full path for that repository means where it actually
1813 exists on a filesystem
1813 exists on a filesystem
1814 """
1814 """
1815 q = Session().query(RhodeCodeUi).filter(
1815 q = Session().query(RhodeCodeUi).filter(
1816 RhodeCodeUi.ui_key == self.NAME_SEP)
1816 RhodeCodeUi.ui_key == self.NAME_SEP)
1817 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1817 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1818 return q.one().ui_value
1818 return q.one().ui_value
1819
1819
1820 @property
1820 @property
1821 def repo_full_path(self):
1821 def repo_full_path(self):
1822 p = [self.repo_path]
1822 p = [self.repo_path]
1823 # we need to split the name by / since this is how we store the
1823 # we need to split the name by / since this is how we store the
1824 # names in the database, but that eventually needs to be converted
1824 # names in the database, but that eventually needs to be converted
1825 # into a valid system path
1825 # into a valid system path
1826 p += self.repo_name.split(self.NAME_SEP)
1826 p += self.repo_name.split(self.NAME_SEP)
1827 return os.path.join(*map(safe_unicode, p))
1827 return os.path.join(*map(safe_unicode, p))
1828
1828
1829 @property
1829 @property
1830 def cache_keys(self):
1830 def cache_keys(self):
1831 """
1831 """
1832 Returns associated cache keys for that repo
1832 Returns associated cache keys for that repo
1833 """
1833 """
1834 return CacheKey.query()\
1834 return CacheKey.query()\
1835 .filter(CacheKey.cache_args == self.repo_name)\
1835 .filter(CacheKey.cache_args == self.repo_name)\
1836 .order_by(CacheKey.cache_key)\
1836 .order_by(CacheKey.cache_key)\
1837 .all()
1837 .all()
1838
1838
1839 def get_new_name(self, repo_name):
1839 def get_new_name(self, repo_name):
1840 """
1840 """
1841 returns new full repository name based on assigned group and new new
1841 returns new full repository name based on assigned group and new new
1842
1842
1843 :param group_name:
1843 :param group_name:
1844 """
1844 """
1845 path_prefix = self.group.full_path_splitted if self.group else []
1845 path_prefix = self.group.full_path_splitted if self.group else []
1846 return self.NAME_SEP.join(path_prefix + [repo_name])
1846 return self.NAME_SEP.join(path_prefix + [repo_name])
1847
1847
1848 @property
1848 @property
1849 def _config(self):
1849 def _config(self):
1850 """
1850 """
1851 Returns db based config object.
1851 Returns db based config object.
1852 """
1852 """
1853 from rhodecode.lib.utils import make_db_config
1853 from rhodecode.lib.utils import make_db_config
1854 return make_db_config(clear_session=False, repo=self)
1854 return make_db_config(clear_session=False, repo=self)
1855
1855
1856 def permissions(self, with_admins=True, with_owner=True):
1856 def permissions(self, with_admins=True, with_owner=True):
1857 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1857 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1858 q = q.options(joinedload(UserRepoToPerm.repository),
1858 q = q.options(joinedload(UserRepoToPerm.repository),
1859 joinedload(UserRepoToPerm.user),
1859 joinedload(UserRepoToPerm.user),
1860 joinedload(UserRepoToPerm.permission),)
1860 joinedload(UserRepoToPerm.permission),)
1861
1861
1862 # get owners and admins and permissions. We do a trick of re-writing
1862 # get owners and admins and permissions. We do a trick of re-writing
1863 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1863 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1864 # has a global reference and changing one object propagates to all
1864 # has a global reference and changing one object propagates to all
1865 # others. This means if admin is also an owner admin_row that change
1865 # others. This means if admin is also an owner admin_row that change
1866 # would propagate to both objects
1866 # would propagate to both objects
1867 perm_rows = []
1867 perm_rows = []
1868 for _usr in q.all():
1868 for _usr in q.all():
1869 usr = AttributeDict(_usr.user.get_dict())
1869 usr = AttributeDict(_usr.user.get_dict())
1870 usr.permission = _usr.permission.permission_name
1870 usr.permission = _usr.permission.permission_name
1871 perm_rows.append(usr)
1871 perm_rows.append(usr)
1872
1872
1873 # filter the perm rows by 'default' first and then sort them by
1873 # filter the perm rows by 'default' first and then sort them by
1874 # admin,write,read,none permissions sorted again alphabetically in
1874 # admin,write,read,none permissions sorted again alphabetically in
1875 # each group
1875 # each group
1876 perm_rows = sorted(perm_rows, key=display_user_sort)
1876 perm_rows = sorted(perm_rows, key=display_user_sort)
1877
1877
1878 _admin_perm = 'repository.admin'
1878 _admin_perm = 'repository.admin'
1879 owner_row = []
1879 owner_row = []
1880 if with_owner:
1880 if with_owner:
1881 usr = AttributeDict(self.user.get_dict())
1881 usr = AttributeDict(self.user.get_dict())
1882 usr.owner_row = True
1882 usr.owner_row = True
1883 usr.permission = _admin_perm
1883 usr.permission = _admin_perm
1884 owner_row.append(usr)
1884 owner_row.append(usr)
1885
1885
1886 super_admin_rows = []
1886 super_admin_rows = []
1887 if with_admins:
1887 if with_admins:
1888 for usr in User.get_all_super_admins():
1888 for usr in User.get_all_super_admins():
1889 # if this admin is also owner, don't double the record
1889 # if this admin is also owner, don't double the record
1890 if usr.user_id == owner_row[0].user_id:
1890 if usr.user_id == owner_row[0].user_id:
1891 owner_row[0].admin_row = True
1891 owner_row[0].admin_row = True
1892 else:
1892 else:
1893 usr = AttributeDict(usr.get_dict())
1893 usr = AttributeDict(usr.get_dict())
1894 usr.admin_row = True
1894 usr.admin_row = True
1895 usr.permission = _admin_perm
1895 usr.permission = _admin_perm
1896 super_admin_rows.append(usr)
1896 super_admin_rows.append(usr)
1897
1897
1898 return super_admin_rows + owner_row + perm_rows
1898 return super_admin_rows + owner_row + perm_rows
1899
1899
1900 def permission_user_groups(self):
1900 def permission_user_groups(self):
1901 q = UserGroupRepoToPerm.query().filter(
1901 q = UserGroupRepoToPerm.query().filter(
1902 UserGroupRepoToPerm.repository == self)
1902 UserGroupRepoToPerm.repository == self)
1903 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1903 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1904 joinedload(UserGroupRepoToPerm.users_group),
1904 joinedload(UserGroupRepoToPerm.users_group),
1905 joinedload(UserGroupRepoToPerm.permission),)
1905 joinedload(UserGroupRepoToPerm.permission),)
1906
1906
1907 perm_rows = []
1907 perm_rows = []
1908 for _user_group in q.all():
1908 for _user_group in q.all():
1909 usr = AttributeDict(_user_group.users_group.get_dict())
1909 usr = AttributeDict(_user_group.users_group.get_dict())
1910 usr.permission = _user_group.permission.permission_name
1910 usr.permission = _user_group.permission.permission_name
1911 perm_rows.append(usr)
1911 perm_rows.append(usr)
1912
1912
1913 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1913 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1914 return perm_rows
1914 return perm_rows
1915
1915
1916 def get_api_data(self, include_secrets=False):
1916 def get_api_data(self, include_secrets=False):
1917 """
1917 """
1918 Common function for generating repo api data
1918 Common function for generating repo api data
1919
1919
1920 :param include_secrets: See :meth:`User.get_api_data`.
1920 :param include_secrets: See :meth:`User.get_api_data`.
1921
1921
1922 """
1922 """
1923 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1923 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1924 # move this methods on models level.
1924 # move this methods on models level.
1925 from rhodecode.model.settings import SettingsModel
1925 from rhodecode.model.settings import SettingsModel
1926 from rhodecode.model.repo import RepoModel
1926 from rhodecode.model.repo import RepoModel
1927
1927
1928 repo = self
1928 repo = self
1929 _user_id, _time, _reason = self.locked
1929 _user_id, _time, _reason = self.locked
1930
1930
1931 data = {
1931 data = {
1932 'repo_id': repo.repo_id,
1932 'repo_id': repo.repo_id,
1933 'repo_name': repo.repo_name,
1933 'repo_name': repo.repo_name,
1934 'repo_type': repo.repo_type,
1934 'repo_type': repo.repo_type,
1935 'clone_uri': repo.clone_uri or '',
1935 'clone_uri': repo.clone_uri or '',
1936 'url': RepoModel().get_url(self),
1936 'url': RepoModel().get_url(self),
1937 'private': repo.private,
1937 'private': repo.private,
1938 'created_on': repo.created_on,
1938 'created_on': repo.created_on,
1939 'description': repo.description_safe,
1939 'description': repo.description_safe,
1940 'landing_rev': repo.landing_rev,
1940 'landing_rev': repo.landing_rev,
1941 'owner': repo.user.username,
1941 'owner': repo.user.username,
1942 'fork_of': repo.fork.repo_name if repo.fork else None,
1942 'fork_of': repo.fork.repo_name if repo.fork else None,
1943 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1943 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1944 'enable_statistics': repo.enable_statistics,
1944 'enable_statistics': repo.enable_statistics,
1945 'enable_locking': repo.enable_locking,
1945 'enable_locking': repo.enable_locking,
1946 'enable_downloads': repo.enable_downloads,
1946 'enable_downloads': repo.enable_downloads,
1947 'last_changeset': repo.changeset_cache,
1947 'last_changeset': repo.changeset_cache,
1948 'locked_by': User.get(_user_id).get_api_data(
1948 'locked_by': User.get(_user_id).get_api_data(
1949 include_secrets=include_secrets) if _user_id else None,
1949 include_secrets=include_secrets) if _user_id else None,
1950 'locked_date': time_to_datetime(_time) if _time else None,
1950 'locked_date': time_to_datetime(_time) if _time else None,
1951 'lock_reason': _reason if _reason else None,
1951 'lock_reason': _reason if _reason else None,
1952 }
1952 }
1953
1953
1954 # TODO: mikhail: should be per-repo settings here
1954 # TODO: mikhail: should be per-repo settings here
1955 rc_config = SettingsModel().get_all_settings()
1955 rc_config = SettingsModel().get_all_settings()
1956 repository_fields = str2bool(
1956 repository_fields = str2bool(
1957 rc_config.get('rhodecode_repository_fields'))
1957 rc_config.get('rhodecode_repository_fields'))
1958 if repository_fields:
1958 if repository_fields:
1959 for f in self.extra_fields:
1959 for f in self.extra_fields:
1960 data[f.field_key_prefixed] = f.field_value
1960 data[f.field_key_prefixed] = f.field_value
1961
1961
1962 return data
1962 return data
1963
1963
1964 @classmethod
1964 @classmethod
1965 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1965 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1966 if not lock_time:
1966 if not lock_time:
1967 lock_time = time.time()
1967 lock_time = time.time()
1968 if not lock_reason:
1968 if not lock_reason:
1969 lock_reason = cls.LOCK_AUTOMATIC
1969 lock_reason = cls.LOCK_AUTOMATIC
1970 repo.locked = [user_id, lock_time, lock_reason]
1970 repo.locked = [user_id, lock_time, lock_reason]
1971 Session().add(repo)
1971 Session().add(repo)
1972 Session().commit()
1972 Session().commit()
1973
1973
1974 @classmethod
1974 @classmethod
1975 def unlock(cls, repo):
1975 def unlock(cls, repo):
1976 repo.locked = None
1976 repo.locked = None
1977 Session().add(repo)
1977 Session().add(repo)
1978 Session().commit()
1978 Session().commit()
1979
1979
1980 @classmethod
1980 @classmethod
1981 def getlock(cls, repo):
1981 def getlock(cls, repo):
1982 return repo.locked
1982 return repo.locked
1983
1983
1984 def is_user_lock(self, user_id):
1984 def is_user_lock(self, user_id):
1985 if self.lock[0]:
1985 if self.lock[0]:
1986 lock_user_id = safe_int(self.lock[0])
1986 lock_user_id = safe_int(self.lock[0])
1987 user_id = safe_int(user_id)
1987 user_id = safe_int(user_id)
1988 # both are ints, and they are equal
1988 # both are ints, and they are equal
1989 return all([lock_user_id, user_id]) and lock_user_id == user_id
1989 return all([lock_user_id, user_id]) and lock_user_id == user_id
1990
1990
1991 return False
1991 return False
1992
1992
1993 def get_locking_state(self, action, user_id, only_when_enabled=True):
1993 def get_locking_state(self, action, user_id, only_when_enabled=True):
1994 """
1994 """
1995 Checks locking on this repository, if locking is enabled and lock is
1995 Checks locking on this repository, if locking is enabled and lock is
1996 present returns a tuple of make_lock, locked, locked_by.
1996 present returns a tuple of make_lock, locked, locked_by.
1997 make_lock can have 3 states None (do nothing) True, make lock
1997 make_lock can have 3 states None (do nothing) True, make lock
1998 False release lock, This value is later propagated to hooks, which
1998 False release lock, This value is later propagated to hooks, which
1999 do the locking. Think about this as signals passed to hooks what to do.
1999 do the locking. Think about this as signals passed to hooks what to do.
2000
2000
2001 """
2001 """
2002 # TODO: johbo: This is part of the business logic and should be moved
2002 # TODO: johbo: This is part of the business logic and should be moved
2003 # into the RepositoryModel.
2003 # into the RepositoryModel.
2004
2004
2005 if action not in ('push', 'pull'):
2005 if action not in ('push', 'pull'):
2006 raise ValueError("Invalid action value: %s" % repr(action))
2006 raise ValueError("Invalid action value: %s" % repr(action))
2007
2007
2008 # defines if locked error should be thrown to user
2008 # defines if locked error should be thrown to user
2009 currently_locked = False
2009 currently_locked = False
2010 # defines if new lock should be made, tri-state
2010 # defines if new lock should be made, tri-state
2011 make_lock = None
2011 make_lock = None
2012 repo = self
2012 repo = self
2013 user = User.get(user_id)
2013 user = User.get(user_id)
2014
2014
2015 lock_info = repo.locked
2015 lock_info = repo.locked
2016
2016
2017 if repo and (repo.enable_locking or not only_when_enabled):
2017 if repo and (repo.enable_locking or not only_when_enabled):
2018 if action == 'push':
2018 if action == 'push':
2019 # check if it's already locked !, if it is compare users
2019 # check if it's already locked !, if it is compare users
2020 locked_by_user_id = lock_info[0]
2020 locked_by_user_id = lock_info[0]
2021 if user.user_id == locked_by_user_id:
2021 if user.user_id == locked_by_user_id:
2022 log.debug(
2022 log.debug(
2023 'Got `push` action from user %s, now unlocking', user)
2023 'Got `push` action from user %s, now unlocking', user)
2024 # unlock if we have push from user who locked
2024 # unlock if we have push from user who locked
2025 make_lock = False
2025 make_lock = False
2026 else:
2026 else:
2027 # we're not the same user who locked, ban with
2027 # we're not the same user who locked, ban with
2028 # code defined in settings (default is 423 HTTP Locked) !
2028 # code defined in settings (default is 423 HTTP Locked) !
2029 log.debug('Repo %s is currently locked by %s', repo, user)
2029 log.debug('Repo %s is currently locked by %s', repo, user)
2030 currently_locked = True
2030 currently_locked = True
2031 elif action == 'pull':
2031 elif action == 'pull':
2032 # [0] user [1] date
2032 # [0] user [1] date
2033 if lock_info[0] and lock_info[1]:
2033 if lock_info[0] and lock_info[1]:
2034 log.debug('Repo %s is currently locked by %s', repo, user)
2034 log.debug('Repo %s is currently locked by %s', repo, user)
2035 currently_locked = True
2035 currently_locked = True
2036 else:
2036 else:
2037 log.debug('Setting lock on repo %s by %s', repo, user)
2037 log.debug('Setting lock on repo %s by %s', repo, user)
2038 make_lock = True
2038 make_lock = True
2039
2039
2040 else:
2040 else:
2041 log.debug('Repository %s do not have locking enabled', repo)
2041 log.debug('Repository %s do not have locking enabled', repo)
2042
2042
2043 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2043 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2044 make_lock, currently_locked, lock_info)
2044 make_lock, currently_locked, lock_info)
2045
2045
2046 from rhodecode.lib.auth import HasRepoPermissionAny
2046 from rhodecode.lib.auth import HasRepoPermissionAny
2047 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2047 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2048 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2048 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2049 # if we don't have at least write permission we cannot make a lock
2049 # if we don't have at least write permission we cannot make a lock
2050 log.debug('lock state reset back to FALSE due to lack '
2050 log.debug('lock state reset back to FALSE due to lack '
2051 'of at least read permission')
2051 'of at least read permission')
2052 make_lock = False
2052 make_lock = False
2053
2053
2054 return make_lock, currently_locked, lock_info
2054 return make_lock, currently_locked, lock_info
2055
2055
2056 @property
2056 @property
2057 def last_db_change(self):
2057 def last_db_change(self):
2058 return self.updated_on
2058 return self.updated_on
2059
2059
2060 @property
2060 @property
2061 def clone_uri_hidden(self):
2061 def clone_uri_hidden(self):
2062 clone_uri = self.clone_uri
2062 clone_uri = self.clone_uri
2063 if clone_uri:
2063 if clone_uri:
2064 import urlobject
2064 import urlobject
2065 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2065 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2066 if url_obj.password:
2066 if url_obj.password:
2067 clone_uri = url_obj.with_password('*****')
2067 clone_uri = url_obj.with_password('*****')
2068 return clone_uri
2068 return clone_uri
2069
2069
2070 def clone_url(self, **override):
2070 def clone_url(self, **override):
2071 from rhodecode.model.settings import SettingsModel
2071 from rhodecode.model.settings import SettingsModel
2072
2072
2073 uri_tmpl = None
2073 uri_tmpl = None
2074 if 'with_id' in override:
2074 if 'with_id' in override:
2075 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2075 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2076 del override['with_id']
2076 del override['with_id']
2077
2077
2078 if 'uri_tmpl' in override:
2078 if 'uri_tmpl' in override:
2079 uri_tmpl = override['uri_tmpl']
2079 uri_tmpl = override['uri_tmpl']
2080 del override['uri_tmpl']
2080 del override['uri_tmpl']
2081
2081
2082 # we didn't override our tmpl from **overrides
2082 # we didn't override our tmpl from **overrides
2083 if not uri_tmpl:
2083 if not uri_tmpl:
2084 rc_config = SettingsModel().get_all_settings(cache=True)
2084 rc_config = SettingsModel().get_all_settings(cache=True)
2085 uri_tmpl = rc_config.get(
2085 uri_tmpl = rc_config.get(
2086 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2086 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2087
2087
2088 request = get_current_request()
2088 request = get_current_request()
2089 return get_clone_url(request=request,
2089 return get_clone_url(request=request,
2090 uri_tmpl=uri_tmpl,
2090 uri_tmpl=uri_tmpl,
2091 repo_name=self.repo_name,
2091 repo_name=self.repo_name,
2092 repo_id=self.repo_id, **override)
2092 repo_id=self.repo_id, **override)
2093
2093
2094 def set_state(self, state):
2094 def set_state(self, state):
2095 self.repo_state = state
2095 self.repo_state = state
2096 Session().add(self)
2096 Session().add(self)
2097 #==========================================================================
2097 #==========================================================================
2098 # SCM PROPERTIES
2098 # SCM PROPERTIES
2099 #==========================================================================
2099 #==========================================================================
2100
2100
2101 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2101 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2102 return get_commit_safe(
2102 return get_commit_safe(
2103 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2103 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2104
2104
2105 def get_changeset(self, rev=None, pre_load=None):
2105 def get_changeset(self, rev=None, pre_load=None):
2106 warnings.warn("Use get_commit", DeprecationWarning)
2106 warnings.warn("Use get_commit", DeprecationWarning)
2107 commit_id = None
2107 commit_id = None
2108 commit_idx = None
2108 commit_idx = None
2109 if isinstance(rev, basestring):
2109 if isinstance(rev, basestring):
2110 commit_id = rev
2110 commit_id = rev
2111 else:
2111 else:
2112 commit_idx = rev
2112 commit_idx = rev
2113 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2113 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2114 pre_load=pre_load)
2114 pre_load=pre_load)
2115
2115
2116 def get_landing_commit(self):
2116 def get_landing_commit(self):
2117 """
2117 """
2118 Returns landing commit, or if that doesn't exist returns the tip
2118 Returns landing commit, or if that doesn't exist returns the tip
2119 """
2119 """
2120 _rev_type, _rev = self.landing_rev
2120 _rev_type, _rev = self.landing_rev
2121 commit = self.get_commit(_rev)
2121 commit = self.get_commit(_rev)
2122 if isinstance(commit, EmptyCommit):
2122 if isinstance(commit, EmptyCommit):
2123 return self.get_commit()
2123 return self.get_commit()
2124 return commit
2124 return commit
2125
2125
2126 def update_commit_cache(self, cs_cache=None, config=None):
2126 def update_commit_cache(self, cs_cache=None, config=None):
2127 """
2127 """
2128 Update cache of last changeset for repository, keys should be::
2128 Update cache of last changeset for repository, keys should be::
2129
2129
2130 short_id
2130 short_id
2131 raw_id
2131 raw_id
2132 revision
2132 revision
2133 parents
2133 parents
2134 message
2134 message
2135 date
2135 date
2136 author
2136 author
2137
2137
2138 :param cs_cache:
2138 :param cs_cache:
2139 """
2139 """
2140 from rhodecode.lib.vcs.backends.base import BaseChangeset
2140 from rhodecode.lib.vcs.backends.base import BaseChangeset
2141 if cs_cache is None:
2141 if cs_cache is None:
2142 # use no-cache version here
2142 # use no-cache version here
2143 scm_repo = self.scm_instance(cache=False, config=config)
2143 scm_repo = self.scm_instance(cache=False, config=config)
2144 if scm_repo:
2144 if scm_repo:
2145 cs_cache = scm_repo.get_commit(
2145 cs_cache = scm_repo.get_commit(
2146 pre_load=["author", "date", "message", "parents"])
2146 pre_load=["author", "date", "message", "parents"])
2147 else:
2147 else:
2148 cs_cache = EmptyCommit()
2148 cs_cache = EmptyCommit()
2149
2149
2150 if isinstance(cs_cache, BaseChangeset):
2150 if isinstance(cs_cache, BaseChangeset):
2151 cs_cache = cs_cache.__json__()
2151 cs_cache = cs_cache.__json__()
2152
2152
2153 def is_outdated(new_cs_cache):
2153 def is_outdated(new_cs_cache):
2154 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2154 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2155 new_cs_cache['revision'] != self.changeset_cache['revision']):
2155 new_cs_cache['revision'] != self.changeset_cache['revision']):
2156 return True
2156 return True
2157 return False
2157 return False
2158
2158
2159 # check if we have maybe already latest cached revision
2159 # check if we have maybe already latest cached revision
2160 if is_outdated(cs_cache) or not self.changeset_cache:
2160 if is_outdated(cs_cache) or not self.changeset_cache:
2161 _default = datetime.datetime.fromtimestamp(0)
2161 _default = datetime.datetime.fromtimestamp(0)
2162 last_change = cs_cache.get('date') or _default
2162 last_change = cs_cache.get('date') or _default
2163 log.debug('updated repo %s with new cs cache %s',
2163 log.debug('updated repo %s with new cs cache %s',
2164 self.repo_name, cs_cache)
2164 self.repo_name, cs_cache)
2165 self.updated_on = last_change
2165 self.updated_on = last_change
2166 self.changeset_cache = cs_cache
2166 self.changeset_cache = cs_cache
2167 Session().add(self)
2167 Session().add(self)
2168 Session().commit()
2168 Session().commit()
2169 else:
2169 else:
2170 log.debug('Skipping update_commit_cache for repo:`%s` '
2170 log.debug('Skipping update_commit_cache for repo:`%s` '
2171 'commit already with latest changes', self.repo_name)
2171 'commit already with latest changes', self.repo_name)
2172
2172
2173 @property
2173 @property
2174 def tip(self):
2174 def tip(self):
2175 return self.get_commit('tip')
2175 return self.get_commit('tip')
2176
2176
2177 @property
2177 @property
2178 def author(self):
2178 def author(self):
2179 return self.tip.author
2179 return self.tip.author
2180
2180
2181 @property
2181 @property
2182 def last_change(self):
2182 def last_change(self):
2183 return self.scm_instance().last_change
2183 return self.scm_instance().last_change
2184
2184
2185 def get_comments(self, revisions=None):
2185 def get_comments(self, revisions=None):
2186 """
2186 """
2187 Returns comments for this repository grouped by revisions
2187 Returns comments for this repository grouped by revisions
2188
2188
2189 :param revisions: filter query by revisions only
2189 :param revisions: filter query by revisions only
2190 """
2190 """
2191 cmts = ChangesetComment.query()\
2191 cmts = ChangesetComment.query()\
2192 .filter(ChangesetComment.repo == self)
2192 .filter(ChangesetComment.repo == self)
2193 if revisions:
2193 if revisions:
2194 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2194 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2195 grouped = collections.defaultdict(list)
2195 grouped = collections.defaultdict(list)
2196 for cmt in cmts.all():
2196 for cmt in cmts.all():
2197 grouped[cmt.revision].append(cmt)
2197 grouped[cmt.revision].append(cmt)
2198 return grouped
2198 return grouped
2199
2199
2200 def statuses(self, revisions=None):
2200 def statuses(self, revisions=None):
2201 """
2201 """
2202 Returns statuses for this repository
2202 Returns statuses for this repository
2203
2203
2204 :param revisions: list of revisions to get statuses for
2204 :param revisions: list of revisions to get statuses for
2205 """
2205 """
2206 statuses = ChangesetStatus.query()\
2206 statuses = ChangesetStatus.query()\
2207 .filter(ChangesetStatus.repo == self)\
2207 .filter(ChangesetStatus.repo == self)\
2208 .filter(ChangesetStatus.version == 0)
2208 .filter(ChangesetStatus.version == 0)
2209
2209
2210 if revisions:
2210 if revisions:
2211 # Try doing the filtering in chunks to avoid hitting limits
2211 # Try doing the filtering in chunks to avoid hitting limits
2212 size = 500
2212 size = 500
2213 status_results = []
2213 status_results = []
2214 for chunk in xrange(0, len(revisions), size):
2214 for chunk in xrange(0, len(revisions), size):
2215 status_results += statuses.filter(
2215 status_results += statuses.filter(
2216 ChangesetStatus.revision.in_(
2216 ChangesetStatus.revision.in_(
2217 revisions[chunk: chunk+size])
2217 revisions[chunk: chunk+size])
2218 ).all()
2218 ).all()
2219 else:
2219 else:
2220 status_results = statuses.all()
2220 status_results = statuses.all()
2221
2221
2222 grouped = {}
2222 grouped = {}
2223
2223
2224 # maybe we have open new pullrequest without a status?
2224 # maybe we have open new pullrequest without a status?
2225 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2225 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2226 status_lbl = ChangesetStatus.get_status_lbl(stat)
2226 status_lbl = ChangesetStatus.get_status_lbl(stat)
2227 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2227 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2228 for rev in pr.revisions:
2228 for rev in pr.revisions:
2229 pr_id = pr.pull_request_id
2229 pr_id = pr.pull_request_id
2230 pr_repo = pr.target_repo.repo_name
2230 pr_repo = pr.target_repo.repo_name
2231 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2231 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2232
2232
2233 for stat in status_results:
2233 for stat in status_results:
2234 pr_id = pr_repo = None
2234 pr_id = pr_repo = None
2235 if stat.pull_request:
2235 if stat.pull_request:
2236 pr_id = stat.pull_request.pull_request_id
2236 pr_id = stat.pull_request.pull_request_id
2237 pr_repo = stat.pull_request.target_repo.repo_name
2237 pr_repo = stat.pull_request.target_repo.repo_name
2238 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2238 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2239 pr_id, pr_repo]
2239 pr_id, pr_repo]
2240 return grouped
2240 return grouped
2241
2241
2242 # ==========================================================================
2242 # ==========================================================================
2243 # SCM CACHE INSTANCE
2243 # SCM CACHE INSTANCE
2244 # ==========================================================================
2244 # ==========================================================================
2245
2245
2246 def scm_instance(self, **kwargs):
2246 def scm_instance(self, **kwargs):
2247 import rhodecode
2247 import rhodecode
2248
2248
2249 # Passing a config will not hit the cache currently only used
2249 # Passing a config will not hit the cache currently only used
2250 # for repo2dbmapper
2250 # for repo2dbmapper
2251 config = kwargs.pop('config', None)
2251 config = kwargs.pop('config', None)
2252 cache = kwargs.pop('cache', None)
2252 cache = kwargs.pop('cache', None)
2253 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2253 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2254 # if cache is NOT defined use default global, else we have a full
2254 # if cache is NOT defined use default global, else we have a full
2255 # control over cache behaviour
2255 # control over cache behaviour
2256 if cache is None and full_cache and not config:
2256 if cache is None and full_cache and not config:
2257 return self._get_instance_cached()
2257 return self._get_instance_cached()
2258 return self._get_instance(cache=bool(cache), config=config)
2258 return self._get_instance(cache=bool(cache), config=config)
2259
2259
2260 def _get_instance_cached(self):
2260 def _get_instance_cached(self):
2261 @cache_region('long_term')
2261 @cache_region('long_term')
2262 def _get_repo(cache_key):
2262 def _get_repo(cache_key):
2263 return self._get_instance()
2263 return self._get_instance()
2264
2264
2265 invalidator_context = CacheKey.repo_context_cache(
2265 invalidator_context = CacheKey.repo_context_cache(
2266 _get_repo, self.repo_name, None, thread_scoped=True)
2266 _get_repo, self.repo_name, None, thread_scoped=True)
2267
2267
2268 with invalidator_context as context:
2268 with invalidator_context as context:
2269 context.invalidate()
2269 context.invalidate()
2270 repo = context.compute()
2270 repo = context.compute()
2271
2271
2272 return repo
2272 return repo
2273
2273
2274 def _get_instance(self, cache=True, config=None):
2274 def _get_instance(self, cache=True, config=None):
2275 config = config or self._config
2275 config = config or self._config
2276 custom_wire = {
2276 custom_wire = {
2277 'cache': cache # controls the vcs.remote cache
2277 'cache': cache # controls the vcs.remote cache
2278 }
2278 }
2279 repo = get_vcs_instance(
2279 repo = get_vcs_instance(
2280 repo_path=safe_str(self.repo_full_path),
2280 repo_path=safe_str(self.repo_full_path),
2281 config=config,
2281 config=config,
2282 with_wire=custom_wire,
2282 with_wire=custom_wire,
2283 create=False,
2283 create=False,
2284 _vcs_alias=self.repo_type)
2284 _vcs_alias=self.repo_type)
2285
2285
2286 return repo
2286 return repo
2287
2287
2288 def __json__(self):
2288 def __json__(self):
2289 return {'landing_rev': self.landing_rev}
2289 return {'landing_rev': self.landing_rev}
2290
2290
2291 def get_dict(self):
2291 def get_dict(self):
2292
2292
2293 # Since we transformed `repo_name` to a hybrid property, we need to
2293 # Since we transformed `repo_name` to a hybrid property, we need to
2294 # keep compatibility with the code which uses `repo_name` field.
2294 # keep compatibility with the code which uses `repo_name` field.
2295
2295
2296 result = super(Repository, self).get_dict()
2296 result = super(Repository, self).get_dict()
2297 result['repo_name'] = result.pop('_repo_name', None)
2297 result['repo_name'] = result.pop('_repo_name', None)
2298 return result
2298 return result
2299
2299
2300
2300
2301 class RepoGroup(Base, BaseModel):
2301 class RepoGroup(Base, BaseModel):
2302 __tablename__ = 'groups'
2302 __tablename__ = 'groups'
2303 __table_args__ = (
2303 __table_args__ = (
2304 UniqueConstraint('group_name', 'group_parent_id'),
2304 UniqueConstraint('group_name', 'group_parent_id'),
2305 CheckConstraint('group_id != group_parent_id'),
2305 CheckConstraint('group_id != group_parent_id'),
2306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2307 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2307 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2308 )
2308 )
2309 __mapper_args__ = {'order_by': 'group_name'}
2309 __mapper_args__ = {'order_by': 'group_name'}
2310
2310
2311 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2311 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2312
2312
2313 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2313 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2314 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2314 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2315 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2315 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2316 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2316 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2317 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2317 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2318 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2318 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2319 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2319 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2320 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2320 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2321 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2321 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2322
2322
2323 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2323 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2324 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2324 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2325 parent_group = relationship('RepoGroup', remote_side=group_id)
2325 parent_group = relationship('RepoGroup', remote_side=group_id)
2326 user = relationship('User')
2326 user = relationship('User')
2327 integrations = relationship('Integration',
2327 integrations = relationship('Integration',
2328 cascade="all, delete, delete-orphan")
2328 cascade="all, delete, delete-orphan")
2329
2329
2330 def __init__(self, group_name='', parent_group=None):
2330 def __init__(self, group_name='', parent_group=None):
2331 self.group_name = group_name
2331 self.group_name = group_name
2332 self.parent_group = parent_group
2332 self.parent_group = parent_group
2333
2333
2334 def __unicode__(self):
2334 def __unicode__(self):
2335 return u"<%s('id:%s:%s')>" % (
2335 return u"<%s('id:%s:%s')>" % (
2336 self.__class__.__name__, self.group_id, self.group_name)
2336 self.__class__.__name__, self.group_id, self.group_name)
2337
2337
2338 @hybrid_property
2338 @hybrid_property
2339 def description_safe(self):
2339 def description_safe(self):
2340 from rhodecode.lib import helpers as h
2340 from rhodecode.lib import helpers as h
2341 return h.escape(self.group_description)
2341 return h.escape(self.group_description)
2342
2342
2343 @classmethod
2343 @classmethod
2344 def _generate_choice(cls, repo_group):
2344 def _generate_choice(cls, repo_group):
2345 from webhelpers.html import literal as _literal
2345 from webhelpers.html import literal as _literal
2346 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2346 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2347 return repo_group.group_id, _name(repo_group.full_path_splitted)
2347 return repo_group.group_id, _name(repo_group.full_path_splitted)
2348
2348
2349 @classmethod
2349 @classmethod
2350 def groups_choices(cls, groups=None, show_empty_group=True):
2350 def groups_choices(cls, groups=None, show_empty_group=True):
2351 if not groups:
2351 if not groups:
2352 groups = cls.query().all()
2352 groups = cls.query().all()
2353
2353
2354 repo_groups = []
2354 repo_groups = []
2355 if show_empty_group:
2355 if show_empty_group:
2356 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2356 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2357
2357
2358 repo_groups.extend([cls._generate_choice(x) for x in groups])
2358 repo_groups.extend([cls._generate_choice(x) for x in groups])
2359
2359
2360 repo_groups = sorted(
2360 repo_groups = sorted(
2361 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2361 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2362 return repo_groups
2362 return repo_groups
2363
2363
2364 @classmethod
2364 @classmethod
2365 def url_sep(cls):
2365 def url_sep(cls):
2366 return URL_SEP
2366 return URL_SEP
2367
2367
2368 @classmethod
2368 @classmethod
2369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2369 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2370 if case_insensitive:
2370 if case_insensitive:
2371 gr = cls.query().filter(func.lower(cls.group_name)
2371 gr = cls.query().filter(func.lower(cls.group_name)
2372 == func.lower(group_name))
2372 == func.lower(group_name))
2373 else:
2373 else:
2374 gr = cls.query().filter(cls.group_name == group_name)
2374 gr = cls.query().filter(cls.group_name == group_name)
2375 if cache:
2375 if cache:
2376 name_key = _hash_key(group_name)
2376 name_key = _hash_key(group_name)
2377 gr = gr.options(
2377 gr = gr.options(
2378 FromCache("sql_cache_short", "get_group_%s" % name_key))
2378 FromCache("sql_cache_short", "get_group_%s" % name_key))
2379 return gr.scalar()
2379 return gr.scalar()
2380
2380
2381 @classmethod
2381 @classmethod
2382 def get_user_personal_repo_group(cls, user_id):
2382 def get_user_personal_repo_group(cls, user_id):
2383 user = User.get(user_id)
2383 user = User.get(user_id)
2384 if user.username == User.DEFAULT_USER:
2384 if user.username == User.DEFAULT_USER:
2385 return None
2385 return None
2386
2386
2387 return cls.query()\
2387 return cls.query()\
2388 .filter(cls.personal == true()) \
2388 .filter(cls.personal == true()) \
2389 .filter(cls.user == user).scalar()
2389 .filter(cls.user == user).scalar()
2390
2390
2391 @classmethod
2391 @classmethod
2392 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2392 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2393 case_insensitive=True):
2393 case_insensitive=True):
2394 q = RepoGroup.query()
2394 q = RepoGroup.query()
2395
2395
2396 if not isinstance(user_id, Optional):
2396 if not isinstance(user_id, Optional):
2397 q = q.filter(RepoGroup.user_id == user_id)
2397 q = q.filter(RepoGroup.user_id == user_id)
2398
2398
2399 if not isinstance(group_id, Optional):
2399 if not isinstance(group_id, Optional):
2400 q = q.filter(RepoGroup.group_parent_id == group_id)
2400 q = q.filter(RepoGroup.group_parent_id == group_id)
2401
2401
2402 if case_insensitive:
2402 if case_insensitive:
2403 q = q.order_by(func.lower(RepoGroup.group_name))
2403 q = q.order_by(func.lower(RepoGroup.group_name))
2404 else:
2404 else:
2405 q = q.order_by(RepoGroup.group_name)
2405 q = q.order_by(RepoGroup.group_name)
2406 return q.all()
2406 return q.all()
2407
2407
2408 @property
2408 @property
2409 def parents(self):
2409 def parents(self):
2410 parents_recursion_limit = 10
2410 parents_recursion_limit = 10
2411 groups = []
2411 groups = []
2412 if self.parent_group is None:
2412 if self.parent_group is None:
2413 return groups
2413 return groups
2414 cur_gr = self.parent_group
2414 cur_gr = self.parent_group
2415 groups.insert(0, cur_gr)
2415 groups.insert(0, cur_gr)
2416 cnt = 0
2416 cnt = 0
2417 while 1:
2417 while 1:
2418 cnt += 1
2418 cnt += 1
2419 gr = getattr(cur_gr, 'parent_group', None)
2419 gr = getattr(cur_gr, 'parent_group', None)
2420 cur_gr = cur_gr.parent_group
2420 cur_gr = cur_gr.parent_group
2421 if gr is None:
2421 if gr is None:
2422 break
2422 break
2423 if cnt == parents_recursion_limit:
2423 if cnt == parents_recursion_limit:
2424 # this will prevent accidental infinit loops
2424 # this will prevent accidental infinit loops
2425 log.error(('more than %s parents found for group %s, stopping '
2425 log.error(('more than %s parents found for group %s, stopping '
2426 'recursive parent fetching' % (parents_recursion_limit, self)))
2426 'recursive parent fetching' % (parents_recursion_limit, self)))
2427 break
2427 break
2428
2428
2429 groups.insert(0, gr)
2429 groups.insert(0, gr)
2430 return groups
2430 return groups
2431
2431
2432 @property
2432 @property
2433 def last_db_change(self):
2433 def last_db_change(self):
2434 return self.updated_on
2434 return self.updated_on
2435
2435
2436 @property
2436 @property
2437 def children(self):
2437 def children(self):
2438 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2438 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2439
2439
2440 @property
2440 @property
2441 def name(self):
2441 def name(self):
2442 return self.group_name.split(RepoGroup.url_sep())[-1]
2442 return self.group_name.split(RepoGroup.url_sep())[-1]
2443
2443
2444 @property
2444 @property
2445 def full_path(self):
2445 def full_path(self):
2446 return self.group_name
2446 return self.group_name
2447
2447
2448 @property
2448 @property
2449 def full_path_splitted(self):
2449 def full_path_splitted(self):
2450 return self.group_name.split(RepoGroup.url_sep())
2450 return self.group_name.split(RepoGroup.url_sep())
2451
2451
2452 @property
2452 @property
2453 def repositories(self):
2453 def repositories(self):
2454 return Repository.query()\
2454 return Repository.query()\
2455 .filter(Repository.group == self)\
2455 .filter(Repository.group == self)\
2456 .order_by(Repository.repo_name)
2456 .order_by(Repository.repo_name)
2457
2457
2458 @property
2458 @property
2459 def repositories_recursive_count(self):
2459 def repositories_recursive_count(self):
2460 cnt = self.repositories.count()
2460 cnt = self.repositories.count()
2461
2461
2462 def children_count(group):
2462 def children_count(group):
2463 cnt = 0
2463 cnt = 0
2464 for child in group.children:
2464 for child in group.children:
2465 cnt += child.repositories.count()
2465 cnt += child.repositories.count()
2466 cnt += children_count(child)
2466 cnt += children_count(child)
2467 return cnt
2467 return cnt
2468
2468
2469 return cnt + children_count(self)
2469 return cnt + children_count(self)
2470
2470
2471 def _recursive_objects(self, include_repos=True):
2471 def _recursive_objects(self, include_repos=True):
2472 all_ = []
2472 all_ = []
2473
2473
2474 def _get_members(root_gr):
2474 def _get_members(root_gr):
2475 if include_repos:
2475 if include_repos:
2476 for r in root_gr.repositories:
2476 for r in root_gr.repositories:
2477 all_.append(r)
2477 all_.append(r)
2478 childs = root_gr.children.all()
2478 childs = root_gr.children.all()
2479 if childs:
2479 if childs:
2480 for gr in childs:
2480 for gr in childs:
2481 all_.append(gr)
2481 all_.append(gr)
2482 _get_members(gr)
2482 _get_members(gr)
2483
2483
2484 _get_members(self)
2484 _get_members(self)
2485 return [self] + all_
2485 return [self] + all_
2486
2486
2487 def recursive_groups_and_repos(self):
2487 def recursive_groups_and_repos(self):
2488 """
2488 """
2489 Recursive return all groups, with repositories in those groups
2489 Recursive return all groups, with repositories in those groups
2490 """
2490 """
2491 return self._recursive_objects()
2491 return self._recursive_objects()
2492
2492
2493 def recursive_groups(self):
2493 def recursive_groups(self):
2494 """
2494 """
2495 Returns all children groups for this group including children of children
2495 Returns all children groups for this group including children of children
2496 """
2496 """
2497 return self._recursive_objects(include_repos=False)
2497 return self._recursive_objects(include_repos=False)
2498
2498
2499 def get_new_name(self, group_name):
2499 def get_new_name(self, group_name):
2500 """
2500 """
2501 returns new full group name based on parent and new name
2501 returns new full group name based on parent and new name
2502
2502
2503 :param group_name:
2503 :param group_name:
2504 """
2504 """
2505 path_prefix = (self.parent_group.full_path_splitted if
2505 path_prefix = (self.parent_group.full_path_splitted if
2506 self.parent_group else [])
2506 self.parent_group else [])
2507 return RepoGroup.url_sep().join(path_prefix + [group_name])
2507 return RepoGroup.url_sep().join(path_prefix + [group_name])
2508
2508
2509 def permissions(self, with_admins=True, with_owner=True):
2509 def permissions(self, with_admins=True, with_owner=True):
2510 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2510 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2511 q = q.options(joinedload(UserRepoGroupToPerm.group),
2511 q = q.options(joinedload(UserRepoGroupToPerm.group),
2512 joinedload(UserRepoGroupToPerm.user),
2512 joinedload(UserRepoGroupToPerm.user),
2513 joinedload(UserRepoGroupToPerm.permission),)
2513 joinedload(UserRepoGroupToPerm.permission),)
2514
2514
2515 # get owners and admins and permissions. We do a trick of re-writing
2515 # get owners and admins and permissions. We do a trick of re-writing
2516 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2516 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2517 # has a global reference and changing one object propagates to all
2517 # has a global reference and changing one object propagates to all
2518 # others. This means if admin is also an owner admin_row that change
2518 # others. This means if admin is also an owner admin_row that change
2519 # would propagate to both objects
2519 # would propagate to both objects
2520 perm_rows = []
2520 perm_rows = []
2521 for _usr in q.all():
2521 for _usr in q.all():
2522 usr = AttributeDict(_usr.user.get_dict())
2522 usr = AttributeDict(_usr.user.get_dict())
2523 usr.permission = _usr.permission.permission_name
2523 usr.permission = _usr.permission.permission_name
2524 perm_rows.append(usr)
2524 perm_rows.append(usr)
2525
2525
2526 # filter the perm rows by 'default' first and then sort them by
2526 # filter the perm rows by 'default' first and then sort them by
2527 # admin,write,read,none permissions sorted again alphabetically in
2527 # admin,write,read,none permissions sorted again alphabetically in
2528 # each group
2528 # each group
2529 perm_rows = sorted(perm_rows, key=display_user_sort)
2529 perm_rows = sorted(perm_rows, key=display_user_sort)
2530
2530
2531 _admin_perm = 'group.admin'
2531 _admin_perm = 'group.admin'
2532 owner_row = []
2532 owner_row = []
2533 if with_owner:
2533 if with_owner:
2534 usr = AttributeDict(self.user.get_dict())
2534 usr = AttributeDict(self.user.get_dict())
2535 usr.owner_row = True
2535 usr.owner_row = True
2536 usr.permission = _admin_perm
2536 usr.permission = _admin_perm
2537 owner_row.append(usr)
2537 owner_row.append(usr)
2538
2538
2539 super_admin_rows = []
2539 super_admin_rows = []
2540 if with_admins:
2540 if with_admins:
2541 for usr in User.get_all_super_admins():
2541 for usr in User.get_all_super_admins():
2542 # if this admin is also owner, don't double the record
2542 # if this admin is also owner, don't double the record
2543 if usr.user_id == owner_row[0].user_id:
2543 if usr.user_id == owner_row[0].user_id:
2544 owner_row[0].admin_row = True
2544 owner_row[0].admin_row = True
2545 else:
2545 else:
2546 usr = AttributeDict(usr.get_dict())
2546 usr = AttributeDict(usr.get_dict())
2547 usr.admin_row = True
2547 usr.admin_row = True
2548 usr.permission = _admin_perm
2548 usr.permission = _admin_perm
2549 super_admin_rows.append(usr)
2549 super_admin_rows.append(usr)
2550
2550
2551 return super_admin_rows + owner_row + perm_rows
2551 return super_admin_rows + owner_row + perm_rows
2552
2552
2553 def permission_user_groups(self):
2553 def permission_user_groups(self):
2554 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2554 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2555 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2555 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2556 joinedload(UserGroupRepoGroupToPerm.users_group),
2556 joinedload(UserGroupRepoGroupToPerm.users_group),
2557 joinedload(UserGroupRepoGroupToPerm.permission),)
2557 joinedload(UserGroupRepoGroupToPerm.permission),)
2558
2558
2559 perm_rows = []
2559 perm_rows = []
2560 for _user_group in q.all():
2560 for _user_group in q.all():
2561 usr = AttributeDict(_user_group.users_group.get_dict())
2561 usr = AttributeDict(_user_group.users_group.get_dict())
2562 usr.permission = _user_group.permission.permission_name
2562 usr.permission = _user_group.permission.permission_name
2563 perm_rows.append(usr)
2563 perm_rows.append(usr)
2564
2564
2565 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2565 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2566 return perm_rows
2566 return perm_rows
2567
2567
2568 def get_api_data(self):
2568 def get_api_data(self):
2569 """
2569 """
2570 Common function for generating api data
2570 Common function for generating api data
2571
2571
2572 """
2572 """
2573 group = self
2573 group = self
2574 data = {
2574 data = {
2575 'group_id': group.group_id,
2575 'group_id': group.group_id,
2576 'group_name': group.group_name,
2576 'group_name': group.group_name,
2577 'group_description': group.description_safe,
2577 'group_description': group.description_safe,
2578 'parent_group': group.parent_group.group_name if group.parent_group else None,
2578 'parent_group': group.parent_group.group_name if group.parent_group else None,
2579 'repositories': [x.repo_name for x in group.repositories],
2579 'repositories': [x.repo_name for x in group.repositories],
2580 'owner': group.user.username,
2580 'owner': group.user.username,
2581 }
2581 }
2582 return data
2582 return data
2583
2583
2584
2584
2585 class Permission(Base, BaseModel):
2585 class Permission(Base, BaseModel):
2586 __tablename__ = 'permissions'
2586 __tablename__ = 'permissions'
2587 __table_args__ = (
2587 __table_args__ = (
2588 Index('p_perm_name_idx', 'permission_name'),
2588 Index('p_perm_name_idx', 'permission_name'),
2589 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2589 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2590 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2590 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2591 )
2591 )
2592 PERMS = [
2592 PERMS = [
2593 ('hg.admin', _('RhodeCode Super Administrator')),
2593 ('hg.admin', _('RhodeCode Super Administrator')),
2594
2594
2595 ('repository.none', _('Repository no access')),
2595 ('repository.none', _('Repository no access')),
2596 ('repository.read', _('Repository read access')),
2596 ('repository.read', _('Repository read access')),
2597 ('repository.write', _('Repository write access')),
2597 ('repository.write', _('Repository write access')),
2598 ('repository.admin', _('Repository admin access')),
2598 ('repository.admin', _('Repository admin access')),
2599
2599
2600 ('group.none', _('Repository group no access')),
2600 ('group.none', _('Repository group no access')),
2601 ('group.read', _('Repository group read access')),
2601 ('group.read', _('Repository group read access')),
2602 ('group.write', _('Repository group write access')),
2602 ('group.write', _('Repository group write access')),
2603 ('group.admin', _('Repository group admin access')),
2603 ('group.admin', _('Repository group admin access')),
2604
2604
2605 ('usergroup.none', _('User group no access')),
2605 ('usergroup.none', _('User group no access')),
2606 ('usergroup.read', _('User group read access')),
2606 ('usergroup.read', _('User group read access')),
2607 ('usergroup.write', _('User group write access')),
2607 ('usergroup.write', _('User group write access')),
2608 ('usergroup.admin', _('User group admin access')),
2608 ('usergroup.admin', _('User group admin access')),
2609
2609
2610 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2610 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2611 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2611 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2612
2612
2613 ('hg.usergroup.create.false', _('User Group creation disabled')),
2613 ('hg.usergroup.create.false', _('User Group creation disabled')),
2614 ('hg.usergroup.create.true', _('User Group creation enabled')),
2614 ('hg.usergroup.create.true', _('User Group creation enabled')),
2615
2615
2616 ('hg.create.none', _('Repository creation disabled')),
2616 ('hg.create.none', _('Repository creation disabled')),
2617 ('hg.create.repository', _('Repository creation enabled')),
2617 ('hg.create.repository', _('Repository creation enabled')),
2618 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2618 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2619 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2619 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2620
2620
2621 ('hg.fork.none', _('Repository forking disabled')),
2621 ('hg.fork.none', _('Repository forking disabled')),
2622 ('hg.fork.repository', _('Repository forking enabled')),
2622 ('hg.fork.repository', _('Repository forking enabled')),
2623
2623
2624 ('hg.register.none', _('Registration disabled')),
2624 ('hg.register.none', _('Registration disabled')),
2625 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2625 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2626 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2626 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2627
2627
2628 ('hg.password_reset.enabled', _('Password reset enabled')),
2628 ('hg.password_reset.enabled', _('Password reset enabled')),
2629 ('hg.password_reset.hidden', _('Password reset hidden')),
2629 ('hg.password_reset.hidden', _('Password reset hidden')),
2630 ('hg.password_reset.disabled', _('Password reset disabled')),
2630 ('hg.password_reset.disabled', _('Password reset disabled')),
2631
2631
2632 ('hg.extern_activate.manual', _('Manual activation of external account')),
2632 ('hg.extern_activate.manual', _('Manual activation of external account')),
2633 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2633 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2634
2634
2635 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2635 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2636 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2636 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2637 ]
2637 ]
2638
2638
2639 # definition of system default permissions for DEFAULT user
2639 # definition of system default permissions for DEFAULT user
2640 DEFAULT_USER_PERMISSIONS = [
2640 DEFAULT_USER_PERMISSIONS = [
2641 'repository.read',
2641 'repository.read',
2642 'group.read',
2642 'group.read',
2643 'usergroup.read',
2643 'usergroup.read',
2644 'hg.create.repository',
2644 'hg.create.repository',
2645 'hg.repogroup.create.false',
2645 'hg.repogroup.create.false',
2646 'hg.usergroup.create.false',
2646 'hg.usergroup.create.false',
2647 'hg.create.write_on_repogroup.true',
2647 'hg.create.write_on_repogroup.true',
2648 'hg.fork.repository',
2648 'hg.fork.repository',
2649 'hg.register.manual_activate',
2649 'hg.register.manual_activate',
2650 'hg.password_reset.enabled',
2650 'hg.password_reset.enabled',
2651 'hg.extern_activate.auto',
2651 'hg.extern_activate.auto',
2652 'hg.inherit_default_perms.true',
2652 'hg.inherit_default_perms.true',
2653 ]
2653 ]
2654
2654
2655 # defines which permissions are more important higher the more important
2655 # defines which permissions are more important higher the more important
2656 # Weight defines which permissions are more important.
2656 # Weight defines which permissions are more important.
2657 # The higher number the more important.
2657 # The higher number the more important.
2658 PERM_WEIGHTS = {
2658 PERM_WEIGHTS = {
2659 'repository.none': 0,
2659 'repository.none': 0,
2660 'repository.read': 1,
2660 'repository.read': 1,
2661 'repository.write': 3,
2661 'repository.write': 3,
2662 'repository.admin': 4,
2662 'repository.admin': 4,
2663
2663
2664 'group.none': 0,
2664 'group.none': 0,
2665 'group.read': 1,
2665 'group.read': 1,
2666 'group.write': 3,
2666 'group.write': 3,
2667 'group.admin': 4,
2667 'group.admin': 4,
2668
2668
2669 'usergroup.none': 0,
2669 'usergroup.none': 0,
2670 'usergroup.read': 1,
2670 'usergroup.read': 1,
2671 'usergroup.write': 3,
2671 'usergroup.write': 3,
2672 'usergroup.admin': 4,
2672 'usergroup.admin': 4,
2673
2673
2674 'hg.repogroup.create.false': 0,
2674 'hg.repogroup.create.false': 0,
2675 'hg.repogroup.create.true': 1,
2675 'hg.repogroup.create.true': 1,
2676
2676
2677 'hg.usergroup.create.false': 0,
2677 'hg.usergroup.create.false': 0,
2678 'hg.usergroup.create.true': 1,
2678 'hg.usergroup.create.true': 1,
2679
2679
2680 'hg.fork.none': 0,
2680 'hg.fork.none': 0,
2681 'hg.fork.repository': 1,
2681 'hg.fork.repository': 1,
2682 'hg.create.none': 0,
2682 'hg.create.none': 0,
2683 'hg.create.repository': 1
2683 'hg.create.repository': 1
2684 }
2684 }
2685
2685
2686 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2686 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2687 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2687 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2688 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2688 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2689
2689
2690 def __unicode__(self):
2690 def __unicode__(self):
2691 return u"<%s('%s:%s')>" % (
2691 return u"<%s('%s:%s')>" % (
2692 self.__class__.__name__, self.permission_id, self.permission_name
2692 self.__class__.__name__, self.permission_id, self.permission_name
2693 )
2693 )
2694
2694
2695 @classmethod
2695 @classmethod
2696 def get_by_key(cls, key):
2696 def get_by_key(cls, key):
2697 return cls.query().filter(cls.permission_name == key).scalar()
2697 return cls.query().filter(cls.permission_name == key).scalar()
2698
2698
2699 @classmethod
2699 @classmethod
2700 def get_default_repo_perms(cls, user_id, repo_id=None):
2700 def get_default_repo_perms(cls, user_id, repo_id=None):
2701 q = Session().query(UserRepoToPerm, Repository, Permission)\
2701 q = Session().query(UserRepoToPerm, Repository, Permission)\
2702 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2702 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2703 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2703 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2704 .filter(UserRepoToPerm.user_id == user_id)
2704 .filter(UserRepoToPerm.user_id == user_id)
2705 if repo_id:
2705 if repo_id:
2706 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2706 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2707 return q.all()
2707 return q.all()
2708
2708
2709 @classmethod
2709 @classmethod
2710 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2710 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2711 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2711 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2712 .join(
2712 .join(
2713 Permission,
2713 Permission,
2714 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2714 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2715 .join(
2715 .join(
2716 Repository,
2716 Repository,
2717 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2717 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2718 .join(
2718 .join(
2719 UserGroup,
2719 UserGroup,
2720 UserGroupRepoToPerm.users_group_id ==
2720 UserGroupRepoToPerm.users_group_id ==
2721 UserGroup.users_group_id)\
2721 UserGroup.users_group_id)\
2722 .join(
2722 .join(
2723 UserGroupMember,
2723 UserGroupMember,
2724 UserGroupRepoToPerm.users_group_id ==
2724 UserGroupRepoToPerm.users_group_id ==
2725 UserGroupMember.users_group_id)\
2725 UserGroupMember.users_group_id)\
2726 .filter(
2726 .filter(
2727 UserGroupMember.user_id == user_id,
2727 UserGroupMember.user_id == user_id,
2728 UserGroup.users_group_active == true())
2728 UserGroup.users_group_active == true())
2729 if repo_id:
2729 if repo_id:
2730 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2730 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2731 return q.all()
2731 return q.all()
2732
2732
2733 @classmethod
2733 @classmethod
2734 def get_default_group_perms(cls, user_id, repo_group_id=None):
2734 def get_default_group_perms(cls, user_id, repo_group_id=None):
2735 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2735 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2736 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2736 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2737 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2737 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2738 .filter(UserRepoGroupToPerm.user_id == user_id)
2738 .filter(UserRepoGroupToPerm.user_id == user_id)
2739 if repo_group_id:
2739 if repo_group_id:
2740 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2740 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2741 return q.all()
2741 return q.all()
2742
2742
2743 @classmethod
2743 @classmethod
2744 def get_default_group_perms_from_user_group(
2744 def get_default_group_perms_from_user_group(
2745 cls, user_id, repo_group_id=None):
2745 cls, user_id, repo_group_id=None):
2746 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2746 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2747 .join(
2747 .join(
2748 Permission,
2748 Permission,
2749 UserGroupRepoGroupToPerm.permission_id ==
2749 UserGroupRepoGroupToPerm.permission_id ==
2750 Permission.permission_id)\
2750 Permission.permission_id)\
2751 .join(
2751 .join(
2752 RepoGroup,
2752 RepoGroup,
2753 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2753 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2754 .join(
2754 .join(
2755 UserGroup,
2755 UserGroup,
2756 UserGroupRepoGroupToPerm.users_group_id ==
2756 UserGroupRepoGroupToPerm.users_group_id ==
2757 UserGroup.users_group_id)\
2757 UserGroup.users_group_id)\
2758 .join(
2758 .join(
2759 UserGroupMember,
2759 UserGroupMember,
2760 UserGroupRepoGroupToPerm.users_group_id ==
2760 UserGroupRepoGroupToPerm.users_group_id ==
2761 UserGroupMember.users_group_id)\
2761 UserGroupMember.users_group_id)\
2762 .filter(
2762 .filter(
2763 UserGroupMember.user_id == user_id,
2763 UserGroupMember.user_id == user_id,
2764 UserGroup.users_group_active == true())
2764 UserGroup.users_group_active == true())
2765 if repo_group_id:
2765 if repo_group_id:
2766 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2766 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2767 return q.all()
2767 return q.all()
2768
2768
2769 @classmethod
2769 @classmethod
2770 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2770 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2771 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2771 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2772 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2772 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2773 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2773 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2774 .filter(UserUserGroupToPerm.user_id == user_id)
2774 .filter(UserUserGroupToPerm.user_id == user_id)
2775 if user_group_id:
2775 if user_group_id:
2776 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2776 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2777 return q.all()
2777 return q.all()
2778
2778
2779 @classmethod
2779 @classmethod
2780 def get_default_user_group_perms_from_user_group(
2780 def get_default_user_group_perms_from_user_group(
2781 cls, user_id, user_group_id=None):
2781 cls, user_id, user_group_id=None):
2782 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2782 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2783 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2783 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2784 .join(
2784 .join(
2785 Permission,
2785 Permission,
2786 UserGroupUserGroupToPerm.permission_id ==
2786 UserGroupUserGroupToPerm.permission_id ==
2787 Permission.permission_id)\
2787 Permission.permission_id)\
2788 .join(
2788 .join(
2789 TargetUserGroup,
2789 TargetUserGroup,
2790 UserGroupUserGroupToPerm.target_user_group_id ==
2790 UserGroupUserGroupToPerm.target_user_group_id ==
2791 TargetUserGroup.users_group_id)\
2791 TargetUserGroup.users_group_id)\
2792 .join(
2792 .join(
2793 UserGroup,
2793 UserGroup,
2794 UserGroupUserGroupToPerm.user_group_id ==
2794 UserGroupUserGroupToPerm.user_group_id ==
2795 UserGroup.users_group_id)\
2795 UserGroup.users_group_id)\
2796 .join(
2796 .join(
2797 UserGroupMember,
2797 UserGroupMember,
2798 UserGroupUserGroupToPerm.user_group_id ==
2798 UserGroupUserGroupToPerm.user_group_id ==
2799 UserGroupMember.users_group_id)\
2799 UserGroupMember.users_group_id)\
2800 .filter(
2800 .filter(
2801 UserGroupMember.user_id == user_id,
2801 UserGroupMember.user_id == user_id,
2802 UserGroup.users_group_active == true())
2802 UserGroup.users_group_active == true())
2803 if user_group_id:
2803 if user_group_id:
2804 q = q.filter(
2804 q = q.filter(
2805 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2805 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2806
2806
2807 return q.all()
2807 return q.all()
2808
2808
2809
2809
2810 class UserRepoToPerm(Base, BaseModel):
2810 class UserRepoToPerm(Base, BaseModel):
2811 __tablename__ = 'repo_to_perm'
2811 __tablename__ = 'repo_to_perm'
2812 __table_args__ = (
2812 __table_args__ = (
2813 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2813 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2814 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2815 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2816 )
2816 )
2817 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2817 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2818 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2818 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2819 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2819 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2820 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2820 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2821
2821
2822 user = relationship('User')
2822 user = relationship('User')
2823 repository = relationship('Repository')
2823 repository = relationship('Repository')
2824 permission = relationship('Permission')
2824 permission = relationship('Permission')
2825
2825
2826 @classmethod
2826 @classmethod
2827 def create(cls, user, repository, permission):
2827 def create(cls, user, repository, permission):
2828 n = cls()
2828 n = cls()
2829 n.user = user
2829 n.user = user
2830 n.repository = repository
2830 n.repository = repository
2831 n.permission = permission
2831 n.permission = permission
2832 Session().add(n)
2832 Session().add(n)
2833 return n
2833 return n
2834
2834
2835 def __unicode__(self):
2835 def __unicode__(self):
2836 return u'<%s => %s >' % (self.user, self.repository)
2836 return u'<%s => %s >' % (self.user, self.repository)
2837
2837
2838
2838
2839 class UserUserGroupToPerm(Base, BaseModel):
2839 class UserUserGroupToPerm(Base, BaseModel):
2840 __tablename__ = 'user_user_group_to_perm'
2840 __tablename__ = 'user_user_group_to_perm'
2841 __table_args__ = (
2841 __table_args__ = (
2842 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2842 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2843 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2843 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2844 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2844 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2845 )
2845 )
2846 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2846 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2848 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2848 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2849 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2849 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2850
2850
2851 user = relationship('User')
2851 user = relationship('User')
2852 user_group = relationship('UserGroup')
2852 user_group = relationship('UserGroup')
2853 permission = relationship('Permission')
2853 permission = relationship('Permission')
2854
2854
2855 @classmethod
2855 @classmethod
2856 def create(cls, user, user_group, permission):
2856 def create(cls, user, user_group, permission):
2857 n = cls()
2857 n = cls()
2858 n.user = user
2858 n.user = user
2859 n.user_group = user_group
2859 n.user_group = user_group
2860 n.permission = permission
2860 n.permission = permission
2861 Session().add(n)
2861 Session().add(n)
2862 return n
2862 return n
2863
2863
2864 def __unicode__(self):
2864 def __unicode__(self):
2865 return u'<%s => %s >' % (self.user, self.user_group)
2865 return u'<%s => %s >' % (self.user, self.user_group)
2866
2866
2867
2867
2868 class UserToPerm(Base, BaseModel):
2868 class UserToPerm(Base, BaseModel):
2869 __tablename__ = 'user_to_perm'
2869 __tablename__ = 'user_to_perm'
2870 __table_args__ = (
2870 __table_args__ = (
2871 UniqueConstraint('user_id', 'permission_id'),
2871 UniqueConstraint('user_id', 'permission_id'),
2872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2874 )
2874 )
2875 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2875 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2876 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2876 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2877 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2877 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2878
2878
2879 user = relationship('User')
2879 user = relationship('User')
2880 permission = relationship('Permission', lazy='joined')
2880 permission = relationship('Permission', lazy='joined')
2881
2881
2882 def __unicode__(self):
2882 def __unicode__(self):
2883 return u'<%s => %s >' % (self.user, self.permission)
2883 return u'<%s => %s >' % (self.user, self.permission)
2884
2884
2885
2885
2886 class UserGroupRepoToPerm(Base, BaseModel):
2886 class UserGroupRepoToPerm(Base, BaseModel):
2887 __tablename__ = 'users_group_repo_to_perm'
2887 __tablename__ = 'users_group_repo_to_perm'
2888 __table_args__ = (
2888 __table_args__ = (
2889 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2889 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2891 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2891 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2892 )
2892 )
2893 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2893 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2894 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2894 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2895 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2895 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2896 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2896 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2897
2897
2898 users_group = relationship('UserGroup')
2898 users_group = relationship('UserGroup')
2899 permission = relationship('Permission')
2899 permission = relationship('Permission')
2900 repository = relationship('Repository')
2900 repository = relationship('Repository')
2901
2901
2902 @classmethod
2902 @classmethod
2903 def create(cls, users_group, repository, permission):
2903 def create(cls, users_group, repository, permission):
2904 n = cls()
2904 n = cls()
2905 n.users_group = users_group
2905 n.users_group = users_group
2906 n.repository = repository
2906 n.repository = repository
2907 n.permission = permission
2907 n.permission = permission
2908 Session().add(n)
2908 Session().add(n)
2909 return n
2909 return n
2910
2910
2911 def __unicode__(self):
2911 def __unicode__(self):
2912 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2912 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2913
2913
2914
2914
2915 class UserGroupUserGroupToPerm(Base, BaseModel):
2915 class UserGroupUserGroupToPerm(Base, BaseModel):
2916 __tablename__ = 'user_group_user_group_to_perm'
2916 __tablename__ = 'user_group_user_group_to_perm'
2917 __table_args__ = (
2917 __table_args__ = (
2918 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2918 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2919 CheckConstraint('target_user_group_id != user_group_id'),
2919 CheckConstraint('target_user_group_id != user_group_id'),
2920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2921 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2922 )
2922 )
2923 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)
2923 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)
2924 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2924 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2925 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2925 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2926 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2926 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2927
2927
2928 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2928 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2929 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2929 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2930 permission = relationship('Permission')
2930 permission = relationship('Permission')
2931
2931
2932 @classmethod
2932 @classmethod
2933 def create(cls, target_user_group, user_group, permission):
2933 def create(cls, target_user_group, user_group, permission):
2934 n = cls()
2934 n = cls()
2935 n.target_user_group = target_user_group
2935 n.target_user_group = target_user_group
2936 n.user_group = user_group
2936 n.user_group = user_group
2937 n.permission = permission
2937 n.permission = permission
2938 Session().add(n)
2938 Session().add(n)
2939 return n
2939 return n
2940
2940
2941 def __unicode__(self):
2941 def __unicode__(self):
2942 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2942 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2943
2943
2944
2944
2945 class UserGroupToPerm(Base, BaseModel):
2945 class UserGroupToPerm(Base, BaseModel):
2946 __tablename__ = 'users_group_to_perm'
2946 __tablename__ = 'users_group_to_perm'
2947 __table_args__ = (
2947 __table_args__ = (
2948 UniqueConstraint('users_group_id', 'permission_id',),
2948 UniqueConstraint('users_group_id', 'permission_id',),
2949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2951 )
2951 )
2952 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2952 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2953 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2953 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2954 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2954 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2955
2955
2956 users_group = relationship('UserGroup')
2956 users_group = relationship('UserGroup')
2957 permission = relationship('Permission')
2957 permission = relationship('Permission')
2958
2958
2959
2959
2960 class UserRepoGroupToPerm(Base, BaseModel):
2960 class UserRepoGroupToPerm(Base, BaseModel):
2961 __tablename__ = 'user_repo_group_to_perm'
2961 __tablename__ = 'user_repo_group_to_perm'
2962 __table_args__ = (
2962 __table_args__ = (
2963 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2963 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2966 )
2966 )
2967
2967
2968 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2968 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2970 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2970 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2971 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2972
2972
2973 user = relationship('User')
2973 user = relationship('User')
2974 group = relationship('RepoGroup')
2974 group = relationship('RepoGroup')
2975 permission = relationship('Permission')
2975 permission = relationship('Permission')
2976
2976
2977 @classmethod
2977 @classmethod
2978 def create(cls, user, repository_group, permission):
2978 def create(cls, user, repository_group, permission):
2979 n = cls()
2979 n = cls()
2980 n.user = user
2980 n.user = user
2981 n.group = repository_group
2981 n.group = repository_group
2982 n.permission = permission
2982 n.permission = permission
2983 Session().add(n)
2983 Session().add(n)
2984 return n
2984 return n
2985
2985
2986
2986
2987 class UserGroupRepoGroupToPerm(Base, BaseModel):
2987 class UserGroupRepoGroupToPerm(Base, BaseModel):
2988 __tablename__ = 'users_group_repo_group_to_perm'
2988 __tablename__ = 'users_group_repo_group_to_perm'
2989 __table_args__ = (
2989 __table_args__ = (
2990 UniqueConstraint('users_group_id', 'group_id'),
2990 UniqueConstraint('users_group_id', 'group_id'),
2991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2992 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2993 )
2993 )
2994
2994
2995 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)
2995 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)
2996 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2996 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2997 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2997 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2998 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2998 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2999
2999
3000 users_group = relationship('UserGroup')
3000 users_group = relationship('UserGroup')
3001 permission = relationship('Permission')
3001 permission = relationship('Permission')
3002 group = relationship('RepoGroup')
3002 group = relationship('RepoGroup')
3003
3003
3004 @classmethod
3004 @classmethod
3005 def create(cls, user_group, repository_group, permission):
3005 def create(cls, user_group, repository_group, permission):
3006 n = cls()
3006 n = cls()
3007 n.users_group = user_group
3007 n.users_group = user_group
3008 n.group = repository_group
3008 n.group = repository_group
3009 n.permission = permission
3009 n.permission = permission
3010 Session().add(n)
3010 Session().add(n)
3011 return n
3011 return n
3012
3012
3013 def __unicode__(self):
3013 def __unicode__(self):
3014 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3014 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3015
3015
3016
3016
3017 class Statistics(Base, BaseModel):
3017 class Statistics(Base, BaseModel):
3018 __tablename__ = 'statistics'
3018 __tablename__ = 'statistics'
3019 __table_args__ = (
3019 __table_args__ = (
3020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3021 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3021 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3022 )
3022 )
3023 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3023 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3024 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3024 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3025 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3025 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3026 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3026 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3027 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3027 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3028 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3028 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3029
3029
3030 repository = relationship('Repository', single_parent=True)
3030 repository = relationship('Repository', single_parent=True)
3031
3031
3032
3032
3033 class UserFollowing(Base, BaseModel):
3033 class UserFollowing(Base, BaseModel):
3034 __tablename__ = 'user_followings'
3034 __tablename__ = 'user_followings'
3035 __table_args__ = (
3035 __table_args__ = (
3036 UniqueConstraint('user_id', 'follows_repository_id'),
3036 UniqueConstraint('user_id', 'follows_repository_id'),
3037 UniqueConstraint('user_id', 'follows_user_id'),
3037 UniqueConstraint('user_id', 'follows_user_id'),
3038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3039 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3039 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3040 )
3040 )
3041
3041
3042 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3042 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3043 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3043 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3044 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3044 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3045 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3045 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3046 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3046 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3047
3047
3048 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3048 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3049
3049
3050 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3050 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3051 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3051 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3052
3052
3053 @classmethod
3053 @classmethod
3054 def get_repo_followers(cls, repo_id):
3054 def get_repo_followers(cls, repo_id):
3055 return cls.query().filter(cls.follows_repo_id == repo_id)
3055 return cls.query().filter(cls.follows_repo_id == repo_id)
3056
3056
3057
3057
3058 class CacheKey(Base, BaseModel):
3058 class CacheKey(Base, BaseModel):
3059 __tablename__ = 'cache_invalidation'
3059 __tablename__ = 'cache_invalidation'
3060 __table_args__ = (
3060 __table_args__ = (
3061 UniqueConstraint('cache_key'),
3061 UniqueConstraint('cache_key'),
3062 Index('key_idx', 'cache_key'),
3062 Index('key_idx', 'cache_key'),
3063 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3063 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3064 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3064 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3065 )
3065 )
3066 CACHE_TYPE_ATOM = 'ATOM'
3066 CACHE_TYPE_ATOM = 'ATOM'
3067 CACHE_TYPE_RSS = 'RSS'
3067 CACHE_TYPE_RSS = 'RSS'
3068 CACHE_TYPE_README = 'README'
3068 CACHE_TYPE_README = 'README'
3069
3069
3070 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3070 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3071 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3071 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3072 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3072 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3073 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3073 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3074
3074
3075 def __init__(self, cache_key, cache_args=''):
3075 def __init__(self, cache_key, cache_args=''):
3076 self.cache_key = cache_key
3076 self.cache_key = cache_key
3077 self.cache_args = cache_args
3077 self.cache_args = cache_args
3078 self.cache_active = False
3078 self.cache_active = False
3079
3079
3080 def __unicode__(self):
3080 def __unicode__(self):
3081 return u"<%s('%s:%s[%s]')>" % (
3081 return u"<%s('%s:%s[%s]')>" % (
3082 self.__class__.__name__,
3082 self.__class__.__name__,
3083 self.cache_id, self.cache_key, self.cache_active)
3083 self.cache_id, self.cache_key, self.cache_active)
3084
3084
3085 def _cache_key_partition(self):
3085 def _cache_key_partition(self):
3086 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3086 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3087 return prefix, repo_name, suffix
3087 return prefix, repo_name, suffix
3088
3088
3089 def get_prefix(self):
3089 def get_prefix(self):
3090 """
3090 """
3091 Try to extract prefix from existing cache key. The key could consist
3091 Try to extract prefix from existing cache key. The key could consist
3092 of prefix, repo_name, suffix
3092 of prefix, repo_name, suffix
3093 """
3093 """
3094 # this returns prefix, repo_name, suffix
3094 # this returns prefix, repo_name, suffix
3095 return self._cache_key_partition()[0]
3095 return self._cache_key_partition()[0]
3096
3096
3097 def get_suffix(self):
3097 def get_suffix(self):
3098 """
3098 """
3099 get suffix that might have been used in _get_cache_key to
3099 get suffix that might have been used in _get_cache_key to
3100 generate self.cache_key. Only used for informational purposes
3100 generate self.cache_key. Only used for informational purposes
3101 in repo_edit.mako.
3101 in repo_edit.mako.
3102 """
3102 """
3103 # prefix, repo_name, suffix
3103 # prefix, repo_name, suffix
3104 return self._cache_key_partition()[2]
3104 return self._cache_key_partition()[2]
3105
3105
3106 @classmethod
3106 @classmethod
3107 def delete_all_cache(cls):
3107 def delete_all_cache(cls):
3108 """
3108 """
3109 Delete all cache keys from database.
3109 Delete all cache keys from database.
3110 Should only be run when all instances are down and all entries
3110 Should only be run when all instances are down and all entries
3111 thus stale.
3111 thus stale.
3112 """
3112 """
3113 cls.query().delete()
3113 cls.query().delete()
3114 Session().commit()
3114 Session().commit()
3115
3115
3116 @classmethod
3116 @classmethod
3117 def get_cache_key(cls, repo_name, cache_type):
3117 def get_cache_key(cls, repo_name, cache_type):
3118 """
3118 """
3119
3119
3120 Generate a cache key for this process of RhodeCode instance.
3120 Generate a cache key for this process of RhodeCode instance.
3121 Prefix most likely will be process id or maybe explicitly set
3121 Prefix most likely will be process id or maybe explicitly set
3122 instance_id from .ini file.
3122 instance_id from .ini file.
3123 """
3123 """
3124 import rhodecode
3124 import rhodecode
3125 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3125 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3126
3126
3127 repo_as_unicode = safe_unicode(repo_name)
3127 repo_as_unicode = safe_unicode(repo_name)
3128 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3128 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3129 if cache_type else repo_as_unicode
3129 if cache_type else repo_as_unicode
3130
3130
3131 return u'{}{}'.format(prefix, key)
3131 return u'{}{}'.format(prefix, key)
3132
3132
3133 @classmethod
3133 @classmethod
3134 def set_invalidate(cls, repo_name, delete=False):
3134 def set_invalidate(cls, repo_name, delete=False):
3135 """
3135 """
3136 Mark all caches of a repo as invalid in the database.
3136 Mark all caches of a repo as invalid in the database.
3137 """
3137 """
3138
3138
3139 try:
3139 try:
3140 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3140 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3141 if delete:
3141 if delete:
3142 log.debug('cache objects deleted for repo %s',
3142 log.debug('cache objects deleted for repo %s',
3143 safe_str(repo_name))
3143 safe_str(repo_name))
3144 qry.delete()
3144 qry.delete()
3145 else:
3145 else:
3146 log.debug('cache objects marked as invalid for repo %s',
3146 log.debug('cache objects marked as invalid for repo %s',
3147 safe_str(repo_name))
3147 safe_str(repo_name))
3148 qry.update({"cache_active": False})
3148 qry.update({"cache_active": False})
3149
3149
3150 Session().commit()
3150 Session().commit()
3151 except Exception:
3151 except Exception:
3152 log.exception(
3152 log.exception(
3153 'Cache key invalidation failed for repository %s',
3153 'Cache key invalidation failed for repository %s',
3154 safe_str(repo_name))
3154 safe_str(repo_name))
3155 Session().rollback()
3155 Session().rollback()
3156
3156
3157 @classmethod
3157 @classmethod
3158 def get_active_cache(cls, cache_key):
3158 def get_active_cache(cls, cache_key):
3159 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3159 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3160 if inv_obj:
3160 if inv_obj:
3161 return inv_obj
3161 return inv_obj
3162 return None
3162 return None
3163
3163
3164 @classmethod
3164 @classmethod
3165 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3165 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3166 thread_scoped=False):
3166 thread_scoped=False):
3167 """
3167 """
3168 @cache_region('long_term')
3168 @cache_region('long_term')
3169 def _heavy_calculation(cache_key):
3169 def _heavy_calculation(cache_key):
3170 return 'result'
3170 return 'result'
3171
3171
3172 cache_context = CacheKey.repo_context_cache(
3172 cache_context = CacheKey.repo_context_cache(
3173 _heavy_calculation, repo_name, cache_type)
3173 _heavy_calculation, repo_name, cache_type)
3174
3174
3175 with cache_context as context:
3175 with cache_context as context:
3176 context.invalidate()
3176 context.invalidate()
3177 computed = context.compute()
3177 computed = context.compute()
3178
3178
3179 assert computed == 'result'
3179 assert computed == 'result'
3180 """
3180 """
3181 from rhodecode.lib import caches
3181 from rhodecode.lib import caches
3182 return caches.InvalidationContext(
3182 return caches.InvalidationContext(
3183 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3183 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3184
3184
3185
3185
3186 class ChangesetComment(Base, BaseModel):
3186 class ChangesetComment(Base, BaseModel):
3187 __tablename__ = 'changeset_comments'
3187 __tablename__ = 'changeset_comments'
3188 __table_args__ = (
3188 __table_args__ = (
3189 Index('cc_revision_idx', 'revision'),
3189 Index('cc_revision_idx', 'revision'),
3190 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3190 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3191 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3191 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3192 )
3192 )
3193
3193
3194 COMMENT_OUTDATED = u'comment_outdated'
3194 COMMENT_OUTDATED = u'comment_outdated'
3195 COMMENT_TYPE_NOTE = u'note'
3195 COMMENT_TYPE_NOTE = u'note'
3196 COMMENT_TYPE_TODO = u'todo'
3196 COMMENT_TYPE_TODO = u'todo'
3197 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3197 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3198
3198
3199 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3199 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3200 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3200 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3201 revision = Column('revision', String(40), nullable=True)
3201 revision = Column('revision', String(40), nullable=True)
3202 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3202 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3203 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3203 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3204 line_no = Column('line_no', Unicode(10), nullable=True)
3204 line_no = Column('line_no', Unicode(10), nullable=True)
3205 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3205 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3206 f_path = Column('f_path', Unicode(1000), nullable=True)
3206 f_path = Column('f_path', Unicode(1000), nullable=True)
3207 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3207 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3208 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3208 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3209 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3209 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3210 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3210 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3211 renderer = Column('renderer', Unicode(64), nullable=True)
3211 renderer = Column('renderer', Unicode(64), nullable=True)
3212 display_state = Column('display_state', Unicode(128), nullable=True)
3212 display_state = Column('display_state', Unicode(128), nullable=True)
3213
3213
3214 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3214 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3215 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3215 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3216 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3216 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3217 author = relationship('User', lazy='joined')
3217 author = relationship('User', lazy='joined')
3218 repo = relationship('Repository')
3218 repo = relationship('Repository')
3219 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3219 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3220 pull_request = relationship('PullRequest', lazy='joined')
3220 pull_request = relationship('PullRequest', lazy='joined')
3221 pull_request_version = relationship('PullRequestVersion')
3221 pull_request_version = relationship('PullRequestVersion')
3222
3222
3223 @classmethod
3223 @classmethod
3224 def get_users(cls, revision=None, pull_request_id=None):
3224 def get_users(cls, revision=None, pull_request_id=None):
3225 """
3225 """
3226 Returns user associated with this ChangesetComment. ie those
3226 Returns user associated with this ChangesetComment. ie those
3227 who actually commented
3227 who actually commented
3228
3228
3229 :param cls:
3229 :param cls:
3230 :param revision:
3230 :param revision:
3231 """
3231 """
3232 q = Session().query(User)\
3232 q = Session().query(User)\
3233 .join(ChangesetComment.author)
3233 .join(ChangesetComment.author)
3234 if revision:
3234 if revision:
3235 q = q.filter(cls.revision == revision)
3235 q = q.filter(cls.revision == revision)
3236 elif pull_request_id:
3236 elif pull_request_id:
3237 q = q.filter(cls.pull_request_id == pull_request_id)
3237 q = q.filter(cls.pull_request_id == pull_request_id)
3238 return q.all()
3238 return q.all()
3239
3239
3240 @classmethod
3240 @classmethod
3241 def get_index_from_version(cls, pr_version, versions):
3241 def get_index_from_version(cls, pr_version, versions):
3242 num_versions = [x.pull_request_version_id for x in versions]
3242 num_versions = [x.pull_request_version_id for x in versions]
3243 try:
3243 try:
3244 return num_versions.index(pr_version) +1
3244 return num_versions.index(pr_version) +1
3245 except (IndexError, ValueError):
3245 except (IndexError, ValueError):
3246 return
3246 return
3247
3247
3248 @property
3248 @property
3249 def outdated(self):
3249 def outdated(self):
3250 return self.display_state == self.COMMENT_OUTDATED
3250 return self.display_state == self.COMMENT_OUTDATED
3251
3251
3252 def outdated_at_version(self, version):
3252 def outdated_at_version(self, version):
3253 """
3253 """
3254 Checks if comment is outdated for given pull request version
3254 Checks if comment is outdated for given pull request version
3255 """
3255 """
3256 return self.outdated and self.pull_request_version_id != version
3256 return self.outdated and self.pull_request_version_id != version
3257
3257
3258 def older_than_version(self, version):
3258 def older_than_version(self, version):
3259 """
3259 """
3260 Checks if comment is made from previous version than given
3260 Checks if comment is made from previous version than given
3261 """
3261 """
3262 if version is None:
3262 if version is None:
3263 return self.pull_request_version_id is not None
3263 return self.pull_request_version_id is not None
3264
3264
3265 return self.pull_request_version_id < version
3265 return self.pull_request_version_id < version
3266
3266
3267 @property
3267 @property
3268 def resolved(self):
3268 def resolved(self):
3269 return self.resolved_by[0] if self.resolved_by else None
3269 return self.resolved_by[0] if self.resolved_by else None
3270
3270
3271 @property
3271 @property
3272 def is_todo(self):
3272 def is_todo(self):
3273 return self.comment_type == self.COMMENT_TYPE_TODO
3273 return self.comment_type == self.COMMENT_TYPE_TODO
3274
3274
3275 @property
3275 @property
3276 def is_inline(self):
3276 def is_inline(self):
3277 return self.line_no and self.f_path
3277 return self.line_no and self.f_path
3278
3278
3279 def get_index_version(self, versions):
3279 def get_index_version(self, versions):
3280 return self.get_index_from_version(
3280 return self.get_index_from_version(
3281 self.pull_request_version_id, versions)
3281 self.pull_request_version_id, versions)
3282
3282
3283 def __repr__(self):
3283 def __repr__(self):
3284 if self.comment_id:
3284 if self.comment_id:
3285 return '<DB:Comment #%s>' % self.comment_id
3285 return '<DB:Comment #%s>' % self.comment_id
3286 else:
3286 else:
3287 return '<DB:Comment at %#x>' % id(self)
3287 return '<DB:Comment at %#x>' % id(self)
3288
3288
3289 def get_api_data(self):
3289 def get_api_data(self):
3290 comment = self
3290 comment = self
3291 data = {
3291 data = {
3292 'comment_id': comment.comment_id,
3292 'comment_id': comment.comment_id,
3293 'comment_type': comment.comment_type,
3293 'comment_type': comment.comment_type,
3294 'comment_text': comment.text,
3294 'comment_text': comment.text,
3295 'comment_status': comment.status_change,
3295 'comment_status': comment.status_change,
3296 'comment_f_path': comment.f_path,
3296 'comment_f_path': comment.f_path,
3297 'comment_lineno': comment.line_no,
3297 'comment_lineno': comment.line_no,
3298 'comment_author': comment.author,
3298 'comment_author': comment.author,
3299 'comment_created_on': comment.created_on
3299 'comment_created_on': comment.created_on
3300 }
3300 }
3301 return data
3301 return data
3302
3302
3303 def __json__(self):
3303 def __json__(self):
3304 data = dict()
3304 data = dict()
3305 data.update(self.get_api_data())
3305 data.update(self.get_api_data())
3306 return data
3306 return data
3307
3307
3308
3308
3309 class ChangesetStatus(Base, BaseModel):
3309 class ChangesetStatus(Base, BaseModel):
3310 __tablename__ = 'changeset_statuses'
3310 __tablename__ = 'changeset_statuses'
3311 __table_args__ = (
3311 __table_args__ = (
3312 Index('cs_revision_idx', 'revision'),
3312 Index('cs_revision_idx', 'revision'),
3313 Index('cs_version_idx', 'version'),
3313 Index('cs_version_idx', 'version'),
3314 UniqueConstraint('repo_id', 'revision', 'version'),
3314 UniqueConstraint('repo_id', 'revision', 'version'),
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3317 )
3317 )
3318 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3318 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3319 STATUS_APPROVED = 'approved'
3319 STATUS_APPROVED = 'approved'
3320 STATUS_REJECTED = 'rejected'
3320 STATUS_REJECTED = 'rejected'
3321 STATUS_UNDER_REVIEW = 'under_review'
3321 STATUS_UNDER_REVIEW = 'under_review'
3322
3322
3323 STATUSES = [
3323 STATUSES = [
3324 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3324 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3325 (STATUS_APPROVED, _("Approved")),
3325 (STATUS_APPROVED, _("Approved")),
3326 (STATUS_REJECTED, _("Rejected")),
3326 (STATUS_REJECTED, _("Rejected")),
3327 (STATUS_UNDER_REVIEW, _("Under Review")),
3327 (STATUS_UNDER_REVIEW, _("Under Review")),
3328 ]
3328 ]
3329
3329
3330 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3330 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3331 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3331 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3333 revision = Column('revision', String(40), nullable=False)
3333 revision = Column('revision', String(40), nullable=False)
3334 status = Column('status', String(128), nullable=False, default=DEFAULT)
3334 status = Column('status', String(128), nullable=False, default=DEFAULT)
3335 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3335 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3336 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3336 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3337 version = Column('version', Integer(), nullable=False, default=0)
3337 version = Column('version', Integer(), nullable=False, default=0)
3338 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3338 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3339
3339
3340 author = relationship('User', lazy='joined')
3340 author = relationship('User', lazy='joined')
3341 repo = relationship('Repository')
3341 repo = relationship('Repository')
3342 comment = relationship('ChangesetComment', lazy='joined')
3342 comment = relationship('ChangesetComment', lazy='joined')
3343 pull_request = relationship('PullRequest', lazy='joined')
3343 pull_request = relationship('PullRequest', lazy='joined')
3344
3344
3345 def __unicode__(self):
3345 def __unicode__(self):
3346 return u"<%s('%s[v%s]:%s')>" % (
3346 return u"<%s('%s[v%s]:%s')>" % (
3347 self.__class__.__name__,
3347 self.__class__.__name__,
3348 self.status, self.version, self.author
3348 self.status, self.version, self.author
3349 )
3349 )
3350
3350
3351 @classmethod
3351 @classmethod
3352 def get_status_lbl(cls, value):
3352 def get_status_lbl(cls, value):
3353 return dict(cls.STATUSES).get(value)
3353 return dict(cls.STATUSES).get(value)
3354
3354
3355 @property
3355 @property
3356 def status_lbl(self):
3356 def status_lbl(self):
3357 return ChangesetStatus.get_status_lbl(self.status)
3357 return ChangesetStatus.get_status_lbl(self.status)
3358
3358
3359 def get_api_data(self):
3359 def get_api_data(self):
3360 status = self
3360 status = self
3361 data = {
3361 data = {
3362 'status_id': status.changeset_status_id,
3362 'status_id': status.changeset_status_id,
3363 'status': status.status,
3363 'status': status.status,
3364 }
3364 }
3365 return data
3365 return data
3366
3366
3367 def __json__(self):
3367 def __json__(self):
3368 data = dict()
3368 data = dict()
3369 data.update(self.get_api_data())
3369 data.update(self.get_api_data())
3370 return data
3370 return data
3371
3371
3372
3372
3373 class _PullRequestBase(BaseModel):
3373 class _PullRequestBase(BaseModel):
3374 """
3374 """
3375 Common attributes of pull request and version entries.
3375 Common attributes of pull request and version entries.
3376 """
3376 """
3377
3377
3378 # .status values
3378 # .status values
3379 STATUS_NEW = u'new'
3379 STATUS_NEW = u'new'
3380 STATUS_OPEN = u'open'
3380 STATUS_OPEN = u'open'
3381 STATUS_CLOSED = u'closed'
3381 STATUS_CLOSED = u'closed'
3382
3382
3383 title = Column('title', Unicode(255), nullable=True)
3383 title = Column('title', Unicode(255), nullable=True)
3384 description = Column(
3384 description = Column(
3385 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3385 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3386 nullable=True)
3386 nullable=True)
3387 # new/open/closed status of pull request (not approve/reject/etc)
3387 # new/open/closed status of pull request (not approve/reject/etc)
3388 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3388 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3389 created_on = Column(
3389 created_on = Column(
3390 'created_on', DateTime(timezone=False), nullable=False,
3390 'created_on', DateTime(timezone=False), nullable=False,
3391 default=datetime.datetime.now)
3391 default=datetime.datetime.now)
3392 updated_on = Column(
3392 updated_on = Column(
3393 'updated_on', DateTime(timezone=False), nullable=False,
3393 'updated_on', DateTime(timezone=False), nullable=False,
3394 default=datetime.datetime.now)
3394 default=datetime.datetime.now)
3395
3395
3396 @declared_attr
3396 @declared_attr
3397 def user_id(cls):
3397 def user_id(cls):
3398 return Column(
3398 return Column(
3399 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3399 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3400 unique=None)
3400 unique=None)
3401
3401
3402 # 500 revisions max
3402 # 500 revisions max
3403 _revisions = Column(
3403 _revisions = Column(
3404 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3404 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3405
3405
3406 @declared_attr
3406 @declared_attr
3407 def source_repo_id(cls):
3407 def source_repo_id(cls):
3408 # TODO: dan: rename column to source_repo_id
3408 # TODO: dan: rename column to source_repo_id
3409 return Column(
3409 return Column(
3410 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3410 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3411 nullable=False)
3411 nullable=False)
3412
3412
3413 source_ref = Column('org_ref', Unicode(255), nullable=False)
3413 source_ref = Column('org_ref', Unicode(255), nullable=False)
3414
3414
3415 @declared_attr
3415 @declared_attr
3416 def target_repo_id(cls):
3416 def target_repo_id(cls):
3417 # TODO: dan: rename column to target_repo_id
3417 # TODO: dan: rename column to target_repo_id
3418 return Column(
3418 return Column(
3419 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3419 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3420 nullable=False)
3420 nullable=False)
3421
3421
3422 target_ref = Column('other_ref', Unicode(255), nullable=False)
3422 target_ref = Column('other_ref', Unicode(255), nullable=False)
3423 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3423 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3424
3424
3425 # TODO: dan: rename column to last_merge_source_rev
3425 # TODO: dan: rename column to last_merge_source_rev
3426 _last_merge_source_rev = Column(
3426 _last_merge_source_rev = Column(
3427 'last_merge_org_rev', String(40), nullable=True)
3427 'last_merge_org_rev', String(40), nullable=True)
3428 # TODO: dan: rename column to last_merge_target_rev
3428 # TODO: dan: rename column to last_merge_target_rev
3429 _last_merge_target_rev = Column(
3429 _last_merge_target_rev = Column(
3430 'last_merge_other_rev', String(40), nullable=True)
3430 'last_merge_other_rev', String(40), nullable=True)
3431 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3431 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3432 merge_rev = Column('merge_rev', String(40), nullable=True)
3432 merge_rev = Column('merge_rev', String(40), nullable=True)
3433
3433
3434 reviewer_data = Column(
3434 reviewer_data = Column(
3435 'reviewer_data_json', MutationObj.as_mutable(
3435 'reviewer_data_json', MutationObj.as_mutable(
3436 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3436 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3437
3437
3438 @property
3438 @property
3439 def reviewer_data_json(self):
3439 def reviewer_data_json(self):
3440 return json.dumps(self.reviewer_data)
3440 return json.dumps(self.reviewer_data)
3441
3441
3442 @hybrid_property
3442 @hybrid_property
3443 def description_safe(self):
3443 def description_safe(self):
3444 from rhodecode.lib import helpers as h
3444 from rhodecode.lib import helpers as h
3445 return h.escape(self.description)
3445 return h.escape(self.description)
3446
3446
3447 @hybrid_property
3447 @hybrid_property
3448 def revisions(self):
3448 def revisions(self):
3449 return self._revisions.split(':') if self._revisions else []
3449 return self._revisions.split(':') if self._revisions else []
3450
3450
3451 @revisions.setter
3451 @revisions.setter
3452 def revisions(self, val):
3452 def revisions(self, val):
3453 self._revisions = ':'.join(val)
3453 self._revisions = ':'.join(val)
3454
3454
3455 @hybrid_property
3455 @hybrid_property
3456 def last_merge_status(self):
3456 def last_merge_status(self):
3457 return safe_int(self._last_merge_status)
3457 return safe_int(self._last_merge_status)
3458
3458
3459 @last_merge_status.setter
3459 @last_merge_status.setter
3460 def last_merge_status(self, val):
3460 def last_merge_status(self, val):
3461 self._last_merge_status = val
3461 self._last_merge_status = val
3462
3462
3463 @declared_attr
3463 @declared_attr
3464 def author(cls):
3464 def author(cls):
3465 return relationship('User', lazy='joined')
3465 return relationship('User', lazy='joined')
3466
3466
3467 @declared_attr
3467 @declared_attr
3468 def source_repo(cls):
3468 def source_repo(cls):
3469 return relationship(
3469 return relationship(
3470 'Repository',
3470 'Repository',
3471 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3471 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3472
3472
3473 @property
3473 @property
3474 def source_ref_parts(self):
3474 def source_ref_parts(self):
3475 return self.unicode_to_reference(self.source_ref)
3475 return self.unicode_to_reference(self.source_ref)
3476
3476
3477 @declared_attr
3477 @declared_attr
3478 def target_repo(cls):
3478 def target_repo(cls):
3479 return relationship(
3479 return relationship(
3480 'Repository',
3480 'Repository',
3481 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3481 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3482
3482
3483 @property
3483 @property
3484 def target_ref_parts(self):
3484 def target_ref_parts(self):
3485 return self.unicode_to_reference(self.target_ref)
3485 return self.unicode_to_reference(self.target_ref)
3486
3486
3487 @property
3487 @property
3488 def shadow_merge_ref(self):
3488 def shadow_merge_ref(self):
3489 return self.unicode_to_reference(self._shadow_merge_ref)
3489 return self.unicode_to_reference(self._shadow_merge_ref)
3490
3490
3491 @shadow_merge_ref.setter
3491 @shadow_merge_ref.setter
3492 def shadow_merge_ref(self, ref):
3492 def shadow_merge_ref(self, ref):
3493 self._shadow_merge_ref = self.reference_to_unicode(ref)
3493 self._shadow_merge_ref = self.reference_to_unicode(ref)
3494
3494
3495 def unicode_to_reference(self, raw):
3495 def unicode_to_reference(self, raw):
3496 """
3496 """
3497 Convert a unicode (or string) to a reference object.
3497 Convert a unicode (or string) to a reference object.
3498 If unicode evaluates to False it returns None.
3498 If unicode evaluates to False it returns None.
3499 """
3499 """
3500 if raw:
3500 if raw:
3501 refs = raw.split(':')
3501 refs = raw.split(':')
3502 return Reference(*refs)
3502 return Reference(*refs)
3503 else:
3503 else:
3504 return None
3504 return None
3505
3505
3506 def reference_to_unicode(self, ref):
3506 def reference_to_unicode(self, ref):
3507 """
3507 """
3508 Convert a reference object to unicode.
3508 Convert a reference object to unicode.
3509 If reference is None it returns None.
3509 If reference is None it returns None.
3510 """
3510 """
3511 if ref:
3511 if ref:
3512 return u':'.join(ref)
3512 return u':'.join(ref)
3513 else:
3513 else:
3514 return None
3514 return None
3515
3515
3516 def get_api_data(self, with_merge_state=True):
3516 def get_api_data(self, with_merge_state=True):
3517 from rhodecode.model.pull_request import PullRequestModel
3517 from rhodecode.model.pull_request import PullRequestModel
3518
3518
3519 pull_request = self
3519 pull_request = self
3520 if with_merge_state:
3520 if with_merge_state:
3521 merge_status = PullRequestModel().merge_status(pull_request)
3521 merge_status = PullRequestModel().merge_status(pull_request)
3522 merge_state = {
3522 merge_state = {
3523 'status': merge_status[0],
3523 'status': merge_status[0],
3524 'message': safe_unicode(merge_status[1]),
3524 'message': safe_unicode(merge_status[1]),
3525 }
3525 }
3526 else:
3526 else:
3527 merge_state = {'status': 'not_available',
3527 merge_state = {'status': 'not_available',
3528 'message': 'not_available'}
3528 'message': 'not_available'}
3529
3529
3530 merge_data = {
3530 merge_data = {
3531 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3531 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3532 'reference': (
3532 'reference': (
3533 pull_request.shadow_merge_ref._asdict()
3533 pull_request.shadow_merge_ref._asdict()
3534 if pull_request.shadow_merge_ref else None),
3534 if pull_request.shadow_merge_ref else None),
3535 }
3535 }
3536
3536
3537 data = {
3537 data = {
3538 'pull_request_id': pull_request.pull_request_id,
3538 'pull_request_id': pull_request.pull_request_id,
3539 'url': PullRequestModel().get_url(pull_request),
3539 'url': PullRequestModel().get_url(pull_request),
3540 'title': pull_request.title,
3540 'title': pull_request.title,
3541 'description': pull_request.description,
3541 'description': pull_request.description,
3542 'status': pull_request.status,
3542 'status': pull_request.status,
3543 'created_on': pull_request.created_on,
3543 'created_on': pull_request.created_on,
3544 'updated_on': pull_request.updated_on,
3544 'updated_on': pull_request.updated_on,
3545 'commit_ids': pull_request.revisions,
3545 'commit_ids': pull_request.revisions,
3546 'review_status': pull_request.calculated_review_status(),
3546 'review_status': pull_request.calculated_review_status(),
3547 'mergeable': merge_state,
3547 'mergeable': merge_state,
3548 'source': {
3548 'source': {
3549 'clone_url': pull_request.source_repo.clone_url(),
3549 'clone_url': pull_request.source_repo.clone_url(),
3550 'repository': pull_request.source_repo.repo_name,
3550 'repository': pull_request.source_repo.repo_name,
3551 'reference': {
3551 'reference': {
3552 'name': pull_request.source_ref_parts.name,
3552 'name': pull_request.source_ref_parts.name,
3553 'type': pull_request.source_ref_parts.type,
3553 'type': pull_request.source_ref_parts.type,
3554 'commit_id': pull_request.source_ref_parts.commit_id,
3554 'commit_id': pull_request.source_ref_parts.commit_id,
3555 },
3555 },
3556 },
3556 },
3557 'target': {
3557 'target': {
3558 'clone_url': pull_request.target_repo.clone_url(),
3558 'clone_url': pull_request.target_repo.clone_url(),
3559 'repository': pull_request.target_repo.repo_name,
3559 'repository': pull_request.target_repo.repo_name,
3560 'reference': {
3560 'reference': {
3561 'name': pull_request.target_ref_parts.name,
3561 'name': pull_request.target_ref_parts.name,
3562 'type': pull_request.target_ref_parts.type,
3562 'type': pull_request.target_ref_parts.type,
3563 'commit_id': pull_request.target_ref_parts.commit_id,
3563 'commit_id': pull_request.target_ref_parts.commit_id,
3564 },
3564 },
3565 },
3565 },
3566 'merge': merge_data,
3566 'merge': merge_data,
3567 'author': pull_request.author.get_api_data(include_secrets=False,
3567 'author': pull_request.author.get_api_data(include_secrets=False,
3568 details='basic'),
3568 details='basic'),
3569 'reviewers': [
3569 'reviewers': [
3570 {
3570 {
3571 'user': reviewer.get_api_data(include_secrets=False,
3571 'user': reviewer.get_api_data(include_secrets=False,
3572 details='basic'),
3572 details='basic'),
3573 'reasons': reasons,
3573 'reasons': reasons,
3574 'review_status': st[0][1].status if st else 'not_reviewed',
3574 'review_status': st[0][1].status if st else 'not_reviewed',
3575 }
3575 }
3576 for reviewer, reasons, mandatory, st in
3576 for reviewer, reasons, mandatory, st in
3577 pull_request.reviewers_statuses()
3577 pull_request.reviewers_statuses()
3578 ]
3578 ]
3579 }
3579 }
3580
3580
3581 return data
3581 return data
3582
3582
3583
3583
3584 class PullRequest(Base, _PullRequestBase):
3584 class PullRequest(Base, _PullRequestBase):
3585 __tablename__ = 'pull_requests'
3585 __tablename__ = 'pull_requests'
3586 __table_args__ = (
3586 __table_args__ = (
3587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3588 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3588 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3589 )
3589 )
3590
3590
3591 pull_request_id = Column(
3591 pull_request_id = Column(
3592 'pull_request_id', Integer(), nullable=False, primary_key=True)
3592 'pull_request_id', Integer(), nullable=False, primary_key=True)
3593
3593
3594 def __repr__(self):
3594 def __repr__(self):
3595 if self.pull_request_id:
3595 if self.pull_request_id:
3596 return '<DB:PullRequest #%s>' % self.pull_request_id
3596 return '<DB:PullRequest #%s>' % self.pull_request_id
3597 else:
3597 else:
3598 return '<DB:PullRequest at %#x>' % id(self)
3598 return '<DB:PullRequest at %#x>' % id(self)
3599
3599
3600 reviewers = relationship('PullRequestReviewers',
3600 reviewers = relationship('PullRequestReviewers',
3601 cascade="all, delete, delete-orphan")
3601 cascade="all, delete, delete-orphan")
3602 statuses = relationship('ChangesetStatus',
3602 statuses = relationship('ChangesetStatus',
3603 cascade="all, delete, delete-orphan")
3603 cascade="all, delete, delete-orphan")
3604 comments = relationship('ChangesetComment',
3604 comments = relationship('ChangesetComment',
3605 cascade="all, delete, delete-orphan")
3605 cascade="all, delete, delete-orphan")
3606 versions = relationship('PullRequestVersion',
3606 versions = relationship('PullRequestVersion',
3607 cascade="all, delete, delete-orphan",
3607 cascade="all, delete, delete-orphan",
3608 lazy='dynamic')
3608 lazy='dynamic')
3609
3609
3610 @classmethod
3610 @classmethod
3611 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3611 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3612 internal_methods=None):
3612 internal_methods=None):
3613
3613
3614 class PullRequestDisplay(object):
3614 class PullRequestDisplay(object):
3615 """
3615 """
3616 Special object wrapper for showing PullRequest data via Versions
3616 Special object wrapper for showing PullRequest data via Versions
3617 It mimics PR object as close as possible. This is read only object
3617 It mimics PR object as close as possible. This is read only object
3618 just for display
3618 just for display
3619 """
3619 """
3620
3620
3621 def __init__(self, attrs, internal=None):
3621 def __init__(self, attrs, internal=None):
3622 self.attrs = attrs
3622 self.attrs = attrs
3623 # internal have priority over the given ones via attrs
3623 # internal have priority over the given ones via attrs
3624 self.internal = internal or ['versions']
3624 self.internal = internal or ['versions']
3625
3625
3626 def __getattr__(self, item):
3626 def __getattr__(self, item):
3627 if item in self.internal:
3627 if item in self.internal:
3628 return getattr(self, item)
3628 return getattr(self, item)
3629 try:
3629 try:
3630 return self.attrs[item]
3630 return self.attrs[item]
3631 except KeyError:
3631 except KeyError:
3632 raise AttributeError(
3632 raise AttributeError(
3633 '%s object has no attribute %s' % (self, item))
3633 '%s object has no attribute %s' % (self, item))
3634
3634
3635 def __repr__(self):
3635 def __repr__(self):
3636 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3636 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3637
3637
3638 def versions(self):
3638 def versions(self):
3639 return pull_request_obj.versions.order_by(
3639 return pull_request_obj.versions.order_by(
3640 PullRequestVersion.pull_request_version_id).all()
3640 PullRequestVersion.pull_request_version_id).all()
3641
3641
3642 def is_closed(self):
3642 def is_closed(self):
3643 return pull_request_obj.is_closed()
3643 return pull_request_obj.is_closed()
3644
3644
3645 @property
3645 @property
3646 def pull_request_version_id(self):
3646 def pull_request_version_id(self):
3647 return getattr(pull_request_obj, 'pull_request_version_id', None)
3647 return getattr(pull_request_obj, 'pull_request_version_id', None)
3648
3648
3649 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3649 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3650
3650
3651 attrs.author = StrictAttributeDict(
3651 attrs.author = StrictAttributeDict(
3652 pull_request_obj.author.get_api_data())
3652 pull_request_obj.author.get_api_data())
3653 if pull_request_obj.target_repo:
3653 if pull_request_obj.target_repo:
3654 attrs.target_repo = StrictAttributeDict(
3654 attrs.target_repo = StrictAttributeDict(
3655 pull_request_obj.target_repo.get_api_data())
3655 pull_request_obj.target_repo.get_api_data())
3656 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3656 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3657
3657
3658 if pull_request_obj.source_repo:
3658 if pull_request_obj.source_repo:
3659 attrs.source_repo = StrictAttributeDict(
3659 attrs.source_repo = StrictAttributeDict(
3660 pull_request_obj.source_repo.get_api_data())
3660 pull_request_obj.source_repo.get_api_data())
3661 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3661 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3662
3662
3663 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3663 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3664 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3664 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3665 attrs.revisions = pull_request_obj.revisions
3665 attrs.revisions = pull_request_obj.revisions
3666
3666
3667 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3667 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3668 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3668 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3669 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3669 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3670
3670
3671 return PullRequestDisplay(attrs, internal=internal_methods)
3671 return PullRequestDisplay(attrs, internal=internal_methods)
3672
3672
3673 def is_closed(self):
3673 def is_closed(self):
3674 return self.status == self.STATUS_CLOSED
3674 return self.status == self.STATUS_CLOSED
3675
3675
3676 def __json__(self):
3676 def __json__(self):
3677 return {
3677 return {
3678 'revisions': self.revisions,
3678 'revisions': self.revisions,
3679 }
3679 }
3680
3680
3681 def calculated_review_status(self):
3681 def calculated_review_status(self):
3682 from rhodecode.model.changeset_status import ChangesetStatusModel
3682 from rhodecode.model.changeset_status import ChangesetStatusModel
3683 return ChangesetStatusModel().calculated_review_status(self)
3683 return ChangesetStatusModel().calculated_review_status(self)
3684
3684
3685 def reviewers_statuses(self):
3685 def reviewers_statuses(self):
3686 from rhodecode.model.changeset_status import ChangesetStatusModel
3686 from rhodecode.model.changeset_status import ChangesetStatusModel
3687 return ChangesetStatusModel().reviewers_statuses(self)
3687 return ChangesetStatusModel().reviewers_statuses(self)
3688
3688
3689 @property
3689 @property
3690 def workspace_id(self):
3690 def workspace_id(self):
3691 from rhodecode.model.pull_request import PullRequestModel
3691 from rhodecode.model.pull_request import PullRequestModel
3692 return PullRequestModel()._workspace_id(self)
3692 return PullRequestModel()._workspace_id(self)
3693
3693
3694 def get_shadow_repo(self):
3694 def get_shadow_repo(self):
3695 workspace_id = self.workspace_id
3695 workspace_id = self.workspace_id
3696 vcs_obj = self.target_repo.scm_instance()
3696 vcs_obj = self.target_repo.scm_instance()
3697 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3697 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3698 workspace_id)
3698 workspace_id)
3699 return vcs_obj._get_shadow_instance(shadow_repository_path)
3699 return vcs_obj._get_shadow_instance(shadow_repository_path)
3700
3700
3701
3701
3702 class PullRequestVersion(Base, _PullRequestBase):
3702 class PullRequestVersion(Base, _PullRequestBase):
3703 __tablename__ = 'pull_request_versions'
3703 __tablename__ = 'pull_request_versions'
3704 __table_args__ = (
3704 __table_args__ = (
3705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3705 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3706 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3706 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3707 )
3707 )
3708
3708
3709 pull_request_version_id = Column(
3709 pull_request_version_id = Column(
3710 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3710 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3711 pull_request_id = Column(
3711 pull_request_id = Column(
3712 'pull_request_id', Integer(),
3712 'pull_request_id', Integer(),
3713 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3713 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3714 pull_request = relationship('PullRequest')
3714 pull_request = relationship('PullRequest')
3715
3715
3716 def __repr__(self):
3716 def __repr__(self):
3717 if self.pull_request_version_id:
3717 if self.pull_request_version_id:
3718 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3718 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3719 else:
3719 else:
3720 return '<DB:PullRequestVersion at %#x>' % id(self)
3720 return '<DB:PullRequestVersion at %#x>' % id(self)
3721
3721
3722 @property
3722 @property
3723 def reviewers(self):
3723 def reviewers(self):
3724 return self.pull_request.reviewers
3724 return self.pull_request.reviewers
3725
3725
3726 @property
3726 @property
3727 def versions(self):
3727 def versions(self):
3728 return self.pull_request.versions
3728 return self.pull_request.versions
3729
3729
3730 def is_closed(self):
3730 def is_closed(self):
3731 # calculate from original
3731 # calculate from original
3732 return self.pull_request.status == self.STATUS_CLOSED
3732 return self.pull_request.status == self.STATUS_CLOSED
3733
3733
3734 def calculated_review_status(self):
3734 def calculated_review_status(self):
3735 return self.pull_request.calculated_review_status()
3735 return self.pull_request.calculated_review_status()
3736
3736
3737 def reviewers_statuses(self):
3737 def reviewers_statuses(self):
3738 return self.pull_request.reviewers_statuses()
3738 return self.pull_request.reviewers_statuses()
3739
3739
3740
3740
3741 class PullRequestReviewers(Base, BaseModel):
3741 class PullRequestReviewers(Base, BaseModel):
3742 __tablename__ = 'pull_request_reviewers'
3742 __tablename__ = 'pull_request_reviewers'
3743 __table_args__ = (
3743 __table_args__ = (
3744 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3744 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3745 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3745 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3746 )
3746 )
3747
3747
3748 @hybrid_property
3748 @hybrid_property
3749 def reasons(self):
3749 def reasons(self):
3750 if not self._reasons:
3750 if not self._reasons:
3751 return []
3751 return []
3752 return self._reasons
3752 return self._reasons
3753
3753
3754 @reasons.setter
3754 @reasons.setter
3755 def reasons(self, val):
3755 def reasons(self, val):
3756 val = val or []
3756 val = val or []
3757 if any(not isinstance(x, basestring) for x in val):
3757 if any(not isinstance(x, basestring) for x in val):
3758 raise Exception('invalid reasons type, must be list of strings')
3758 raise Exception('invalid reasons type, must be list of strings')
3759 self._reasons = val
3759 self._reasons = val
3760
3760
3761 pull_requests_reviewers_id = Column(
3761 pull_requests_reviewers_id = Column(
3762 'pull_requests_reviewers_id', Integer(), nullable=False,
3762 'pull_requests_reviewers_id', Integer(), nullable=False,
3763 primary_key=True)
3763 primary_key=True)
3764 pull_request_id = Column(
3764 pull_request_id = Column(
3765 "pull_request_id", Integer(),
3765 "pull_request_id", Integer(),
3766 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3766 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3767 user_id = Column(
3767 user_id = Column(
3768 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3768 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3769 _reasons = Column(
3769 _reasons = Column(
3770 'reason', MutationList.as_mutable(
3770 'reason', MutationList.as_mutable(
3771 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3771 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3772 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3772 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3773 user = relationship('User')
3773 user = relationship('User')
3774 pull_request = relationship('PullRequest')
3774 pull_request = relationship('PullRequest')
3775
3775
3776
3776
3777 class Notification(Base, BaseModel):
3777 class Notification(Base, BaseModel):
3778 __tablename__ = 'notifications'
3778 __tablename__ = 'notifications'
3779 __table_args__ = (
3779 __table_args__ = (
3780 Index('notification_type_idx', 'type'),
3780 Index('notification_type_idx', 'type'),
3781 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3781 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3782 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3782 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3783 )
3783 )
3784
3784
3785 TYPE_CHANGESET_COMMENT = u'cs_comment'
3785 TYPE_CHANGESET_COMMENT = u'cs_comment'
3786 TYPE_MESSAGE = u'message'
3786 TYPE_MESSAGE = u'message'
3787 TYPE_MENTION = u'mention'
3787 TYPE_MENTION = u'mention'
3788 TYPE_REGISTRATION = u'registration'
3788 TYPE_REGISTRATION = u'registration'
3789 TYPE_PULL_REQUEST = u'pull_request'
3789 TYPE_PULL_REQUEST = u'pull_request'
3790 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3790 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3791
3791
3792 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3792 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3793 subject = Column('subject', Unicode(512), nullable=True)
3793 subject = Column('subject', Unicode(512), nullable=True)
3794 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3794 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3795 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3795 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3796 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3796 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3797 type_ = Column('type', Unicode(255))
3797 type_ = Column('type', Unicode(255))
3798
3798
3799 created_by_user = relationship('User')
3799 created_by_user = relationship('User')
3800 notifications_to_users = relationship('UserNotification', lazy='joined',
3800 notifications_to_users = relationship('UserNotification', lazy='joined',
3801 cascade="all, delete, delete-orphan")
3801 cascade="all, delete, delete-orphan")
3802
3802
3803 @property
3803 @property
3804 def recipients(self):
3804 def recipients(self):
3805 return [x.user for x in UserNotification.query()\
3805 return [x.user for x in UserNotification.query()\
3806 .filter(UserNotification.notification == self)\
3806 .filter(UserNotification.notification == self)\
3807 .order_by(UserNotification.user_id.asc()).all()]
3807 .order_by(UserNotification.user_id.asc()).all()]
3808
3808
3809 @classmethod
3809 @classmethod
3810 def create(cls, created_by, subject, body, recipients, type_=None):
3810 def create(cls, created_by, subject, body, recipients, type_=None):
3811 if type_ is None:
3811 if type_ is None:
3812 type_ = Notification.TYPE_MESSAGE
3812 type_ = Notification.TYPE_MESSAGE
3813
3813
3814 notification = cls()
3814 notification = cls()
3815 notification.created_by_user = created_by
3815 notification.created_by_user = created_by
3816 notification.subject = subject
3816 notification.subject = subject
3817 notification.body = body
3817 notification.body = body
3818 notification.type_ = type_
3818 notification.type_ = type_
3819 notification.created_on = datetime.datetime.now()
3819 notification.created_on = datetime.datetime.now()
3820
3820
3821 for u in recipients:
3821 for u in recipients:
3822 assoc = UserNotification()
3822 assoc = UserNotification()
3823 assoc.notification = notification
3823 assoc.notification = notification
3824
3824
3825 # if created_by is inside recipients mark his notification
3825 # if created_by is inside recipients mark his notification
3826 # as read
3826 # as read
3827 if u.user_id == created_by.user_id:
3827 if u.user_id == created_by.user_id:
3828 assoc.read = True
3828 assoc.read = True
3829
3829
3830 u.notifications.append(assoc)
3830 u.notifications.append(assoc)
3831 Session().add(notification)
3831 Session().add(notification)
3832
3832
3833 return notification
3833 return notification
3834
3834
3835
3835
3836 class UserNotification(Base, BaseModel):
3836 class UserNotification(Base, BaseModel):
3837 __tablename__ = 'user_to_notification'
3837 __tablename__ = 'user_to_notification'
3838 __table_args__ = (
3838 __table_args__ = (
3839 UniqueConstraint('user_id', 'notification_id'),
3839 UniqueConstraint('user_id', 'notification_id'),
3840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3841 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3841 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3842 )
3842 )
3843 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3843 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3844 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3844 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3845 read = Column('read', Boolean, default=False)
3845 read = Column('read', Boolean, default=False)
3846 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3846 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3847
3847
3848 user = relationship('User', lazy="joined")
3848 user = relationship('User', lazy="joined")
3849 notification = relationship('Notification', lazy="joined",
3849 notification = relationship('Notification', lazy="joined",
3850 order_by=lambda: Notification.created_on.desc(),)
3850 order_by=lambda: Notification.created_on.desc(),)
3851
3851
3852 def mark_as_read(self):
3852 def mark_as_read(self):
3853 self.read = True
3853 self.read = True
3854 Session().add(self)
3854 Session().add(self)
3855
3855
3856
3856
3857 class Gist(Base, BaseModel):
3857 class Gist(Base, BaseModel):
3858 __tablename__ = 'gists'
3858 __tablename__ = 'gists'
3859 __table_args__ = (
3859 __table_args__ = (
3860 Index('g_gist_access_id_idx', 'gist_access_id'),
3860 Index('g_gist_access_id_idx', 'gist_access_id'),
3861 Index('g_created_on_idx', 'created_on'),
3861 Index('g_created_on_idx', 'created_on'),
3862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3862 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3863 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3863 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3864 )
3864 )
3865 GIST_PUBLIC = u'public'
3865 GIST_PUBLIC = u'public'
3866 GIST_PRIVATE = u'private'
3866 GIST_PRIVATE = u'private'
3867 DEFAULT_FILENAME = u'gistfile1.txt'
3867 DEFAULT_FILENAME = u'gistfile1.txt'
3868
3868
3869 ACL_LEVEL_PUBLIC = u'acl_public'
3869 ACL_LEVEL_PUBLIC = u'acl_public'
3870 ACL_LEVEL_PRIVATE = u'acl_private'
3870 ACL_LEVEL_PRIVATE = u'acl_private'
3871
3871
3872 gist_id = Column('gist_id', Integer(), primary_key=True)
3872 gist_id = Column('gist_id', Integer(), primary_key=True)
3873 gist_access_id = Column('gist_access_id', Unicode(250))
3873 gist_access_id = Column('gist_access_id', Unicode(250))
3874 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3874 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3875 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3875 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3876 gist_expires = Column('gist_expires', Float(53), nullable=False)
3876 gist_expires = Column('gist_expires', Float(53), nullable=False)
3877 gist_type = Column('gist_type', Unicode(128), nullable=False)
3877 gist_type = Column('gist_type', Unicode(128), nullable=False)
3878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3879 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3879 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3880 acl_level = Column('acl_level', Unicode(128), nullable=True)
3880 acl_level = Column('acl_level', Unicode(128), nullable=True)
3881
3881
3882 owner = relationship('User')
3882 owner = relationship('User')
3883
3883
3884 def __repr__(self):
3884 def __repr__(self):
3885 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3885 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3886
3886
3887 @hybrid_property
3887 @hybrid_property
3888 def description_safe(self):
3888 def description_safe(self):
3889 from rhodecode.lib import helpers as h
3889 from rhodecode.lib import helpers as h
3890 return h.escape(self.gist_description)
3890 return h.escape(self.gist_description)
3891
3891
3892 @classmethod
3892 @classmethod
3893 def get_or_404(cls, id_):
3893 def get_or_404(cls, id_):
3894 from pyramid.httpexceptions import HTTPNotFound
3894 from pyramid.httpexceptions import HTTPNotFound
3895
3895
3896 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3896 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3897 if not res:
3897 if not res:
3898 raise HTTPNotFound()
3898 raise HTTPNotFound()
3899 return res
3899 return res
3900
3900
3901 @classmethod
3901 @classmethod
3902 def get_by_access_id(cls, gist_access_id):
3902 def get_by_access_id(cls, gist_access_id):
3903 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3903 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3904
3904
3905 def gist_url(self):
3905 def gist_url(self):
3906 from rhodecode.model.gist import GistModel
3906 from rhodecode.model.gist import GistModel
3907 return GistModel().get_url(self)
3907 return GistModel().get_url(self)
3908
3908
3909 @classmethod
3909 @classmethod
3910 def base_path(cls):
3910 def base_path(cls):
3911 """
3911 """
3912 Returns base path when all gists are stored
3912 Returns base path when all gists are stored
3913
3913
3914 :param cls:
3914 :param cls:
3915 """
3915 """
3916 from rhodecode.model.gist import GIST_STORE_LOC
3916 from rhodecode.model.gist import GIST_STORE_LOC
3917 q = Session().query(RhodeCodeUi)\
3917 q = Session().query(RhodeCodeUi)\
3918 .filter(RhodeCodeUi.ui_key == URL_SEP)
3918 .filter(RhodeCodeUi.ui_key == URL_SEP)
3919 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3919 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3920 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3920 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3921
3921
3922 def get_api_data(self):
3922 def get_api_data(self):
3923 """
3923 """
3924 Common function for generating gist related data for API
3924 Common function for generating gist related data for API
3925 """
3925 """
3926 gist = self
3926 gist = self
3927 data = {
3927 data = {
3928 'gist_id': gist.gist_id,
3928 'gist_id': gist.gist_id,
3929 'type': gist.gist_type,
3929 'type': gist.gist_type,
3930 'access_id': gist.gist_access_id,
3930 'access_id': gist.gist_access_id,
3931 'description': gist.gist_description,
3931 'description': gist.gist_description,
3932 'url': gist.gist_url(),
3932 'url': gist.gist_url(),
3933 'expires': gist.gist_expires,
3933 'expires': gist.gist_expires,
3934 'created_on': gist.created_on,
3934 'created_on': gist.created_on,
3935 'modified_at': gist.modified_at,
3935 'modified_at': gist.modified_at,
3936 'content': None,
3936 'content': None,
3937 'acl_level': gist.acl_level,
3937 'acl_level': gist.acl_level,
3938 }
3938 }
3939 return data
3939 return data
3940
3940
3941 def __json__(self):
3941 def __json__(self):
3942 data = dict(
3942 data = dict(
3943 )
3943 )
3944 data.update(self.get_api_data())
3944 data.update(self.get_api_data())
3945 return data
3945 return data
3946 # SCM functions
3946 # SCM functions
3947
3947
3948 def scm_instance(self, **kwargs):
3948 def scm_instance(self, **kwargs):
3949 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3949 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3950 return get_vcs_instance(
3950 return get_vcs_instance(
3951 repo_path=safe_str(full_repo_path), create=False)
3951 repo_path=safe_str(full_repo_path), create=False)
3952
3952
3953
3953
3954 class ExternalIdentity(Base, BaseModel):
3954 class ExternalIdentity(Base, BaseModel):
3955 __tablename__ = 'external_identities'
3955 __tablename__ = 'external_identities'
3956 __table_args__ = (
3956 __table_args__ = (
3957 Index('local_user_id_idx', 'local_user_id'),
3957 Index('local_user_id_idx', 'local_user_id'),
3958 Index('external_id_idx', 'external_id'),
3958 Index('external_id_idx', 'external_id'),
3959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3960 'mysql_charset': 'utf8'})
3960 'mysql_charset': 'utf8'})
3961
3961
3962 external_id = Column('external_id', Unicode(255), default=u'',
3962 external_id = Column('external_id', Unicode(255), default=u'',
3963 primary_key=True)
3963 primary_key=True)
3964 external_username = Column('external_username', Unicode(1024), default=u'')
3964 external_username = Column('external_username', Unicode(1024), default=u'')
3965 local_user_id = Column('local_user_id', Integer(),
3965 local_user_id = Column('local_user_id', Integer(),
3966 ForeignKey('users.user_id'), primary_key=True)
3966 ForeignKey('users.user_id'), primary_key=True)
3967 provider_name = Column('provider_name', Unicode(255), default=u'',
3967 provider_name = Column('provider_name', Unicode(255), default=u'',
3968 primary_key=True)
3968 primary_key=True)
3969 access_token = Column('access_token', String(1024), default=u'')
3969 access_token = Column('access_token', String(1024), default=u'')
3970 alt_token = Column('alt_token', String(1024), default=u'')
3970 alt_token = Column('alt_token', String(1024), default=u'')
3971 token_secret = Column('token_secret', String(1024), default=u'')
3971 token_secret = Column('token_secret', String(1024), default=u'')
3972
3972
3973 @classmethod
3973 @classmethod
3974 def by_external_id_and_provider(cls, external_id, provider_name,
3974 def by_external_id_and_provider(cls, external_id, provider_name,
3975 local_user_id=None):
3975 local_user_id=None):
3976 """
3976 """
3977 Returns ExternalIdentity instance based on search params
3977 Returns ExternalIdentity instance based on search params
3978
3978
3979 :param external_id:
3979 :param external_id:
3980 :param provider_name:
3980 :param provider_name:
3981 :return: ExternalIdentity
3981 :return: ExternalIdentity
3982 """
3982 """
3983 query = cls.query()
3983 query = cls.query()
3984 query = query.filter(cls.external_id == external_id)
3984 query = query.filter(cls.external_id == external_id)
3985 query = query.filter(cls.provider_name == provider_name)
3985 query = query.filter(cls.provider_name == provider_name)
3986 if local_user_id:
3986 if local_user_id:
3987 query = query.filter(cls.local_user_id == local_user_id)
3987 query = query.filter(cls.local_user_id == local_user_id)
3988 return query.first()
3988 return query.first()
3989
3989
3990 @classmethod
3990 @classmethod
3991 def user_by_external_id_and_provider(cls, external_id, provider_name):
3991 def user_by_external_id_and_provider(cls, external_id, provider_name):
3992 """
3992 """
3993 Returns User instance based on search params
3993 Returns User instance based on search params
3994
3994
3995 :param external_id:
3995 :param external_id:
3996 :param provider_name:
3996 :param provider_name:
3997 :return: User
3997 :return: User
3998 """
3998 """
3999 query = User.query()
3999 query = User.query()
4000 query = query.filter(cls.external_id == external_id)
4000 query = query.filter(cls.external_id == external_id)
4001 query = query.filter(cls.provider_name == provider_name)
4001 query = query.filter(cls.provider_name == provider_name)
4002 query = query.filter(User.user_id == cls.local_user_id)
4002 query = query.filter(User.user_id == cls.local_user_id)
4003 return query.first()
4003 return query.first()
4004
4004
4005 @classmethod
4005 @classmethod
4006 def by_local_user_id(cls, local_user_id):
4006 def by_local_user_id(cls, local_user_id):
4007 """
4007 """
4008 Returns all tokens for user
4008 Returns all tokens for user
4009
4009
4010 :param local_user_id:
4010 :param local_user_id:
4011 :return: ExternalIdentity
4011 :return: ExternalIdentity
4012 """
4012 """
4013 query = cls.query()
4013 query = cls.query()
4014 query = query.filter(cls.local_user_id == local_user_id)
4014 query = query.filter(cls.local_user_id == local_user_id)
4015 return query
4015 return query
4016
4016
4017
4017
4018 class Integration(Base, BaseModel):
4018 class Integration(Base, BaseModel):
4019 __tablename__ = 'integrations'
4019 __tablename__ = 'integrations'
4020 __table_args__ = (
4020 __table_args__ = (
4021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4023 )
4023 )
4024
4024
4025 integration_id = Column('integration_id', Integer(), primary_key=True)
4025 integration_id = Column('integration_id', Integer(), primary_key=True)
4026 integration_type = Column('integration_type', String(255))
4026 integration_type = Column('integration_type', String(255))
4027 enabled = Column('enabled', Boolean(), nullable=False)
4027 enabled = Column('enabled', Boolean(), nullable=False)
4028 name = Column('name', String(255), nullable=False)
4028 name = Column('name', String(255), nullable=False)
4029 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4029 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4030 default=False)
4030 default=False)
4031
4031
4032 settings = Column(
4032 settings = Column(
4033 'settings_json', MutationObj.as_mutable(
4033 'settings_json', MutationObj.as_mutable(
4034 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4034 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4035 repo_id = Column(
4035 repo_id = Column(
4036 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4036 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4037 nullable=True, unique=None, default=None)
4037 nullable=True, unique=None, default=None)
4038 repo = relationship('Repository', lazy='joined')
4038 repo = relationship('Repository', lazy='joined')
4039
4039
4040 repo_group_id = Column(
4040 repo_group_id = Column(
4041 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4041 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4042 nullable=True, unique=None, default=None)
4042 nullable=True, unique=None, default=None)
4043 repo_group = relationship('RepoGroup', lazy='joined')
4043 repo_group = relationship('RepoGroup', lazy='joined')
4044
4044
4045 @property
4045 @property
4046 def scope(self):
4046 def scope(self):
4047 if self.repo:
4047 if self.repo:
4048 return repr(self.repo)
4048 return repr(self.repo)
4049 if self.repo_group:
4049 if self.repo_group:
4050 if self.child_repos_only:
4050 if self.child_repos_only:
4051 return repr(self.repo_group) + ' (child repos only)'
4051 return repr(self.repo_group) + ' (child repos only)'
4052 else:
4052 else:
4053 return repr(self.repo_group) + ' (recursive)'
4053 return repr(self.repo_group) + ' (recursive)'
4054 if self.child_repos_only:
4054 if self.child_repos_only:
4055 return 'root_repos'
4055 return 'root_repos'
4056 return 'global'
4056 return 'global'
4057
4057
4058 def __repr__(self):
4058 def __repr__(self):
4059 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4059 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4060
4060
4061
4061
4062 class RepoReviewRuleUser(Base, BaseModel):
4062 class RepoReviewRuleUser(Base, BaseModel):
4063 __tablename__ = 'repo_review_rules_users'
4063 __tablename__ = 'repo_review_rules_users'
4064 __table_args__ = (
4064 __table_args__ = (
4065 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4065 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4066 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4066 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4067 )
4067 )
4068 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4068 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4069 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4069 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4070 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4070 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4071 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4071 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4072 user = relationship('User')
4072 user = relationship('User')
4073
4073
4074 def rule_data(self):
4074 def rule_data(self):
4075 return {
4075 return {
4076 'mandatory': self.mandatory
4076 'mandatory': self.mandatory
4077 }
4077 }
4078
4078
4079
4079
4080 class RepoReviewRuleUserGroup(Base, BaseModel):
4080 class RepoReviewRuleUserGroup(Base, BaseModel):
4081 __tablename__ = 'repo_review_rules_users_groups'
4081 __tablename__ = 'repo_review_rules_users_groups'
4082 __table_args__ = (
4082 __table_args__ = (
4083 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4083 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4084 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4084 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4085 )
4085 )
4086 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4086 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4087 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4087 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4088 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4088 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4089 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4089 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4090 users_group = relationship('UserGroup')
4090 users_group = relationship('UserGroup')
4091
4091
4092 def rule_data(self):
4092 def rule_data(self):
4093 return {
4093 return {
4094 'mandatory': self.mandatory
4094 'mandatory': self.mandatory
4095 }
4095 }
4096
4096
4097
4097
4098 class RepoReviewRule(Base, BaseModel):
4098 class RepoReviewRule(Base, BaseModel):
4099 __tablename__ = 'repo_review_rules'
4099 __tablename__ = 'repo_review_rules'
4100 __table_args__ = (
4100 __table_args__ = (
4101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4103 )
4103 )
4104
4104
4105 repo_review_rule_id = Column(
4105 repo_review_rule_id = Column(
4106 'repo_review_rule_id', Integer(), primary_key=True)
4106 'repo_review_rule_id', Integer(), primary_key=True)
4107 repo_id = Column(
4107 repo_id = Column(
4108 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4108 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4109 repo = relationship('Repository', backref='review_rules')
4109 repo = relationship('Repository', backref='review_rules')
4110
4110
4111 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4111 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4112 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4112 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4113
4113
4114 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4114 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4115 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4115 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4116 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4116 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4117 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4117 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4118
4118
4119 rule_users = relationship('RepoReviewRuleUser')
4119 rule_users = relationship('RepoReviewRuleUser')
4120 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4120 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4121
4121
4122 @hybrid_property
4122 @hybrid_property
4123 def branch_pattern(self):
4123 def branch_pattern(self):
4124 return self._branch_pattern or '*'
4124 return self._branch_pattern or '*'
4125
4125
4126 def _validate_glob(self, value):
4126 def _validate_glob(self, value):
4127 re.compile('^' + glob2re(value) + '$')
4127 re.compile('^' + glob2re(value) + '$')
4128
4128
4129 @branch_pattern.setter
4129 @branch_pattern.setter
4130 def branch_pattern(self, value):
4130 def branch_pattern(self, value):
4131 self._validate_glob(value)
4131 self._validate_glob(value)
4132 self._branch_pattern = value or '*'
4132 self._branch_pattern = value or '*'
4133
4133
4134 @hybrid_property
4134 @hybrid_property
4135 def file_pattern(self):
4135 def file_pattern(self):
4136 return self._file_pattern or '*'
4136 return self._file_pattern or '*'
4137
4137
4138 @file_pattern.setter
4138 @file_pattern.setter
4139 def file_pattern(self, value):
4139 def file_pattern(self, value):
4140 self._validate_glob(value)
4140 self._validate_glob(value)
4141 self._file_pattern = value or '*'
4141 self._file_pattern = value or '*'
4142
4142
4143 def matches(self, branch, files_changed):
4143 def matches(self, branch, files_changed):
4144 """
4144 """
4145 Check if this review rule matches a branch/files in a pull request
4145 Check if this review rule matches a branch/files in a pull request
4146
4146
4147 :param branch: branch name for the commit
4147 :param branch: branch name for the commit
4148 :param files_changed: list of file paths changed in the pull request
4148 :param files_changed: list of file paths changed in the pull request
4149 """
4149 """
4150
4150
4151 branch = branch or ''
4151 branch = branch or ''
4152 files_changed = files_changed or []
4152 files_changed = files_changed or []
4153
4153
4154 branch_matches = True
4154 branch_matches = True
4155 if branch:
4155 if branch:
4156 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4156 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4157 branch_matches = bool(branch_regex.search(branch))
4157 branch_matches = bool(branch_regex.search(branch))
4158
4158
4159 files_matches = True
4159 files_matches = True
4160 if self.file_pattern != '*':
4160 if self.file_pattern != '*':
4161 files_matches = False
4161 files_matches = False
4162 file_regex = re.compile(glob2re(self.file_pattern))
4162 file_regex = re.compile(glob2re(self.file_pattern))
4163 for filename in files_changed:
4163 for filename in files_changed:
4164 if file_regex.search(filename):
4164 if file_regex.search(filename):
4165 files_matches = True
4165 files_matches = True
4166 break
4166 break
4167
4167
4168 return branch_matches and files_matches
4168 return branch_matches and files_matches
4169
4169
4170 @property
4170 @property
4171 def review_users(self):
4171 def review_users(self):
4172 """ Returns the users which this rule applies to """
4172 """ Returns the users which this rule applies to """
4173
4173
4174 users = collections.OrderedDict()
4174 users = collections.OrderedDict()
4175
4175
4176 for rule_user in self.rule_users:
4176 for rule_user in self.rule_users:
4177 if rule_user.user.active:
4177 if rule_user.user.active:
4178 if rule_user.user not in users:
4178 if rule_user.user not in users:
4179 users[rule_user.user.username] = {
4179 users[rule_user.user.username] = {
4180 'user': rule_user.user,
4180 'user': rule_user.user,
4181 'source': 'user',
4181 'source': 'user',
4182 'source_data': {},
4182 'source_data': {},
4183 'data': rule_user.rule_data()
4183 'data': rule_user.rule_data()
4184 }
4184 }
4185
4185
4186 for rule_user_group in self.rule_user_groups:
4186 for rule_user_group in self.rule_user_groups:
4187 source_data = {
4187 source_data = {
4188 'name': rule_user_group.users_group.users_group_name,
4188 'name': rule_user_group.users_group.users_group_name,
4189 'members': len(rule_user_group.users_group.members)
4189 'members': len(rule_user_group.users_group.members)
4190 }
4190 }
4191 for member in rule_user_group.users_group.members:
4191 for member in rule_user_group.users_group.members:
4192 if member.user.active:
4192 if member.user.active:
4193 users[member.user.username] = {
4193 users[member.user.username] = {
4194 'user': member.user,
4194 'user': member.user,
4195 'source': 'user_group',
4195 'source': 'user_group',
4196 'source_data': source_data,
4196 'source_data': source_data,
4197 'data': rule_user_group.rule_data()
4197 'data': rule_user_group.rule_data()
4198 }
4198 }
4199
4199
4200 return users
4200 return users
4201
4201
4202 def __repr__(self):
4202 def __repr__(self):
4203 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4203 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4204 self.repo_review_rule_id, self.repo)
4204 self.repo_review_rule_id, self.repo)
4205
4205
4206
4206
4207 class DbMigrateVersion(Base, BaseModel):
4207 class DbMigrateVersion(Base, BaseModel):
4208 __tablename__ = 'db_migrate_version'
4208 __tablename__ = 'db_migrate_version'
4209 __table_args__ = (
4209 __table_args__ = (
4210 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4210 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4211 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4211 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4212 )
4212 )
4213 repository_id = Column('repository_id', String(250), primary_key=True)
4213 repository_id = Column('repository_id', String(250), primary_key=True)
4214 repository_path = Column('repository_path', Text)
4214 repository_path = Column('repository_path', Text)
4215 version = Column('version', Integer)
4215 version = Column('version', Integer)
4216
4216
4217
4217
4218 class DbSession(Base, BaseModel):
4218 class DbSession(Base, BaseModel):
4219 __tablename__ = 'db_session'
4219 __tablename__ = 'db_session'
4220 __table_args__ = (
4220 __table_args__ = (
4221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4222 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4222 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4223 )
4223 )
4224
4224
4225 def __repr__(self):
4225 def __repr__(self):
4226 return '<DB:DbSession({})>'.format(self.id)
4226 return '<DB:DbSession({})>'.format(self.id)
4227
4227
4228 id = Column('id', Integer())
4228 id = Column('id', Integer())
4229 namespace = Column('namespace', String(255), primary_key=True)
4229 namespace = Column('namespace', String(255), primary_key=True)
4230 accessed = Column('accessed', DateTime, nullable=False)
4230 accessed = Column('accessed', DateTime, nullable=False)
4231 created = Column('created', DateTime, nullable=False)
4231 created = Column('created', DateTime, nullable=False)
4232 data = Column('data', PickleType, nullable=False)
4232 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now