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