##// END OF EJS Templates
commits: pre-load branch on commit cache to skip excessive calls to vcsserver
marcink -
r3886:33e11360 default
parent child Browse files
Show More
@@ -1,5194 +1,5194 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, TypeDecorator, event,
40 or_, and_, not_, func, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType)
43 Text, Float, PickleType)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers.text import collapse, remove_formatting
55 from webhelpers.text import collapse, remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.model.meta import Base, Session
70 from rhodecode.model.meta import Base, Session
71
71
72 URL_SEP = '/'
72 URL_SEP = '/'
73 log = logging.getLogger(__name__)
73 log = logging.getLogger(__name__)
74
74
75 # =============================================================================
75 # =============================================================================
76 # BASE CLASSES
76 # BASE CLASSES
77 # =============================================================================
77 # =============================================================================
78
78
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
80 # beaker.session.secret if first is not set.
80 # beaker.session.secret if first is not set.
81 # and initialized at environment.py
81 # and initialized at environment.py
82 ENCRYPTION_KEY = None
82 ENCRYPTION_KEY = None
83
83
84 # used to sort permissions by types, '#' used here is not allowed to be in
84 # used to sort permissions by types, '#' used here is not allowed to be in
85 # usernames, and it's very early in sorted string.printable table.
85 # usernames, and it's very early in sorted string.printable table.
86 PERMISSION_TYPE_SORT = {
86 PERMISSION_TYPE_SORT = {
87 'admin': '####',
87 'admin': '####',
88 'write': '###',
88 'write': '###',
89 'read': '##',
89 'read': '##',
90 'none': '#',
90 'none': '#',
91 }
91 }
92
92
93
93
94 def display_user_sort(obj):
94 def display_user_sort(obj):
95 """
95 """
96 Sort function used to sort permissions in .permissions() function of
96 Sort function used to sort permissions in .permissions() function of
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
98 of all other resources
98 of all other resources
99 """
99 """
100
100
101 if obj.username == User.DEFAULT_USER:
101 if obj.username == User.DEFAULT_USER:
102 return '#####'
102 return '#####'
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
104 return prefix + obj.username
104 return prefix + obj.username
105
105
106
106
107 def display_user_group_sort(obj):
107 def display_user_group_sort(obj):
108 """
108 """
109 Sort function used to sort permissions in .permissions() function of
109 Sort function used to sort permissions in .permissions() function of
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
111 of all other resources
111 of all other resources
112 """
112 """
113
113
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
115 return prefix + obj.users_group_name
115 return prefix + obj.users_group_name
116
116
117
117
118 def _hash_key(k):
118 def _hash_key(k):
119 return sha1_safe(k)
119 return sha1_safe(k)
120
120
121
121
122 def in_filter_generator(qry, items, limit=500):
122 def in_filter_generator(qry, items, limit=500):
123 """
123 """
124 Splits IN() into multiple with OR
124 Splits IN() into multiple with OR
125 e.g.::
125 e.g.::
126 cnt = Repository.query().filter(
126 cnt = Repository.query().filter(
127 or_(
127 or_(
128 *in_filter_generator(Repository.repo_id, range(100000))
128 *in_filter_generator(Repository.repo_id, range(100000))
129 )).count()
129 )).count()
130 """
130 """
131 if not items:
131 if not items:
132 # empty list will cause empty query which might cause security issues
132 # empty list will cause empty query which might cause security issues
133 # this can lead to hidden unpleasant results
133 # this can lead to hidden unpleasant results
134 items = [-1]
134 items = [-1]
135
135
136 parts = []
136 parts = []
137 for chunk in xrange(0, len(items), limit):
137 for chunk in xrange(0, len(items), limit):
138 parts.append(
138 parts.append(
139 qry.in_(items[chunk: chunk + limit])
139 qry.in_(items[chunk: chunk + limit])
140 )
140 )
141
141
142 return parts
142 return parts
143
143
144
144
145 base_table_args = {
145 base_table_args = {
146 'extend_existing': True,
146 'extend_existing': True,
147 'mysql_engine': 'InnoDB',
147 'mysql_engine': 'InnoDB',
148 'mysql_charset': 'utf8',
148 'mysql_charset': 'utf8',
149 'sqlite_autoincrement': True
149 'sqlite_autoincrement': True
150 }
150 }
151
151
152
152
153 class EncryptedTextValue(TypeDecorator):
153 class EncryptedTextValue(TypeDecorator):
154 """
154 """
155 Special column for encrypted long text data, use like::
155 Special column for encrypted long text data, use like::
156
156
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
158
158
159 This column is intelligent so if value is in unencrypted form it return
159 This column is intelligent so if value is in unencrypted form it return
160 unencrypted form, but on save it always encrypts
160 unencrypted form, but on save it always encrypts
161 """
161 """
162 impl = Text
162 impl = Text
163
163
164 def process_bind_param(self, value, dialect):
164 def process_bind_param(self, value, dialect):
165 """
165 """
166 Setter for storing value
166 Setter for storing value
167 """
167 """
168 import rhodecode
168 import rhodecode
169 if not value:
169 if not value:
170 return value
170 return value
171
171
172 # protect against double encrypting if values is already encrypted
172 # protect against double encrypting if values is already encrypted
173 if value.startswith('enc$aes$') \
173 if value.startswith('enc$aes$') \
174 or value.startswith('enc$aes_hmac$') \
174 or value.startswith('enc$aes_hmac$') \
175 or value.startswith('enc2$'):
175 or value.startswith('enc2$'):
176 raise ValueError('value needs to be in unencrypted format, '
176 raise ValueError('value needs to be in unencrypted format, '
177 'ie. not starting with enc$ or enc2$')
177 'ie. not starting with enc$ or enc2$')
178
178
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
180 if algo == 'aes':
180 if algo == 'aes':
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
182 elif algo == 'fernet':
182 elif algo == 'fernet':
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
184 else:
184 else:
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
186
186
187 def process_result_value(self, value, dialect):
187 def process_result_value(self, value, dialect):
188 """
188 """
189 Getter for retrieving value
189 Getter for retrieving value
190 """
190 """
191
191
192 import rhodecode
192 import rhodecode
193 if not value:
193 if not value:
194 return value
194 return value
195
195
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
198 if algo == 'aes':
198 if algo == 'aes':
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
200 elif algo == 'fernet':
200 elif algo == 'fernet':
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
202 else:
202 else:
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
204 return decrypted_data
204 return decrypted_data
205
205
206
206
207 class BaseModel(object):
207 class BaseModel(object):
208 """
208 """
209 Base Model for all classes
209 Base Model for all classes
210 """
210 """
211
211
212 @classmethod
212 @classmethod
213 def _get_keys(cls):
213 def _get_keys(cls):
214 """return column names for this model """
214 """return column names for this model """
215 return class_mapper(cls).c.keys()
215 return class_mapper(cls).c.keys()
216
216
217 def get_dict(self):
217 def get_dict(self):
218 """
218 """
219 return dict with keys and values corresponding
219 return dict with keys and values corresponding
220 to this model data """
220 to this model data """
221
221
222 d = {}
222 d = {}
223 for k in self._get_keys():
223 for k in self._get_keys():
224 d[k] = getattr(self, k)
224 d[k] = getattr(self, k)
225
225
226 # also use __json__() if present to get additional fields
226 # also use __json__() if present to get additional fields
227 _json_attr = getattr(self, '__json__', None)
227 _json_attr = getattr(self, '__json__', None)
228 if _json_attr:
228 if _json_attr:
229 # update with attributes from __json__
229 # update with attributes from __json__
230 if callable(_json_attr):
230 if callable(_json_attr):
231 _json_attr = _json_attr()
231 _json_attr = _json_attr()
232 for k, val in _json_attr.iteritems():
232 for k, val in _json_attr.iteritems():
233 d[k] = val
233 d[k] = val
234 return d
234 return d
235
235
236 def get_appstruct(self):
236 def get_appstruct(self):
237 """return list with keys and values tuples corresponding
237 """return list with keys and values tuples corresponding
238 to this model data """
238 to this model data """
239
239
240 lst = []
240 lst = []
241 for k in self._get_keys():
241 for k in self._get_keys():
242 lst.append((k, getattr(self, k),))
242 lst.append((k, getattr(self, k),))
243 return lst
243 return lst
244
244
245 def populate_obj(self, populate_dict):
245 def populate_obj(self, populate_dict):
246 """populate model with data from given populate_dict"""
246 """populate model with data from given populate_dict"""
247
247
248 for k in self._get_keys():
248 for k in self._get_keys():
249 if k in populate_dict:
249 if k in populate_dict:
250 setattr(self, k, populate_dict[k])
250 setattr(self, k, populate_dict[k])
251
251
252 @classmethod
252 @classmethod
253 def query(cls):
253 def query(cls):
254 return Session().query(cls)
254 return Session().query(cls)
255
255
256 @classmethod
256 @classmethod
257 def get(cls, id_):
257 def get(cls, id_):
258 if id_:
258 if id_:
259 return cls.query().get(id_)
259 return cls.query().get(id_)
260
260
261 @classmethod
261 @classmethod
262 def get_or_404(cls, id_):
262 def get_or_404(cls, id_):
263 from pyramid.httpexceptions import HTTPNotFound
263 from pyramid.httpexceptions import HTTPNotFound
264
264
265 try:
265 try:
266 id_ = int(id_)
266 id_ = int(id_)
267 except (TypeError, ValueError):
267 except (TypeError, ValueError):
268 raise HTTPNotFound()
268 raise HTTPNotFound()
269
269
270 res = cls.query().get(id_)
270 res = cls.query().get(id_)
271 if not res:
271 if not res:
272 raise HTTPNotFound()
272 raise HTTPNotFound()
273 return res
273 return res
274
274
275 @classmethod
275 @classmethod
276 def getAll(cls):
276 def getAll(cls):
277 # deprecated and left for backward compatibility
277 # deprecated and left for backward compatibility
278 return cls.get_all()
278 return cls.get_all()
279
279
280 @classmethod
280 @classmethod
281 def get_all(cls):
281 def get_all(cls):
282 return cls.query().all()
282 return cls.query().all()
283
283
284 @classmethod
284 @classmethod
285 def delete(cls, id_):
285 def delete(cls, id_):
286 obj = cls.query().get(id_)
286 obj = cls.query().get(id_)
287 Session().delete(obj)
287 Session().delete(obj)
288
288
289 @classmethod
289 @classmethod
290 def identity_cache(cls, session, attr_name, value):
290 def identity_cache(cls, session, attr_name, value):
291 exist_in_session = []
291 exist_in_session = []
292 for (item_cls, pkey), instance in session.identity_map.items():
292 for (item_cls, pkey), instance in session.identity_map.items():
293 if cls == item_cls and getattr(instance, attr_name) == value:
293 if cls == item_cls and getattr(instance, attr_name) == value:
294 exist_in_session.append(instance)
294 exist_in_session.append(instance)
295 if exist_in_session:
295 if exist_in_session:
296 if len(exist_in_session) == 1:
296 if len(exist_in_session) == 1:
297 return exist_in_session[0]
297 return exist_in_session[0]
298 log.exception(
298 log.exception(
299 'multiple objects with attr %s and '
299 'multiple objects with attr %s and '
300 'value %s found with same name: %r',
300 'value %s found with same name: %r',
301 attr_name, value, exist_in_session)
301 attr_name, value, exist_in_session)
302
302
303 def __repr__(self):
303 def __repr__(self):
304 if hasattr(self, '__unicode__'):
304 if hasattr(self, '__unicode__'):
305 # python repr needs to return str
305 # python repr needs to return str
306 try:
306 try:
307 return safe_str(self.__unicode__())
307 return safe_str(self.__unicode__())
308 except UnicodeDecodeError:
308 except UnicodeDecodeError:
309 pass
309 pass
310 return '<DB:%s>' % (self.__class__.__name__)
310 return '<DB:%s>' % (self.__class__.__name__)
311
311
312
312
313 class RhodeCodeSetting(Base, BaseModel):
313 class RhodeCodeSetting(Base, BaseModel):
314 __tablename__ = 'rhodecode_settings'
314 __tablename__ = 'rhodecode_settings'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('app_settings_name'),
316 UniqueConstraint('app_settings_name'),
317 base_table_args
317 base_table_args
318 )
318 )
319
319
320 SETTINGS_TYPES = {
320 SETTINGS_TYPES = {
321 'str': safe_str,
321 'str': safe_str,
322 'int': safe_int,
322 'int': safe_int,
323 'unicode': safe_unicode,
323 'unicode': safe_unicode,
324 'bool': str2bool,
324 'bool': str2bool,
325 'list': functools.partial(aslist, sep=',')
325 'list': functools.partial(aslist, sep=',')
326 }
326 }
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
328 GLOBAL_CONF_KEY = 'app_settings'
328 GLOBAL_CONF_KEY = 'app_settings'
329
329
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
334
334
335 def __init__(self, key='', val='', type='unicode'):
335 def __init__(self, key='', val='', type='unicode'):
336 self.app_settings_name = key
336 self.app_settings_name = key
337 self.app_settings_type = type
337 self.app_settings_type = type
338 self.app_settings_value = val
338 self.app_settings_value = val
339
339
340 @validates('_app_settings_value')
340 @validates('_app_settings_value')
341 def validate_settings_value(self, key, val):
341 def validate_settings_value(self, key, val):
342 assert type(val) == unicode
342 assert type(val) == unicode
343 return val
343 return val
344
344
345 @hybrid_property
345 @hybrid_property
346 def app_settings_value(self):
346 def app_settings_value(self):
347 v = self._app_settings_value
347 v = self._app_settings_value
348 _type = self.app_settings_type
348 _type = self.app_settings_type
349 if _type:
349 if _type:
350 _type = self.app_settings_type.split('.')[0]
350 _type = self.app_settings_type.split('.')[0]
351 # decode the encrypted value
351 # decode the encrypted value
352 if 'encrypted' in self.app_settings_type:
352 if 'encrypted' in self.app_settings_type:
353 cipher = EncryptedTextValue()
353 cipher = EncryptedTextValue()
354 v = safe_unicode(cipher.process_result_value(v, None))
354 v = safe_unicode(cipher.process_result_value(v, None))
355
355
356 converter = self.SETTINGS_TYPES.get(_type) or \
356 converter = self.SETTINGS_TYPES.get(_type) or \
357 self.SETTINGS_TYPES['unicode']
357 self.SETTINGS_TYPES['unicode']
358 return converter(v)
358 return converter(v)
359
359
360 @app_settings_value.setter
360 @app_settings_value.setter
361 def app_settings_value(self, val):
361 def app_settings_value(self, val):
362 """
362 """
363 Setter that will always make sure we use unicode in app_settings_value
363 Setter that will always make sure we use unicode in app_settings_value
364
364
365 :param val:
365 :param val:
366 """
366 """
367 val = safe_unicode(val)
367 val = safe_unicode(val)
368 # encode the encrypted value
368 # encode the encrypted value
369 if 'encrypted' in self.app_settings_type:
369 if 'encrypted' in self.app_settings_type:
370 cipher = EncryptedTextValue()
370 cipher = EncryptedTextValue()
371 val = safe_unicode(cipher.process_bind_param(val, None))
371 val = safe_unicode(cipher.process_bind_param(val, None))
372 self._app_settings_value = val
372 self._app_settings_value = val
373
373
374 @hybrid_property
374 @hybrid_property
375 def app_settings_type(self):
375 def app_settings_type(self):
376 return self._app_settings_type
376 return self._app_settings_type
377
377
378 @app_settings_type.setter
378 @app_settings_type.setter
379 def app_settings_type(self, val):
379 def app_settings_type(self, val):
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
381 raise Exception('type must be one of %s got %s'
381 raise Exception('type must be one of %s got %s'
382 % (self.SETTINGS_TYPES.keys(), val))
382 % (self.SETTINGS_TYPES.keys(), val))
383 self._app_settings_type = val
383 self._app_settings_type = val
384
384
385 @classmethod
385 @classmethod
386 def get_by_prefix(cls, prefix):
386 def get_by_prefix(cls, prefix):
387 return RhodeCodeSetting.query()\
387 return RhodeCodeSetting.query()\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
389 .all()
389 .all()
390
390
391 def __unicode__(self):
391 def __unicode__(self):
392 return u"<%s('%s:%s[%s]')>" % (
392 return u"<%s('%s:%s[%s]')>" % (
393 self.__class__.__name__,
393 self.__class__.__name__,
394 self.app_settings_name, self.app_settings_value,
394 self.app_settings_name, self.app_settings_value,
395 self.app_settings_type
395 self.app_settings_type
396 )
396 )
397
397
398
398
399 class RhodeCodeUi(Base, BaseModel):
399 class RhodeCodeUi(Base, BaseModel):
400 __tablename__ = 'rhodecode_ui'
400 __tablename__ = 'rhodecode_ui'
401 __table_args__ = (
401 __table_args__ = (
402 UniqueConstraint('ui_key'),
402 UniqueConstraint('ui_key'),
403 base_table_args
403 base_table_args
404 )
404 )
405
405
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
407 # HG
407 # HG
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
409 HOOK_PULL = 'outgoing.pull_logger'
409 HOOK_PULL = 'outgoing.pull_logger'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
412 HOOK_PUSH = 'changegroup.push_logger'
412 HOOK_PUSH = 'changegroup.push_logger'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
414
414
415 HOOKS_BUILTIN = [
415 HOOKS_BUILTIN = [
416 HOOK_PRE_PULL,
416 HOOK_PRE_PULL,
417 HOOK_PULL,
417 HOOK_PULL,
418 HOOK_PRE_PUSH,
418 HOOK_PRE_PUSH,
419 HOOK_PRETX_PUSH,
419 HOOK_PRETX_PUSH,
420 HOOK_PUSH,
420 HOOK_PUSH,
421 HOOK_PUSH_KEY,
421 HOOK_PUSH_KEY,
422 ]
422 ]
423
423
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
425 # git part is currently hardcoded.
425 # git part is currently hardcoded.
426
426
427 # SVN PATTERNS
427 # SVN PATTERNS
428 SVN_BRANCH_ID = 'vcs_svn_branch'
428 SVN_BRANCH_ID = 'vcs_svn_branch'
429 SVN_TAG_ID = 'vcs_svn_tag'
429 SVN_TAG_ID = 'vcs_svn_tag'
430
430
431 ui_id = Column(
431 ui_id = Column(
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
433 primary_key=True)
433 primary_key=True)
434 ui_section = Column(
434 ui_section = Column(
435 "ui_section", String(255), nullable=True, unique=None, default=None)
435 "ui_section", String(255), nullable=True, unique=None, default=None)
436 ui_key = Column(
436 ui_key = Column(
437 "ui_key", String(255), nullable=True, unique=None, default=None)
437 "ui_key", String(255), nullable=True, unique=None, default=None)
438 ui_value = Column(
438 ui_value = Column(
439 "ui_value", String(255), nullable=True, unique=None, default=None)
439 "ui_value", String(255), nullable=True, unique=None, default=None)
440 ui_active = Column(
440 ui_active = Column(
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
442
442
443 def __repr__(self):
443 def __repr__(self):
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
445 self.ui_key, self.ui_value)
445 self.ui_key, self.ui_value)
446
446
447
447
448 class RepoRhodeCodeSetting(Base, BaseModel):
448 class RepoRhodeCodeSetting(Base, BaseModel):
449 __tablename__ = 'repo_rhodecode_settings'
449 __tablename__ = 'repo_rhodecode_settings'
450 __table_args__ = (
450 __table_args__ = (
451 UniqueConstraint(
451 UniqueConstraint(
452 'app_settings_name', 'repository_id',
452 'app_settings_name', 'repository_id',
453 name='uq_repo_rhodecode_setting_name_repo_id'),
453 name='uq_repo_rhodecode_setting_name_repo_id'),
454 base_table_args
454 base_table_args
455 )
455 )
456
456
457 repository_id = Column(
457 repository_id = Column(
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
459 nullable=False)
459 nullable=False)
460 app_settings_id = Column(
460 app_settings_id = Column(
461 "app_settings_id", Integer(), nullable=False, unique=True,
461 "app_settings_id", Integer(), nullable=False, unique=True,
462 default=None, primary_key=True)
462 default=None, primary_key=True)
463 app_settings_name = Column(
463 app_settings_name = Column(
464 "app_settings_name", String(255), nullable=True, unique=None,
464 "app_settings_name", String(255), nullable=True, unique=None,
465 default=None)
465 default=None)
466 _app_settings_value = Column(
466 _app_settings_value = Column(
467 "app_settings_value", String(4096), nullable=True, unique=None,
467 "app_settings_value", String(4096), nullable=True, unique=None,
468 default=None)
468 default=None)
469 _app_settings_type = Column(
469 _app_settings_type = Column(
470 "app_settings_type", String(255), nullable=True, unique=None,
470 "app_settings_type", String(255), nullable=True, unique=None,
471 default=None)
471 default=None)
472
472
473 repository = relationship('Repository')
473 repository = relationship('Repository')
474
474
475 def __init__(self, repository_id, key='', val='', type='unicode'):
475 def __init__(self, repository_id, key='', val='', type='unicode'):
476 self.repository_id = repository_id
476 self.repository_id = repository_id
477 self.app_settings_name = key
477 self.app_settings_name = key
478 self.app_settings_type = type
478 self.app_settings_type = type
479 self.app_settings_value = val
479 self.app_settings_value = val
480
480
481 @validates('_app_settings_value')
481 @validates('_app_settings_value')
482 def validate_settings_value(self, key, val):
482 def validate_settings_value(self, key, val):
483 assert type(val) == unicode
483 assert type(val) == unicode
484 return val
484 return val
485
485
486 @hybrid_property
486 @hybrid_property
487 def app_settings_value(self):
487 def app_settings_value(self):
488 v = self._app_settings_value
488 v = self._app_settings_value
489 type_ = self.app_settings_type
489 type_ = self.app_settings_type
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
492 return converter(v)
492 return converter(v)
493
493
494 @app_settings_value.setter
494 @app_settings_value.setter
495 def app_settings_value(self, val):
495 def app_settings_value(self, val):
496 """
496 """
497 Setter that will always make sure we use unicode in app_settings_value
497 Setter that will always make sure we use unicode in app_settings_value
498
498
499 :param val:
499 :param val:
500 """
500 """
501 self._app_settings_value = safe_unicode(val)
501 self._app_settings_value = safe_unicode(val)
502
502
503 @hybrid_property
503 @hybrid_property
504 def app_settings_type(self):
504 def app_settings_type(self):
505 return self._app_settings_type
505 return self._app_settings_type
506
506
507 @app_settings_type.setter
507 @app_settings_type.setter
508 def app_settings_type(self, val):
508 def app_settings_type(self, val):
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
510 if val not in SETTINGS_TYPES:
510 if val not in SETTINGS_TYPES:
511 raise Exception('type must be one of %s got %s'
511 raise Exception('type must be one of %s got %s'
512 % (SETTINGS_TYPES.keys(), val))
512 % (SETTINGS_TYPES.keys(), val))
513 self._app_settings_type = val
513 self._app_settings_type = val
514
514
515 def __unicode__(self):
515 def __unicode__(self):
516 return u"<%s('%s:%s:%s[%s]')>" % (
516 return u"<%s('%s:%s:%s[%s]')>" % (
517 self.__class__.__name__, self.repository.repo_name,
517 self.__class__.__name__, self.repository.repo_name,
518 self.app_settings_name, self.app_settings_value,
518 self.app_settings_name, self.app_settings_value,
519 self.app_settings_type
519 self.app_settings_type
520 )
520 )
521
521
522
522
523 class RepoRhodeCodeUi(Base, BaseModel):
523 class RepoRhodeCodeUi(Base, BaseModel):
524 __tablename__ = 'repo_rhodecode_ui'
524 __tablename__ = 'repo_rhodecode_ui'
525 __table_args__ = (
525 __table_args__ = (
526 UniqueConstraint(
526 UniqueConstraint(
527 'repository_id', 'ui_section', 'ui_key',
527 'repository_id', 'ui_section', 'ui_key',
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
529 base_table_args
529 base_table_args
530 )
530 )
531
531
532 repository_id = Column(
532 repository_id = Column(
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
534 nullable=False)
534 nullable=False)
535 ui_id = Column(
535 ui_id = Column(
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
537 primary_key=True)
537 primary_key=True)
538 ui_section = Column(
538 ui_section = Column(
539 "ui_section", String(255), nullable=True, unique=None, default=None)
539 "ui_section", String(255), nullable=True, unique=None, default=None)
540 ui_key = Column(
540 ui_key = Column(
541 "ui_key", String(255), nullable=True, unique=None, default=None)
541 "ui_key", String(255), nullable=True, unique=None, default=None)
542 ui_value = Column(
542 ui_value = Column(
543 "ui_value", String(255), nullable=True, unique=None, default=None)
543 "ui_value", String(255), nullable=True, unique=None, default=None)
544 ui_active = Column(
544 ui_active = Column(
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
546
546
547 repository = relationship('Repository')
547 repository = relationship('Repository')
548
548
549 def __repr__(self):
549 def __repr__(self):
550 return '<%s[%s:%s]%s=>%s]>' % (
550 return '<%s[%s:%s]%s=>%s]>' % (
551 self.__class__.__name__, self.repository.repo_name,
551 self.__class__.__name__, self.repository.repo_name,
552 self.ui_section, self.ui_key, self.ui_value)
552 self.ui_section, self.ui_key, self.ui_value)
553
553
554
554
555 class User(Base, BaseModel):
555 class User(Base, BaseModel):
556 __tablename__ = 'users'
556 __tablename__ = 'users'
557 __table_args__ = (
557 __table_args__ = (
558 UniqueConstraint('username'), UniqueConstraint('email'),
558 UniqueConstraint('username'), UniqueConstraint('email'),
559 Index('u_username_idx', 'username'),
559 Index('u_username_idx', 'username'),
560 Index('u_email_idx', 'email'),
560 Index('u_email_idx', 'email'),
561 base_table_args
561 base_table_args
562 )
562 )
563
563
564 DEFAULT_USER = 'default'
564 DEFAULT_USER = 'default'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
567
567
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
578
578
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
585
585
586 user_log = relationship('UserLog')
586 user_log = relationship('UserLog')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
588
588
589 repositories = relationship('Repository')
589 repositories = relationship('Repository')
590 repository_groups = relationship('RepoGroup')
590 repository_groups = relationship('RepoGroup')
591 user_groups = relationship('UserGroup')
591 user_groups = relationship('UserGroup')
592
592
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
595
595
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
599
599
600 group_member = relationship('UserGroupMember', cascade='all')
600 group_member = relationship('UserGroupMember', cascade='all')
601
601
602 notifications = relationship('UserNotification', cascade='all')
602 notifications = relationship('UserNotification', cascade='all')
603 # notifications assigned to this user
603 # notifications assigned to this user
604 user_created_notifications = relationship('Notification', cascade='all')
604 user_created_notifications = relationship('Notification', cascade='all')
605 # comments created by this user
605 # comments created by this user
606 user_comments = relationship('ChangesetComment', cascade='all')
606 user_comments = relationship('ChangesetComment', cascade='all')
607 # user profile extra info
607 # user profile extra info
608 user_emails = relationship('UserEmailMap', cascade='all')
608 user_emails = relationship('UserEmailMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
612
612
613 # gists
613 # gists
614 user_gists = relationship('Gist', cascade='all')
614 user_gists = relationship('Gist', cascade='all')
615 # user pull requests
615 # user pull requests
616 user_pull_requests = relationship('PullRequest', cascade='all')
616 user_pull_requests = relationship('PullRequest', cascade='all')
617 # external identities
617 # external identities
618 extenal_identities = relationship(
618 extenal_identities = relationship(
619 'ExternalIdentity',
619 'ExternalIdentity',
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
621 cascade='all')
621 cascade='all')
622 # review rules
622 # review rules
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
624
624
625 def __unicode__(self):
625 def __unicode__(self):
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
627 self.user_id, self.username)
627 self.user_id, self.username)
628
628
629 @hybrid_property
629 @hybrid_property
630 def email(self):
630 def email(self):
631 return self._email
631 return self._email
632
632
633 @email.setter
633 @email.setter
634 def email(self, val):
634 def email(self, val):
635 self._email = val.lower() if val else None
635 self._email = val.lower() if val else None
636
636
637 @hybrid_property
637 @hybrid_property
638 def first_name(self):
638 def first_name(self):
639 from rhodecode.lib import helpers as h
639 from rhodecode.lib import helpers as h
640 if self.name:
640 if self.name:
641 return h.escape(self.name)
641 return h.escape(self.name)
642 return self.name
642 return self.name
643
643
644 @hybrid_property
644 @hybrid_property
645 def last_name(self):
645 def last_name(self):
646 from rhodecode.lib import helpers as h
646 from rhodecode.lib import helpers as h
647 if self.lastname:
647 if self.lastname:
648 return h.escape(self.lastname)
648 return h.escape(self.lastname)
649 return self.lastname
649 return self.lastname
650
650
651 @hybrid_property
651 @hybrid_property
652 def api_key(self):
652 def api_key(self):
653 """
653 """
654 Fetch if exist an auth-token with role ALL connected to this user
654 Fetch if exist an auth-token with role ALL connected to this user
655 """
655 """
656 user_auth_token = UserApiKeys.query()\
656 user_auth_token = UserApiKeys.query()\
657 .filter(UserApiKeys.user_id == self.user_id)\
657 .filter(UserApiKeys.user_id == self.user_id)\
658 .filter(or_(UserApiKeys.expires == -1,
658 .filter(or_(UserApiKeys.expires == -1,
659 UserApiKeys.expires >= time.time()))\
659 UserApiKeys.expires >= time.time()))\
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
661 if user_auth_token:
661 if user_auth_token:
662 user_auth_token = user_auth_token.api_key
662 user_auth_token = user_auth_token.api_key
663
663
664 return user_auth_token
664 return user_auth_token
665
665
666 @api_key.setter
666 @api_key.setter
667 def api_key(self, val):
667 def api_key(self, val):
668 # don't allow to set API key this is deprecated for now
668 # don't allow to set API key this is deprecated for now
669 self._api_key = None
669 self._api_key = None
670
670
671 @property
671 @property
672 def reviewer_pull_requests(self):
672 def reviewer_pull_requests(self):
673 return PullRequestReviewers.query() \
673 return PullRequestReviewers.query() \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
676 .all()
676 .all()
677
677
678 @property
678 @property
679 def firstname(self):
679 def firstname(self):
680 # alias for future
680 # alias for future
681 return self.name
681 return self.name
682
682
683 @property
683 @property
684 def emails(self):
684 def emails(self):
685 other = UserEmailMap.query()\
685 other = UserEmailMap.query()\
686 .filter(UserEmailMap.user == self) \
686 .filter(UserEmailMap.user == self) \
687 .order_by(UserEmailMap.email_id.asc()) \
687 .order_by(UserEmailMap.email_id.asc()) \
688 .all()
688 .all()
689 return [self.email] + [x.email for x in other]
689 return [self.email] + [x.email for x in other]
690
690
691 @property
691 @property
692 def auth_tokens(self):
692 def auth_tokens(self):
693 auth_tokens = self.get_auth_tokens()
693 auth_tokens = self.get_auth_tokens()
694 return [x.api_key for x in auth_tokens]
694 return [x.api_key for x in auth_tokens]
695
695
696 def get_auth_tokens(self):
696 def get_auth_tokens(self):
697 return UserApiKeys.query()\
697 return UserApiKeys.query()\
698 .filter(UserApiKeys.user == self)\
698 .filter(UserApiKeys.user == self)\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
700 .all()
700 .all()
701
701
702 @LazyProperty
702 @LazyProperty
703 def feed_token(self):
703 def feed_token(self):
704 return self.get_feed_token()
704 return self.get_feed_token()
705
705
706 def get_feed_token(self, cache=True):
706 def get_feed_token(self, cache=True):
707 feed_tokens = UserApiKeys.query()\
707 feed_tokens = UserApiKeys.query()\
708 .filter(UserApiKeys.user == self)\
708 .filter(UserApiKeys.user == self)\
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
710 if cache:
710 if cache:
711 feed_tokens = feed_tokens.options(
711 feed_tokens = feed_tokens.options(
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
713
713
714 feed_tokens = feed_tokens.all()
714 feed_tokens = feed_tokens.all()
715 if feed_tokens:
715 if feed_tokens:
716 return feed_tokens[0].api_key
716 return feed_tokens[0].api_key
717 return 'NO_FEED_TOKEN_AVAILABLE'
717 return 'NO_FEED_TOKEN_AVAILABLE'
718
718
719 @classmethod
719 @classmethod
720 def get(cls, user_id, cache=False):
720 def get(cls, user_id, cache=False):
721 if not user_id:
721 if not user_id:
722 return
722 return
723
723
724 user = cls.query()
724 user = cls.query()
725 if cache:
725 if cache:
726 user = user.options(
726 user = user.options(
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
728 return user.get(user_id)
728 return user.get(user_id)
729
729
730 @classmethod
730 @classmethod
731 def extra_valid_auth_tokens(cls, user, role=None):
731 def extra_valid_auth_tokens(cls, user, role=None):
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
733 .filter(or_(UserApiKeys.expires == -1,
733 .filter(or_(UserApiKeys.expires == -1,
734 UserApiKeys.expires >= time.time()))
734 UserApiKeys.expires >= time.time()))
735 if role:
735 if role:
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
738 return tokens.all()
738 return tokens.all()
739
739
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
741 from rhodecode.lib import auth
741 from rhodecode.lib import auth
742
742
743 log.debug('Trying to authenticate user: %s via auth-token, '
743 log.debug('Trying to authenticate user: %s via auth-token, '
744 'and roles: %s', self, roles)
744 'and roles: %s', self, roles)
745
745
746 if not auth_token:
746 if not auth_token:
747 return False
747 return False
748
748
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
750 tokens_q = UserApiKeys.query()\
750 tokens_q = UserApiKeys.query()\
751 .filter(UserApiKeys.user_id == self.user_id)\
751 .filter(UserApiKeys.user_id == self.user_id)\
752 .filter(or_(UserApiKeys.expires == -1,
752 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))
753 UserApiKeys.expires >= time.time()))
754
754
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
756
756
757 crypto_backend = auth.crypto_backend()
757 crypto_backend = auth.crypto_backend()
758 enc_token_map = {}
758 enc_token_map = {}
759 plain_token_map = {}
759 plain_token_map = {}
760 for token in tokens_q:
760 for token in tokens_q:
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
762 enc_token_map[token.api_key] = token
762 enc_token_map[token.api_key] = token
763 else:
763 else:
764 plain_token_map[token.api_key] = token
764 plain_token_map[token.api_key] = token
765 log.debug(
765 log.debug(
766 'Found %s plain and %s encrypted user tokens to check for authentication',
766 'Found %s plain and %s encrypted user tokens to check for authentication',
767 len(plain_token_map), len(enc_token_map))
767 len(plain_token_map), len(enc_token_map))
768
768
769 # plain token match comes first
769 # plain token match comes first
770 match = plain_token_map.get(auth_token)
770 match = plain_token_map.get(auth_token)
771
771
772 # check encrypted tokens now
772 # check encrypted tokens now
773 if not match:
773 if not match:
774 for token_hash, token in enc_token_map.items():
774 for token_hash, token in enc_token_map.items():
775 # NOTE(marcink): this is expensive to calculate, but most secure
775 # NOTE(marcink): this is expensive to calculate, but most secure
776 if crypto_backend.hash_check(auth_token, token_hash):
776 if crypto_backend.hash_check(auth_token, token_hash):
777 match = token
777 match = token
778 break
778 break
779
779
780 if match:
780 if match:
781 log.debug('Found matching token %s', match)
781 log.debug('Found matching token %s', match)
782 if match.repo_id:
782 if match.repo_id:
783 log.debug('Found scope, checking for scope match of token %s', match)
783 log.debug('Found scope, checking for scope match of token %s', match)
784 if match.repo_id == scope_repo_id:
784 if match.repo_id == scope_repo_id:
785 return True
785 return True
786 else:
786 else:
787 log.debug(
787 log.debug(
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
789 'and calling scope is:%s, skipping further checks',
789 'and calling scope is:%s, skipping further checks',
790 match.repo, scope_repo_id)
790 match.repo, scope_repo_id)
791 return False
791 return False
792 else:
792 else:
793 return True
793 return True
794
794
795 return False
795 return False
796
796
797 @property
797 @property
798 def ip_addresses(self):
798 def ip_addresses(self):
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
800 return [x.ip_addr for x in ret]
800 return [x.ip_addr for x in ret]
801
801
802 @property
802 @property
803 def username_and_name(self):
803 def username_and_name(self):
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
805
805
806 @property
806 @property
807 def username_or_name_or_email(self):
807 def username_or_name_or_email(self):
808 full_name = self.full_name if self.full_name is not ' ' else None
808 full_name = self.full_name if self.full_name is not ' ' else None
809 return self.username or full_name or self.email
809 return self.username or full_name or self.email
810
810
811 @property
811 @property
812 def full_name(self):
812 def full_name(self):
813 return '%s %s' % (self.first_name, self.last_name)
813 return '%s %s' % (self.first_name, self.last_name)
814
814
815 @property
815 @property
816 def full_name_or_username(self):
816 def full_name_or_username(self):
817 return ('%s %s' % (self.first_name, self.last_name)
817 return ('%s %s' % (self.first_name, self.last_name)
818 if (self.first_name and self.last_name) else self.username)
818 if (self.first_name and self.last_name) else self.username)
819
819
820 @property
820 @property
821 def full_contact(self):
821 def full_contact(self):
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
823
823
824 @property
824 @property
825 def short_contact(self):
825 def short_contact(self):
826 return '%s %s' % (self.first_name, self.last_name)
826 return '%s %s' % (self.first_name, self.last_name)
827
827
828 @property
828 @property
829 def is_admin(self):
829 def is_admin(self):
830 return self.admin
830 return self.admin
831
831
832 def AuthUser(self, **kwargs):
832 def AuthUser(self, **kwargs):
833 """
833 """
834 Returns instance of AuthUser for this user
834 Returns instance of AuthUser for this user
835 """
835 """
836 from rhodecode.lib.auth import AuthUser
836 from rhodecode.lib.auth import AuthUser
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
838
838
839 @hybrid_property
839 @hybrid_property
840 def user_data(self):
840 def user_data(self):
841 if not self._user_data:
841 if not self._user_data:
842 return {}
842 return {}
843
843
844 try:
844 try:
845 return json.loads(self._user_data)
845 return json.loads(self._user_data)
846 except TypeError:
846 except TypeError:
847 return {}
847 return {}
848
848
849 @user_data.setter
849 @user_data.setter
850 def user_data(self, val):
850 def user_data(self, val):
851 if not isinstance(val, dict):
851 if not isinstance(val, dict):
852 raise Exception('user_data must be dict, got %s' % type(val))
852 raise Exception('user_data must be dict, got %s' % type(val))
853 try:
853 try:
854 self._user_data = json.dumps(val)
854 self._user_data = json.dumps(val)
855 except Exception:
855 except Exception:
856 log.error(traceback.format_exc())
856 log.error(traceback.format_exc())
857
857
858 @classmethod
858 @classmethod
859 def get_by_username(cls, username, case_insensitive=False,
859 def get_by_username(cls, username, case_insensitive=False,
860 cache=False, identity_cache=False):
860 cache=False, identity_cache=False):
861 session = Session()
861 session = Session()
862
862
863 if case_insensitive:
863 if case_insensitive:
864 q = cls.query().filter(
864 q = cls.query().filter(
865 func.lower(cls.username) == func.lower(username))
865 func.lower(cls.username) == func.lower(username))
866 else:
866 else:
867 q = cls.query().filter(cls.username == username)
867 q = cls.query().filter(cls.username == username)
868
868
869 if cache:
869 if cache:
870 if identity_cache:
870 if identity_cache:
871 val = cls.identity_cache(session, 'username', username)
871 val = cls.identity_cache(session, 'username', username)
872 if val:
872 if val:
873 return val
873 return val
874 else:
874 else:
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
876 q = q.options(
876 q = q.options(
877 FromCache("sql_cache_short", cache_key))
877 FromCache("sql_cache_short", cache_key))
878
878
879 return q.scalar()
879 return q.scalar()
880
880
881 @classmethod
881 @classmethod
882 def get_by_auth_token(cls, auth_token, cache=False):
882 def get_by_auth_token(cls, auth_token, cache=False):
883 q = UserApiKeys.query()\
883 q = UserApiKeys.query()\
884 .filter(UserApiKeys.api_key == auth_token)\
884 .filter(UserApiKeys.api_key == auth_token)\
885 .filter(or_(UserApiKeys.expires == -1,
885 .filter(or_(UserApiKeys.expires == -1,
886 UserApiKeys.expires >= time.time()))
886 UserApiKeys.expires >= time.time()))
887 if cache:
887 if cache:
888 q = q.options(
888 q = q.options(
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
890
890
891 match = q.first()
891 match = q.first()
892 if match:
892 if match:
893 return match.user
893 return match.user
894
894
895 @classmethod
895 @classmethod
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
897
897
898 if case_insensitive:
898 if case_insensitive:
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
900
900
901 else:
901 else:
902 q = cls.query().filter(cls.email == email)
902 q = cls.query().filter(cls.email == email)
903
903
904 email_key = _hash_key(email)
904 email_key = _hash_key(email)
905 if cache:
905 if cache:
906 q = q.options(
906 q = q.options(
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
908
908
909 ret = q.scalar()
909 ret = q.scalar()
910 if ret is None:
910 if ret is None:
911 q = UserEmailMap.query()
911 q = UserEmailMap.query()
912 # try fetching in alternate email map
912 # try fetching in alternate email map
913 if case_insensitive:
913 if case_insensitive:
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
915 else:
915 else:
916 q = q.filter(UserEmailMap.email == email)
916 q = q.filter(UserEmailMap.email == email)
917 q = q.options(joinedload(UserEmailMap.user))
917 q = q.options(joinedload(UserEmailMap.user))
918 if cache:
918 if cache:
919 q = q.options(
919 q = q.options(
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
921 ret = getattr(q.scalar(), 'user', None)
921 ret = getattr(q.scalar(), 'user', None)
922
922
923 return ret
923 return ret
924
924
925 @classmethod
925 @classmethod
926 def get_from_cs_author(cls, author):
926 def get_from_cs_author(cls, author):
927 """
927 """
928 Tries to get User objects out of commit author string
928 Tries to get User objects out of commit author string
929
929
930 :param author:
930 :param author:
931 """
931 """
932 from rhodecode.lib.helpers import email, author_name
932 from rhodecode.lib.helpers import email, author_name
933 # Valid email in the attribute passed, see if they're in the system
933 # Valid email in the attribute passed, see if they're in the system
934 _email = email(author)
934 _email = email(author)
935 if _email:
935 if _email:
936 user = cls.get_by_email(_email, case_insensitive=True)
936 user = cls.get_by_email(_email, case_insensitive=True)
937 if user:
937 if user:
938 return user
938 return user
939 # Maybe we can match by username?
939 # Maybe we can match by username?
940 _author = author_name(author)
940 _author = author_name(author)
941 user = cls.get_by_username(_author, case_insensitive=True)
941 user = cls.get_by_username(_author, case_insensitive=True)
942 if user:
942 if user:
943 return user
943 return user
944
944
945 def update_userdata(self, **kwargs):
945 def update_userdata(self, **kwargs):
946 usr = self
946 usr = self
947 old = usr.user_data
947 old = usr.user_data
948 old.update(**kwargs)
948 old.update(**kwargs)
949 usr.user_data = old
949 usr.user_data = old
950 Session().add(usr)
950 Session().add(usr)
951 log.debug('updated userdata with ', kwargs)
951 log.debug('updated userdata with ', kwargs)
952
952
953 def update_lastlogin(self):
953 def update_lastlogin(self):
954 """Update user lastlogin"""
954 """Update user lastlogin"""
955 self.last_login = datetime.datetime.now()
955 self.last_login = datetime.datetime.now()
956 Session().add(self)
956 Session().add(self)
957 log.debug('updated user %s lastlogin', self.username)
957 log.debug('updated user %s lastlogin', self.username)
958
958
959 def update_password(self, new_password):
959 def update_password(self, new_password):
960 from rhodecode.lib.auth import get_crypt_password
960 from rhodecode.lib.auth import get_crypt_password
961
961
962 self.password = get_crypt_password(new_password)
962 self.password = get_crypt_password(new_password)
963 Session().add(self)
963 Session().add(self)
964
964
965 @classmethod
965 @classmethod
966 def get_first_super_admin(cls):
966 def get_first_super_admin(cls):
967 user = User.query()\
967 user = User.query()\
968 .filter(User.admin == true()) \
968 .filter(User.admin == true()) \
969 .order_by(User.user_id.asc()) \
969 .order_by(User.user_id.asc()) \
970 .first()
970 .first()
971
971
972 if user is None:
972 if user is None:
973 raise Exception('FATAL: Missing administrative account!')
973 raise Exception('FATAL: Missing administrative account!')
974 return user
974 return user
975
975
976 @classmethod
976 @classmethod
977 def get_all_super_admins(cls, only_active=False):
977 def get_all_super_admins(cls, only_active=False):
978 """
978 """
979 Returns all admin accounts sorted by username
979 Returns all admin accounts sorted by username
980 """
980 """
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
982 if only_active:
982 if only_active:
983 qry = qry.filter(User.active == true())
983 qry = qry.filter(User.active == true())
984 return qry.all()
984 return qry.all()
985
985
986 @classmethod
986 @classmethod
987 def get_default_user(cls, cache=False, refresh=False):
987 def get_default_user(cls, cache=False, refresh=False):
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
989 if user is None:
989 if user is None:
990 raise Exception('FATAL: Missing default account!')
990 raise Exception('FATAL: Missing default account!')
991 if refresh:
991 if refresh:
992 # The default user might be based on outdated state which
992 # The default user might be based on outdated state which
993 # has been loaded from the cache.
993 # has been loaded from the cache.
994 # A call to refresh() ensures that the
994 # A call to refresh() ensures that the
995 # latest state from the database is used.
995 # latest state from the database is used.
996 Session().refresh(user)
996 Session().refresh(user)
997 return user
997 return user
998
998
999 def _get_default_perms(self, user, suffix=''):
999 def _get_default_perms(self, user, suffix=''):
1000 from rhodecode.model.permission import PermissionModel
1000 from rhodecode.model.permission import PermissionModel
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1002
1002
1003 def get_default_perms(self, suffix=''):
1003 def get_default_perms(self, suffix=''):
1004 return self._get_default_perms(self, suffix)
1004 return self._get_default_perms(self, suffix)
1005
1005
1006 def get_api_data(self, include_secrets=False, details='full'):
1006 def get_api_data(self, include_secrets=False, details='full'):
1007 """
1007 """
1008 Common function for generating user related data for API
1008 Common function for generating user related data for API
1009
1009
1010 :param include_secrets: By default secrets in the API data will be replaced
1010 :param include_secrets: By default secrets in the API data will be replaced
1011 by a placeholder value to prevent exposing this data by accident. In case
1011 by a placeholder value to prevent exposing this data by accident. In case
1012 this data shall be exposed, set this flag to ``True``.
1012 this data shall be exposed, set this flag to ``True``.
1013
1013
1014 :param details: details can be 'basic|full' basic gives only a subset of
1014 :param details: details can be 'basic|full' basic gives only a subset of
1015 the available user information that includes user_id, name and emails.
1015 the available user information that includes user_id, name and emails.
1016 """
1016 """
1017 user = self
1017 user = self
1018 user_data = self.user_data
1018 user_data = self.user_data
1019 data = {
1019 data = {
1020 'user_id': user.user_id,
1020 'user_id': user.user_id,
1021 'username': user.username,
1021 'username': user.username,
1022 'firstname': user.name,
1022 'firstname': user.name,
1023 'lastname': user.lastname,
1023 'lastname': user.lastname,
1024 'email': user.email,
1024 'email': user.email,
1025 'emails': user.emails,
1025 'emails': user.emails,
1026 }
1026 }
1027 if details == 'basic':
1027 if details == 'basic':
1028 return data
1028 return data
1029
1029
1030 auth_token_length = 40
1030 auth_token_length = 40
1031 auth_token_replacement = '*' * auth_token_length
1031 auth_token_replacement = '*' * auth_token_length
1032
1032
1033 extras = {
1033 extras = {
1034 'auth_tokens': [auth_token_replacement],
1034 'auth_tokens': [auth_token_replacement],
1035 'active': user.active,
1035 'active': user.active,
1036 'admin': user.admin,
1036 'admin': user.admin,
1037 'extern_type': user.extern_type,
1037 'extern_type': user.extern_type,
1038 'extern_name': user.extern_name,
1038 'extern_name': user.extern_name,
1039 'last_login': user.last_login,
1039 'last_login': user.last_login,
1040 'last_activity': user.last_activity,
1040 'last_activity': user.last_activity,
1041 'ip_addresses': user.ip_addresses,
1041 'ip_addresses': user.ip_addresses,
1042 'language': user_data.get('language')
1042 'language': user_data.get('language')
1043 }
1043 }
1044 data.update(extras)
1044 data.update(extras)
1045
1045
1046 if include_secrets:
1046 if include_secrets:
1047 data['auth_tokens'] = user.auth_tokens
1047 data['auth_tokens'] = user.auth_tokens
1048 return data
1048 return data
1049
1049
1050 def __json__(self):
1050 def __json__(self):
1051 data = {
1051 data = {
1052 'full_name': self.full_name,
1052 'full_name': self.full_name,
1053 'full_name_or_username': self.full_name_or_username,
1053 'full_name_or_username': self.full_name_or_username,
1054 'short_contact': self.short_contact,
1054 'short_contact': self.short_contact,
1055 'full_contact': self.full_contact,
1055 'full_contact': self.full_contact,
1056 }
1056 }
1057 data.update(self.get_api_data())
1057 data.update(self.get_api_data())
1058 return data
1058 return data
1059
1059
1060
1060
1061 class UserApiKeys(Base, BaseModel):
1061 class UserApiKeys(Base, BaseModel):
1062 __tablename__ = 'user_api_keys'
1062 __tablename__ = 'user_api_keys'
1063 __table_args__ = (
1063 __table_args__ = (
1064 Index('uak_api_key_idx', 'api_key', unique=True),
1064 Index('uak_api_key_idx', 'api_key', unique=True),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1066 base_table_args
1066 base_table_args
1067 )
1067 )
1068 __mapper_args__ = {}
1068 __mapper_args__ = {}
1069
1069
1070 # ApiKey role
1070 # ApiKey role
1071 ROLE_ALL = 'token_role_all'
1071 ROLE_ALL = 'token_role_all'
1072 ROLE_HTTP = 'token_role_http'
1072 ROLE_HTTP = 'token_role_http'
1073 ROLE_VCS = 'token_role_vcs'
1073 ROLE_VCS = 'token_role_vcs'
1074 ROLE_API = 'token_role_api'
1074 ROLE_API = 'token_role_api'
1075 ROLE_FEED = 'token_role_feed'
1075 ROLE_FEED = 'token_role_feed'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1077
1077
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1079
1079
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1084 expires = Column('expires', Float(53), nullable=False)
1084 expires = Column('expires', Float(53), nullable=False)
1085 role = Column('role', String(255), nullable=True)
1085 role = Column('role', String(255), nullable=True)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1087
1087
1088 # scope columns
1088 # scope columns
1089 repo_id = Column(
1089 repo_id = Column(
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1091 nullable=True, unique=None, default=None)
1091 nullable=True, unique=None, default=None)
1092 repo = relationship('Repository', lazy='joined')
1092 repo = relationship('Repository', lazy='joined')
1093
1093
1094 repo_group_id = Column(
1094 repo_group_id = Column(
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1096 nullable=True, unique=None, default=None)
1096 nullable=True, unique=None, default=None)
1097 repo_group = relationship('RepoGroup', lazy='joined')
1097 repo_group = relationship('RepoGroup', lazy='joined')
1098
1098
1099 user = relationship('User', lazy='joined')
1099 user = relationship('User', lazy='joined')
1100
1100
1101 def __unicode__(self):
1101 def __unicode__(self):
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1103
1103
1104 def __json__(self):
1104 def __json__(self):
1105 data = {
1105 data = {
1106 'auth_token': self.api_key,
1106 'auth_token': self.api_key,
1107 'role': self.role,
1107 'role': self.role,
1108 'scope': self.scope_humanized,
1108 'scope': self.scope_humanized,
1109 'expired': self.expired
1109 'expired': self.expired
1110 }
1110 }
1111 return data
1111 return data
1112
1112
1113 def get_api_data(self, include_secrets=False):
1113 def get_api_data(self, include_secrets=False):
1114 data = self.__json__()
1114 data = self.__json__()
1115 if include_secrets:
1115 if include_secrets:
1116 return data
1116 return data
1117 else:
1117 else:
1118 data['auth_token'] = self.token_obfuscated
1118 data['auth_token'] = self.token_obfuscated
1119 return data
1119 return data
1120
1120
1121 @hybrid_property
1121 @hybrid_property
1122 def description_safe(self):
1122 def description_safe(self):
1123 from rhodecode.lib import helpers as h
1123 from rhodecode.lib import helpers as h
1124 return h.escape(self.description)
1124 return h.escape(self.description)
1125
1125
1126 @property
1126 @property
1127 def expired(self):
1127 def expired(self):
1128 if self.expires == -1:
1128 if self.expires == -1:
1129 return False
1129 return False
1130 return time.time() > self.expires
1130 return time.time() > self.expires
1131
1131
1132 @classmethod
1132 @classmethod
1133 def _get_role_name(cls, role):
1133 def _get_role_name(cls, role):
1134 return {
1134 return {
1135 cls.ROLE_ALL: _('all'),
1135 cls.ROLE_ALL: _('all'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1138 cls.ROLE_API: _('api calls'),
1138 cls.ROLE_API: _('api calls'),
1139 cls.ROLE_FEED: _('feed access'),
1139 cls.ROLE_FEED: _('feed access'),
1140 }.get(role, role)
1140 }.get(role, role)
1141
1141
1142 @property
1142 @property
1143 def role_humanized(self):
1143 def role_humanized(self):
1144 return self._get_role_name(self.role)
1144 return self._get_role_name(self.role)
1145
1145
1146 def _get_scope(self):
1146 def _get_scope(self):
1147 if self.repo:
1147 if self.repo:
1148 return 'Repository: {}'.format(self.repo.repo_name)
1148 return 'Repository: {}'.format(self.repo.repo_name)
1149 if self.repo_group:
1149 if self.repo_group:
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1151 return 'Global'
1151 return 'Global'
1152
1152
1153 @property
1153 @property
1154 def scope_humanized(self):
1154 def scope_humanized(self):
1155 return self._get_scope()
1155 return self._get_scope()
1156
1156
1157 @property
1157 @property
1158 def token_obfuscated(self):
1158 def token_obfuscated(self):
1159 if self.api_key:
1159 if self.api_key:
1160 return self.api_key[:4] + "****"
1160 return self.api_key[:4] + "****"
1161
1161
1162
1162
1163 class UserEmailMap(Base, BaseModel):
1163 class UserEmailMap(Base, BaseModel):
1164 __tablename__ = 'user_email_map'
1164 __tablename__ = 'user_email_map'
1165 __table_args__ = (
1165 __table_args__ = (
1166 Index('uem_email_idx', 'email'),
1166 Index('uem_email_idx', 'email'),
1167 UniqueConstraint('email'),
1167 UniqueConstraint('email'),
1168 base_table_args
1168 base_table_args
1169 )
1169 )
1170 __mapper_args__ = {}
1170 __mapper_args__ = {}
1171
1171
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1175 user = relationship('User', lazy='joined')
1175 user = relationship('User', lazy='joined')
1176
1176
1177 @validates('_email')
1177 @validates('_email')
1178 def validate_email(self, key, email):
1178 def validate_email(self, key, email):
1179 # check if this email is not main one
1179 # check if this email is not main one
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1181 if main_email is not None:
1181 if main_email is not None:
1182 raise AttributeError('email %s is present is user table' % email)
1182 raise AttributeError('email %s is present is user table' % email)
1183 return email
1183 return email
1184
1184
1185 @hybrid_property
1185 @hybrid_property
1186 def email(self):
1186 def email(self):
1187 return self._email
1187 return self._email
1188
1188
1189 @email.setter
1189 @email.setter
1190 def email(self, val):
1190 def email(self, val):
1191 self._email = val.lower() if val else None
1191 self._email = val.lower() if val else None
1192
1192
1193
1193
1194 class UserIpMap(Base, BaseModel):
1194 class UserIpMap(Base, BaseModel):
1195 __tablename__ = 'user_ip_map'
1195 __tablename__ = 'user_ip_map'
1196 __table_args__ = (
1196 __table_args__ = (
1197 UniqueConstraint('user_id', 'ip_addr'),
1197 UniqueConstraint('user_id', 'ip_addr'),
1198 base_table_args
1198 base_table_args
1199 )
1199 )
1200 __mapper_args__ = {}
1200 __mapper_args__ = {}
1201
1201
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1207 user = relationship('User', lazy='joined')
1207 user = relationship('User', lazy='joined')
1208
1208
1209 @hybrid_property
1209 @hybrid_property
1210 def description_safe(self):
1210 def description_safe(self):
1211 from rhodecode.lib import helpers as h
1211 from rhodecode.lib import helpers as h
1212 return h.escape(self.description)
1212 return h.escape(self.description)
1213
1213
1214 @classmethod
1214 @classmethod
1215 def _get_ip_range(cls, ip_addr):
1215 def _get_ip_range(cls, ip_addr):
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1217 return [str(net.network_address), str(net.broadcast_address)]
1217 return [str(net.network_address), str(net.broadcast_address)]
1218
1218
1219 def __json__(self):
1219 def __json__(self):
1220 return {
1220 return {
1221 'ip_addr': self.ip_addr,
1221 'ip_addr': self.ip_addr,
1222 'ip_range': self._get_ip_range(self.ip_addr),
1222 'ip_range': self._get_ip_range(self.ip_addr),
1223 }
1223 }
1224
1224
1225 def __unicode__(self):
1225 def __unicode__(self):
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1227 self.user_id, self.ip_addr)
1227 self.user_id, self.ip_addr)
1228
1228
1229
1229
1230 class UserSshKeys(Base, BaseModel):
1230 class UserSshKeys(Base, BaseModel):
1231 __tablename__ = 'user_ssh_keys'
1231 __tablename__ = 'user_ssh_keys'
1232 __table_args__ = (
1232 __table_args__ = (
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1234
1234
1235 UniqueConstraint('ssh_key_fingerprint'),
1235 UniqueConstraint('ssh_key_fingerprint'),
1236
1236
1237 base_table_args
1237 base_table_args
1238 )
1238 )
1239 __mapper_args__ = {}
1239 __mapper_args__ = {}
1240
1240
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1244
1244
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1246
1246
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1250
1250
1251 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1252
1252
1253 def __json__(self):
1253 def __json__(self):
1254 data = {
1254 data = {
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1256 'description': self.description,
1256 'description': self.description,
1257 'created_on': self.created_on
1257 'created_on': self.created_on
1258 }
1258 }
1259 return data
1259 return data
1260
1260
1261 def get_api_data(self):
1261 def get_api_data(self):
1262 data = self.__json__()
1262 data = self.__json__()
1263 return data
1263 return data
1264
1264
1265
1265
1266 class UserLog(Base, BaseModel):
1266 class UserLog(Base, BaseModel):
1267 __tablename__ = 'user_logs'
1267 __tablename__ = 'user_logs'
1268 __table_args__ = (
1268 __table_args__ = (
1269 base_table_args,
1269 base_table_args,
1270 )
1270 )
1271
1271
1272 VERSION_1 = 'v1'
1272 VERSION_1 = 'v1'
1273 VERSION_2 = 'v2'
1273 VERSION_2 = 'v2'
1274 VERSIONS = [VERSION_1, VERSION_2]
1274 VERSIONS = [VERSION_1, VERSION_2]
1275
1275
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1284
1284
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1288
1288
1289 def __unicode__(self):
1289 def __unicode__(self):
1290 return u"<%s('id:%s:%s')>" % (
1290 return u"<%s('id:%s:%s')>" % (
1291 self.__class__.__name__, self.repository_name, self.action)
1291 self.__class__.__name__, self.repository_name, self.action)
1292
1292
1293 def __json__(self):
1293 def __json__(self):
1294 return {
1294 return {
1295 'user_id': self.user_id,
1295 'user_id': self.user_id,
1296 'username': self.username,
1296 'username': self.username,
1297 'repository_id': self.repository_id,
1297 'repository_id': self.repository_id,
1298 'repository_name': self.repository_name,
1298 'repository_name': self.repository_name,
1299 'user_ip': self.user_ip,
1299 'user_ip': self.user_ip,
1300 'action_date': self.action_date,
1300 'action_date': self.action_date,
1301 'action': self.action,
1301 'action': self.action,
1302 }
1302 }
1303
1303
1304 @hybrid_property
1304 @hybrid_property
1305 def entry_id(self):
1305 def entry_id(self):
1306 return self.user_log_id
1306 return self.user_log_id
1307
1307
1308 @property
1308 @property
1309 def action_as_day(self):
1309 def action_as_day(self):
1310 return datetime.date(*self.action_date.timetuple()[:3])
1310 return datetime.date(*self.action_date.timetuple()[:3])
1311
1311
1312 user = relationship('User')
1312 user = relationship('User')
1313 repository = relationship('Repository', cascade='')
1313 repository = relationship('Repository', cascade='')
1314
1314
1315
1315
1316 class UserGroup(Base, BaseModel):
1316 class UserGroup(Base, BaseModel):
1317 __tablename__ = 'users_groups'
1317 __tablename__ = 'users_groups'
1318 __table_args__ = (
1318 __table_args__ = (
1319 base_table_args,
1319 base_table_args,
1320 )
1320 )
1321
1321
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1330
1330
1331 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1331 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1337
1337
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1340
1340
1341 @classmethod
1341 @classmethod
1342 def _load_group_data(cls, column):
1342 def _load_group_data(cls, column):
1343 if not column:
1343 if not column:
1344 return {}
1344 return {}
1345
1345
1346 try:
1346 try:
1347 return json.loads(column) or {}
1347 return json.loads(column) or {}
1348 except TypeError:
1348 except TypeError:
1349 return {}
1349 return {}
1350
1350
1351 @hybrid_property
1351 @hybrid_property
1352 def description_safe(self):
1352 def description_safe(self):
1353 from rhodecode.lib import helpers as h
1353 from rhodecode.lib import helpers as h
1354 return h.escape(self.user_group_description)
1354 return h.escape(self.user_group_description)
1355
1355
1356 @hybrid_property
1356 @hybrid_property
1357 def group_data(self):
1357 def group_data(self):
1358 return self._load_group_data(self._group_data)
1358 return self._load_group_data(self._group_data)
1359
1359
1360 @group_data.expression
1360 @group_data.expression
1361 def group_data(self, **kwargs):
1361 def group_data(self, **kwargs):
1362 return self._group_data
1362 return self._group_data
1363
1363
1364 @group_data.setter
1364 @group_data.setter
1365 def group_data(self, val):
1365 def group_data(self, val):
1366 try:
1366 try:
1367 self._group_data = json.dumps(val)
1367 self._group_data = json.dumps(val)
1368 except Exception:
1368 except Exception:
1369 log.error(traceback.format_exc())
1369 log.error(traceback.format_exc())
1370
1370
1371 @classmethod
1371 @classmethod
1372 def _load_sync(cls, group_data):
1372 def _load_sync(cls, group_data):
1373 if group_data:
1373 if group_data:
1374 return group_data.get('extern_type')
1374 return group_data.get('extern_type')
1375
1375
1376 @property
1376 @property
1377 def sync(self):
1377 def sync(self):
1378 return self._load_sync(self.group_data)
1378 return self._load_sync(self.group_data)
1379
1379
1380 def __unicode__(self):
1380 def __unicode__(self):
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1382 self.users_group_id,
1382 self.users_group_id,
1383 self.users_group_name)
1383 self.users_group_name)
1384
1384
1385 @classmethod
1385 @classmethod
1386 def get_by_group_name(cls, group_name, cache=False,
1386 def get_by_group_name(cls, group_name, cache=False,
1387 case_insensitive=False):
1387 case_insensitive=False):
1388 if case_insensitive:
1388 if case_insensitive:
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1390 func.lower(group_name))
1390 func.lower(group_name))
1391
1391
1392 else:
1392 else:
1393 q = cls.query().filter(cls.users_group_name == group_name)
1393 q = cls.query().filter(cls.users_group_name == group_name)
1394 if cache:
1394 if cache:
1395 q = q.options(
1395 q = q.options(
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1397 return q.scalar()
1397 return q.scalar()
1398
1398
1399 @classmethod
1399 @classmethod
1400 def get(cls, user_group_id, cache=False):
1400 def get(cls, user_group_id, cache=False):
1401 if not user_group_id:
1401 if not user_group_id:
1402 return
1402 return
1403
1403
1404 user_group = cls.query()
1404 user_group = cls.query()
1405 if cache:
1405 if cache:
1406 user_group = user_group.options(
1406 user_group = user_group.options(
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1408 return user_group.get(user_group_id)
1408 return user_group.get(user_group_id)
1409
1409
1410 def permissions(self, with_admins=True, with_owner=True,
1410 def permissions(self, with_admins=True, with_owner=True,
1411 expand_from_user_groups=False):
1411 expand_from_user_groups=False):
1412 """
1412 """
1413 Permissions for user groups
1413 Permissions for user groups
1414 """
1414 """
1415 _admin_perm = 'usergroup.admin'
1415 _admin_perm = 'usergroup.admin'
1416
1416
1417 owner_row = []
1417 owner_row = []
1418 if with_owner:
1418 if with_owner:
1419 usr = AttributeDict(self.user.get_dict())
1419 usr = AttributeDict(self.user.get_dict())
1420 usr.owner_row = True
1420 usr.owner_row = True
1421 usr.permission = _admin_perm
1421 usr.permission = _admin_perm
1422 owner_row.append(usr)
1422 owner_row.append(usr)
1423
1423
1424 super_admin_ids = []
1424 super_admin_ids = []
1425 super_admin_rows = []
1425 super_admin_rows = []
1426 if with_admins:
1426 if with_admins:
1427 for usr in User.get_all_super_admins():
1427 for usr in User.get_all_super_admins():
1428 super_admin_ids.append(usr.user_id)
1428 super_admin_ids.append(usr.user_id)
1429 # if this admin is also owner, don't double the record
1429 # if this admin is also owner, don't double the record
1430 if usr.user_id == owner_row[0].user_id:
1430 if usr.user_id == owner_row[0].user_id:
1431 owner_row[0].admin_row = True
1431 owner_row[0].admin_row = True
1432 else:
1432 else:
1433 usr = AttributeDict(usr.get_dict())
1433 usr = AttributeDict(usr.get_dict())
1434 usr.admin_row = True
1434 usr.admin_row = True
1435 usr.permission = _admin_perm
1435 usr.permission = _admin_perm
1436 super_admin_rows.append(usr)
1436 super_admin_rows.append(usr)
1437
1437
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1440 joinedload(UserUserGroupToPerm.user),
1440 joinedload(UserUserGroupToPerm.user),
1441 joinedload(UserUserGroupToPerm.permission),)
1441 joinedload(UserUserGroupToPerm.permission),)
1442
1442
1443 # get owners and admins and permissions. We do a trick of re-writing
1443 # get owners and admins and permissions. We do a trick of re-writing
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1445 # has a global reference and changing one object propagates to all
1445 # has a global reference and changing one object propagates to all
1446 # others. This means if admin is also an owner admin_row that change
1446 # others. This means if admin is also an owner admin_row that change
1447 # would propagate to both objects
1447 # would propagate to both objects
1448 perm_rows = []
1448 perm_rows = []
1449 for _usr in q.all():
1449 for _usr in q.all():
1450 usr = AttributeDict(_usr.user.get_dict())
1450 usr = AttributeDict(_usr.user.get_dict())
1451 # if this user is also owner/admin, mark as duplicate record
1451 # if this user is also owner/admin, mark as duplicate record
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1453 usr.duplicate_perm = True
1453 usr.duplicate_perm = True
1454 usr.permission = _usr.permission.permission_name
1454 usr.permission = _usr.permission.permission_name
1455 perm_rows.append(usr)
1455 perm_rows.append(usr)
1456
1456
1457 # filter the perm rows by 'default' first and then sort them by
1457 # filter the perm rows by 'default' first and then sort them by
1458 # admin,write,read,none permissions sorted again alphabetically in
1458 # admin,write,read,none permissions sorted again alphabetically in
1459 # each group
1459 # each group
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1461
1461
1462 user_groups_rows = []
1462 user_groups_rows = []
1463 if expand_from_user_groups:
1463 if expand_from_user_groups:
1464 for ug in self.permission_user_groups(with_members=True):
1464 for ug in self.permission_user_groups(with_members=True):
1465 for user_data in ug.members:
1465 for user_data in ug.members:
1466 user_groups_rows.append(user_data)
1466 user_groups_rows.append(user_data)
1467
1467
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1469
1469
1470 def permission_user_groups(self, with_members=False):
1470 def permission_user_groups(self, with_members=False):
1471 q = UserGroupUserGroupToPerm.query()\
1471 q = UserGroupUserGroupToPerm.query()\
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1476
1476
1477 perm_rows = []
1477 perm_rows = []
1478 for _user_group in q.all():
1478 for _user_group in q.all():
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1480 entry.permission = _user_group.permission.permission_name
1480 entry.permission = _user_group.permission.permission_name
1481 if with_members:
1481 if with_members:
1482 entry.members = [x.user.get_dict()
1482 entry.members = [x.user.get_dict()
1483 for x in _user_group.user_group.members]
1483 for x in _user_group.user_group.members]
1484 perm_rows.append(entry)
1484 perm_rows.append(entry)
1485
1485
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1487 return perm_rows
1487 return perm_rows
1488
1488
1489 def _get_default_perms(self, user_group, suffix=''):
1489 def _get_default_perms(self, user_group, suffix=''):
1490 from rhodecode.model.permission import PermissionModel
1490 from rhodecode.model.permission import PermissionModel
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1492
1492
1493 def get_default_perms(self, suffix=''):
1493 def get_default_perms(self, suffix=''):
1494 return self._get_default_perms(self, suffix)
1494 return self._get_default_perms(self, suffix)
1495
1495
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1497 """
1497 """
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1499 basically forwarded.
1499 basically forwarded.
1500
1500
1501 """
1501 """
1502 user_group = self
1502 user_group = self
1503 data = {
1503 data = {
1504 'users_group_id': user_group.users_group_id,
1504 'users_group_id': user_group.users_group_id,
1505 'group_name': user_group.users_group_name,
1505 'group_name': user_group.users_group_name,
1506 'group_description': user_group.user_group_description,
1506 'group_description': user_group.user_group_description,
1507 'active': user_group.users_group_active,
1507 'active': user_group.users_group_active,
1508 'owner': user_group.user.username,
1508 'owner': user_group.user.username,
1509 'sync': user_group.sync,
1509 'sync': user_group.sync,
1510 'owner_email': user_group.user.email,
1510 'owner_email': user_group.user.email,
1511 }
1511 }
1512
1512
1513 if with_group_members:
1513 if with_group_members:
1514 users = []
1514 users = []
1515 for user in user_group.members:
1515 for user in user_group.members:
1516 user = user.user
1516 user = user.user
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1518 data['users'] = users
1518 data['users'] = users
1519
1519
1520 return data
1520 return data
1521
1521
1522
1522
1523 class UserGroupMember(Base, BaseModel):
1523 class UserGroupMember(Base, BaseModel):
1524 __tablename__ = 'users_groups_members'
1524 __tablename__ = 'users_groups_members'
1525 __table_args__ = (
1525 __table_args__ = (
1526 base_table_args,
1526 base_table_args,
1527 )
1527 )
1528
1528
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1532
1532
1533 user = relationship('User', lazy='joined')
1533 user = relationship('User', lazy='joined')
1534 users_group = relationship('UserGroup')
1534 users_group = relationship('UserGroup')
1535
1535
1536 def __init__(self, gr_id='', u_id=''):
1536 def __init__(self, gr_id='', u_id=''):
1537 self.users_group_id = gr_id
1537 self.users_group_id = gr_id
1538 self.user_id = u_id
1538 self.user_id = u_id
1539
1539
1540
1540
1541 class RepositoryField(Base, BaseModel):
1541 class RepositoryField(Base, BaseModel):
1542 __tablename__ = 'repositories_fields'
1542 __tablename__ = 'repositories_fields'
1543 __table_args__ = (
1543 __table_args__ = (
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1545 base_table_args,
1545 base_table_args,
1546 )
1546 )
1547
1547
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1549
1549
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1552 field_key = Column("field_key", String(250))
1552 field_key = Column("field_key", String(250))
1553 field_label = Column("field_label", String(1024), nullable=False)
1553 field_label = Column("field_label", String(1024), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1558
1558
1559 repository = relationship('Repository')
1559 repository = relationship('Repository')
1560
1560
1561 @property
1561 @property
1562 def field_key_prefixed(self):
1562 def field_key_prefixed(self):
1563 return 'ex_%s' % self.field_key
1563 return 'ex_%s' % self.field_key
1564
1564
1565 @classmethod
1565 @classmethod
1566 def un_prefix_key(cls, key):
1566 def un_prefix_key(cls, key):
1567 if key.startswith(cls.PREFIX):
1567 if key.startswith(cls.PREFIX):
1568 return key[len(cls.PREFIX):]
1568 return key[len(cls.PREFIX):]
1569 return key
1569 return key
1570
1570
1571 @classmethod
1571 @classmethod
1572 def get_by_key_name(cls, key, repo):
1572 def get_by_key_name(cls, key, repo):
1573 row = cls.query()\
1573 row = cls.query()\
1574 .filter(cls.repository == repo)\
1574 .filter(cls.repository == repo)\
1575 .filter(cls.field_key == key).scalar()
1575 .filter(cls.field_key == key).scalar()
1576 return row
1576 return row
1577
1577
1578
1578
1579 class Repository(Base, BaseModel):
1579 class Repository(Base, BaseModel):
1580 __tablename__ = 'repositories'
1580 __tablename__ = 'repositories'
1581 __table_args__ = (
1581 __table_args__ = (
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1583 base_table_args,
1583 base_table_args,
1584 )
1584 )
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1588
1588
1589 STATE_CREATED = 'repo_state_created'
1589 STATE_CREATED = 'repo_state_created'
1590 STATE_PENDING = 'repo_state_pending'
1590 STATE_PENDING = 'repo_state_pending'
1591 STATE_ERROR = 'repo_state_error'
1591 STATE_ERROR = 'repo_state_error'
1592
1592
1593 LOCK_AUTOMATIC = 'lock_auto'
1593 LOCK_AUTOMATIC = 'lock_auto'
1594 LOCK_API = 'lock_api'
1594 LOCK_API = 'lock_api'
1595 LOCK_WEB = 'lock_web'
1595 LOCK_WEB = 'lock_web'
1596 LOCK_PULL = 'lock_pull'
1596 LOCK_PULL = 'lock_pull'
1597
1597
1598 NAME_SEP = URL_SEP
1598 NAME_SEP = URL_SEP
1599
1599
1600 repo_id = Column(
1600 repo_id = Column(
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1602 primary_key=True)
1602 primary_key=True)
1603 _repo_name = Column(
1603 _repo_name = Column(
1604 "repo_name", Text(), nullable=False, default=None)
1604 "repo_name", Text(), nullable=False, default=None)
1605 _repo_name_hash = Column(
1605 _repo_name_hash = Column(
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1608
1608
1609 clone_uri = Column(
1609 clone_uri = Column(
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1611 default=None)
1611 default=None)
1612 push_uri = Column(
1612 push_uri = Column(
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1614 default=None)
1614 default=None)
1615 repo_type = Column(
1615 repo_type = Column(
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1617 user_id = Column(
1617 user_id = Column(
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1619 unique=False, default=None)
1619 unique=False, default=None)
1620 private = Column(
1620 private = Column(
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1622 archived = Column(
1622 archived = Column(
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1624 enable_statistics = Column(
1624 enable_statistics = Column(
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1626 enable_downloads = Column(
1626 enable_downloads = Column(
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1628 description = Column(
1628 description = Column(
1629 "description", String(10000), nullable=True, unique=None, default=None)
1629 "description", String(10000), nullable=True, unique=None, default=None)
1630 created_on = Column(
1630 created_on = Column(
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1632 default=datetime.datetime.now)
1632 default=datetime.datetime.now)
1633 updated_on = Column(
1633 updated_on = Column(
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1635 default=datetime.datetime.now)
1635 default=datetime.datetime.now)
1636 _landing_revision = Column(
1636 _landing_revision = Column(
1637 "landing_revision", String(255), nullable=False, unique=False,
1637 "landing_revision", String(255), nullable=False, unique=False,
1638 default=None)
1638 default=None)
1639 enable_locking = Column(
1639 enable_locking = Column(
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1641 default=False)
1641 default=False)
1642 _locked = Column(
1642 _locked = Column(
1643 "locked", String(255), nullable=True, unique=False, default=None)
1643 "locked", String(255), nullable=True, unique=False, default=None)
1644 _changeset_cache = Column(
1644 _changeset_cache = Column(
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1646
1646
1647 fork_id = Column(
1647 fork_id = Column(
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1649 nullable=True, unique=False, default=None)
1649 nullable=True, unique=False, default=None)
1650 group_id = Column(
1650 group_id = Column(
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1652 unique=False, default=None)
1652 unique=False, default=None)
1653
1653
1654 user = relationship('User', lazy='joined')
1654 user = relationship('User', lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1657 repo_to_perm = relationship(
1657 repo_to_perm = relationship(
1658 'UserRepoToPerm', cascade='all',
1658 'UserRepoToPerm', cascade='all',
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1662
1662
1663 followers = relationship(
1663 followers = relationship(
1664 'UserFollowing',
1664 'UserFollowing',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1666 cascade='all')
1666 cascade='all')
1667 extra_fields = relationship(
1667 extra_fields = relationship(
1668 'RepositoryField', cascade="all, delete, delete-orphan")
1668 'RepositoryField', cascade="all, delete, delete-orphan")
1669 logs = relationship('UserLog')
1669 logs = relationship('UserLog')
1670 comments = relationship(
1670 comments = relationship(
1671 'ChangesetComment', cascade="all, delete, delete-orphan")
1671 'ChangesetComment', cascade="all, delete, delete-orphan")
1672 pull_requests_source = relationship(
1672 pull_requests_source = relationship(
1673 'PullRequest',
1673 'PullRequest',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1675 cascade="all, delete, delete-orphan")
1675 cascade="all, delete, delete-orphan")
1676 pull_requests_target = relationship(
1676 pull_requests_target = relationship(
1677 'PullRequest',
1677 'PullRequest',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1679 cascade="all, delete, delete-orphan")
1679 cascade="all, delete, delete-orphan")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1682 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
1682 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
1683
1683
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1685
1685
1686 artifacts = relationship('FileStore', cascade="all")
1686 artifacts = relationship('FileStore', cascade="all")
1687
1687
1688 def __unicode__(self):
1688 def __unicode__(self):
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1690 safe_unicode(self.repo_name))
1690 safe_unicode(self.repo_name))
1691
1691
1692 @hybrid_property
1692 @hybrid_property
1693 def description_safe(self):
1693 def description_safe(self):
1694 from rhodecode.lib import helpers as h
1694 from rhodecode.lib import helpers as h
1695 return h.escape(self.description)
1695 return h.escape(self.description)
1696
1696
1697 @hybrid_property
1697 @hybrid_property
1698 def landing_rev(self):
1698 def landing_rev(self):
1699 # always should return [rev_type, rev]
1699 # always should return [rev_type, rev]
1700 if self._landing_revision:
1700 if self._landing_revision:
1701 _rev_info = self._landing_revision.split(':')
1701 _rev_info = self._landing_revision.split(':')
1702 if len(_rev_info) < 2:
1702 if len(_rev_info) < 2:
1703 _rev_info.insert(0, 'rev')
1703 _rev_info.insert(0, 'rev')
1704 return [_rev_info[0], _rev_info[1]]
1704 return [_rev_info[0], _rev_info[1]]
1705 return [None, None]
1705 return [None, None]
1706
1706
1707 @landing_rev.setter
1707 @landing_rev.setter
1708 def landing_rev(self, val):
1708 def landing_rev(self, val):
1709 if ':' not in val:
1709 if ':' not in val:
1710 raise ValueError('value must be delimited with `:` and consist '
1710 raise ValueError('value must be delimited with `:` and consist '
1711 'of <rev_type>:<rev>, got %s instead' % val)
1711 'of <rev_type>:<rev>, got %s instead' % val)
1712 self._landing_revision = val
1712 self._landing_revision = val
1713
1713
1714 @hybrid_property
1714 @hybrid_property
1715 def locked(self):
1715 def locked(self):
1716 if self._locked:
1716 if self._locked:
1717 user_id, timelocked, reason = self._locked.split(':')
1717 user_id, timelocked, reason = self._locked.split(':')
1718 lock_values = int(user_id), timelocked, reason
1718 lock_values = int(user_id), timelocked, reason
1719 else:
1719 else:
1720 lock_values = [None, None, None]
1720 lock_values = [None, None, None]
1721 return lock_values
1721 return lock_values
1722
1722
1723 @locked.setter
1723 @locked.setter
1724 def locked(self, val):
1724 def locked(self, val):
1725 if val and isinstance(val, (list, tuple)):
1725 if val and isinstance(val, (list, tuple)):
1726 self._locked = ':'.join(map(str, val))
1726 self._locked = ':'.join(map(str, val))
1727 else:
1727 else:
1728 self._locked = None
1728 self._locked = None
1729
1729
1730 @hybrid_property
1730 @hybrid_property
1731 def changeset_cache(self):
1731 def changeset_cache(self):
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1733 dummy = EmptyCommit().__json__()
1733 dummy = EmptyCommit().__json__()
1734 if not self._changeset_cache:
1734 if not self._changeset_cache:
1735 dummy['source_repo_id'] = self.repo_id
1735 dummy['source_repo_id'] = self.repo_id
1736 return json.loads(json.dumps(dummy))
1736 return json.loads(json.dumps(dummy))
1737
1737
1738 try:
1738 try:
1739 return json.loads(self._changeset_cache)
1739 return json.loads(self._changeset_cache)
1740 except TypeError:
1740 except TypeError:
1741 return dummy
1741 return dummy
1742 except Exception:
1742 except Exception:
1743 log.error(traceback.format_exc())
1743 log.error(traceback.format_exc())
1744 return dummy
1744 return dummy
1745
1745
1746 @changeset_cache.setter
1746 @changeset_cache.setter
1747 def changeset_cache(self, val):
1747 def changeset_cache(self, val):
1748 try:
1748 try:
1749 self._changeset_cache = json.dumps(val)
1749 self._changeset_cache = json.dumps(val)
1750 except Exception:
1750 except Exception:
1751 log.error(traceback.format_exc())
1751 log.error(traceback.format_exc())
1752
1752
1753 @hybrid_property
1753 @hybrid_property
1754 def repo_name(self):
1754 def repo_name(self):
1755 return self._repo_name
1755 return self._repo_name
1756
1756
1757 @repo_name.setter
1757 @repo_name.setter
1758 def repo_name(self, value):
1758 def repo_name(self, value):
1759 self._repo_name = value
1759 self._repo_name = value
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1761
1761
1762 @classmethod
1762 @classmethod
1763 def normalize_repo_name(cls, repo_name):
1763 def normalize_repo_name(cls, repo_name):
1764 """
1764 """
1765 Normalizes os specific repo_name to the format internally stored inside
1765 Normalizes os specific repo_name to the format internally stored inside
1766 database using URL_SEP
1766 database using URL_SEP
1767
1767
1768 :param cls:
1768 :param cls:
1769 :param repo_name:
1769 :param repo_name:
1770 """
1770 """
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1772
1772
1773 @classmethod
1773 @classmethod
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1775 session = Session()
1775 session = Session()
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1777
1777
1778 if cache:
1778 if cache:
1779 if identity_cache:
1779 if identity_cache:
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1781 if val:
1781 if val:
1782 return val
1782 return val
1783 else:
1783 else:
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1785 q = q.options(
1785 q = q.options(
1786 FromCache("sql_cache_short", cache_key))
1786 FromCache("sql_cache_short", cache_key))
1787
1787
1788 return q.scalar()
1788 return q.scalar()
1789
1789
1790 @classmethod
1790 @classmethod
1791 def get_by_id_or_repo_name(cls, repoid):
1791 def get_by_id_or_repo_name(cls, repoid):
1792 if isinstance(repoid, (int, long)):
1792 if isinstance(repoid, (int, long)):
1793 try:
1793 try:
1794 repo = cls.get(repoid)
1794 repo = cls.get(repoid)
1795 except ValueError:
1795 except ValueError:
1796 repo = None
1796 repo = None
1797 else:
1797 else:
1798 repo = cls.get_by_repo_name(repoid)
1798 repo = cls.get_by_repo_name(repoid)
1799 return repo
1799 return repo
1800
1800
1801 @classmethod
1801 @classmethod
1802 def get_by_full_path(cls, repo_full_path):
1802 def get_by_full_path(cls, repo_full_path):
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1804 repo_name = cls.normalize_repo_name(repo_name)
1804 repo_name = cls.normalize_repo_name(repo_name)
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1806
1806
1807 @classmethod
1807 @classmethod
1808 def get_repo_forks(cls, repo_id):
1808 def get_repo_forks(cls, repo_id):
1809 return cls.query().filter(Repository.fork_id == repo_id)
1809 return cls.query().filter(Repository.fork_id == repo_id)
1810
1810
1811 @classmethod
1811 @classmethod
1812 def base_path(cls):
1812 def base_path(cls):
1813 """
1813 """
1814 Returns base path when all repos are stored
1814 Returns base path when all repos are stored
1815
1815
1816 :param cls:
1816 :param cls:
1817 """
1817 """
1818 q = Session().query(RhodeCodeUi)\
1818 q = Session().query(RhodeCodeUi)\
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 return q.one().ui_value
1821 return q.one().ui_value
1822
1822
1823 @classmethod
1823 @classmethod
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1825 case_insensitive=True, archived=False):
1825 case_insensitive=True, archived=False):
1826 q = Repository.query()
1826 q = Repository.query()
1827
1827
1828 if not archived:
1828 if not archived:
1829 q = q.filter(Repository.archived.isnot(true()))
1829 q = q.filter(Repository.archived.isnot(true()))
1830
1830
1831 if not isinstance(user_id, Optional):
1831 if not isinstance(user_id, Optional):
1832 q = q.filter(Repository.user_id == user_id)
1832 q = q.filter(Repository.user_id == user_id)
1833
1833
1834 if not isinstance(group_id, Optional):
1834 if not isinstance(group_id, Optional):
1835 q = q.filter(Repository.group_id == group_id)
1835 q = q.filter(Repository.group_id == group_id)
1836
1836
1837 if case_insensitive:
1837 if case_insensitive:
1838 q = q.order_by(func.lower(Repository.repo_name))
1838 q = q.order_by(func.lower(Repository.repo_name))
1839 else:
1839 else:
1840 q = q.order_by(Repository.repo_name)
1840 q = q.order_by(Repository.repo_name)
1841
1841
1842 return q.all()
1842 return q.all()
1843
1843
1844 @property
1844 @property
1845 def repo_uid(self):
1845 def repo_uid(self):
1846 return '_{}'.format(self.repo_id)
1846 return '_{}'.format(self.repo_id)
1847
1847
1848 @property
1848 @property
1849 def forks(self):
1849 def forks(self):
1850 """
1850 """
1851 Return forks of this repo
1851 Return forks of this repo
1852 """
1852 """
1853 return Repository.get_repo_forks(self.repo_id)
1853 return Repository.get_repo_forks(self.repo_id)
1854
1854
1855 @property
1855 @property
1856 def parent(self):
1856 def parent(self):
1857 """
1857 """
1858 Returns fork parent
1858 Returns fork parent
1859 """
1859 """
1860 return self.fork
1860 return self.fork
1861
1861
1862 @property
1862 @property
1863 def just_name(self):
1863 def just_name(self):
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1865
1865
1866 @property
1866 @property
1867 def groups_with_parents(self):
1867 def groups_with_parents(self):
1868 groups = []
1868 groups = []
1869 if self.group is None:
1869 if self.group is None:
1870 return groups
1870 return groups
1871
1871
1872 cur_gr = self.group
1872 cur_gr = self.group
1873 groups.insert(0, cur_gr)
1873 groups.insert(0, cur_gr)
1874 while 1:
1874 while 1:
1875 gr = getattr(cur_gr, 'parent_group', None)
1875 gr = getattr(cur_gr, 'parent_group', None)
1876 cur_gr = cur_gr.parent_group
1876 cur_gr = cur_gr.parent_group
1877 if gr is None:
1877 if gr is None:
1878 break
1878 break
1879 groups.insert(0, gr)
1879 groups.insert(0, gr)
1880
1880
1881 return groups
1881 return groups
1882
1882
1883 @property
1883 @property
1884 def groups_and_repo(self):
1884 def groups_and_repo(self):
1885 return self.groups_with_parents, self
1885 return self.groups_with_parents, self
1886
1886
1887 @LazyProperty
1887 @LazyProperty
1888 def repo_path(self):
1888 def repo_path(self):
1889 """
1889 """
1890 Returns base full path for that repository means where it actually
1890 Returns base full path for that repository means where it actually
1891 exists on a filesystem
1891 exists on a filesystem
1892 """
1892 """
1893 q = Session().query(RhodeCodeUi).filter(
1893 q = Session().query(RhodeCodeUi).filter(
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1896 return q.one().ui_value
1896 return q.one().ui_value
1897
1897
1898 @property
1898 @property
1899 def repo_full_path(self):
1899 def repo_full_path(self):
1900 p = [self.repo_path]
1900 p = [self.repo_path]
1901 # we need to split the name by / since this is how we store the
1901 # we need to split the name by / since this is how we store the
1902 # names in the database, but that eventually needs to be converted
1902 # names in the database, but that eventually needs to be converted
1903 # into a valid system path
1903 # into a valid system path
1904 p += self.repo_name.split(self.NAME_SEP)
1904 p += self.repo_name.split(self.NAME_SEP)
1905 return os.path.join(*map(safe_unicode, p))
1905 return os.path.join(*map(safe_unicode, p))
1906
1906
1907 @property
1907 @property
1908 def cache_keys(self):
1908 def cache_keys(self):
1909 """
1909 """
1910 Returns associated cache keys for that repo
1910 Returns associated cache keys for that repo
1911 """
1911 """
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1913 repo_id=self.repo_id)
1913 repo_id=self.repo_id)
1914 return CacheKey.query()\
1914 return CacheKey.query()\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1916 .order_by(CacheKey.cache_key)\
1916 .order_by(CacheKey.cache_key)\
1917 .all()
1917 .all()
1918
1918
1919 @property
1919 @property
1920 def cached_diffs_relative_dir(self):
1920 def cached_diffs_relative_dir(self):
1921 """
1921 """
1922 Return a relative to the repository store path of cached diffs
1922 Return a relative to the repository store path of cached diffs
1923 used for safe display for users, who shouldn't know the absolute store
1923 used for safe display for users, who shouldn't know the absolute store
1924 path
1924 path
1925 """
1925 """
1926 return os.path.join(
1926 return os.path.join(
1927 os.path.dirname(self.repo_name),
1927 os.path.dirname(self.repo_name),
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1929
1929
1930 @property
1930 @property
1931 def cached_diffs_dir(self):
1931 def cached_diffs_dir(self):
1932 path = self.repo_full_path
1932 path = self.repo_full_path
1933 return os.path.join(
1933 return os.path.join(
1934 os.path.dirname(path),
1934 os.path.dirname(path),
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1936
1936
1937 def cached_diffs(self):
1937 def cached_diffs(self):
1938 diff_cache_dir = self.cached_diffs_dir
1938 diff_cache_dir = self.cached_diffs_dir
1939 if os.path.isdir(diff_cache_dir):
1939 if os.path.isdir(diff_cache_dir):
1940 return os.listdir(diff_cache_dir)
1940 return os.listdir(diff_cache_dir)
1941 return []
1941 return []
1942
1942
1943 def shadow_repos(self):
1943 def shadow_repos(self):
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1945 return [
1945 return [
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1947 if x.startswith(shadow_repos_pattern)]
1947 if x.startswith(shadow_repos_pattern)]
1948
1948
1949 def get_new_name(self, repo_name):
1949 def get_new_name(self, repo_name):
1950 """
1950 """
1951 returns new full repository name based on assigned group and new new
1951 returns new full repository name based on assigned group and new new
1952
1952
1953 :param group_name:
1953 :param group_name:
1954 """
1954 """
1955 path_prefix = self.group.full_path_splitted if self.group else []
1955 path_prefix = self.group.full_path_splitted if self.group else []
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1957
1957
1958 @property
1958 @property
1959 def _config(self):
1959 def _config(self):
1960 """
1960 """
1961 Returns db based config object.
1961 Returns db based config object.
1962 """
1962 """
1963 from rhodecode.lib.utils import make_db_config
1963 from rhodecode.lib.utils import make_db_config
1964 return make_db_config(clear_session=False, repo=self)
1964 return make_db_config(clear_session=False, repo=self)
1965
1965
1966 def permissions(self, with_admins=True, with_owner=True,
1966 def permissions(self, with_admins=True, with_owner=True,
1967 expand_from_user_groups=False):
1967 expand_from_user_groups=False):
1968 """
1968 """
1969 Permissions for repositories
1969 Permissions for repositories
1970 """
1970 """
1971 _admin_perm = 'repository.admin'
1971 _admin_perm = 'repository.admin'
1972
1972
1973 owner_row = []
1973 owner_row = []
1974 if with_owner:
1974 if with_owner:
1975 usr = AttributeDict(self.user.get_dict())
1975 usr = AttributeDict(self.user.get_dict())
1976 usr.owner_row = True
1976 usr.owner_row = True
1977 usr.permission = _admin_perm
1977 usr.permission = _admin_perm
1978 usr.permission_id = None
1978 usr.permission_id = None
1979 owner_row.append(usr)
1979 owner_row.append(usr)
1980
1980
1981 super_admin_ids = []
1981 super_admin_ids = []
1982 super_admin_rows = []
1982 super_admin_rows = []
1983 if with_admins:
1983 if with_admins:
1984 for usr in User.get_all_super_admins():
1984 for usr in User.get_all_super_admins():
1985 super_admin_ids.append(usr.user_id)
1985 super_admin_ids.append(usr.user_id)
1986 # if this admin is also owner, don't double the record
1986 # if this admin is also owner, don't double the record
1987 if usr.user_id == owner_row[0].user_id:
1987 if usr.user_id == owner_row[0].user_id:
1988 owner_row[0].admin_row = True
1988 owner_row[0].admin_row = True
1989 else:
1989 else:
1990 usr = AttributeDict(usr.get_dict())
1990 usr = AttributeDict(usr.get_dict())
1991 usr.admin_row = True
1991 usr.admin_row = True
1992 usr.permission = _admin_perm
1992 usr.permission = _admin_perm
1993 usr.permission_id = None
1993 usr.permission_id = None
1994 super_admin_rows.append(usr)
1994 super_admin_rows.append(usr)
1995
1995
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1998 joinedload(UserRepoToPerm.user),
1998 joinedload(UserRepoToPerm.user),
1999 joinedload(UserRepoToPerm.permission),)
1999 joinedload(UserRepoToPerm.permission),)
2000
2000
2001 # get owners and admins and permissions. We do a trick of re-writing
2001 # get owners and admins and permissions. We do a trick of re-writing
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2003 # has a global reference and changing one object propagates to all
2003 # has a global reference and changing one object propagates to all
2004 # others. This means if admin is also an owner admin_row that change
2004 # others. This means if admin is also an owner admin_row that change
2005 # would propagate to both objects
2005 # would propagate to both objects
2006 perm_rows = []
2006 perm_rows = []
2007 for _usr in q.all():
2007 for _usr in q.all():
2008 usr = AttributeDict(_usr.user.get_dict())
2008 usr = AttributeDict(_usr.user.get_dict())
2009 # if this user is also owner/admin, mark as duplicate record
2009 # if this user is also owner/admin, mark as duplicate record
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2011 usr.duplicate_perm = True
2011 usr.duplicate_perm = True
2012 # also check if this permission is maybe used by branch_permissions
2012 # also check if this permission is maybe used by branch_permissions
2013 if _usr.branch_perm_entry:
2013 if _usr.branch_perm_entry:
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2015
2015
2016 usr.permission = _usr.permission.permission_name
2016 usr.permission = _usr.permission.permission_name
2017 usr.permission_id = _usr.repo_to_perm_id
2017 usr.permission_id = _usr.repo_to_perm_id
2018 perm_rows.append(usr)
2018 perm_rows.append(usr)
2019
2019
2020 # filter the perm rows by 'default' first and then sort them by
2020 # filter the perm rows by 'default' first and then sort them by
2021 # admin,write,read,none permissions sorted again alphabetically in
2021 # admin,write,read,none permissions sorted again alphabetically in
2022 # each group
2022 # each group
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2024
2024
2025 user_groups_rows = []
2025 user_groups_rows = []
2026 if expand_from_user_groups:
2026 if expand_from_user_groups:
2027 for ug in self.permission_user_groups(with_members=True):
2027 for ug in self.permission_user_groups(with_members=True):
2028 for user_data in ug.members:
2028 for user_data in ug.members:
2029 user_groups_rows.append(user_data)
2029 user_groups_rows.append(user_data)
2030
2030
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2032
2032
2033 def permission_user_groups(self, with_members=True):
2033 def permission_user_groups(self, with_members=True):
2034 q = UserGroupRepoToPerm.query()\
2034 q = UserGroupRepoToPerm.query()\
2035 .filter(UserGroupRepoToPerm.repository == self)
2035 .filter(UserGroupRepoToPerm.repository == self)
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2037 joinedload(UserGroupRepoToPerm.users_group),
2037 joinedload(UserGroupRepoToPerm.users_group),
2038 joinedload(UserGroupRepoToPerm.permission),)
2038 joinedload(UserGroupRepoToPerm.permission),)
2039
2039
2040 perm_rows = []
2040 perm_rows = []
2041 for _user_group in q.all():
2041 for _user_group in q.all():
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2043 entry.permission = _user_group.permission.permission_name
2043 entry.permission = _user_group.permission.permission_name
2044 if with_members:
2044 if with_members:
2045 entry.members = [x.user.get_dict()
2045 entry.members = [x.user.get_dict()
2046 for x in _user_group.users_group.members]
2046 for x in _user_group.users_group.members]
2047 perm_rows.append(entry)
2047 perm_rows.append(entry)
2048
2048
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2050 return perm_rows
2050 return perm_rows
2051
2051
2052 def get_api_data(self, include_secrets=False):
2052 def get_api_data(self, include_secrets=False):
2053 """
2053 """
2054 Common function for generating repo api data
2054 Common function for generating repo api data
2055
2055
2056 :param include_secrets: See :meth:`User.get_api_data`.
2056 :param include_secrets: See :meth:`User.get_api_data`.
2057
2057
2058 """
2058 """
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2060 # move this methods on models level.
2060 # move this methods on models level.
2061 from rhodecode.model.settings import SettingsModel
2061 from rhodecode.model.settings import SettingsModel
2062 from rhodecode.model.repo import RepoModel
2062 from rhodecode.model.repo import RepoModel
2063
2063
2064 repo = self
2064 repo = self
2065 _user_id, _time, _reason = self.locked
2065 _user_id, _time, _reason = self.locked
2066
2066
2067 data = {
2067 data = {
2068 'repo_id': repo.repo_id,
2068 'repo_id': repo.repo_id,
2069 'repo_name': repo.repo_name,
2069 'repo_name': repo.repo_name,
2070 'repo_type': repo.repo_type,
2070 'repo_type': repo.repo_type,
2071 'clone_uri': repo.clone_uri or '',
2071 'clone_uri': repo.clone_uri or '',
2072 'push_uri': repo.push_uri or '',
2072 'push_uri': repo.push_uri or '',
2073 'url': RepoModel().get_url(self),
2073 'url': RepoModel().get_url(self),
2074 'private': repo.private,
2074 'private': repo.private,
2075 'created_on': repo.created_on,
2075 'created_on': repo.created_on,
2076 'description': repo.description_safe,
2076 'description': repo.description_safe,
2077 'landing_rev': repo.landing_rev,
2077 'landing_rev': repo.landing_rev,
2078 'owner': repo.user.username,
2078 'owner': repo.user.username,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2081 'enable_statistics': repo.enable_statistics,
2081 'enable_statistics': repo.enable_statistics,
2082 'enable_locking': repo.enable_locking,
2082 'enable_locking': repo.enable_locking,
2083 'enable_downloads': repo.enable_downloads,
2083 'enable_downloads': repo.enable_downloads,
2084 'last_changeset': repo.changeset_cache,
2084 'last_changeset': repo.changeset_cache,
2085 'locked_by': User.get(_user_id).get_api_data(
2085 'locked_by': User.get(_user_id).get_api_data(
2086 include_secrets=include_secrets) if _user_id else None,
2086 include_secrets=include_secrets) if _user_id else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2088 'lock_reason': _reason if _reason else None,
2088 'lock_reason': _reason if _reason else None,
2089 }
2089 }
2090
2090
2091 # TODO: mikhail: should be per-repo settings here
2091 # TODO: mikhail: should be per-repo settings here
2092 rc_config = SettingsModel().get_all_settings()
2092 rc_config = SettingsModel().get_all_settings()
2093 repository_fields = str2bool(
2093 repository_fields = str2bool(
2094 rc_config.get('rhodecode_repository_fields'))
2094 rc_config.get('rhodecode_repository_fields'))
2095 if repository_fields:
2095 if repository_fields:
2096 for f in self.extra_fields:
2096 for f in self.extra_fields:
2097 data[f.field_key_prefixed] = f.field_value
2097 data[f.field_key_prefixed] = f.field_value
2098
2098
2099 return data
2099 return data
2100
2100
2101 @classmethod
2101 @classmethod
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2103 if not lock_time:
2103 if not lock_time:
2104 lock_time = time.time()
2104 lock_time = time.time()
2105 if not lock_reason:
2105 if not lock_reason:
2106 lock_reason = cls.LOCK_AUTOMATIC
2106 lock_reason = cls.LOCK_AUTOMATIC
2107 repo.locked = [user_id, lock_time, lock_reason]
2107 repo.locked = [user_id, lock_time, lock_reason]
2108 Session().add(repo)
2108 Session().add(repo)
2109 Session().commit()
2109 Session().commit()
2110
2110
2111 @classmethod
2111 @classmethod
2112 def unlock(cls, repo):
2112 def unlock(cls, repo):
2113 repo.locked = None
2113 repo.locked = None
2114 Session().add(repo)
2114 Session().add(repo)
2115 Session().commit()
2115 Session().commit()
2116
2116
2117 @classmethod
2117 @classmethod
2118 def getlock(cls, repo):
2118 def getlock(cls, repo):
2119 return repo.locked
2119 return repo.locked
2120
2120
2121 def is_user_lock(self, user_id):
2121 def is_user_lock(self, user_id):
2122 if self.lock[0]:
2122 if self.lock[0]:
2123 lock_user_id = safe_int(self.lock[0])
2123 lock_user_id = safe_int(self.lock[0])
2124 user_id = safe_int(user_id)
2124 user_id = safe_int(user_id)
2125 # both are ints, and they are equal
2125 # both are ints, and they are equal
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2127
2127
2128 return False
2128 return False
2129
2129
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2131 """
2131 """
2132 Checks locking on this repository, if locking is enabled and lock is
2132 Checks locking on this repository, if locking is enabled and lock is
2133 present returns a tuple of make_lock, locked, locked_by.
2133 present returns a tuple of make_lock, locked, locked_by.
2134 make_lock can have 3 states None (do nothing) True, make lock
2134 make_lock can have 3 states None (do nothing) True, make lock
2135 False release lock, This value is later propagated to hooks, which
2135 False release lock, This value is later propagated to hooks, which
2136 do the locking. Think about this as signals passed to hooks what to do.
2136 do the locking. Think about this as signals passed to hooks what to do.
2137
2137
2138 """
2138 """
2139 # TODO: johbo: This is part of the business logic and should be moved
2139 # TODO: johbo: This is part of the business logic and should be moved
2140 # into the RepositoryModel.
2140 # into the RepositoryModel.
2141
2141
2142 if action not in ('push', 'pull'):
2142 if action not in ('push', 'pull'):
2143 raise ValueError("Invalid action value: %s" % repr(action))
2143 raise ValueError("Invalid action value: %s" % repr(action))
2144
2144
2145 # defines if locked error should be thrown to user
2145 # defines if locked error should be thrown to user
2146 currently_locked = False
2146 currently_locked = False
2147 # defines if new lock should be made, tri-state
2147 # defines if new lock should be made, tri-state
2148 make_lock = None
2148 make_lock = None
2149 repo = self
2149 repo = self
2150 user = User.get(user_id)
2150 user = User.get(user_id)
2151
2151
2152 lock_info = repo.locked
2152 lock_info = repo.locked
2153
2153
2154 if repo and (repo.enable_locking or not only_when_enabled):
2154 if repo and (repo.enable_locking or not only_when_enabled):
2155 if action == 'push':
2155 if action == 'push':
2156 # check if it's already locked !, if it is compare users
2156 # check if it's already locked !, if it is compare users
2157 locked_by_user_id = lock_info[0]
2157 locked_by_user_id = lock_info[0]
2158 if user.user_id == locked_by_user_id:
2158 if user.user_id == locked_by_user_id:
2159 log.debug(
2159 log.debug(
2160 'Got `push` action from user %s, now unlocking', user)
2160 'Got `push` action from user %s, now unlocking', user)
2161 # unlock if we have push from user who locked
2161 # unlock if we have push from user who locked
2162 make_lock = False
2162 make_lock = False
2163 else:
2163 else:
2164 # we're not the same user who locked, ban with
2164 # we're not the same user who locked, ban with
2165 # code defined in settings (default is 423 HTTP Locked) !
2165 # code defined in settings (default is 423 HTTP Locked) !
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2167 currently_locked = True
2167 currently_locked = True
2168 elif action == 'pull':
2168 elif action == 'pull':
2169 # [0] user [1] date
2169 # [0] user [1] date
2170 if lock_info[0] and lock_info[1]:
2170 if lock_info[0] and lock_info[1]:
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2172 currently_locked = True
2172 currently_locked = True
2173 else:
2173 else:
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2175 make_lock = True
2175 make_lock = True
2176
2176
2177 else:
2177 else:
2178 log.debug('Repository %s do not have locking enabled', repo)
2178 log.debug('Repository %s do not have locking enabled', repo)
2179
2179
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2181 make_lock, currently_locked, lock_info)
2181 make_lock, currently_locked, lock_info)
2182
2182
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2186 # if we don't have at least write permission we cannot make a lock
2186 # if we don't have at least write permission we cannot make a lock
2187 log.debug('lock state reset back to FALSE due to lack '
2187 log.debug('lock state reset back to FALSE due to lack '
2188 'of at least read permission')
2188 'of at least read permission')
2189 make_lock = False
2189 make_lock = False
2190
2190
2191 return make_lock, currently_locked, lock_info
2191 return make_lock, currently_locked, lock_info
2192
2192
2193 @property
2193 @property
2194 def last_commit_cache_update_diff(self):
2194 def last_commit_cache_update_diff(self):
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2196
2196
2197 @property
2197 @property
2198 def last_commit_change(self):
2198 def last_commit_change(self):
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2200 empty_date = datetime.datetime.fromtimestamp(0)
2200 empty_date = datetime.datetime.fromtimestamp(0)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2202 try:
2202 try:
2203 return parse_datetime(date_latest)
2203 return parse_datetime(date_latest)
2204 except Exception:
2204 except Exception:
2205 return empty_date
2205 return empty_date
2206
2206
2207 @property
2207 @property
2208 def last_db_change(self):
2208 def last_db_change(self):
2209 return self.updated_on
2209 return self.updated_on
2210
2210
2211 @property
2211 @property
2212 def clone_uri_hidden(self):
2212 def clone_uri_hidden(self):
2213 clone_uri = self.clone_uri
2213 clone_uri = self.clone_uri
2214 if clone_uri:
2214 if clone_uri:
2215 import urlobject
2215 import urlobject
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2217 if url_obj.password:
2217 if url_obj.password:
2218 clone_uri = url_obj.with_password('*****')
2218 clone_uri = url_obj.with_password('*****')
2219 return clone_uri
2219 return clone_uri
2220
2220
2221 @property
2221 @property
2222 def push_uri_hidden(self):
2222 def push_uri_hidden(self):
2223 push_uri = self.push_uri
2223 push_uri = self.push_uri
2224 if push_uri:
2224 if push_uri:
2225 import urlobject
2225 import urlobject
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2227 if url_obj.password:
2227 if url_obj.password:
2228 push_uri = url_obj.with_password('*****')
2228 push_uri = url_obj.with_password('*****')
2229 return push_uri
2229 return push_uri
2230
2230
2231 def clone_url(self, **override):
2231 def clone_url(self, **override):
2232 from rhodecode.model.settings import SettingsModel
2232 from rhodecode.model.settings import SettingsModel
2233
2233
2234 uri_tmpl = None
2234 uri_tmpl = None
2235 if 'with_id' in override:
2235 if 'with_id' in override:
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2237 del override['with_id']
2237 del override['with_id']
2238
2238
2239 if 'uri_tmpl' in override:
2239 if 'uri_tmpl' in override:
2240 uri_tmpl = override['uri_tmpl']
2240 uri_tmpl = override['uri_tmpl']
2241 del override['uri_tmpl']
2241 del override['uri_tmpl']
2242
2242
2243 ssh = False
2243 ssh = False
2244 if 'ssh' in override:
2244 if 'ssh' in override:
2245 ssh = True
2245 ssh = True
2246 del override['ssh']
2246 del override['ssh']
2247
2247
2248 # we didn't override our tmpl from **overrides
2248 # we didn't override our tmpl from **overrides
2249 request = get_current_request()
2249 request = get_current_request()
2250 if not uri_tmpl:
2250 if not uri_tmpl:
2251 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2251 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2252 rc_config = request.call_context.rc_config
2252 rc_config = request.call_context.rc_config
2253 else:
2253 else:
2254 rc_config = SettingsModel().get_all_settings(cache=True)
2254 rc_config = SettingsModel().get_all_settings(cache=True)
2255 if ssh:
2255 if ssh:
2256 uri_tmpl = rc_config.get(
2256 uri_tmpl = rc_config.get(
2257 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2257 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2258 else:
2258 else:
2259 uri_tmpl = rc_config.get(
2259 uri_tmpl = rc_config.get(
2260 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2260 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2261
2261
2262 return get_clone_url(request=request,
2262 return get_clone_url(request=request,
2263 uri_tmpl=uri_tmpl,
2263 uri_tmpl=uri_tmpl,
2264 repo_name=self.repo_name,
2264 repo_name=self.repo_name,
2265 repo_id=self.repo_id, **override)
2265 repo_id=self.repo_id, **override)
2266
2266
2267 def set_state(self, state):
2267 def set_state(self, state):
2268 self.repo_state = state
2268 self.repo_state = state
2269 Session().add(self)
2269 Session().add(self)
2270 #==========================================================================
2270 #==========================================================================
2271 # SCM PROPERTIES
2271 # SCM PROPERTIES
2272 #==========================================================================
2272 #==========================================================================
2273
2273
2274 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2274 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2275 return get_commit_safe(
2275 return get_commit_safe(
2276 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2276 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2277
2277
2278 def get_changeset(self, rev=None, pre_load=None):
2278 def get_changeset(self, rev=None, pre_load=None):
2279 warnings.warn("Use get_commit", DeprecationWarning)
2279 warnings.warn("Use get_commit", DeprecationWarning)
2280 commit_id = None
2280 commit_id = None
2281 commit_idx = None
2281 commit_idx = None
2282 if isinstance(rev, compat.string_types):
2282 if isinstance(rev, compat.string_types):
2283 commit_id = rev
2283 commit_id = rev
2284 else:
2284 else:
2285 commit_idx = rev
2285 commit_idx = rev
2286 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2286 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2287 pre_load=pre_load)
2287 pre_load=pre_load)
2288
2288
2289 def get_landing_commit(self):
2289 def get_landing_commit(self):
2290 """
2290 """
2291 Returns landing commit, or if that doesn't exist returns the tip
2291 Returns landing commit, or if that doesn't exist returns the tip
2292 """
2292 """
2293 _rev_type, _rev = self.landing_rev
2293 _rev_type, _rev = self.landing_rev
2294 commit = self.get_commit(_rev)
2294 commit = self.get_commit(_rev)
2295 if isinstance(commit, EmptyCommit):
2295 if isinstance(commit, EmptyCommit):
2296 return self.get_commit()
2296 return self.get_commit()
2297 return commit
2297 return commit
2298
2298
2299 def update_commit_cache(self, cs_cache=None, config=None):
2299 def update_commit_cache(self, cs_cache=None, config=None):
2300 """
2300 """
2301 Update cache of last changeset for repository, keys should be::
2301 Update cache of last commit for repository, keys should be::
2302
2302
2303 source_repo_id
2303 source_repo_id
2304 short_id
2304 short_id
2305 raw_id
2305 raw_id
2306 revision
2306 revision
2307 parents
2307 parents
2308 message
2308 message
2309 date
2309 date
2310 author
2310 author
2311 updated_on
2311 updated_on
2312
2312
2313 """
2313 """
2314 from rhodecode.lib.vcs.backends.base import BaseChangeset
2314 from rhodecode.lib.vcs.backends.base import BaseChangeset
2315 if cs_cache is None:
2315 if cs_cache is None:
2316 # use no-cache version here
2316 # use no-cache version here
2317 scm_repo = self.scm_instance(cache=False, config=config)
2317 scm_repo = self.scm_instance(cache=False, config=config)
2318
2318
2319 empty = scm_repo is None or scm_repo.is_empty()
2319 empty = scm_repo is None or scm_repo.is_empty()
2320 if not empty:
2320 if not empty:
2321 cs_cache = scm_repo.get_commit(
2321 cs_cache = scm_repo.get_commit(
2322 pre_load=["author", "date", "message", "parents"])
2322 pre_load=["author", "date", "message", "parents", "branch"])
2323 else:
2323 else:
2324 cs_cache = EmptyCommit()
2324 cs_cache = EmptyCommit()
2325
2325
2326 if isinstance(cs_cache, BaseChangeset):
2326 if isinstance(cs_cache, BaseChangeset):
2327 cs_cache = cs_cache.__json__()
2327 cs_cache = cs_cache.__json__()
2328
2328
2329 def is_outdated(new_cs_cache):
2329 def is_outdated(new_cs_cache):
2330 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2330 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2331 new_cs_cache['revision'] != self.changeset_cache['revision']):
2331 new_cs_cache['revision'] != self.changeset_cache['revision']):
2332 return True
2332 return True
2333 return False
2333 return False
2334
2334
2335 # check if we have maybe already latest cached revision
2335 # check if we have maybe already latest cached revision
2336 if is_outdated(cs_cache) or not self.changeset_cache:
2336 if is_outdated(cs_cache) or not self.changeset_cache:
2337 _default = datetime.datetime.utcnow()
2337 _default = datetime.datetime.utcnow()
2338 last_change = cs_cache.get('date') or _default
2338 last_change = cs_cache.get('date') or _default
2339 # we check if last update is newer than the new value
2339 # we check if last update is newer than the new value
2340 # if yes, we use the current timestamp instead. Imagine you get
2340 # if yes, we use the current timestamp instead. Imagine you get
2341 # old commit pushed 1y ago, we'd set last update 1y to ago.
2341 # old commit pushed 1y ago, we'd set last update 1y to ago.
2342 last_change_timestamp = datetime_to_time(last_change)
2342 last_change_timestamp = datetime_to_time(last_change)
2343 current_timestamp = datetime_to_time(last_change)
2343 current_timestamp = datetime_to_time(last_change)
2344 if last_change_timestamp > current_timestamp:
2344 if last_change_timestamp > current_timestamp:
2345 cs_cache['date'] = _default
2345 cs_cache['date'] = _default
2346
2346
2347 cs_cache['updated_on'] = time.time()
2347 cs_cache['updated_on'] = time.time()
2348 self.changeset_cache = cs_cache
2348 self.changeset_cache = cs_cache
2349 Session().add(self)
2349 Session().add(self)
2350 Session().commit()
2350 Session().commit()
2351
2351
2352 log.debug('updated repo %s with new commit cache %s',
2352 log.debug('updated repo %s with new commit cache %s',
2353 self.repo_name, cs_cache)
2353 self.repo_name, cs_cache)
2354 else:
2354 else:
2355 cs_cache = self.changeset_cache
2355 cs_cache = self.changeset_cache
2356 cs_cache['updated_on'] = time.time()
2356 cs_cache['updated_on'] = time.time()
2357 self.changeset_cache = cs_cache
2357 self.changeset_cache = cs_cache
2358 Session().add(self)
2358 Session().add(self)
2359 Session().commit()
2359 Session().commit()
2360
2360
2361 log.debug('Skipping update_commit_cache for repo:`%s` '
2361 log.debug('Skipping update_commit_cache for repo:`%s` '
2362 'commit already with latest changes', self.repo_name)
2362 'commit already with latest changes', self.repo_name)
2363
2363
2364 @property
2364 @property
2365 def tip(self):
2365 def tip(self):
2366 return self.get_commit('tip')
2366 return self.get_commit('tip')
2367
2367
2368 @property
2368 @property
2369 def author(self):
2369 def author(self):
2370 return self.tip.author
2370 return self.tip.author
2371
2371
2372 @property
2372 @property
2373 def last_change(self):
2373 def last_change(self):
2374 return self.scm_instance().last_change
2374 return self.scm_instance().last_change
2375
2375
2376 def get_comments(self, revisions=None):
2376 def get_comments(self, revisions=None):
2377 """
2377 """
2378 Returns comments for this repository grouped by revisions
2378 Returns comments for this repository grouped by revisions
2379
2379
2380 :param revisions: filter query by revisions only
2380 :param revisions: filter query by revisions only
2381 """
2381 """
2382 cmts = ChangesetComment.query()\
2382 cmts = ChangesetComment.query()\
2383 .filter(ChangesetComment.repo == self)
2383 .filter(ChangesetComment.repo == self)
2384 if revisions:
2384 if revisions:
2385 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2385 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2386 grouped = collections.defaultdict(list)
2386 grouped = collections.defaultdict(list)
2387 for cmt in cmts.all():
2387 for cmt in cmts.all():
2388 grouped[cmt.revision].append(cmt)
2388 grouped[cmt.revision].append(cmt)
2389 return grouped
2389 return grouped
2390
2390
2391 def statuses(self, revisions=None):
2391 def statuses(self, revisions=None):
2392 """
2392 """
2393 Returns statuses for this repository
2393 Returns statuses for this repository
2394
2394
2395 :param revisions: list of revisions to get statuses for
2395 :param revisions: list of revisions to get statuses for
2396 """
2396 """
2397 statuses = ChangesetStatus.query()\
2397 statuses = ChangesetStatus.query()\
2398 .filter(ChangesetStatus.repo == self)\
2398 .filter(ChangesetStatus.repo == self)\
2399 .filter(ChangesetStatus.version == 0)
2399 .filter(ChangesetStatus.version == 0)
2400
2400
2401 if revisions:
2401 if revisions:
2402 # Try doing the filtering in chunks to avoid hitting limits
2402 # Try doing the filtering in chunks to avoid hitting limits
2403 size = 500
2403 size = 500
2404 status_results = []
2404 status_results = []
2405 for chunk in xrange(0, len(revisions), size):
2405 for chunk in xrange(0, len(revisions), size):
2406 status_results += statuses.filter(
2406 status_results += statuses.filter(
2407 ChangesetStatus.revision.in_(
2407 ChangesetStatus.revision.in_(
2408 revisions[chunk: chunk+size])
2408 revisions[chunk: chunk+size])
2409 ).all()
2409 ).all()
2410 else:
2410 else:
2411 status_results = statuses.all()
2411 status_results = statuses.all()
2412
2412
2413 grouped = {}
2413 grouped = {}
2414
2414
2415 # maybe we have open new pullrequest without a status?
2415 # maybe we have open new pullrequest without a status?
2416 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2416 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2417 status_lbl = ChangesetStatus.get_status_lbl(stat)
2417 status_lbl = ChangesetStatus.get_status_lbl(stat)
2418 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2418 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2419 for rev in pr.revisions:
2419 for rev in pr.revisions:
2420 pr_id = pr.pull_request_id
2420 pr_id = pr.pull_request_id
2421 pr_repo = pr.target_repo.repo_name
2421 pr_repo = pr.target_repo.repo_name
2422 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2422 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2423
2423
2424 for stat in status_results:
2424 for stat in status_results:
2425 pr_id = pr_repo = None
2425 pr_id = pr_repo = None
2426 if stat.pull_request:
2426 if stat.pull_request:
2427 pr_id = stat.pull_request.pull_request_id
2427 pr_id = stat.pull_request.pull_request_id
2428 pr_repo = stat.pull_request.target_repo.repo_name
2428 pr_repo = stat.pull_request.target_repo.repo_name
2429 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2429 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2430 pr_id, pr_repo]
2430 pr_id, pr_repo]
2431 return grouped
2431 return grouped
2432
2432
2433 # ==========================================================================
2433 # ==========================================================================
2434 # SCM CACHE INSTANCE
2434 # SCM CACHE INSTANCE
2435 # ==========================================================================
2435 # ==========================================================================
2436
2436
2437 def scm_instance(self, **kwargs):
2437 def scm_instance(self, **kwargs):
2438 import rhodecode
2438 import rhodecode
2439
2439
2440 # Passing a config will not hit the cache currently only used
2440 # Passing a config will not hit the cache currently only used
2441 # for repo2dbmapper
2441 # for repo2dbmapper
2442 config = kwargs.pop('config', None)
2442 config = kwargs.pop('config', None)
2443 cache = kwargs.pop('cache', None)
2443 cache = kwargs.pop('cache', None)
2444 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2444 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2445 if vcs_full_cache is not None:
2445 if vcs_full_cache is not None:
2446 # allows override global config
2446 # allows override global config
2447 full_cache = vcs_full_cache
2447 full_cache = vcs_full_cache
2448 else:
2448 else:
2449 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2449 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2450 # if cache is NOT defined use default global, else we have a full
2450 # if cache is NOT defined use default global, else we have a full
2451 # control over cache behaviour
2451 # control over cache behaviour
2452 if cache is None and full_cache and not config:
2452 if cache is None and full_cache and not config:
2453 log.debug('Initializing pure cached instance for %s', self.repo_path)
2453 log.debug('Initializing pure cached instance for %s', self.repo_path)
2454 return self._get_instance_cached()
2454 return self._get_instance_cached()
2455
2455
2456 # cache here is sent to the "vcs server"
2456 # cache here is sent to the "vcs server"
2457 return self._get_instance(cache=bool(cache), config=config)
2457 return self._get_instance(cache=bool(cache), config=config)
2458
2458
2459 def _get_instance_cached(self):
2459 def _get_instance_cached(self):
2460 from rhodecode.lib import rc_cache
2460 from rhodecode.lib import rc_cache
2461
2461
2462 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2462 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2463 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2463 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2464 repo_id=self.repo_id)
2464 repo_id=self.repo_id)
2465 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2465 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2466
2466
2467 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2467 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2468 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2468 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2469 return self._get_instance(repo_state_uid=_cache_state_uid)
2469 return self._get_instance(repo_state_uid=_cache_state_uid)
2470
2470
2471 # we must use thread scoped cache here,
2471 # we must use thread scoped cache here,
2472 # because each thread of gevent needs it's own not shared connection and cache
2472 # because each thread of gevent needs it's own not shared connection and cache
2473 # we also alter `args` so the cache key is individual for every green thread.
2473 # we also alter `args` so the cache key is individual for every green thread.
2474 inv_context_manager = rc_cache.InvalidationContext(
2474 inv_context_manager = rc_cache.InvalidationContext(
2475 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2475 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2476 thread_scoped=True)
2476 thread_scoped=True)
2477 with inv_context_manager as invalidation_context:
2477 with inv_context_manager as invalidation_context:
2478 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2478 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2479 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2479 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2480
2480
2481 # re-compute and store cache if we get invalidate signal
2481 # re-compute and store cache if we get invalidate signal
2482 if invalidation_context.should_invalidate():
2482 if invalidation_context.should_invalidate():
2483 instance = get_instance_cached.refresh(*args)
2483 instance = get_instance_cached.refresh(*args)
2484 else:
2484 else:
2485 instance = get_instance_cached(*args)
2485 instance = get_instance_cached(*args)
2486
2486
2487 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2487 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2488 return instance
2488 return instance
2489
2489
2490 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2490 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2491 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2491 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2492 self.repo_type, self.repo_path, cache)
2492 self.repo_type, self.repo_path, cache)
2493 config = config or self._config
2493 config = config or self._config
2494 custom_wire = {
2494 custom_wire = {
2495 'cache': cache, # controls the vcs.remote cache
2495 'cache': cache, # controls the vcs.remote cache
2496 'repo_state_uid': repo_state_uid
2496 'repo_state_uid': repo_state_uid
2497 }
2497 }
2498 repo = get_vcs_instance(
2498 repo = get_vcs_instance(
2499 repo_path=safe_str(self.repo_full_path),
2499 repo_path=safe_str(self.repo_full_path),
2500 config=config,
2500 config=config,
2501 with_wire=custom_wire,
2501 with_wire=custom_wire,
2502 create=False,
2502 create=False,
2503 _vcs_alias=self.repo_type)
2503 _vcs_alias=self.repo_type)
2504 if repo is not None:
2504 if repo is not None:
2505 repo.count() # cache rebuild
2505 repo.count() # cache rebuild
2506 return repo
2506 return repo
2507
2507
2508 def __json__(self):
2508 def __json__(self):
2509 return {'landing_rev': self.landing_rev}
2509 return {'landing_rev': self.landing_rev}
2510
2510
2511 def get_dict(self):
2511 def get_dict(self):
2512
2512
2513 # Since we transformed `repo_name` to a hybrid property, we need to
2513 # Since we transformed `repo_name` to a hybrid property, we need to
2514 # keep compatibility with the code which uses `repo_name` field.
2514 # keep compatibility with the code which uses `repo_name` field.
2515
2515
2516 result = super(Repository, self).get_dict()
2516 result = super(Repository, self).get_dict()
2517 result['repo_name'] = result.pop('_repo_name', None)
2517 result['repo_name'] = result.pop('_repo_name', None)
2518 return result
2518 return result
2519
2519
2520
2520
2521 class RepoGroup(Base, BaseModel):
2521 class RepoGroup(Base, BaseModel):
2522 __tablename__ = 'groups'
2522 __tablename__ = 'groups'
2523 __table_args__ = (
2523 __table_args__ = (
2524 UniqueConstraint('group_name', 'group_parent_id'),
2524 UniqueConstraint('group_name', 'group_parent_id'),
2525 base_table_args,
2525 base_table_args,
2526 )
2526 )
2527 __mapper_args__ = {'order_by': 'group_name'}
2527 __mapper_args__ = {'order_by': 'group_name'}
2528
2528
2529 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2529 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2530
2530
2531 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2531 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2532 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2532 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2533 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2533 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2534 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2534 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2535 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2535 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2536 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2536 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2538 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2538 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2539 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2539 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2540 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2540 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2541 _changeset_cache = Column(
2541 _changeset_cache = Column(
2542 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2542 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2543
2543
2544 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2544 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2545 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2545 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2546 parent_group = relationship('RepoGroup', remote_side=group_id)
2546 parent_group = relationship('RepoGroup', remote_side=group_id)
2547 user = relationship('User')
2547 user = relationship('User')
2548 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2548 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2549
2549
2550 def __init__(self, group_name='', parent_group=None):
2550 def __init__(self, group_name='', parent_group=None):
2551 self.group_name = group_name
2551 self.group_name = group_name
2552 self.parent_group = parent_group
2552 self.parent_group = parent_group
2553
2553
2554 def __unicode__(self):
2554 def __unicode__(self):
2555 return u"<%s('id:%s:%s')>" % (
2555 return u"<%s('id:%s:%s')>" % (
2556 self.__class__.__name__, self.group_id, self.group_name)
2556 self.__class__.__name__, self.group_id, self.group_name)
2557
2557
2558 @hybrid_property
2558 @hybrid_property
2559 def group_name(self):
2559 def group_name(self):
2560 return self._group_name
2560 return self._group_name
2561
2561
2562 @group_name.setter
2562 @group_name.setter
2563 def group_name(self, value):
2563 def group_name(self, value):
2564 self._group_name = value
2564 self._group_name = value
2565 self.group_name_hash = self.hash_repo_group_name(value)
2565 self.group_name_hash = self.hash_repo_group_name(value)
2566
2566
2567 @hybrid_property
2567 @hybrid_property
2568 def changeset_cache(self):
2568 def changeset_cache(self):
2569 from rhodecode.lib.vcs.backends.base import EmptyCommit
2569 from rhodecode.lib.vcs.backends.base import EmptyCommit
2570 dummy = EmptyCommit().__json__()
2570 dummy = EmptyCommit().__json__()
2571 if not self._changeset_cache:
2571 if not self._changeset_cache:
2572 dummy['source_repo_id'] = ''
2572 dummy['source_repo_id'] = ''
2573 return json.loads(json.dumps(dummy))
2573 return json.loads(json.dumps(dummy))
2574
2574
2575 try:
2575 try:
2576 return json.loads(self._changeset_cache)
2576 return json.loads(self._changeset_cache)
2577 except TypeError:
2577 except TypeError:
2578 return dummy
2578 return dummy
2579 except Exception:
2579 except Exception:
2580 log.error(traceback.format_exc())
2580 log.error(traceback.format_exc())
2581 return dummy
2581 return dummy
2582
2582
2583 @changeset_cache.setter
2583 @changeset_cache.setter
2584 def changeset_cache(self, val):
2584 def changeset_cache(self, val):
2585 try:
2585 try:
2586 self._changeset_cache = json.dumps(val)
2586 self._changeset_cache = json.dumps(val)
2587 except Exception:
2587 except Exception:
2588 log.error(traceback.format_exc())
2588 log.error(traceback.format_exc())
2589
2589
2590 @validates('group_parent_id')
2590 @validates('group_parent_id')
2591 def validate_group_parent_id(self, key, val):
2591 def validate_group_parent_id(self, key, val):
2592 """
2592 """
2593 Check cycle references for a parent group to self
2593 Check cycle references for a parent group to self
2594 """
2594 """
2595 if self.group_id and val:
2595 if self.group_id and val:
2596 assert val != self.group_id
2596 assert val != self.group_id
2597
2597
2598 return val
2598 return val
2599
2599
2600 @hybrid_property
2600 @hybrid_property
2601 def description_safe(self):
2601 def description_safe(self):
2602 from rhodecode.lib import helpers as h
2602 from rhodecode.lib import helpers as h
2603 return h.escape(self.group_description)
2603 return h.escape(self.group_description)
2604
2604
2605 @classmethod
2605 @classmethod
2606 def hash_repo_group_name(cls, repo_group_name):
2606 def hash_repo_group_name(cls, repo_group_name):
2607 val = remove_formatting(repo_group_name)
2607 val = remove_formatting(repo_group_name)
2608 val = safe_str(val).lower()
2608 val = safe_str(val).lower()
2609 chars = []
2609 chars = []
2610 for c in val:
2610 for c in val:
2611 if c not in string.ascii_letters:
2611 if c not in string.ascii_letters:
2612 c = str(ord(c))
2612 c = str(ord(c))
2613 chars.append(c)
2613 chars.append(c)
2614
2614
2615 return ''.join(chars)
2615 return ''.join(chars)
2616
2616
2617 @classmethod
2617 @classmethod
2618 def _generate_choice(cls, repo_group):
2618 def _generate_choice(cls, repo_group):
2619 from webhelpers.html import literal as _literal
2619 from webhelpers.html import literal as _literal
2620 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2620 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2621 return repo_group.group_id, _name(repo_group.full_path_splitted)
2621 return repo_group.group_id, _name(repo_group.full_path_splitted)
2622
2622
2623 @classmethod
2623 @classmethod
2624 def groups_choices(cls, groups=None, show_empty_group=True):
2624 def groups_choices(cls, groups=None, show_empty_group=True):
2625 if not groups:
2625 if not groups:
2626 groups = cls.query().all()
2626 groups = cls.query().all()
2627
2627
2628 repo_groups = []
2628 repo_groups = []
2629 if show_empty_group:
2629 if show_empty_group:
2630 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2630 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2631
2631
2632 repo_groups.extend([cls._generate_choice(x) for x in groups])
2632 repo_groups.extend([cls._generate_choice(x) for x in groups])
2633
2633
2634 repo_groups = sorted(
2634 repo_groups = sorted(
2635 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2635 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2636 return repo_groups
2636 return repo_groups
2637
2637
2638 @classmethod
2638 @classmethod
2639 def url_sep(cls):
2639 def url_sep(cls):
2640 return URL_SEP
2640 return URL_SEP
2641
2641
2642 @classmethod
2642 @classmethod
2643 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2643 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2644 if case_insensitive:
2644 if case_insensitive:
2645 gr = cls.query().filter(func.lower(cls.group_name)
2645 gr = cls.query().filter(func.lower(cls.group_name)
2646 == func.lower(group_name))
2646 == func.lower(group_name))
2647 else:
2647 else:
2648 gr = cls.query().filter(cls.group_name == group_name)
2648 gr = cls.query().filter(cls.group_name == group_name)
2649 if cache:
2649 if cache:
2650 name_key = _hash_key(group_name)
2650 name_key = _hash_key(group_name)
2651 gr = gr.options(
2651 gr = gr.options(
2652 FromCache("sql_cache_short", "get_group_%s" % name_key))
2652 FromCache("sql_cache_short", "get_group_%s" % name_key))
2653 return gr.scalar()
2653 return gr.scalar()
2654
2654
2655 @classmethod
2655 @classmethod
2656 def get_user_personal_repo_group(cls, user_id):
2656 def get_user_personal_repo_group(cls, user_id):
2657 user = User.get(user_id)
2657 user = User.get(user_id)
2658 if user.username == User.DEFAULT_USER:
2658 if user.username == User.DEFAULT_USER:
2659 return None
2659 return None
2660
2660
2661 return cls.query()\
2661 return cls.query()\
2662 .filter(cls.personal == true()) \
2662 .filter(cls.personal == true()) \
2663 .filter(cls.user == user) \
2663 .filter(cls.user == user) \
2664 .order_by(cls.group_id.asc()) \
2664 .order_by(cls.group_id.asc()) \
2665 .first()
2665 .first()
2666
2666
2667 @classmethod
2667 @classmethod
2668 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2668 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2669 case_insensitive=True):
2669 case_insensitive=True):
2670 q = RepoGroup.query()
2670 q = RepoGroup.query()
2671
2671
2672 if not isinstance(user_id, Optional):
2672 if not isinstance(user_id, Optional):
2673 q = q.filter(RepoGroup.user_id == user_id)
2673 q = q.filter(RepoGroup.user_id == user_id)
2674
2674
2675 if not isinstance(group_id, Optional):
2675 if not isinstance(group_id, Optional):
2676 q = q.filter(RepoGroup.group_parent_id == group_id)
2676 q = q.filter(RepoGroup.group_parent_id == group_id)
2677
2677
2678 if case_insensitive:
2678 if case_insensitive:
2679 q = q.order_by(func.lower(RepoGroup.group_name))
2679 q = q.order_by(func.lower(RepoGroup.group_name))
2680 else:
2680 else:
2681 q = q.order_by(RepoGroup.group_name)
2681 q = q.order_by(RepoGroup.group_name)
2682 return q.all()
2682 return q.all()
2683
2683
2684 @property
2684 @property
2685 def parents(self, parents_recursion_limit = 10):
2685 def parents(self, parents_recursion_limit = 10):
2686 groups = []
2686 groups = []
2687 if self.parent_group is None:
2687 if self.parent_group is None:
2688 return groups
2688 return groups
2689 cur_gr = self.parent_group
2689 cur_gr = self.parent_group
2690 groups.insert(0, cur_gr)
2690 groups.insert(0, cur_gr)
2691 cnt = 0
2691 cnt = 0
2692 while 1:
2692 while 1:
2693 cnt += 1
2693 cnt += 1
2694 gr = getattr(cur_gr, 'parent_group', None)
2694 gr = getattr(cur_gr, 'parent_group', None)
2695 cur_gr = cur_gr.parent_group
2695 cur_gr = cur_gr.parent_group
2696 if gr is None:
2696 if gr is None:
2697 break
2697 break
2698 if cnt == parents_recursion_limit:
2698 if cnt == parents_recursion_limit:
2699 # this will prevent accidental infinit loops
2699 # this will prevent accidental infinit loops
2700 log.error('more than %s parents found for group %s, stopping '
2700 log.error('more than %s parents found for group %s, stopping '
2701 'recursive parent fetching', parents_recursion_limit, self)
2701 'recursive parent fetching', parents_recursion_limit, self)
2702 break
2702 break
2703
2703
2704 groups.insert(0, gr)
2704 groups.insert(0, gr)
2705 return groups
2705 return groups
2706
2706
2707 @property
2707 @property
2708 def last_commit_cache_update_diff(self):
2708 def last_commit_cache_update_diff(self):
2709 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2709 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2710
2710
2711 @property
2711 @property
2712 def last_commit_change(self):
2712 def last_commit_change(self):
2713 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2713 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2714 empty_date = datetime.datetime.fromtimestamp(0)
2714 empty_date = datetime.datetime.fromtimestamp(0)
2715 date_latest = self.changeset_cache.get('date', empty_date)
2715 date_latest = self.changeset_cache.get('date', empty_date)
2716 try:
2716 try:
2717 return parse_datetime(date_latest)
2717 return parse_datetime(date_latest)
2718 except Exception:
2718 except Exception:
2719 return empty_date
2719 return empty_date
2720
2720
2721 @property
2721 @property
2722 def last_db_change(self):
2722 def last_db_change(self):
2723 return self.updated_on
2723 return self.updated_on
2724
2724
2725 @property
2725 @property
2726 def children(self):
2726 def children(self):
2727 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2727 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2728
2728
2729 @property
2729 @property
2730 def name(self):
2730 def name(self):
2731 return self.group_name.split(RepoGroup.url_sep())[-1]
2731 return self.group_name.split(RepoGroup.url_sep())[-1]
2732
2732
2733 @property
2733 @property
2734 def full_path(self):
2734 def full_path(self):
2735 return self.group_name
2735 return self.group_name
2736
2736
2737 @property
2737 @property
2738 def full_path_splitted(self):
2738 def full_path_splitted(self):
2739 return self.group_name.split(RepoGroup.url_sep())
2739 return self.group_name.split(RepoGroup.url_sep())
2740
2740
2741 @property
2741 @property
2742 def repositories(self):
2742 def repositories(self):
2743 return Repository.query()\
2743 return Repository.query()\
2744 .filter(Repository.group == self)\
2744 .filter(Repository.group == self)\
2745 .order_by(Repository.repo_name)
2745 .order_by(Repository.repo_name)
2746
2746
2747 @property
2747 @property
2748 def repositories_recursive_count(self):
2748 def repositories_recursive_count(self):
2749 cnt = self.repositories.count()
2749 cnt = self.repositories.count()
2750
2750
2751 def children_count(group):
2751 def children_count(group):
2752 cnt = 0
2752 cnt = 0
2753 for child in group.children:
2753 for child in group.children:
2754 cnt += child.repositories.count()
2754 cnt += child.repositories.count()
2755 cnt += children_count(child)
2755 cnt += children_count(child)
2756 return cnt
2756 return cnt
2757
2757
2758 return cnt + children_count(self)
2758 return cnt + children_count(self)
2759
2759
2760 def _recursive_objects(self, include_repos=True, include_groups=True):
2760 def _recursive_objects(self, include_repos=True, include_groups=True):
2761 all_ = []
2761 all_ = []
2762
2762
2763 def _get_members(root_gr):
2763 def _get_members(root_gr):
2764 if include_repos:
2764 if include_repos:
2765 for r in root_gr.repositories:
2765 for r in root_gr.repositories:
2766 all_.append(r)
2766 all_.append(r)
2767 childs = root_gr.children.all()
2767 childs = root_gr.children.all()
2768 if childs:
2768 if childs:
2769 for gr in childs:
2769 for gr in childs:
2770 if include_groups:
2770 if include_groups:
2771 all_.append(gr)
2771 all_.append(gr)
2772 _get_members(gr)
2772 _get_members(gr)
2773
2773
2774 root_group = []
2774 root_group = []
2775 if include_groups:
2775 if include_groups:
2776 root_group = [self]
2776 root_group = [self]
2777
2777
2778 _get_members(self)
2778 _get_members(self)
2779 return root_group + all_
2779 return root_group + all_
2780
2780
2781 def recursive_groups_and_repos(self):
2781 def recursive_groups_and_repos(self):
2782 """
2782 """
2783 Recursive return all groups, with repositories in those groups
2783 Recursive return all groups, with repositories in those groups
2784 """
2784 """
2785 return self._recursive_objects()
2785 return self._recursive_objects()
2786
2786
2787 def recursive_groups(self):
2787 def recursive_groups(self):
2788 """
2788 """
2789 Returns all children groups for this group including children of children
2789 Returns all children groups for this group including children of children
2790 """
2790 """
2791 return self._recursive_objects(include_repos=False)
2791 return self._recursive_objects(include_repos=False)
2792
2792
2793 def recursive_repos(self):
2793 def recursive_repos(self):
2794 """
2794 """
2795 Returns all children repositories for this group
2795 Returns all children repositories for this group
2796 """
2796 """
2797 return self._recursive_objects(include_groups=False)
2797 return self._recursive_objects(include_groups=False)
2798
2798
2799 def get_new_name(self, group_name):
2799 def get_new_name(self, group_name):
2800 """
2800 """
2801 returns new full group name based on parent and new name
2801 returns new full group name based on parent and new name
2802
2802
2803 :param group_name:
2803 :param group_name:
2804 """
2804 """
2805 path_prefix = (self.parent_group.full_path_splitted if
2805 path_prefix = (self.parent_group.full_path_splitted if
2806 self.parent_group else [])
2806 self.parent_group else [])
2807 return RepoGroup.url_sep().join(path_prefix + [group_name])
2807 return RepoGroup.url_sep().join(path_prefix + [group_name])
2808
2808
2809 def update_commit_cache(self, config=None):
2809 def update_commit_cache(self, config=None):
2810 """
2810 """
2811 Update cache of last changeset for newest repository inside this group, keys should be::
2811 Update cache of last changeset for newest repository inside this group, keys should be::
2812
2812
2813 source_repo_id
2813 source_repo_id
2814 short_id
2814 short_id
2815 raw_id
2815 raw_id
2816 revision
2816 revision
2817 parents
2817 parents
2818 message
2818 message
2819 date
2819 date
2820 author
2820 author
2821
2821
2822 """
2822 """
2823 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2823 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2824
2824
2825 def repo_groups_and_repos():
2825 def repo_groups_and_repos():
2826 all_entries = OrderedDefaultDict(list)
2826 all_entries = OrderedDefaultDict(list)
2827
2827
2828 def _get_members(root_gr, pos=0):
2828 def _get_members(root_gr, pos=0):
2829
2829
2830 for repo in root_gr.repositories:
2830 for repo in root_gr.repositories:
2831 all_entries[root_gr].append(repo)
2831 all_entries[root_gr].append(repo)
2832
2832
2833 # fill in all parent positions
2833 # fill in all parent positions
2834 for parent_group in root_gr.parents:
2834 for parent_group in root_gr.parents:
2835 all_entries[parent_group].extend(all_entries[root_gr])
2835 all_entries[parent_group].extend(all_entries[root_gr])
2836
2836
2837 children_groups = root_gr.children.all()
2837 children_groups = root_gr.children.all()
2838 if children_groups:
2838 if children_groups:
2839 for cnt, gr in enumerate(children_groups, 1):
2839 for cnt, gr in enumerate(children_groups, 1):
2840 _get_members(gr, pos=pos+cnt)
2840 _get_members(gr, pos=pos+cnt)
2841
2841
2842 _get_members(root_gr=self)
2842 _get_members(root_gr=self)
2843 return all_entries
2843 return all_entries
2844
2844
2845 empty_date = datetime.datetime.fromtimestamp(0)
2845 empty_date = datetime.datetime.fromtimestamp(0)
2846 for repo_group, repos in repo_groups_and_repos().items():
2846 for repo_group, repos in repo_groups_and_repos().items():
2847
2847
2848 latest_repo_cs_cache = {}
2848 latest_repo_cs_cache = {}
2849 for repo in repos:
2849 for repo in repos:
2850 repo_cs_cache = repo.changeset_cache
2850 repo_cs_cache = repo.changeset_cache
2851 date_latest = latest_repo_cs_cache.get('date', empty_date)
2851 date_latest = latest_repo_cs_cache.get('date', empty_date)
2852 date_current = repo_cs_cache.get('date', empty_date)
2852 date_current = repo_cs_cache.get('date', empty_date)
2853 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2853 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2854 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2854 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2855 latest_repo_cs_cache = repo_cs_cache
2855 latest_repo_cs_cache = repo_cs_cache
2856 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2856 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2857
2857
2858 latest_repo_cs_cache['updated_on'] = time.time()
2858 latest_repo_cs_cache['updated_on'] = time.time()
2859 repo_group.changeset_cache = latest_repo_cs_cache
2859 repo_group.changeset_cache = latest_repo_cs_cache
2860 Session().add(repo_group)
2860 Session().add(repo_group)
2861 Session().commit()
2861 Session().commit()
2862
2862
2863 log.debug('updated repo group %s with new commit cache %s',
2863 log.debug('updated repo group %s with new commit cache %s',
2864 repo_group.group_name, latest_repo_cs_cache)
2864 repo_group.group_name, latest_repo_cs_cache)
2865
2865
2866 def permissions(self, with_admins=True, with_owner=True,
2866 def permissions(self, with_admins=True, with_owner=True,
2867 expand_from_user_groups=False):
2867 expand_from_user_groups=False):
2868 """
2868 """
2869 Permissions for repository groups
2869 Permissions for repository groups
2870 """
2870 """
2871 _admin_perm = 'group.admin'
2871 _admin_perm = 'group.admin'
2872
2872
2873 owner_row = []
2873 owner_row = []
2874 if with_owner:
2874 if with_owner:
2875 usr = AttributeDict(self.user.get_dict())
2875 usr = AttributeDict(self.user.get_dict())
2876 usr.owner_row = True
2876 usr.owner_row = True
2877 usr.permission = _admin_perm
2877 usr.permission = _admin_perm
2878 owner_row.append(usr)
2878 owner_row.append(usr)
2879
2879
2880 super_admin_ids = []
2880 super_admin_ids = []
2881 super_admin_rows = []
2881 super_admin_rows = []
2882 if with_admins:
2882 if with_admins:
2883 for usr in User.get_all_super_admins():
2883 for usr in User.get_all_super_admins():
2884 super_admin_ids.append(usr.user_id)
2884 super_admin_ids.append(usr.user_id)
2885 # if this admin is also owner, don't double the record
2885 # if this admin is also owner, don't double the record
2886 if usr.user_id == owner_row[0].user_id:
2886 if usr.user_id == owner_row[0].user_id:
2887 owner_row[0].admin_row = True
2887 owner_row[0].admin_row = True
2888 else:
2888 else:
2889 usr = AttributeDict(usr.get_dict())
2889 usr = AttributeDict(usr.get_dict())
2890 usr.admin_row = True
2890 usr.admin_row = True
2891 usr.permission = _admin_perm
2891 usr.permission = _admin_perm
2892 super_admin_rows.append(usr)
2892 super_admin_rows.append(usr)
2893
2893
2894 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2894 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2895 q = q.options(joinedload(UserRepoGroupToPerm.group),
2895 q = q.options(joinedload(UserRepoGroupToPerm.group),
2896 joinedload(UserRepoGroupToPerm.user),
2896 joinedload(UserRepoGroupToPerm.user),
2897 joinedload(UserRepoGroupToPerm.permission),)
2897 joinedload(UserRepoGroupToPerm.permission),)
2898
2898
2899 # get owners and admins and permissions. We do a trick of re-writing
2899 # get owners and admins and permissions. We do a trick of re-writing
2900 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2900 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2901 # has a global reference and changing one object propagates to all
2901 # has a global reference and changing one object propagates to all
2902 # others. This means if admin is also an owner admin_row that change
2902 # others. This means if admin is also an owner admin_row that change
2903 # would propagate to both objects
2903 # would propagate to both objects
2904 perm_rows = []
2904 perm_rows = []
2905 for _usr in q.all():
2905 for _usr in q.all():
2906 usr = AttributeDict(_usr.user.get_dict())
2906 usr = AttributeDict(_usr.user.get_dict())
2907 # if this user is also owner/admin, mark as duplicate record
2907 # if this user is also owner/admin, mark as duplicate record
2908 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2908 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2909 usr.duplicate_perm = True
2909 usr.duplicate_perm = True
2910 usr.permission = _usr.permission.permission_name
2910 usr.permission = _usr.permission.permission_name
2911 perm_rows.append(usr)
2911 perm_rows.append(usr)
2912
2912
2913 # filter the perm rows by 'default' first and then sort them by
2913 # filter the perm rows by 'default' first and then sort them by
2914 # admin,write,read,none permissions sorted again alphabetically in
2914 # admin,write,read,none permissions sorted again alphabetically in
2915 # each group
2915 # each group
2916 perm_rows = sorted(perm_rows, key=display_user_sort)
2916 perm_rows = sorted(perm_rows, key=display_user_sort)
2917
2917
2918 user_groups_rows = []
2918 user_groups_rows = []
2919 if expand_from_user_groups:
2919 if expand_from_user_groups:
2920 for ug in self.permission_user_groups(with_members=True):
2920 for ug in self.permission_user_groups(with_members=True):
2921 for user_data in ug.members:
2921 for user_data in ug.members:
2922 user_groups_rows.append(user_data)
2922 user_groups_rows.append(user_data)
2923
2923
2924 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2924 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2925
2925
2926 def permission_user_groups(self, with_members=False):
2926 def permission_user_groups(self, with_members=False):
2927 q = UserGroupRepoGroupToPerm.query()\
2927 q = UserGroupRepoGroupToPerm.query()\
2928 .filter(UserGroupRepoGroupToPerm.group == self)
2928 .filter(UserGroupRepoGroupToPerm.group == self)
2929 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2929 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2930 joinedload(UserGroupRepoGroupToPerm.users_group),
2930 joinedload(UserGroupRepoGroupToPerm.users_group),
2931 joinedload(UserGroupRepoGroupToPerm.permission),)
2931 joinedload(UserGroupRepoGroupToPerm.permission),)
2932
2932
2933 perm_rows = []
2933 perm_rows = []
2934 for _user_group in q.all():
2934 for _user_group in q.all():
2935 entry = AttributeDict(_user_group.users_group.get_dict())
2935 entry = AttributeDict(_user_group.users_group.get_dict())
2936 entry.permission = _user_group.permission.permission_name
2936 entry.permission = _user_group.permission.permission_name
2937 if with_members:
2937 if with_members:
2938 entry.members = [x.user.get_dict()
2938 entry.members = [x.user.get_dict()
2939 for x in _user_group.users_group.members]
2939 for x in _user_group.users_group.members]
2940 perm_rows.append(entry)
2940 perm_rows.append(entry)
2941
2941
2942 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2942 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2943 return perm_rows
2943 return perm_rows
2944
2944
2945 def get_api_data(self):
2945 def get_api_data(self):
2946 """
2946 """
2947 Common function for generating api data
2947 Common function for generating api data
2948
2948
2949 """
2949 """
2950 group = self
2950 group = self
2951 data = {
2951 data = {
2952 'group_id': group.group_id,
2952 'group_id': group.group_id,
2953 'group_name': group.group_name,
2953 'group_name': group.group_name,
2954 'group_description': group.description_safe,
2954 'group_description': group.description_safe,
2955 'parent_group': group.parent_group.group_name if group.parent_group else None,
2955 'parent_group': group.parent_group.group_name if group.parent_group else None,
2956 'repositories': [x.repo_name for x in group.repositories],
2956 'repositories': [x.repo_name for x in group.repositories],
2957 'owner': group.user.username,
2957 'owner': group.user.username,
2958 }
2958 }
2959 return data
2959 return data
2960
2960
2961 def get_dict(self):
2961 def get_dict(self):
2962 # Since we transformed `group_name` to a hybrid property, we need to
2962 # Since we transformed `group_name` to a hybrid property, we need to
2963 # keep compatibility with the code which uses `group_name` field.
2963 # keep compatibility with the code which uses `group_name` field.
2964 result = super(RepoGroup, self).get_dict()
2964 result = super(RepoGroup, self).get_dict()
2965 result['group_name'] = result.pop('_group_name', None)
2965 result['group_name'] = result.pop('_group_name', None)
2966 return result
2966 return result
2967
2967
2968
2968
2969 class Permission(Base, BaseModel):
2969 class Permission(Base, BaseModel):
2970 __tablename__ = 'permissions'
2970 __tablename__ = 'permissions'
2971 __table_args__ = (
2971 __table_args__ = (
2972 Index('p_perm_name_idx', 'permission_name'),
2972 Index('p_perm_name_idx', 'permission_name'),
2973 base_table_args,
2973 base_table_args,
2974 )
2974 )
2975
2975
2976 PERMS = [
2976 PERMS = [
2977 ('hg.admin', _('RhodeCode Super Administrator')),
2977 ('hg.admin', _('RhodeCode Super Administrator')),
2978
2978
2979 ('repository.none', _('Repository no access')),
2979 ('repository.none', _('Repository no access')),
2980 ('repository.read', _('Repository read access')),
2980 ('repository.read', _('Repository read access')),
2981 ('repository.write', _('Repository write access')),
2981 ('repository.write', _('Repository write access')),
2982 ('repository.admin', _('Repository admin access')),
2982 ('repository.admin', _('Repository admin access')),
2983
2983
2984 ('group.none', _('Repository group no access')),
2984 ('group.none', _('Repository group no access')),
2985 ('group.read', _('Repository group read access')),
2985 ('group.read', _('Repository group read access')),
2986 ('group.write', _('Repository group write access')),
2986 ('group.write', _('Repository group write access')),
2987 ('group.admin', _('Repository group admin access')),
2987 ('group.admin', _('Repository group admin access')),
2988
2988
2989 ('usergroup.none', _('User group no access')),
2989 ('usergroup.none', _('User group no access')),
2990 ('usergroup.read', _('User group read access')),
2990 ('usergroup.read', _('User group read access')),
2991 ('usergroup.write', _('User group write access')),
2991 ('usergroup.write', _('User group write access')),
2992 ('usergroup.admin', _('User group admin access')),
2992 ('usergroup.admin', _('User group admin access')),
2993
2993
2994 ('branch.none', _('Branch no permissions')),
2994 ('branch.none', _('Branch no permissions')),
2995 ('branch.merge', _('Branch access by web merge')),
2995 ('branch.merge', _('Branch access by web merge')),
2996 ('branch.push', _('Branch access by push')),
2996 ('branch.push', _('Branch access by push')),
2997 ('branch.push_force', _('Branch access by push with force')),
2997 ('branch.push_force', _('Branch access by push with force')),
2998
2998
2999 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2999 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3000 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3000 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3001
3001
3002 ('hg.usergroup.create.false', _('User Group creation disabled')),
3002 ('hg.usergroup.create.false', _('User Group creation disabled')),
3003 ('hg.usergroup.create.true', _('User Group creation enabled')),
3003 ('hg.usergroup.create.true', _('User Group creation enabled')),
3004
3004
3005 ('hg.create.none', _('Repository creation disabled')),
3005 ('hg.create.none', _('Repository creation disabled')),
3006 ('hg.create.repository', _('Repository creation enabled')),
3006 ('hg.create.repository', _('Repository creation enabled')),
3007 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3007 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3008 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3008 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3009
3009
3010 ('hg.fork.none', _('Repository forking disabled')),
3010 ('hg.fork.none', _('Repository forking disabled')),
3011 ('hg.fork.repository', _('Repository forking enabled')),
3011 ('hg.fork.repository', _('Repository forking enabled')),
3012
3012
3013 ('hg.register.none', _('Registration disabled')),
3013 ('hg.register.none', _('Registration disabled')),
3014 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3014 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3015 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3015 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3016
3016
3017 ('hg.password_reset.enabled', _('Password reset enabled')),
3017 ('hg.password_reset.enabled', _('Password reset enabled')),
3018 ('hg.password_reset.hidden', _('Password reset hidden')),
3018 ('hg.password_reset.hidden', _('Password reset hidden')),
3019 ('hg.password_reset.disabled', _('Password reset disabled')),
3019 ('hg.password_reset.disabled', _('Password reset disabled')),
3020
3020
3021 ('hg.extern_activate.manual', _('Manual activation of external account')),
3021 ('hg.extern_activate.manual', _('Manual activation of external account')),
3022 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3022 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3023
3023
3024 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3024 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3025 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3025 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3026 ]
3026 ]
3027
3027
3028 # definition of system default permissions for DEFAULT user, created on
3028 # definition of system default permissions for DEFAULT user, created on
3029 # system setup
3029 # system setup
3030 DEFAULT_USER_PERMISSIONS = [
3030 DEFAULT_USER_PERMISSIONS = [
3031 # object perms
3031 # object perms
3032 'repository.read',
3032 'repository.read',
3033 'group.read',
3033 'group.read',
3034 'usergroup.read',
3034 'usergroup.read',
3035 # branch, for backward compat we need same value as before so forced pushed
3035 # branch, for backward compat we need same value as before so forced pushed
3036 'branch.push_force',
3036 'branch.push_force',
3037 # global
3037 # global
3038 'hg.create.repository',
3038 'hg.create.repository',
3039 'hg.repogroup.create.false',
3039 'hg.repogroup.create.false',
3040 'hg.usergroup.create.false',
3040 'hg.usergroup.create.false',
3041 'hg.create.write_on_repogroup.true',
3041 'hg.create.write_on_repogroup.true',
3042 'hg.fork.repository',
3042 'hg.fork.repository',
3043 'hg.register.manual_activate',
3043 'hg.register.manual_activate',
3044 'hg.password_reset.enabled',
3044 'hg.password_reset.enabled',
3045 'hg.extern_activate.auto',
3045 'hg.extern_activate.auto',
3046 'hg.inherit_default_perms.true',
3046 'hg.inherit_default_perms.true',
3047 ]
3047 ]
3048
3048
3049 # defines which permissions are more important higher the more important
3049 # defines which permissions are more important higher the more important
3050 # Weight defines which permissions are more important.
3050 # Weight defines which permissions are more important.
3051 # The higher number the more important.
3051 # The higher number the more important.
3052 PERM_WEIGHTS = {
3052 PERM_WEIGHTS = {
3053 'repository.none': 0,
3053 'repository.none': 0,
3054 'repository.read': 1,
3054 'repository.read': 1,
3055 'repository.write': 3,
3055 'repository.write': 3,
3056 'repository.admin': 4,
3056 'repository.admin': 4,
3057
3057
3058 'group.none': 0,
3058 'group.none': 0,
3059 'group.read': 1,
3059 'group.read': 1,
3060 'group.write': 3,
3060 'group.write': 3,
3061 'group.admin': 4,
3061 'group.admin': 4,
3062
3062
3063 'usergroup.none': 0,
3063 'usergroup.none': 0,
3064 'usergroup.read': 1,
3064 'usergroup.read': 1,
3065 'usergroup.write': 3,
3065 'usergroup.write': 3,
3066 'usergroup.admin': 4,
3066 'usergroup.admin': 4,
3067
3067
3068 'branch.none': 0,
3068 'branch.none': 0,
3069 'branch.merge': 1,
3069 'branch.merge': 1,
3070 'branch.push': 3,
3070 'branch.push': 3,
3071 'branch.push_force': 4,
3071 'branch.push_force': 4,
3072
3072
3073 'hg.repogroup.create.false': 0,
3073 'hg.repogroup.create.false': 0,
3074 'hg.repogroup.create.true': 1,
3074 'hg.repogroup.create.true': 1,
3075
3075
3076 'hg.usergroup.create.false': 0,
3076 'hg.usergroup.create.false': 0,
3077 'hg.usergroup.create.true': 1,
3077 'hg.usergroup.create.true': 1,
3078
3078
3079 'hg.fork.none': 0,
3079 'hg.fork.none': 0,
3080 'hg.fork.repository': 1,
3080 'hg.fork.repository': 1,
3081 'hg.create.none': 0,
3081 'hg.create.none': 0,
3082 'hg.create.repository': 1
3082 'hg.create.repository': 1
3083 }
3083 }
3084
3084
3085 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3085 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3086 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3086 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3087 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3087 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3088
3088
3089 def __unicode__(self):
3089 def __unicode__(self):
3090 return u"<%s('%s:%s')>" % (
3090 return u"<%s('%s:%s')>" % (
3091 self.__class__.__name__, self.permission_id, self.permission_name
3091 self.__class__.__name__, self.permission_id, self.permission_name
3092 )
3092 )
3093
3093
3094 @classmethod
3094 @classmethod
3095 def get_by_key(cls, key):
3095 def get_by_key(cls, key):
3096 return cls.query().filter(cls.permission_name == key).scalar()
3096 return cls.query().filter(cls.permission_name == key).scalar()
3097
3097
3098 @classmethod
3098 @classmethod
3099 def get_default_repo_perms(cls, user_id, repo_id=None):
3099 def get_default_repo_perms(cls, user_id, repo_id=None):
3100 q = Session().query(UserRepoToPerm, Repository, Permission)\
3100 q = Session().query(UserRepoToPerm, Repository, Permission)\
3101 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3101 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3102 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3102 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3103 .filter(UserRepoToPerm.user_id == user_id)
3103 .filter(UserRepoToPerm.user_id == user_id)
3104 if repo_id:
3104 if repo_id:
3105 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3105 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3106 return q.all()
3106 return q.all()
3107
3107
3108 @classmethod
3108 @classmethod
3109 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3109 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3110 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3110 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3111 .join(
3111 .join(
3112 Permission,
3112 Permission,
3113 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3113 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3114 .join(
3114 .join(
3115 UserRepoToPerm,
3115 UserRepoToPerm,
3116 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3116 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3117 .filter(UserRepoToPerm.user_id == user_id)
3117 .filter(UserRepoToPerm.user_id == user_id)
3118
3118
3119 if repo_id:
3119 if repo_id:
3120 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3120 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3121 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3121 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3122
3122
3123 @classmethod
3123 @classmethod
3124 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3124 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3125 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3125 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3126 .join(
3126 .join(
3127 Permission,
3127 Permission,
3128 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3128 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3129 .join(
3129 .join(
3130 Repository,
3130 Repository,
3131 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3131 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3132 .join(
3132 .join(
3133 UserGroup,
3133 UserGroup,
3134 UserGroupRepoToPerm.users_group_id ==
3134 UserGroupRepoToPerm.users_group_id ==
3135 UserGroup.users_group_id)\
3135 UserGroup.users_group_id)\
3136 .join(
3136 .join(
3137 UserGroupMember,
3137 UserGroupMember,
3138 UserGroupRepoToPerm.users_group_id ==
3138 UserGroupRepoToPerm.users_group_id ==
3139 UserGroupMember.users_group_id)\
3139 UserGroupMember.users_group_id)\
3140 .filter(
3140 .filter(
3141 UserGroupMember.user_id == user_id,
3141 UserGroupMember.user_id == user_id,
3142 UserGroup.users_group_active == true())
3142 UserGroup.users_group_active == true())
3143 if repo_id:
3143 if repo_id:
3144 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3144 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3145 return q.all()
3145 return q.all()
3146
3146
3147 @classmethod
3147 @classmethod
3148 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3148 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3149 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3149 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3150 .join(
3150 .join(
3151 Permission,
3151 Permission,
3152 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3152 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3153 .join(
3153 .join(
3154 UserGroupRepoToPerm,
3154 UserGroupRepoToPerm,
3155 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3155 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3156 .join(
3156 .join(
3157 UserGroup,
3157 UserGroup,
3158 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3158 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3159 .join(
3159 .join(
3160 UserGroupMember,
3160 UserGroupMember,
3161 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3161 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3162 .filter(
3162 .filter(
3163 UserGroupMember.user_id == user_id,
3163 UserGroupMember.user_id == user_id,
3164 UserGroup.users_group_active == true())
3164 UserGroup.users_group_active == true())
3165
3165
3166 if repo_id:
3166 if repo_id:
3167 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3167 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3168 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3168 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3169
3169
3170 @classmethod
3170 @classmethod
3171 def get_default_group_perms(cls, user_id, repo_group_id=None):
3171 def get_default_group_perms(cls, user_id, repo_group_id=None):
3172 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3172 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3173 .join(
3173 .join(
3174 Permission,
3174 Permission,
3175 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3175 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3176 .join(
3176 .join(
3177 RepoGroup,
3177 RepoGroup,
3178 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3178 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3179 .filter(UserRepoGroupToPerm.user_id == user_id)
3179 .filter(UserRepoGroupToPerm.user_id == user_id)
3180 if repo_group_id:
3180 if repo_group_id:
3181 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3181 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3182 return q.all()
3182 return q.all()
3183
3183
3184 @classmethod
3184 @classmethod
3185 def get_default_group_perms_from_user_group(
3185 def get_default_group_perms_from_user_group(
3186 cls, user_id, repo_group_id=None):
3186 cls, user_id, repo_group_id=None):
3187 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3187 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3188 .join(
3188 .join(
3189 Permission,
3189 Permission,
3190 UserGroupRepoGroupToPerm.permission_id ==
3190 UserGroupRepoGroupToPerm.permission_id ==
3191 Permission.permission_id)\
3191 Permission.permission_id)\
3192 .join(
3192 .join(
3193 RepoGroup,
3193 RepoGroup,
3194 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3194 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3195 .join(
3195 .join(
3196 UserGroup,
3196 UserGroup,
3197 UserGroupRepoGroupToPerm.users_group_id ==
3197 UserGroupRepoGroupToPerm.users_group_id ==
3198 UserGroup.users_group_id)\
3198 UserGroup.users_group_id)\
3199 .join(
3199 .join(
3200 UserGroupMember,
3200 UserGroupMember,
3201 UserGroupRepoGroupToPerm.users_group_id ==
3201 UserGroupRepoGroupToPerm.users_group_id ==
3202 UserGroupMember.users_group_id)\
3202 UserGroupMember.users_group_id)\
3203 .filter(
3203 .filter(
3204 UserGroupMember.user_id == user_id,
3204 UserGroupMember.user_id == user_id,
3205 UserGroup.users_group_active == true())
3205 UserGroup.users_group_active == true())
3206 if repo_group_id:
3206 if repo_group_id:
3207 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3207 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3208 return q.all()
3208 return q.all()
3209
3209
3210 @classmethod
3210 @classmethod
3211 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3211 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3212 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3212 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3213 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3213 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3214 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3214 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3215 .filter(UserUserGroupToPerm.user_id == user_id)
3215 .filter(UserUserGroupToPerm.user_id == user_id)
3216 if user_group_id:
3216 if user_group_id:
3217 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3217 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3218 return q.all()
3218 return q.all()
3219
3219
3220 @classmethod
3220 @classmethod
3221 def get_default_user_group_perms_from_user_group(
3221 def get_default_user_group_perms_from_user_group(
3222 cls, user_id, user_group_id=None):
3222 cls, user_id, user_group_id=None):
3223 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3223 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3224 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3224 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3225 .join(
3225 .join(
3226 Permission,
3226 Permission,
3227 UserGroupUserGroupToPerm.permission_id ==
3227 UserGroupUserGroupToPerm.permission_id ==
3228 Permission.permission_id)\
3228 Permission.permission_id)\
3229 .join(
3229 .join(
3230 TargetUserGroup,
3230 TargetUserGroup,
3231 UserGroupUserGroupToPerm.target_user_group_id ==
3231 UserGroupUserGroupToPerm.target_user_group_id ==
3232 TargetUserGroup.users_group_id)\
3232 TargetUserGroup.users_group_id)\
3233 .join(
3233 .join(
3234 UserGroup,
3234 UserGroup,
3235 UserGroupUserGroupToPerm.user_group_id ==
3235 UserGroupUserGroupToPerm.user_group_id ==
3236 UserGroup.users_group_id)\
3236 UserGroup.users_group_id)\
3237 .join(
3237 .join(
3238 UserGroupMember,
3238 UserGroupMember,
3239 UserGroupUserGroupToPerm.user_group_id ==
3239 UserGroupUserGroupToPerm.user_group_id ==
3240 UserGroupMember.users_group_id)\
3240 UserGroupMember.users_group_id)\
3241 .filter(
3241 .filter(
3242 UserGroupMember.user_id == user_id,
3242 UserGroupMember.user_id == user_id,
3243 UserGroup.users_group_active == true())
3243 UserGroup.users_group_active == true())
3244 if user_group_id:
3244 if user_group_id:
3245 q = q.filter(
3245 q = q.filter(
3246 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3246 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3247
3247
3248 return q.all()
3248 return q.all()
3249
3249
3250
3250
3251 class UserRepoToPerm(Base, BaseModel):
3251 class UserRepoToPerm(Base, BaseModel):
3252 __tablename__ = 'repo_to_perm'
3252 __tablename__ = 'repo_to_perm'
3253 __table_args__ = (
3253 __table_args__ = (
3254 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3254 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3255 base_table_args
3255 base_table_args
3256 )
3256 )
3257
3257
3258 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3258 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3259 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3260 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3260 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3261 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3261 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3262
3262
3263 user = relationship('User')
3263 user = relationship('User')
3264 repository = relationship('Repository')
3264 repository = relationship('Repository')
3265 permission = relationship('Permission')
3265 permission = relationship('Permission')
3266
3266
3267 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3267 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3268
3268
3269 @classmethod
3269 @classmethod
3270 def create(cls, user, repository, permission):
3270 def create(cls, user, repository, permission):
3271 n = cls()
3271 n = cls()
3272 n.user = user
3272 n.user = user
3273 n.repository = repository
3273 n.repository = repository
3274 n.permission = permission
3274 n.permission = permission
3275 Session().add(n)
3275 Session().add(n)
3276 return n
3276 return n
3277
3277
3278 def __unicode__(self):
3278 def __unicode__(self):
3279 return u'<%s => %s >' % (self.user, self.repository)
3279 return u'<%s => %s >' % (self.user, self.repository)
3280
3280
3281
3281
3282 class UserUserGroupToPerm(Base, BaseModel):
3282 class UserUserGroupToPerm(Base, BaseModel):
3283 __tablename__ = 'user_user_group_to_perm'
3283 __tablename__ = 'user_user_group_to_perm'
3284 __table_args__ = (
3284 __table_args__ = (
3285 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3285 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3286 base_table_args
3286 base_table_args
3287 )
3287 )
3288
3288
3289 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3289 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3291 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3291 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3292 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3292 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3293
3293
3294 user = relationship('User')
3294 user = relationship('User')
3295 user_group = relationship('UserGroup')
3295 user_group = relationship('UserGroup')
3296 permission = relationship('Permission')
3296 permission = relationship('Permission')
3297
3297
3298 @classmethod
3298 @classmethod
3299 def create(cls, user, user_group, permission):
3299 def create(cls, user, user_group, permission):
3300 n = cls()
3300 n = cls()
3301 n.user = user
3301 n.user = user
3302 n.user_group = user_group
3302 n.user_group = user_group
3303 n.permission = permission
3303 n.permission = permission
3304 Session().add(n)
3304 Session().add(n)
3305 return n
3305 return n
3306
3306
3307 def __unicode__(self):
3307 def __unicode__(self):
3308 return u'<%s => %s >' % (self.user, self.user_group)
3308 return u'<%s => %s >' % (self.user, self.user_group)
3309
3309
3310
3310
3311 class UserToPerm(Base, BaseModel):
3311 class UserToPerm(Base, BaseModel):
3312 __tablename__ = 'user_to_perm'
3312 __tablename__ = 'user_to_perm'
3313 __table_args__ = (
3313 __table_args__ = (
3314 UniqueConstraint('user_id', 'permission_id'),
3314 UniqueConstraint('user_id', 'permission_id'),
3315 base_table_args
3315 base_table_args
3316 )
3316 )
3317
3317
3318 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3318 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3319 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3319 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3320 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3320 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3321
3321
3322 user = relationship('User')
3322 user = relationship('User')
3323 permission = relationship('Permission', lazy='joined')
3323 permission = relationship('Permission', lazy='joined')
3324
3324
3325 def __unicode__(self):
3325 def __unicode__(self):
3326 return u'<%s => %s >' % (self.user, self.permission)
3326 return u'<%s => %s >' % (self.user, self.permission)
3327
3327
3328
3328
3329 class UserGroupRepoToPerm(Base, BaseModel):
3329 class UserGroupRepoToPerm(Base, BaseModel):
3330 __tablename__ = 'users_group_repo_to_perm'
3330 __tablename__ = 'users_group_repo_to_perm'
3331 __table_args__ = (
3331 __table_args__ = (
3332 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3332 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3333 base_table_args
3333 base_table_args
3334 )
3334 )
3335
3335
3336 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3336 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3337 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3337 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3338 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3338 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3339 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3339 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3340
3340
3341 users_group = relationship('UserGroup')
3341 users_group = relationship('UserGroup')
3342 permission = relationship('Permission')
3342 permission = relationship('Permission')
3343 repository = relationship('Repository')
3343 repository = relationship('Repository')
3344 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3344 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3345
3345
3346 @classmethod
3346 @classmethod
3347 def create(cls, users_group, repository, permission):
3347 def create(cls, users_group, repository, permission):
3348 n = cls()
3348 n = cls()
3349 n.users_group = users_group
3349 n.users_group = users_group
3350 n.repository = repository
3350 n.repository = repository
3351 n.permission = permission
3351 n.permission = permission
3352 Session().add(n)
3352 Session().add(n)
3353 return n
3353 return n
3354
3354
3355 def __unicode__(self):
3355 def __unicode__(self):
3356 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3356 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3357
3357
3358
3358
3359 class UserGroupUserGroupToPerm(Base, BaseModel):
3359 class UserGroupUserGroupToPerm(Base, BaseModel):
3360 __tablename__ = 'user_group_user_group_to_perm'
3360 __tablename__ = 'user_group_user_group_to_perm'
3361 __table_args__ = (
3361 __table_args__ = (
3362 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3362 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3363 CheckConstraint('target_user_group_id != user_group_id'),
3363 CheckConstraint('target_user_group_id != user_group_id'),
3364 base_table_args
3364 base_table_args
3365 )
3365 )
3366
3366
3367 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)
3367 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)
3368 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3368 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3369 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3369 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3370 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3370 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3371
3371
3372 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3372 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3373 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3373 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3374 permission = relationship('Permission')
3374 permission = relationship('Permission')
3375
3375
3376 @classmethod
3376 @classmethod
3377 def create(cls, target_user_group, user_group, permission):
3377 def create(cls, target_user_group, user_group, permission):
3378 n = cls()
3378 n = cls()
3379 n.target_user_group = target_user_group
3379 n.target_user_group = target_user_group
3380 n.user_group = user_group
3380 n.user_group = user_group
3381 n.permission = permission
3381 n.permission = permission
3382 Session().add(n)
3382 Session().add(n)
3383 return n
3383 return n
3384
3384
3385 def __unicode__(self):
3385 def __unicode__(self):
3386 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3386 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3387
3387
3388
3388
3389 class UserGroupToPerm(Base, BaseModel):
3389 class UserGroupToPerm(Base, BaseModel):
3390 __tablename__ = 'users_group_to_perm'
3390 __tablename__ = 'users_group_to_perm'
3391 __table_args__ = (
3391 __table_args__ = (
3392 UniqueConstraint('users_group_id', 'permission_id',),
3392 UniqueConstraint('users_group_id', 'permission_id',),
3393 base_table_args
3393 base_table_args
3394 )
3394 )
3395
3395
3396 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3396 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3397 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3397 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3398 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3398 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3399
3399
3400 users_group = relationship('UserGroup')
3400 users_group = relationship('UserGroup')
3401 permission = relationship('Permission')
3401 permission = relationship('Permission')
3402
3402
3403
3403
3404 class UserRepoGroupToPerm(Base, BaseModel):
3404 class UserRepoGroupToPerm(Base, BaseModel):
3405 __tablename__ = 'user_repo_group_to_perm'
3405 __tablename__ = 'user_repo_group_to_perm'
3406 __table_args__ = (
3406 __table_args__ = (
3407 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3407 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3408 base_table_args
3408 base_table_args
3409 )
3409 )
3410
3410
3411 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3411 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3413 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3413 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3415
3415
3416 user = relationship('User')
3416 user = relationship('User')
3417 group = relationship('RepoGroup')
3417 group = relationship('RepoGroup')
3418 permission = relationship('Permission')
3418 permission = relationship('Permission')
3419
3419
3420 @classmethod
3420 @classmethod
3421 def create(cls, user, repository_group, permission):
3421 def create(cls, user, repository_group, permission):
3422 n = cls()
3422 n = cls()
3423 n.user = user
3423 n.user = user
3424 n.group = repository_group
3424 n.group = repository_group
3425 n.permission = permission
3425 n.permission = permission
3426 Session().add(n)
3426 Session().add(n)
3427 return n
3427 return n
3428
3428
3429
3429
3430 class UserGroupRepoGroupToPerm(Base, BaseModel):
3430 class UserGroupRepoGroupToPerm(Base, BaseModel):
3431 __tablename__ = 'users_group_repo_group_to_perm'
3431 __tablename__ = 'users_group_repo_group_to_perm'
3432 __table_args__ = (
3432 __table_args__ = (
3433 UniqueConstraint('users_group_id', 'group_id'),
3433 UniqueConstraint('users_group_id', 'group_id'),
3434 base_table_args
3434 base_table_args
3435 )
3435 )
3436
3436
3437 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)
3437 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)
3438 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3438 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3439 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3439 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3440 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3440 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3441
3441
3442 users_group = relationship('UserGroup')
3442 users_group = relationship('UserGroup')
3443 permission = relationship('Permission')
3443 permission = relationship('Permission')
3444 group = relationship('RepoGroup')
3444 group = relationship('RepoGroup')
3445
3445
3446 @classmethod
3446 @classmethod
3447 def create(cls, user_group, repository_group, permission):
3447 def create(cls, user_group, repository_group, permission):
3448 n = cls()
3448 n = cls()
3449 n.users_group = user_group
3449 n.users_group = user_group
3450 n.group = repository_group
3450 n.group = repository_group
3451 n.permission = permission
3451 n.permission = permission
3452 Session().add(n)
3452 Session().add(n)
3453 return n
3453 return n
3454
3454
3455 def __unicode__(self):
3455 def __unicode__(self):
3456 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3456 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3457
3457
3458
3458
3459 class Statistics(Base, BaseModel):
3459 class Statistics(Base, BaseModel):
3460 __tablename__ = 'statistics'
3460 __tablename__ = 'statistics'
3461 __table_args__ = (
3461 __table_args__ = (
3462 base_table_args
3462 base_table_args
3463 )
3463 )
3464
3464
3465 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3465 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3466 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3466 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3467 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3467 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3468 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3468 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3469 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3469 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3470 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3470 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3471
3471
3472 repository = relationship('Repository', single_parent=True)
3472 repository = relationship('Repository', single_parent=True)
3473
3473
3474
3474
3475 class UserFollowing(Base, BaseModel):
3475 class UserFollowing(Base, BaseModel):
3476 __tablename__ = 'user_followings'
3476 __tablename__ = 'user_followings'
3477 __table_args__ = (
3477 __table_args__ = (
3478 UniqueConstraint('user_id', 'follows_repository_id'),
3478 UniqueConstraint('user_id', 'follows_repository_id'),
3479 UniqueConstraint('user_id', 'follows_user_id'),
3479 UniqueConstraint('user_id', 'follows_user_id'),
3480 base_table_args
3480 base_table_args
3481 )
3481 )
3482
3482
3483 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3483 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3484 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3484 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3485 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3485 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3486 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3486 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3487 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3487 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3488
3488
3489 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3489 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3490
3490
3491 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3491 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3492 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3492 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3493
3493
3494 @classmethod
3494 @classmethod
3495 def get_repo_followers(cls, repo_id):
3495 def get_repo_followers(cls, repo_id):
3496 return cls.query().filter(cls.follows_repo_id == repo_id)
3496 return cls.query().filter(cls.follows_repo_id == repo_id)
3497
3497
3498
3498
3499 class CacheKey(Base, BaseModel):
3499 class CacheKey(Base, BaseModel):
3500 __tablename__ = 'cache_invalidation'
3500 __tablename__ = 'cache_invalidation'
3501 __table_args__ = (
3501 __table_args__ = (
3502 UniqueConstraint('cache_key'),
3502 UniqueConstraint('cache_key'),
3503 Index('key_idx', 'cache_key'),
3503 Index('key_idx', 'cache_key'),
3504 base_table_args,
3504 base_table_args,
3505 )
3505 )
3506
3506
3507 CACHE_TYPE_FEED = 'FEED'
3507 CACHE_TYPE_FEED = 'FEED'
3508 CACHE_TYPE_README = 'README'
3508 CACHE_TYPE_README = 'README'
3509 # namespaces used to register process/thread aware caches
3509 # namespaces used to register process/thread aware caches
3510 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3510 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3511 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3511 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3512
3512
3513 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3513 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3514 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3514 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3515 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3515 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3516 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3516 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3517 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3517 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3518
3518
3519 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3519 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3520 self.cache_key = cache_key
3520 self.cache_key = cache_key
3521 self.cache_args = cache_args
3521 self.cache_args = cache_args
3522 self.cache_active = False
3522 self.cache_active = False
3523 # first key should be same for all entries, since all workers should share it
3523 # first key should be same for all entries, since all workers should share it
3524 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3524 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3525
3525
3526 def __unicode__(self):
3526 def __unicode__(self):
3527 return u"<%s('%s:%s[%s]')>" % (
3527 return u"<%s('%s:%s[%s]')>" % (
3528 self.__class__.__name__,
3528 self.__class__.__name__,
3529 self.cache_id, self.cache_key, self.cache_active)
3529 self.cache_id, self.cache_key, self.cache_active)
3530
3530
3531 def _cache_key_partition(self):
3531 def _cache_key_partition(self):
3532 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3532 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3533 return prefix, repo_name, suffix
3533 return prefix, repo_name, suffix
3534
3534
3535 def get_prefix(self):
3535 def get_prefix(self):
3536 """
3536 """
3537 Try to extract prefix from existing cache key. The key could consist
3537 Try to extract prefix from existing cache key. The key could consist
3538 of prefix, repo_name, suffix
3538 of prefix, repo_name, suffix
3539 """
3539 """
3540 # this returns prefix, repo_name, suffix
3540 # this returns prefix, repo_name, suffix
3541 return self._cache_key_partition()[0]
3541 return self._cache_key_partition()[0]
3542
3542
3543 def get_suffix(self):
3543 def get_suffix(self):
3544 """
3544 """
3545 get suffix that might have been used in _get_cache_key to
3545 get suffix that might have been used in _get_cache_key to
3546 generate self.cache_key. Only used for informational purposes
3546 generate self.cache_key. Only used for informational purposes
3547 in repo_edit.mako.
3547 in repo_edit.mako.
3548 """
3548 """
3549 # prefix, repo_name, suffix
3549 # prefix, repo_name, suffix
3550 return self._cache_key_partition()[2]
3550 return self._cache_key_partition()[2]
3551
3551
3552 @classmethod
3552 @classmethod
3553 def generate_new_state_uid(cls, based_on=None):
3553 def generate_new_state_uid(cls, based_on=None):
3554 if based_on:
3554 if based_on:
3555 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3555 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3556 else:
3556 else:
3557 return str(uuid.uuid4())
3557 return str(uuid.uuid4())
3558
3558
3559 @classmethod
3559 @classmethod
3560 def delete_all_cache(cls):
3560 def delete_all_cache(cls):
3561 """
3561 """
3562 Delete all cache keys from database.
3562 Delete all cache keys from database.
3563 Should only be run when all instances are down and all entries
3563 Should only be run when all instances are down and all entries
3564 thus stale.
3564 thus stale.
3565 """
3565 """
3566 cls.query().delete()
3566 cls.query().delete()
3567 Session().commit()
3567 Session().commit()
3568
3568
3569 @classmethod
3569 @classmethod
3570 def set_invalidate(cls, cache_uid, delete=False):
3570 def set_invalidate(cls, cache_uid, delete=False):
3571 """
3571 """
3572 Mark all caches of a repo as invalid in the database.
3572 Mark all caches of a repo as invalid in the database.
3573 """
3573 """
3574
3574
3575 try:
3575 try:
3576 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3576 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3577 if delete:
3577 if delete:
3578 qry.delete()
3578 qry.delete()
3579 log.debug('cache objects deleted for cache args %s',
3579 log.debug('cache objects deleted for cache args %s',
3580 safe_str(cache_uid))
3580 safe_str(cache_uid))
3581 else:
3581 else:
3582 qry.update({"cache_active": False,
3582 qry.update({"cache_active": False,
3583 "cache_state_uid": cls.generate_new_state_uid()})
3583 "cache_state_uid": cls.generate_new_state_uid()})
3584 log.debug('cache objects marked as invalid for cache args %s',
3584 log.debug('cache objects marked as invalid for cache args %s',
3585 safe_str(cache_uid))
3585 safe_str(cache_uid))
3586
3586
3587 Session().commit()
3587 Session().commit()
3588 except Exception:
3588 except Exception:
3589 log.exception(
3589 log.exception(
3590 'Cache key invalidation failed for cache args %s',
3590 'Cache key invalidation failed for cache args %s',
3591 safe_str(cache_uid))
3591 safe_str(cache_uid))
3592 Session().rollback()
3592 Session().rollback()
3593
3593
3594 @classmethod
3594 @classmethod
3595 def get_active_cache(cls, cache_key):
3595 def get_active_cache(cls, cache_key):
3596 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3596 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3597 if inv_obj:
3597 if inv_obj:
3598 return inv_obj
3598 return inv_obj
3599 return None
3599 return None
3600
3600
3601 @classmethod
3601 @classmethod
3602 def get_namespace_map(cls, namespace):
3602 def get_namespace_map(cls, namespace):
3603 return {
3603 return {
3604 x.cache_key: x
3604 x.cache_key: x
3605 for x in cls.query().filter(cls.cache_args == namespace)}
3605 for x in cls.query().filter(cls.cache_args == namespace)}
3606
3606
3607
3607
3608 class ChangesetComment(Base, BaseModel):
3608 class ChangesetComment(Base, BaseModel):
3609 __tablename__ = 'changeset_comments'
3609 __tablename__ = 'changeset_comments'
3610 __table_args__ = (
3610 __table_args__ = (
3611 Index('cc_revision_idx', 'revision'),
3611 Index('cc_revision_idx', 'revision'),
3612 base_table_args,
3612 base_table_args,
3613 )
3613 )
3614
3614
3615 COMMENT_OUTDATED = u'comment_outdated'
3615 COMMENT_OUTDATED = u'comment_outdated'
3616 COMMENT_TYPE_NOTE = u'note'
3616 COMMENT_TYPE_NOTE = u'note'
3617 COMMENT_TYPE_TODO = u'todo'
3617 COMMENT_TYPE_TODO = u'todo'
3618 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3618 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3619
3619
3620 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3620 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3621 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3621 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3622 revision = Column('revision', String(40), nullable=True)
3622 revision = Column('revision', String(40), nullable=True)
3623 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3623 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3624 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3624 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3625 line_no = Column('line_no', Unicode(10), nullable=True)
3625 line_no = Column('line_no', Unicode(10), nullable=True)
3626 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3626 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3627 f_path = Column('f_path', Unicode(1000), nullable=True)
3627 f_path = Column('f_path', Unicode(1000), nullable=True)
3628 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3628 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3629 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3629 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3630 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3630 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3631 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3631 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3632 renderer = Column('renderer', Unicode(64), nullable=True)
3632 renderer = Column('renderer', Unicode(64), nullable=True)
3633 display_state = Column('display_state', Unicode(128), nullable=True)
3633 display_state = Column('display_state', Unicode(128), nullable=True)
3634
3634
3635 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3635 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3636 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3636 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3637
3637
3638 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3638 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3639 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3639 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3640
3640
3641 author = relationship('User', lazy='joined')
3641 author = relationship('User', lazy='joined')
3642 repo = relationship('Repository')
3642 repo = relationship('Repository')
3643 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3643 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3644 pull_request = relationship('PullRequest', lazy='joined')
3644 pull_request = relationship('PullRequest', lazy='joined')
3645 pull_request_version = relationship('PullRequestVersion')
3645 pull_request_version = relationship('PullRequestVersion')
3646
3646
3647 @classmethod
3647 @classmethod
3648 def get_users(cls, revision=None, pull_request_id=None):
3648 def get_users(cls, revision=None, pull_request_id=None):
3649 """
3649 """
3650 Returns user associated with this ChangesetComment. ie those
3650 Returns user associated with this ChangesetComment. ie those
3651 who actually commented
3651 who actually commented
3652
3652
3653 :param cls:
3653 :param cls:
3654 :param revision:
3654 :param revision:
3655 """
3655 """
3656 q = Session().query(User)\
3656 q = Session().query(User)\
3657 .join(ChangesetComment.author)
3657 .join(ChangesetComment.author)
3658 if revision:
3658 if revision:
3659 q = q.filter(cls.revision == revision)
3659 q = q.filter(cls.revision == revision)
3660 elif pull_request_id:
3660 elif pull_request_id:
3661 q = q.filter(cls.pull_request_id == pull_request_id)
3661 q = q.filter(cls.pull_request_id == pull_request_id)
3662 return q.all()
3662 return q.all()
3663
3663
3664 @classmethod
3664 @classmethod
3665 def get_index_from_version(cls, pr_version, versions):
3665 def get_index_from_version(cls, pr_version, versions):
3666 num_versions = [x.pull_request_version_id for x in versions]
3666 num_versions = [x.pull_request_version_id for x in versions]
3667 try:
3667 try:
3668 return num_versions.index(pr_version) +1
3668 return num_versions.index(pr_version) +1
3669 except (IndexError, ValueError):
3669 except (IndexError, ValueError):
3670 return
3670 return
3671
3671
3672 @property
3672 @property
3673 def outdated(self):
3673 def outdated(self):
3674 return self.display_state == self.COMMENT_OUTDATED
3674 return self.display_state == self.COMMENT_OUTDATED
3675
3675
3676 def outdated_at_version(self, version):
3676 def outdated_at_version(self, version):
3677 """
3677 """
3678 Checks if comment is outdated for given pull request version
3678 Checks if comment is outdated for given pull request version
3679 """
3679 """
3680 return self.outdated and self.pull_request_version_id != version
3680 return self.outdated and self.pull_request_version_id != version
3681
3681
3682 def older_than_version(self, version):
3682 def older_than_version(self, version):
3683 """
3683 """
3684 Checks if comment is made from previous version than given
3684 Checks if comment is made from previous version than given
3685 """
3685 """
3686 if version is None:
3686 if version is None:
3687 return self.pull_request_version_id is not None
3687 return self.pull_request_version_id is not None
3688
3688
3689 return self.pull_request_version_id < version
3689 return self.pull_request_version_id < version
3690
3690
3691 @property
3691 @property
3692 def resolved(self):
3692 def resolved(self):
3693 return self.resolved_by[0] if self.resolved_by else None
3693 return self.resolved_by[0] if self.resolved_by else None
3694
3694
3695 @property
3695 @property
3696 def is_todo(self):
3696 def is_todo(self):
3697 return self.comment_type == self.COMMENT_TYPE_TODO
3697 return self.comment_type == self.COMMENT_TYPE_TODO
3698
3698
3699 @property
3699 @property
3700 def is_inline(self):
3700 def is_inline(self):
3701 return self.line_no and self.f_path
3701 return self.line_no and self.f_path
3702
3702
3703 def get_index_version(self, versions):
3703 def get_index_version(self, versions):
3704 return self.get_index_from_version(
3704 return self.get_index_from_version(
3705 self.pull_request_version_id, versions)
3705 self.pull_request_version_id, versions)
3706
3706
3707 def __repr__(self):
3707 def __repr__(self):
3708 if self.comment_id:
3708 if self.comment_id:
3709 return '<DB:Comment #%s>' % self.comment_id
3709 return '<DB:Comment #%s>' % self.comment_id
3710 else:
3710 else:
3711 return '<DB:Comment at %#x>' % id(self)
3711 return '<DB:Comment at %#x>' % id(self)
3712
3712
3713 def get_api_data(self):
3713 def get_api_data(self):
3714 comment = self
3714 comment = self
3715 data = {
3715 data = {
3716 'comment_id': comment.comment_id,
3716 'comment_id': comment.comment_id,
3717 'comment_type': comment.comment_type,
3717 'comment_type': comment.comment_type,
3718 'comment_text': comment.text,
3718 'comment_text': comment.text,
3719 'comment_status': comment.status_change,
3719 'comment_status': comment.status_change,
3720 'comment_f_path': comment.f_path,
3720 'comment_f_path': comment.f_path,
3721 'comment_lineno': comment.line_no,
3721 'comment_lineno': comment.line_no,
3722 'comment_author': comment.author,
3722 'comment_author': comment.author,
3723 'comment_created_on': comment.created_on,
3723 'comment_created_on': comment.created_on,
3724 'comment_resolved_by': self.resolved
3724 'comment_resolved_by': self.resolved
3725 }
3725 }
3726 return data
3726 return data
3727
3727
3728 def __json__(self):
3728 def __json__(self):
3729 data = dict()
3729 data = dict()
3730 data.update(self.get_api_data())
3730 data.update(self.get_api_data())
3731 return data
3731 return data
3732
3732
3733
3733
3734 class ChangesetStatus(Base, BaseModel):
3734 class ChangesetStatus(Base, BaseModel):
3735 __tablename__ = 'changeset_statuses'
3735 __tablename__ = 'changeset_statuses'
3736 __table_args__ = (
3736 __table_args__ = (
3737 Index('cs_revision_idx', 'revision'),
3737 Index('cs_revision_idx', 'revision'),
3738 Index('cs_version_idx', 'version'),
3738 Index('cs_version_idx', 'version'),
3739 UniqueConstraint('repo_id', 'revision', 'version'),
3739 UniqueConstraint('repo_id', 'revision', 'version'),
3740 base_table_args
3740 base_table_args
3741 )
3741 )
3742
3742
3743 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3743 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3744 STATUS_APPROVED = 'approved'
3744 STATUS_APPROVED = 'approved'
3745 STATUS_REJECTED = 'rejected'
3745 STATUS_REJECTED = 'rejected'
3746 STATUS_UNDER_REVIEW = 'under_review'
3746 STATUS_UNDER_REVIEW = 'under_review'
3747
3747
3748 STATUSES = [
3748 STATUSES = [
3749 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3749 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3750 (STATUS_APPROVED, _("Approved")),
3750 (STATUS_APPROVED, _("Approved")),
3751 (STATUS_REJECTED, _("Rejected")),
3751 (STATUS_REJECTED, _("Rejected")),
3752 (STATUS_UNDER_REVIEW, _("Under Review")),
3752 (STATUS_UNDER_REVIEW, _("Under Review")),
3753 ]
3753 ]
3754
3754
3755 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3755 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3756 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3756 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3757 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3757 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3758 revision = Column('revision', String(40), nullable=False)
3758 revision = Column('revision', String(40), nullable=False)
3759 status = Column('status', String(128), nullable=False, default=DEFAULT)
3759 status = Column('status', String(128), nullable=False, default=DEFAULT)
3760 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3760 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3761 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3761 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3762 version = Column('version', Integer(), nullable=False, default=0)
3762 version = Column('version', Integer(), nullable=False, default=0)
3763 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3763 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3764
3764
3765 author = relationship('User', lazy='joined')
3765 author = relationship('User', lazy='joined')
3766 repo = relationship('Repository')
3766 repo = relationship('Repository')
3767 comment = relationship('ChangesetComment', lazy='joined')
3767 comment = relationship('ChangesetComment', lazy='joined')
3768 pull_request = relationship('PullRequest', lazy='joined')
3768 pull_request = relationship('PullRequest', lazy='joined')
3769
3769
3770 def __unicode__(self):
3770 def __unicode__(self):
3771 return u"<%s('%s[v%s]:%s')>" % (
3771 return u"<%s('%s[v%s]:%s')>" % (
3772 self.__class__.__name__,
3772 self.__class__.__name__,
3773 self.status, self.version, self.author
3773 self.status, self.version, self.author
3774 )
3774 )
3775
3775
3776 @classmethod
3776 @classmethod
3777 def get_status_lbl(cls, value):
3777 def get_status_lbl(cls, value):
3778 return dict(cls.STATUSES).get(value)
3778 return dict(cls.STATUSES).get(value)
3779
3779
3780 @property
3780 @property
3781 def status_lbl(self):
3781 def status_lbl(self):
3782 return ChangesetStatus.get_status_lbl(self.status)
3782 return ChangesetStatus.get_status_lbl(self.status)
3783
3783
3784 def get_api_data(self):
3784 def get_api_data(self):
3785 status = self
3785 status = self
3786 data = {
3786 data = {
3787 'status_id': status.changeset_status_id,
3787 'status_id': status.changeset_status_id,
3788 'status': status.status,
3788 'status': status.status,
3789 }
3789 }
3790 return data
3790 return data
3791
3791
3792 def __json__(self):
3792 def __json__(self):
3793 data = dict()
3793 data = dict()
3794 data.update(self.get_api_data())
3794 data.update(self.get_api_data())
3795 return data
3795 return data
3796
3796
3797
3797
3798 class _SetState(object):
3798 class _SetState(object):
3799 """
3799 """
3800 Context processor allowing changing state for sensitive operation such as
3800 Context processor allowing changing state for sensitive operation such as
3801 pull request update or merge
3801 pull request update or merge
3802 """
3802 """
3803
3803
3804 def __init__(self, pull_request, pr_state, back_state=None):
3804 def __init__(self, pull_request, pr_state, back_state=None):
3805 self._pr = pull_request
3805 self._pr = pull_request
3806 self._org_state = back_state or pull_request.pull_request_state
3806 self._org_state = back_state or pull_request.pull_request_state
3807 self._pr_state = pr_state
3807 self._pr_state = pr_state
3808
3808
3809 def __enter__(self):
3809 def __enter__(self):
3810 log.debug('StateLock: entering set state context, setting state to: `%s`',
3810 log.debug('StateLock: entering set state context, setting state to: `%s`',
3811 self._pr_state)
3811 self._pr_state)
3812 self._pr.pull_request_state = self._pr_state
3812 self._pr.pull_request_state = self._pr_state
3813 Session().add(self._pr)
3813 Session().add(self._pr)
3814 Session().commit()
3814 Session().commit()
3815
3815
3816 def __exit__(self, exc_type, exc_val, exc_tb):
3816 def __exit__(self, exc_type, exc_val, exc_tb):
3817 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3817 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3818 self._org_state)
3818 self._org_state)
3819 self._pr.pull_request_state = self._org_state
3819 self._pr.pull_request_state = self._org_state
3820 Session().add(self._pr)
3820 Session().add(self._pr)
3821 Session().commit()
3821 Session().commit()
3822
3822
3823
3823
3824 class _PullRequestBase(BaseModel):
3824 class _PullRequestBase(BaseModel):
3825 """
3825 """
3826 Common attributes of pull request and version entries.
3826 Common attributes of pull request and version entries.
3827 """
3827 """
3828
3828
3829 # .status values
3829 # .status values
3830 STATUS_NEW = u'new'
3830 STATUS_NEW = u'new'
3831 STATUS_OPEN = u'open'
3831 STATUS_OPEN = u'open'
3832 STATUS_CLOSED = u'closed'
3832 STATUS_CLOSED = u'closed'
3833
3833
3834 # available states
3834 # available states
3835 STATE_CREATING = u'creating'
3835 STATE_CREATING = u'creating'
3836 STATE_UPDATING = u'updating'
3836 STATE_UPDATING = u'updating'
3837 STATE_MERGING = u'merging'
3837 STATE_MERGING = u'merging'
3838 STATE_CREATED = u'created'
3838 STATE_CREATED = u'created'
3839
3839
3840 title = Column('title', Unicode(255), nullable=True)
3840 title = Column('title', Unicode(255), nullable=True)
3841 description = Column(
3841 description = Column(
3842 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3842 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3843 nullable=True)
3843 nullable=True)
3844 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3844 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3845
3845
3846 # new/open/closed status of pull request (not approve/reject/etc)
3846 # new/open/closed status of pull request (not approve/reject/etc)
3847 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3847 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3848 created_on = Column(
3848 created_on = Column(
3849 'created_on', DateTime(timezone=False), nullable=False,
3849 'created_on', DateTime(timezone=False), nullable=False,
3850 default=datetime.datetime.now)
3850 default=datetime.datetime.now)
3851 updated_on = Column(
3851 updated_on = Column(
3852 'updated_on', DateTime(timezone=False), nullable=False,
3852 'updated_on', DateTime(timezone=False), nullable=False,
3853 default=datetime.datetime.now)
3853 default=datetime.datetime.now)
3854
3854
3855 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3855 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3856
3856
3857 @declared_attr
3857 @declared_attr
3858 def user_id(cls):
3858 def user_id(cls):
3859 return Column(
3859 return Column(
3860 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3860 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3861 unique=None)
3861 unique=None)
3862
3862
3863 # 500 revisions max
3863 # 500 revisions max
3864 _revisions = Column(
3864 _revisions = Column(
3865 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3865 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3866
3866
3867 @declared_attr
3867 @declared_attr
3868 def source_repo_id(cls):
3868 def source_repo_id(cls):
3869 # TODO: dan: rename column to source_repo_id
3869 # TODO: dan: rename column to source_repo_id
3870 return Column(
3870 return Column(
3871 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3871 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3872 nullable=False)
3872 nullable=False)
3873
3873
3874 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3874 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3875
3875
3876 @hybrid_property
3876 @hybrid_property
3877 def source_ref(self):
3877 def source_ref(self):
3878 return self._source_ref
3878 return self._source_ref
3879
3879
3880 @source_ref.setter
3880 @source_ref.setter
3881 def source_ref(self, val):
3881 def source_ref(self, val):
3882 parts = (val or '').split(':')
3882 parts = (val or '').split(':')
3883 if len(parts) != 3:
3883 if len(parts) != 3:
3884 raise ValueError(
3884 raise ValueError(
3885 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3885 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3886 self._source_ref = safe_unicode(val)
3886 self._source_ref = safe_unicode(val)
3887
3887
3888 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3888 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3889
3889
3890 @hybrid_property
3890 @hybrid_property
3891 def target_ref(self):
3891 def target_ref(self):
3892 return self._target_ref
3892 return self._target_ref
3893
3893
3894 @target_ref.setter
3894 @target_ref.setter
3895 def target_ref(self, val):
3895 def target_ref(self, val):
3896 parts = (val or '').split(':')
3896 parts = (val or '').split(':')
3897 if len(parts) != 3:
3897 if len(parts) != 3:
3898 raise ValueError(
3898 raise ValueError(
3899 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3899 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3900 self._target_ref = safe_unicode(val)
3900 self._target_ref = safe_unicode(val)
3901
3901
3902 @declared_attr
3902 @declared_attr
3903 def target_repo_id(cls):
3903 def target_repo_id(cls):
3904 # TODO: dan: rename column to target_repo_id
3904 # TODO: dan: rename column to target_repo_id
3905 return Column(
3905 return Column(
3906 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3906 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3907 nullable=False)
3907 nullable=False)
3908
3908
3909 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3909 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3910
3910
3911 # TODO: dan: rename column to last_merge_source_rev
3911 # TODO: dan: rename column to last_merge_source_rev
3912 _last_merge_source_rev = Column(
3912 _last_merge_source_rev = Column(
3913 'last_merge_org_rev', String(40), nullable=True)
3913 'last_merge_org_rev', String(40), nullable=True)
3914 # TODO: dan: rename column to last_merge_target_rev
3914 # TODO: dan: rename column to last_merge_target_rev
3915 _last_merge_target_rev = Column(
3915 _last_merge_target_rev = Column(
3916 'last_merge_other_rev', String(40), nullable=True)
3916 'last_merge_other_rev', String(40), nullable=True)
3917 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3917 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3918 merge_rev = Column('merge_rev', String(40), nullable=True)
3918 merge_rev = Column('merge_rev', String(40), nullable=True)
3919
3919
3920 reviewer_data = Column(
3920 reviewer_data = Column(
3921 'reviewer_data_json', MutationObj.as_mutable(
3921 'reviewer_data_json', MutationObj.as_mutable(
3922 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3922 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3923
3923
3924 @property
3924 @property
3925 def reviewer_data_json(self):
3925 def reviewer_data_json(self):
3926 return json.dumps(self.reviewer_data)
3926 return json.dumps(self.reviewer_data)
3927
3927
3928 @hybrid_property
3928 @hybrid_property
3929 def description_safe(self):
3929 def description_safe(self):
3930 from rhodecode.lib import helpers as h
3930 from rhodecode.lib import helpers as h
3931 return h.escape(self.description)
3931 return h.escape(self.description)
3932
3932
3933 @hybrid_property
3933 @hybrid_property
3934 def revisions(self):
3934 def revisions(self):
3935 return self._revisions.split(':') if self._revisions else []
3935 return self._revisions.split(':') if self._revisions else []
3936
3936
3937 @revisions.setter
3937 @revisions.setter
3938 def revisions(self, val):
3938 def revisions(self, val):
3939 self._revisions = ':'.join(val)
3939 self._revisions = ':'.join(val)
3940
3940
3941 @hybrid_property
3941 @hybrid_property
3942 def last_merge_status(self):
3942 def last_merge_status(self):
3943 return safe_int(self._last_merge_status)
3943 return safe_int(self._last_merge_status)
3944
3944
3945 @last_merge_status.setter
3945 @last_merge_status.setter
3946 def last_merge_status(self, val):
3946 def last_merge_status(self, val):
3947 self._last_merge_status = val
3947 self._last_merge_status = val
3948
3948
3949 @declared_attr
3949 @declared_attr
3950 def author(cls):
3950 def author(cls):
3951 return relationship('User', lazy='joined')
3951 return relationship('User', lazy='joined')
3952
3952
3953 @declared_attr
3953 @declared_attr
3954 def source_repo(cls):
3954 def source_repo(cls):
3955 return relationship(
3955 return relationship(
3956 'Repository',
3956 'Repository',
3957 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3957 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3958
3958
3959 @property
3959 @property
3960 def source_ref_parts(self):
3960 def source_ref_parts(self):
3961 return self.unicode_to_reference(self.source_ref)
3961 return self.unicode_to_reference(self.source_ref)
3962
3962
3963 @declared_attr
3963 @declared_attr
3964 def target_repo(cls):
3964 def target_repo(cls):
3965 return relationship(
3965 return relationship(
3966 'Repository',
3966 'Repository',
3967 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3967 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3968
3968
3969 @property
3969 @property
3970 def target_ref_parts(self):
3970 def target_ref_parts(self):
3971 return self.unicode_to_reference(self.target_ref)
3971 return self.unicode_to_reference(self.target_ref)
3972
3972
3973 @property
3973 @property
3974 def shadow_merge_ref(self):
3974 def shadow_merge_ref(self):
3975 return self.unicode_to_reference(self._shadow_merge_ref)
3975 return self.unicode_to_reference(self._shadow_merge_ref)
3976
3976
3977 @shadow_merge_ref.setter
3977 @shadow_merge_ref.setter
3978 def shadow_merge_ref(self, ref):
3978 def shadow_merge_ref(self, ref):
3979 self._shadow_merge_ref = self.reference_to_unicode(ref)
3979 self._shadow_merge_ref = self.reference_to_unicode(ref)
3980
3980
3981 @staticmethod
3981 @staticmethod
3982 def unicode_to_reference(raw):
3982 def unicode_to_reference(raw):
3983 """
3983 """
3984 Convert a unicode (or string) to a reference object.
3984 Convert a unicode (or string) to a reference object.
3985 If unicode evaluates to False it returns None.
3985 If unicode evaluates to False it returns None.
3986 """
3986 """
3987 if raw:
3987 if raw:
3988 refs = raw.split(':')
3988 refs = raw.split(':')
3989 return Reference(*refs)
3989 return Reference(*refs)
3990 else:
3990 else:
3991 return None
3991 return None
3992
3992
3993 @staticmethod
3993 @staticmethod
3994 def reference_to_unicode(ref):
3994 def reference_to_unicode(ref):
3995 """
3995 """
3996 Convert a reference object to unicode.
3996 Convert a reference object to unicode.
3997 If reference is None it returns None.
3997 If reference is None it returns None.
3998 """
3998 """
3999 if ref:
3999 if ref:
4000 return u':'.join(ref)
4000 return u':'.join(ref)
4001 else:
4001 else:
4002 return None
4002 return None
4003
4003
4004 def get_api_data(self, with_merge_state=True):
4004 def get_api_data(self, with_merge_state=True):
4005 from rhodecode.model.pull_request import PullRequestModel
4005 from rhodecode.model.pull_request import PullRequestModel
4006
4006
4007 pull_request = self
4007 pull_request = self
4008 if with_merge_state:
4008 if with_merge_state:
4009 merge_status = PullRequestModel().merge_status(pull_request)
4009 merge_status = PullRequestModel().merge_status(pull_request)
4010 merge_state = {
4010 merge_state = {
4011 'status': merge_status[0],
4011 'status': merge_status[0],
4012 'message': safe_unicode(merge_status[1]),
4012 'message': safe_unicode(merge_status[1]),
4013 }
4013 }
4014 else:
4014 else:
4015 merge_state = {'status': 'not_available',
4015 merge_state = {'status': 'not_available',
4016 'message': 'not_available'}
4016 'message': 'not_available'}
4017
4017
4018 merge_data = {
4018 merge_data = {
4019 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4019 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4020 'reference': (
4020 'reference': (
4021 pull_request.shadow_merge_ref._asdict()
4021 pull_request.shadow_merge_ref._asdict()
4022 if pull_request.shadow_merge_ref else None),
4022 if pull_request.shadow_merge_ref else None),
4023 }
4023 }
4024
4024
4025 data = {
4025 data = {
4026 'pull_request_id': pull_request.pull_request_id,
4026 'pull_request_id': pull_request.pull_request_id,
4027 'url': PullRequestModel().get_url(pull_request),
4027 'url': PullRequestModel().get_url(pull_request),
4028 'title': pull_request.title,
4028 'title': pull_request.title,
4029 'description': pull_request.description,
4029 'description': pull_request.description,
4030 'status': pull_request.status,
4030 'status': pull_request.status,
4031 'state': pull_request.pull_request_state,
4031 'state': pull_request.pull_request_state,
4032 'created_on': pull_request.created_on,
4032 'created_on': pull_request.created_on,
4033 'updated_on': pull_request.updated_on,
4033 'updated_on': pull_request.updated_on,
4034 'commit_ids': pull_request.revisions,
4034 'commit_ids': pull_request.revisions,
4035 'review_status': pull_request.calculated_review_status(),
4035 'review_status': pull_request.calculated_review_status(),
4036 'mergeable': merge_state,
4036 'mergeable': merge_state,
4037 'source': {
4037 'source': {
4038 'clone_url': pull_request.source_repo.clone_url(),
4038 'clone_url': pull_request.source_repo.clone_url(),
4039 'repository': pull_request.source_repo.repo_name,
4039 'repository': pull_request.source_repo.repo_name,
4040 'reference': {
4040 'reference': {
4041 'name': pull_request.source_ref_parts.name,
4041 'name': pull_request.source_ref_parts.name,
4042 'type': pull_request.source_ref_parts.type,
4042 'type': pull_request.source_ref_parts.type,
4043 'commit_id': pull_request.source_ref_parts.commit_id,
4043 'commit_id': pull_request.source_ref_parts.commit_id,
4044 },
4044 },
4045 },
4045 },
4046 'target': {
4046 'target': {
4047 'clone_url': pull_request.target_repo.clone_url(),
4047 'clone_url': pull_request.target_repo.clone_url(),
4048 'repository': pull_request.target_repo.repo_name,
4048 'repository': pull_request.target_repo.repo_name,
4049 'reference': {
4049 'reference': {
4050 'name': pull_request.target_ref_parts.name,
4050 'name': pull_request.target_ref_parts.name,
4051 'type': pull_request.target_ref_parts.type,
4051 'type': pull_request.target_ref_parts.type,
4052 'commit_id': pull_request.target_ref_parts.commit_id,
4052 'commit_id': pull_request.target_ref_parts.commit_id,
4053 },
4053 },
4054 },
4054 },
4055 'merge': merge_data,
4055 'merge': merge_data,
4056 'author': pull_request.author.get_api_data(include_secrets=False,
4056 'author': pull_request.author.get_api_data(include_secrets=False,
4057 details='basic'),
4057 details='basic'),
4058 'reviewers': [
4058 'reviewers': [
4059 {
4059 {
4060 'user': reviewer.get_api_data(include_secrets=False,
4060 'user': reviewer.get_api_data(include_secrets=False,
4061 details='basic'),
4061 details='basic'),
4062 'reasons': reasons,
4062 'reasons': reasons,
4063 'review_status': st[0][1].status if st else 'not_reviewed',
4063 'review_status': st[0][1].status if st else 'not_reviewed',
4064 }
4064 }
4065 for obj, reviewer, reasons, mandatory, st in
4065 for obj, reviewer, reasons, mandatory, st in
4066 pull_request.reviewers_statuses()
4066 pull_request.reviewers_statuses()
4067 ]
4067 ]
4068 }
4068 }
4069
4069
4070 return data
4070 return data
4071
4071
4072 def set_state(self, pull_request_state, final_state=None):
4072 def set_state(self, pull_request_state, final_state=None):
4073 """
4073 """
4074 # goes from initial state to updating to initial state.
4074 # goes from initial state to updating to initial state.
4075 # initial state can be changed by specifying back_state=
4075 # initial state can be changed by specifying back_state=
4076 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4076 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4077 pull_request.merge()
4077 pull_request.merge()
4078
4078
4079 :param pull_request_state:
4079 :param pull_request_state:
4080 :param final_state:
4080 :param final_state:
4081
4081
4082 """
4082 """
4083
4083
4084 return _SetState(self, pull_request_state, back_state=final_state)
4084 return _SetState(self, pull_request_state, back_state=final_state)
4085
4085
4086
4086
4087 class PullRequest(Base, _PullRequestBase):
4087 class PullRequest(Base, _PullRequestBase):
4088 __tablename__ = 'pull_requests'
4088 __tablename__ = 'pull_requests'
4089 __table_args__ = (
4089 __table_args__ = (
4090 base_table_args,
4090 base_table_args,
4091 )
4091 )
4092
4092
4093 pull_request_id = Column(
4093 pull_request_id = Column(
4094 'pull_request_id', Integer(), nullable=False, primary_key=True)
4094 'pull_request_id', Integer(), nullable=False, primary_key=True)
4095
4095
4096 def __repr__(self):
4096 def __repr__(self):
4097 if self.pull_request_id:
4097 if self.pull_request_id:
4098 return '<DB:PullRequest #%s>' % self.pull_request_id
4098 return '<DB:PullRequest #%s>' % self.pull_request_id
4099 else:
4099 else:
4100 return '<DB:PullRequest at %#x>' % id(self)
4100 return '<DB:PullRequest at %#x>' % id(self)
4101
4101
4102 reviewers = relationship('PullRequestReviewers',
4102 reviewers = relationship('PullRequestReviewers',
4103 cascade="all, delete, delete-orphan")
4103 cascade="all, delete, delete-orphan")
4104 statuses = relationship('ChangesetStatus',
4104 statuses = relationship('ChangesetStatus',
4105 cascade="all, delete, delete-orphan")
4105 cascade="all, delete, delete-orphan")
4106 comments = relationship('ChangesetComment',
4106 comments = relationship('ChangesetComment',
4107 cascade="all, delete, delete-orphan")
4107 cascade="all, delete, delete-orphan")
4108 versions = relationship('PullRequestVersion',
4108 versions = relationship('PullRequestVersion',
4109 cascade="all, delete, delete-orphan",
4109 cascade="all, delete, delete-orphan",
4110 lazy='dynamic')
4110 lazy='dynamic')
4111
4111
4112 @classmethod
4112 @classmethod
4113 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4113 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4114 internal_methods=None):
4114 internal_methods=None):
4115
4115
4116 class PullRequestDisplay(object):
4116 class PullRequestDisplay(object):
4117 """
4117 """
4118 Special object wrapper for showing PullRequest data via Versions
4118 Special object wrapper for showing PullRequest data via Versions
4119 It mimics PR object as close as possible. This is read only object
4119 It mimics PR object as close as possible. This is read only object
4120 just for display
4120 just for display
4121 """
4121 """
4122
4122
4123 def __init__(self, attrs, internal=None):
4123 def __init__(self, attrs, internal=None):
4124 self.attrs = attrs
4124 self.attrs = attrs
4125 # internal have priority over the given ones via attrs
4125 # internal have priority over the given ones via attrs
4126 self.internal = internal or ['versions']
4126 self.internal = internal or ['versions']
4127
4127
4128 def __getattr__(self, item):
4128 def __getattr__(self, item):
4129 if item in self.internal:
4129 if item in self.internal:
4130 return getattr(self, item)
4130 return getattr(self, item)
4131 try:
4131 try:
4132 return self.attrs[item]
4132 return self.attrs[item]
4133 except KeyError:
4133 except KeyError:
4134 raise AttributeError(
4134 raise AttributeError(
4135 '%s object has no attribute %s' % (self, item))
4135 '%s object has no attribute %s' % (self, item))
4136
4136
4137 def __repr__(self):
4137 def __repr__(self):
4138 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4138 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4139
4139
4140 def versions(self):
4140 def versions(self):
4141 return pull_request_obj.versions.order_by(
4141 return pull_request_obj.versions.order_by(
4142 PullRequestVersion.pull_request_version_id).all()
4142 PullRequestVersion.pull_request_version_id).all()
4143
4143
4144 def is_closed(self):
4144 def is_closed(self):
4145 return pull_request_obj.is_closed()
4145 return pull_request_obj.is_closed()
4146
4146
4147 @property
4147 @property
4148 def pull_request_version_id(self):
4148 def pull_request_version_id(self):
4149 return getattr(pull_request_obj, 'pull_request_version_id', None)
4149 return getattr(pull_request_obj, 'pull_request_version_id', None)
4150
4150
4151 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4151 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4152
4152
4153 attrs.author = StrictAttributeDict(
4153 attrs.author = StrictAttributeDict(
4154 pull_request_obj.author.get_api_data())
4154 pull_request_obj.author.get_api_data())
4155 if pull_request_obj.target_repo:
4155 if pull_request_obj.target_repo:
4156 attrs.target_repo = StrictAttributeDict(
4156 attrs.target_repo = StrictAttributeDict(
4157 pull_request_obj.target_repo.get_api_data())
4157 pull_request_obj.target_repo.get_api_data())
4158 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4158 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4159
4159
4160 if pull_request_obj.source_repo:
4160 if pull_request_obj.source_repo:
4161 attrs.source_repo = StrictAttributeDict(
4161 attrs.source_repo = StrictAttributeDict(
4162 pull_request_obj.source_repo.get_api_data())
4162 pull_request_obj.source_repo.get_api_data())
4163 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4163 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4164
4164
4165 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4165 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4166 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4166 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4167 attrs.revisions = pull_request_obj.revisions
4167 attrs.revisions = pull_request_obj.revisions
4168
4168
4169 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4169 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4170 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4170 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4171 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4171 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4172
4172
4173 return PullRequestDisplay(attrs, internal=internal_methods)
4173 return PullRequestDisplay(attrs, internal=internal_methods)
4174
4174
4175 def is_closed(self):
4175 def is_closed(self):
4176 return self.status == self.STATUS_CLOSED
4176 return self.status == self.STATUS_CLOSED
4177
4177
4178 def __json__(self):
4178 def __json__(self):
4179 return {
4179 return {
4180 'revisions': self.revisions,
4180 'revisions': self.revisions,
4181 }
4181 }
4182
4182
4183 def calculated_review_status(self):
4183 def calculated_review_status(self):
4184 from rhodecode.model.changeset_status import ChangesetStatusModel
4184 from rhodecode.model.changeset_status import ChangesetStatusModel
4185 return ChangesetStatusModel().calculated_review_status(self)
4185 return ChangesetStatusModel().calculated_review_status(self)
4186
4186
4187 def reviewers_statuses(self):
4187 def reviewers_statuses(self):
4188 from rhodecode.model.changeset_status import ChangesetStatusModel
4188 from rhodecode.model.changeset_status import ChangesetStatusModel
4189 return ChangesetStatusModel().reviewers_statuses(self)
4189 return ChangesetStatusModel().reviewers_statuses(self)
4190
4190
4191 @property
4191 @property
4192 def workspace_id(self):
4192 def workspace_id(self):
4193 from rhodecode.model.pull_request import PullRequestModel
4193 from rhodecode.model.pull_request import PullRequestModel
4194 return PullRequestModel()._workspace_id(self)
4194 return PullRequestModel()._workspace_id(self)
4195
4195
4196 def get_shadow_repo(self):
4196 def get_shadow_repo(self):
4197 workspace_id = self.workspace_id
4197 workspace_id = self.workspace_id
4198 vcs_obj = self.target_repo.scm_instance()
4198 vcs_obj = self.target_repo.scm_instance()
4199 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4199 shadow_repository_path = vcs_obj._get_shadow_repository_path(
4200 self.target_repo.repo_id, workspace_id)
4200 self.target_repo.repo_id, workspace_id)
4201 if os.path.isdir(shadow_repository_path):
4201 if os.path.isdir(shadow_repository_path):
4202 return vcs_obj.get_shadow_instance(shadow_repository_path)
4202 return vcs_obj.get_shadow_instance(shadow_repository_path)
4203
4203
4204
4204
4205 class PullRequestVersion(Base, _PullRequestBase):
4205 class PullRequestVersion(Base, _PullRequestBase):
4206 __tablename__ = 'pull_request_versions'
4206 __tablename__ = 'pull_request_versions'
4207 __table_args__ = (
4207 __table_args__ = (
4208 base_table_args,
4208 base_table_args,
4209 )
4209 )
4210
4210
4211 pull_request_version_id = Column(
4211 pull_request_version_id = Column(
4212 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4212 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4213 pull_request_id = Column(
4213 pull_request_id = Column(
4214 'pull_request_id', Integer(),
4214 'pull_request_id', Integer(),
4215 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4215 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4216 pull_request = relationship('PullRequest')
4216 pull_request = relationship('PullRequest')
4217
4217
4218 def __repr__(self):
4218 def __repr__(self):
4219 if self.pull_request_version_id:
4219 if self.pull_request_version_id:
4220 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4220 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4221 else:
4221 else:
4222 return '<DB:PullRequestVersion at %#x>' % id(self)
4222 return '<DB:PullRequestVersion at %#x>' % id(self)
4223
4223
4224 @property
4224 @property
4225 def reviewers(self):
4225 def reviewers(self):
4226 return self.pull_request.reviewers
4226 return self.pull_request.reviewers
4227
4227
4228 @property
4228 @property
4229 def versions(self):
4229 def versions(self):
4230 return self.pull_request.versions
4230 return self.pull_request.versions
4231
4231
4232 def is_closed(self):
4232 def is_closed(self):
4233 # calculate from original
4233 # calculate from original
4234 return self.pull_request.status == self.STATUS_CLOSED
4234 return self.pull_request.status == self.STATUS_CLOSED
4235
4235
4236 def calculated_review_status(self):
4236 def calculated_review_status(self):
4237 return self.pull_request.calculated_review_status()
4237 return self.pull_request.calculated_review_status()
4238
4238
4239 def reviewers_statuses(self):
4239 def reviewers_statuses(self):
4240 return self.pull_request.reviewers_statuses()
4240 return self.pull_request.reviewers_statuses()
4241
4241
4242
4242
4243 class PullRequestReviewers(Base, BaseModel):
4243 class PullRequestReviewers(Base, BaseModel):
4244 __tablename__ = 'pull_request_reviewers'
4244 __tablename__ = 'pull_request_reviewers'
4245 __table_args__ = (
4245 __table_args__ = (
4246 base_table_args,
4246 base_table_args,
4247 )
4247 )
4248
4248
4249 @hybrid_property
4249 @hybrid_property
4250 def reasons(self):
4250 def reasons(self):
4251 if not self._reasons:
4251 if not self._reasons:
4252 return []
4252 return []
4253 return self._reasons
4253 return self._reasons
4254
4254
4255 @reasons.setter
4255 @reasons.setter
4256 def reasons(self, val):
4256 def reasons(self, val):
4257 val = val or []
4257 val = val or []
4258 if any(not isinstance(x, compat.string_types) for x in val):
4258 if any(not isinstance(x, compat.string_types) for x in val):
4259 raise Exception('invalid reasons type, must be list of strings')
4259 raise Exception('invalid reasons type, must be list of strings')
4260 self._reasons = val
4260 self._reasons = val
4261
4261
4262 pull_requests_reviewers_id = Column(
4262 pull_requests_reviewers_id = Column(
4263 'pull_requests_reviewers_id', Integer(), nullable=False,
4263 'pull_requests_reviewers_id', Integer(), nullable=False,
4264 primary_key=True)
4264 primary_key=True)
4265 pull_request_id = Column(
4265 pull_request_id = Column(
4266 "pull_request_id", Integer(),
4266 "pull_request_id", Integer(),
4267 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4267 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4268 user_id = Column(
4268 user_id = Column(
4269 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4269 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4270 _reasons = Column(
4270 _reasons = Column(
4271 'reason', MutationList.as_mutable(
4271 'reason', MutationList.as_mutable(
4272 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4272 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4273
4273
4274 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4274 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4275 user = relationship('User')
4275 user = relationship('User')
4276 pull_request = relationship('PullRequest')
4276 pull_request = relationship('PullRequest')
4277
4277
4278 rule_data = Column(
4278 rule_data = Column(
4279 'rule_data_json',
4279 'rule_data_json',
4280 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4280 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4281
4281
4282 def rule_user_group_data(self):
4282 def rule_user_group_data(self):
4283 """
4283 """
4284 Returns the voting user group rule data for this reviewer
4284 Returns the voting user group rule data for this reviewer
4285 """
4285 """
4286
4286
4287 if self.rule_data and 'vote_rule' in self.rule_data:
4287 if self.rule_data and 'vote_rule' in self.rule_data:
4288 user_group_data = {}
4288 user_group_data = {}
4289 if 'rule_user_group_entry_id' in self.rule_data:
4289 if 'rule_user_group_entry_id' in self.rule_data:
4290 # means a group with voting rules !
4290 # means a group with voting rules !
4291 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4291 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4292 user_group_data['name'] = self.rule_data['rule_name']
4292 user_group_data['name'] = self.rule_data['rule_name']
4293 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4293 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4294
4294
4295 return user_group_data
4295 return user_group_data
4296
4296
4297 def __unicode__(self):
4297 def __unicode__(self):
4298 return u"<%s('id:%s')>" % (self.__class__.__name__,
4298 return u"<%s('id:%s')>" % (self.__class__.__name__,
4299 self.pull_requests_reviewers_id)
4299 self.pull_requests_reviewers_id)
4300
4300
4301
4301
4302 class Notification(Base, BaseModel):
4302 class Notification(Base, BaseModel):
4303 __tablename__ = 'notifications'
4303 __tablename__ = 'notifications'
4304 __table_args__ = (
4304 __table_args__ = (
4305 Index('notification_type_idx', 'type'),
4305 Index('notification_type_idx', 'type'),
4306 base_table_args,
4306 base_table_args,
4307 )
4307 )
4308
4308
4309 TYPE_CHANGESET_COMMENT = u'cs_comment'
4309 TYPE_CHANGESET_COMMENT = u'cs_comment'
4310 TYPE_MESSAGE = u'message'
4310 TYPE_MESSAGE = u'message'
4311 TYPE_MENTION = u'mention'
4311 TYPE_MENTION = u'mention'
4312 TYPE_REGISTRATION = u'registration'
4312 TYPE_REGISTRATION = u'registration'
4313 TYPE_PULL_REQUEST = u'pull_request'
4313 TYPE_PULL_REQUEST = u'pull_request'
4314 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4314 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4315
4315
4316 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4316 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4317 subject = Column('subject', Unicode(512), nullable=True)
4317 subject = Column('subject', Unicode(512), nullable=True)
4318 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4318 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4319 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4319 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4320 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4320 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4321 type_ = Column('type', Unicode(255))
4321 type_ = Column('type', Unicode(255))
4322
4322
4323 created_by_user = relationship('User')
4323 created_by_user = relationship('User')
4324 notifications_to_users = relationship('UserNotification', lazy='joined',
4324 notifications_to_users = relationship('UserNotification', lazy='joined',
4325 cascade="all, delete, delete-orphan")
4325 cascade="all, delete, delete-orphan")
4326
4326
4327 @property
4327 @property
4328 def recipients(self):
4328 def recipients(self):
4329 return [x.user for x in UserNotification.query()\
4329 return [x.user for x in UserNotification.query()\
4330 .filter(UserNotification.notification == self)\
4330 .filter(UserNotification.notification == self)\
4331 .order_by(UserNotification.user_id.asc()).all()]
4331 .order_by(UserNotification.user_id.asc()).all()]
4332
4332
4333 @classmethod
4333 @classmethod
4334 def create(cls, created_by, subject, body, recipients, type_=None):
4334 def create(cls, created_by, subject, body, recipients, type_=None):
4335 if type_ is None:
4335 if type_ is None:
4336 type_ = Notification.TYPE_MESSAGE
4336 type_ = Notification.TYPE_MESSAGE
4337
4337
4338 notification = cls()
4338 notification = cls()
4339 notification.created_by_user = created_by
4339 notification.created_by_user = created_by
4340 notification.subject = subject
4340 notification.subject = subject
4341 notification.body = body
4341 notification.body = body
4342 notification.type_ = type_
4342 notification.type_ = type_
4343 notification.created_on = datetime.datetime.now()
4343 notification.created_on = datetime.datetime.now()
4344
4344
4345 # For each recipient link the created notification to his account
4345 # For each recipient link the created notification to his account
4346 for u in recipients:
4346 for u in recipients:
4347 assoc = UserNotification()
4347 assoc = UserNotification()
4348 assoc.user_id = u.user_id
4348 assoc.user_id = u.user_id
4349 assoc.notification = notification
4349 assoc.notification = notification
4350
4350
4351 # if created_by is inside recipients mark his notification
4351 # if created_by is inside recipients mark his notification
4352 # as read
4352 # as read
4353 if u.user_id == created_by.user_id:
4353 if u.user_id == created_by.user_id:
4354 assoc.read = True
4354 assoc.read = True
4355 Session().add(assoc)
4355 Session().add(assoc)
4356
4356
4357 Session().add(notification)
4357 Session().add(notification)
4358
4358
4359 return notification
4359 return notification
4360
4360
4361
4361
4362 class UserNotification(Base, BaseModel):
4362 class UserNotification(Base, BaseModel):
4363 __tablename__ = 'user_to_notification'
4363 __tablename__ = 'user_to_notification'
4364 __table_args__ = (
4364 __table_args__ = (
4365 UniqueConstraint('user_id', 'notification_id'),
4365 UniqueConstraint('user_id', 'notification_id'),
4366 base_table_args
4366 base_table_args
4367 )
4367 )
4368
4368
4369 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4369 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4370 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4370 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4371 read = Column('read', Boolean, default=False)
4371 read = Column('read', Boolean, default=False)
4372 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4372 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4373
4373
4374 user = relationship('User', lazy="joined")
4374 user = relationship('User', lazy="joined")
4375 notification = relationship('Notification', lazy="joined",
4375 notification = relationship('Notification', lazy="joined",
4376 order_by=lambda: Notification.created_on.desc(),)
4376 order_by=lambda: Notification.created_on.desc(),)
4377
4377
4378 def mark_as_read(self):
4378 def mark_as_read(self):
4379 self.read = True
4379 self.read = True
4380 Session().add(self)
4380 Session().add(self)
4381
4381
4382
4382
4383 class Gist(Base, BaseModel):
4383 class Gist(Base, BaseModel):
4384 __tablename__ = 'gists'
4384 __tablename__ = 'gists'
4385 __table_args__ = (
4385 __table_args__ = (
4386 Index('g_gist_access_id_idx', 'gist_access_id'),
4386 Index('g_gist_access_id_idx', 'gist_access_id'),
4387 Index('g_created_on_idx', 'created_on'),
4387 Index('g_created_on_idx', 'created_on'),
4388 base_table_args
4388 base_table_args
4389 )
4389 )
4390
4390
4391 GIST_PUBLIC = u'public'
4391 GIST_PUBLIC = u'public'
4392 GIST_PRIVATE = u'private'
4392 GIST_PRIVATE = u'private'
4393 DEFAULT_FILENAME = u'gistfile1.txt'
4393 DEFAULT_FILENAME = u'gistfile1.txt'
4394
4394
4395 ACL_LEVEL_PUBLIC = u'acl_public'
4395 ACL_LEVEL_PUBLIC = u'acl_public'
4396 ACL_LEVEL_PRIVATE = u'acl_private'
4396 ACL_LEVEL_PRIVATE = u'acl_private'
4397
4397
4398 gist_id = Column('gist_id', Integer(), primary_key=True)
4398 gist_id = Column('gist_id', Integer(), primary_key=True)
4399 gist_access_id = Column('gist_access_id', Unicode(250))
4399 gist_access_id = Column('gist_access_id', Unicode(250))
4400 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4400 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4401 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4401 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4402 gist_expires = Column('gist_expires', Float(53), nullable=False)
4402 gist_expires = Column('gist_expires', Float(53), nullable=False)
4403 gist_type = Column('gist_type', Unicode(128), nullable=False)
4403 gist_type = Column('gist_type', Unicode(128), nullable=False)
4404 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4404 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4405 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4405 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4406 acl_level = Column('acl_level', Unicode(128), nullable=True)
4406 acl_level = Column('acl_level', Unicode(128), nullable=True)
4407
4407
4408 owner = relationship('User')
4408 owner = relationship('User')
4409
4409
4410 def __repr__(self):
4410 def __repr__(self):
4411 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4411 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4412
4412
4413 @hybrid_property
4413 @hybrid_property
4414 def description_safe(self):
4414 def description_safe(self):
4415 from rhodecode.lib import helpers as h
4415 from rhodecode.lib import helpers as h
4416 return h.escape(self.gist_description)
4416 return h.escape(self.gist_description)
4417
4417
4418 @classmethod
4418 @classmethod
4419 def get_or_404(cls, id_):
4419 def get_or_404(cls, id_):
4420 from pyramid.httpexceptions import HTTPNotFound
4420 from pyramid.httpexceptions import HTTPNotFound
4421
4421
4422 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4422 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4423 if not res:
4423 if not res:
4424 raise HTTPNotFound()
4424 raise HTTPNotFound()
4425 return res
4425 return res
4426
4426
4427 @classmethod
4427 @classmethod
4428 def get_by_access_id(cls, gist_access_id):
4428 def get_by_access_id(cls, gist_access_id):
4429 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4429 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4430
4430
4431 def gist_url(self):
4431 def gist_url(self):
4432 from rhodecode.model.gist import GistModel
4432 from rhodecode.model.gist import GistModel
4433 return GistModel().get_url(self)
4433 return GistModel().get_url(self)
4434
4434
4435 @classmethod
4435 @classmethod
4436 def base_path(cls):
4436 def base_path(cls):
4437 """
4437 """
4438 Returns base path when all gists are stored
4438 Returns base path when all gists are stored
4439
4439
4440 :param cls:
4440 :param cls:
4441 """
4441 """
4442 from rhodecode.model.gist import GIST_STORE_LOC
4442 from rhodecode.model.gist import GIST_STORE_LOC
4443 q = Session().query(RhodeCodeUi)\
4443 q = Session().query(RhodeCodeUi)\
4444 .filter(RhodeCodeUi.ui_key == URL_SEP)
4444 .filter(RhodeCodeUi.ui_key == URL_SEP)
4445 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4445 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4446 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4446 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4447
4447
4448 def get_api_data(self):
4448 def get_api_data(self):
4449 """
4449 """
4450 Common function for generating gist related data for API
4450 Common function for generating gist related data for API
4451 """
4451 """
4452 gist = self
4452 gist = self
4453 data = {
4453 data = {
4454 'gist_id': gist.gist_id,
4454 'gist_id': gist.gist_id,
4455 'type': gist.gist_type,
4455 'type': gist.gist_type,
4456 'access_id': gist.gist_access_id,
4456 'access_id': gist.gist_access_id,
4457 'description': gist.gist_description,
4457 'description': gist.gist_description,
4458 'url': gist.gist_url(),
4458 'url': gist.gist_url(),
4459 'expires': gist.gist_expires,
4459 'expires': gist.gist_expires,
4460 'created_on': gist.created_on,
4460 'created_on': gist.created_on,
4461 'modified_at': gist.modified_at,
4461 'modified_at': gist.modified_at,
4462 'content': None,
4462 'content': None,
4463 'acl_level': gist.acl_level,
4463 'acl_level': gist.acl_level,
4464 }
4464 }
4465 return data
4465 return data
4466
4466
4467 def __json__(self):
4467 def __json__(self):
4468 data = dict(
4468 data = dict(
4469 )
4469 )
4470 data.update(self.get_api_data())
4470 data.update(self.get_api_data())
4471 return data
4471 return data
4472 # SCM functions
4472 # SCM functions
4473
4473
4474 def scm_instance(self, **kwargs):
4474 def scm_instance(self, **kwargs):
4475 """
4475 """
4476 Get an instance of VCS Repository
4476 Get an instance of VCS Repository
4477
4477
4478 :param kwargs:
4478 :param kwargs:
4479 """
4479 """
4480 from rhodecode.model.gist import GistModel
4480 from rhodecode.model.gist import GistModel
4481 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4481 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4482 return get_vcs_instance(
4482 return get_vcs_instance(
4483 repo_path=safe_str(full_repo_path), create=False,
4483 repo_path=safe_str(full_repo_path), create=False,
4484 _vcs_alias=GistModel.vcs_backend)
4484 _vcs_alias=GistModel.vcs_backend)
4485
4485
4486
4486
4487 class ExternalIdentity(Base, BaseModel):
4487 class ExternalIdentity(Base, BaseModel):
4488 __tablename__ = 'external_identities'
4488 __tablename__ = 'external_identities'
4489 __table_args__ = (
4489 __table_args__ = (
4490 Index('local_user_id_idx', 'local_user_id'),
4490 Index('local_user_id_idx', 'local_user_id'),
4491 Index('external_id_idx', 'external_id'),
4491 Index('external_id_idx', 'external_id'),
4492 base_table_args
4492 base_table_args
4493 )
4493 )
4494
4494
4495 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4495 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4496 external_username = Column('external_username', Unicode(1024), default=u'')
4496 external_username = Column('external_username', Unicode(1024), default=u'')
4497 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4497 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4498 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4498 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4499 access_token = Column('access_token', String(1024), default=u'')
4499 access_token = Column('access_token', String(1024), default=u'')
4500 alt_token = Column('alt_token', String(1024), default=u'')
4500 alt_token = Column('alt_token', String(1024), default=u'')
4501 token_secret = Column('token_secret', String(1024), default=u'')
4501 token_secret = Column('token_secret', String(1024), default=u'')
4502
4502
4503 @classmethod
4503 @classmethod
4504 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4504 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4505 """
4505 """
4506 Returns ExternalIdentity instance based on search params
4506 Returns ExternalIdentity instance based on search params
4507
4507
4508 :param external_id:
4508 :param external_id:
4509 :param provider_name:
4509 :param provider_name:
4510 :return: ExternalIdentity
4510 :return: ExternalIdentity
4511 """
4511 """
4512 query = cls.query()
4512 query = cls.query()
4513 query = query.filter(cls.external_id == external_id)
4513 query = query.filter(cls.external_id == external_id)
4514 query = query.filter(cls.provider_name == provider_name)
4514 query = query.filter(cls.provider_name == provider_name)
4515 if local_user_id:
4515 if local_user_id:
4516 query = query.filter(cls.local_user_id == local_user_id)
4516 query = query.filter(cls.local_user_id == local_user_id)
4517 return query.first()
4517 return query.first()
4518
4518
4519 @classmethod
4519 @classmethod
4520 def user_by_external_id_and_provider(cls, external_id, provider_name):
4520 def user_by_external_id_and_provider(cls, external_id, provider_name):
4521 """
4521 """
4522 Returns User instance based on search params
4522 Returns User instance based on search params
4523
4523
4524 :param external_id:
4524 :param external_id:
4525 :param provider_name:
4525 :param provider_name:
4526 :return: User
4526 :return: User
4527 """
4527 """
4528 query = User.query()
4528 query = User.query()
4529 query = query.filter(cls.external_id == external_id)
4529 query = query.filter(cls.external_id == external_id)
4530 query = query.filter(cls.provider_name == provider_name)
4530 query = query.filter(cls.provider_name == provider_name)
4531 query = query.filter(User.user_id == cls.local_user_id)
4531 query = query.filter(User.user_id == cls.local_user_id)
4532 return query.first()
4532 return query.first()
4533
4533
4534 @classmethod
4534 @classmethod
4535 def by_local_user_id(cls, local_user_id):
4535 def by_local_user_id(cls, local_user_id):
4536 """
4536 """
4537 Returns all tokens for user
4537 Returns all tokens for user
4538
4538
4539 :param local_user_id:
4539 :param local_user_id:
4540 :return: ExternalIdentity
4540 :return: ExternalIdentity
4541 """
4541 """
4542 query = cls.query()
4542 query = cls.query()
4543 query = query.filter(cls.local_user_id == local_user_id)
4543 query = query.filter(cls.local_user_id == local_user_id)
4544 return query
4544 return query
4545
4545
4546 @classmethod
4546 @classmethod
4547 def load_provider_plugin(cls, plugin_id):
4547 def load_provider_plugin(cls, plugin_id):
4548 from rhodecode.authentication.base import loadplugin
4548 from rhodecode.authentication.base import loadplugin
4549 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4549 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4550 auth_plugin = loadplugin(_plugin_id)
4550 auth_plugin = loadplugin(_plugin_id)
4551 return auth_plugin
4551 return auth_plugin
4552
4552
4553
4553
4554 class Integration(Base, BaseModel):
4554 class Integration(Base, BaseModel):
4555 __tablename__ = 'integrations'
4555 __tablename__ = 'integrations'
4556 __table_args__ = (
4556 __table_args__ = (
4557 base_table_args
4557 base_table_args
4558 )
4558 )
4559
4559
4560 integration_id = Column('integration_id', Integer(), primary_key=True)
4560 integration_id = Column('integration_id', Integer(), primary_key=True)
4561 integration_type = Column('integration_type', String(255))
4561 integration_type = Column('integration_type', String(255))
4562 enabled = Column('enabled', Boolean(), nullable=False)
4562 enabled = Column('enabled', Boolean(), nullable=False)
4563 name = Column('name', String(255), nullable=False)
4563 name = Column('name', String(255), nullable=False)
4564 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4564 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4565 default=False)
4565 default=False)
4566
4566
4567 settings = Column(
4567 settings = Column(
4568 'settings_json', MutationObj.as_mutable(
4568 'settings_json', MutationObj.as_mutable(
4569 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4569 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4570 repo_id = Column(
4570 repo_id = Column(
4571 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4571 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4572 nullable=True, unique=None, default=None)
4572 nullable=True, unique=None, default=None)
4573 repo = relationship('Repository', lazy='joined')
4573 repo = relationship('Repository', lazy='joined')
4574
4574
4575 repo_group_id = Column(
4575 repo_group_id = Column(
4576 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4576 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4577 nullable=True, unique=None, default=None)
4577 nullable=True, unique=None, default=None)
4578 repo_group = relationship('RepoGroup', lazy='joined')
4578 repo_group = relationship('RepoGroup', lazy='joined')
4579
4579
4580 @property
4580 @property
4581 def scope(self):
4581 def scope(self):
4582 if self.repo:
4582 if self.repo:
4583 return repr(self.repo)
4583 return repr(self.repo)
4584 if self.repo_group:
4584 if self.repo_group:
4585 if self.child_repos_only:
4585 if self.child_repos_only:
4586 return repr(self.repo_group) + ' (child repos only)'
4586 return repr(self.repo_group) + ' (child repos only)'
4587 else:
4587 else:
4588 return repr(self.repo_group) + ' (recursive)'
4588 return repr(self.repo_group) + ' (recursive)'
4589 if self.child_repos_only:
4589 if self.child_repos_only:
4590 return 'root_repos'
4590 return 'root_repos'
4591 return 'global'
4591 return 'global'
4592
4592
4593 def __repr__(self):
4593 def __repr__(self):
4594 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4594 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4595
4595
4596
4596
4597 class RepoReviewRuleUser(Base, BaseModel):
4597 class RepoReviewRuleUser(Base, BaseModel):
4598 __tablename__ = 'repo_review_rules_users'
4598 __tablename__ = 'repo_review_rules_users'
4599 __table_args__ = (
4599 __table_args__ = (
4600 base_table_args
4600 base_table_args
4601 )
4601 )
4602
4602
4603 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4603 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4604 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4604 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4605 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4605 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4606 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4606 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4607 user = relationship('User')
4607 user = relationship('User')
4608
4608
4609 def rule_data(self):
4609 def rule_data(self):
4610 return {
4610 return {
4611 'mandatory': self.mandatory
4611 'mandatory': self.mandatory
4612 }
4612 }
4613
4613
4614
4614
4615 class RepoReviewRuleUserGroup(Base, BaseModel):
4615 class RepoReviewRuleUserGroup(Base, BaseModel):
4616 __tablename__ = 'repo_review_rules_users_groups'
4616 __tablename__ = 'repo_review_rules_users_groups'
4617 __table_args__ = (
4617 __table_args__ = (
4618 base_table_args
4618 base_table_args
4619 )
4619 )
4620
4620
4621 VOTE_RULE_ALL = -1
4621 VOTE_RULE_ALL = -1
4622
4622
4623 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4623 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4624 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4624 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4625 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4625 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4626 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4626 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4627 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4627 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4628 users_group = relationship('UserGroup')
4628 users_group = relationship('UserGroup')
4629
4629
4630 def rule_data(self):
4630 def rule_data(self):
4631 return {
4631 return {
4632 'mandatory': self.mandatory,
4632 'mandatory': self.mandatory,
4633 'vote_rule': self.vote_rule
4633 'vote_rule': self.vote_rule
4634 }
4634 }
4635
4635
4636 @property
4636 @property
4637 def vote_rule_label(self):
4637 def vote_rule_label(self):
4638 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4638 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4639 return 'all must vote'
4639 return 'all must vote'
4640 else:
4640 else:
4641 return 'min. vote {}'.format(self.vote_rule)
4641 return 'min. vote {}'.format(self.vote_rule)
4642
4642
4643
4643
4644 class RepoReviewRule(Base, BaseModel):
4644 class RepoReviewRule(Base, BaseModel):
4645 __tablename__ = 'repo_review_rules'
4645 __tablename__ = 'repo_review_rules'
4646 __table_args__ = (
4646 __table_args__ = (
4647 base_table_args
4647 base_table_args
4648 )
4648 )
4649
4649
4650 repo_review_rule_id = Column(
4650 repo_review_rule_id = Column(
4651 'repo_review_rule_id', Integer(), primary_key=True)
4651 'repo_review_rule_id', Integer(), primary_key=True)
4652 repo_id = Column(
4652 repo_id = Column(
4653 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4653 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4654 repo = relationship('Repository', backref='review_rules')
4654 repo = relationship('Repository', backref='review_rules')
4655
4655
4656 review_rule_name = Column('review_rule_name', String(255))
4656 review_rule_name = Column('review_rule_name', String(255))
4657 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4657 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4658 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4658 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4659 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4659 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4660
4660
4661 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4661 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4662 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4662 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4663 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4663 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4664 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4664 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4665
4665
4666 rule_users = relationship('RepoReviewRuleUser')
4666 rule_users = relationship('RepoReviewRuleUser')
4667 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4667 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4668
4668
4669 def _validate_pattern(self, value):
4669 def _validate_pattern(self, value):
4670 re.compile('^' + glob2re(value) + '$')
4670 re.compile('^' + glob2re(value) + '$')
4671
4671
4672 @hybrid_property
4672 @hybrid_property
4673 def source_branch_pattern(self):
4673 def source_branch_pattern(self):
4674 return self._branch_pattern or '*'
4674 return self._branch_pattern or '*'
4675
4675
4676 @source_branch_pattern.setter
4676 @source_branch_pattern.setter
4677 def source_branch_pattern(self, value):
4677 def source_branch_pattern(self, value):
4678 self._validate_pattern(value)
4678 self._validate_pattern(value)
4679 self._branch_pattern = value or '*'
4679 self._branch_pattern = value or '*'
4680
4680
4681 @hybrid_property
4681 @hybrid_property
4682 def target_branch_pattern(self):
4682 def target_branch_pattern(self):
4683 return self._target_branch_pattern or '*'
4683 return self._target_branch_pattern or '*'
4684
4684
4685 @target_branch_pattern.setter
4685 @target_branch_pattern.setter
4686 def target_branch_pattern(self, value):
4686 def target_branch_pattern(self, value):
4687 self._validate_pattern(value)
4687 self._validate_pattern(value)
4688 self._target_branch_pattern = value or '*'
4688 self._target_branch_pattern = value or '*'
4689
4689
4690 @hybrid_property
4690 @hybrid_property
4691 def file_pattern(self):
4691 def file_pattern(self):
4692 return self._file_pattern or '*'
4692 return self._file_pattern or '*'
4693
4693
4694 @file_pattern.setter
4694 @file_pattern.setter
4695 def file_pattern(self, value):
4695 def file_pattern(self, value):
4696 self._validate_pattern(value)
4696 self._validate_pattern(value)
4697 self._file_pattern = value or '*'
4697 self._file_pattern = value or '*'
4698
4698
4699 def matches(self, source_branch, target_branch, files_changed):
4699 def matches(self, source_branch, target_branch, files_changed):
4700 """
4700 """
4701 Check if this review rule matches a branch/files in a pull request
4701 Check if this review rule matches a branch/files in a pull request
4702
4702
4703 :param source_branch: source branch name for the commit
4703 :param source_branch: source branch name for the commit
4704 :param target_branch: target branch name for the commit
4704 :param target_branch: target branch name for the commit
4705 :param files_changed: list of file paths changed in the pull request
4705 :param files_changed: list of file paths changed in the pull request
4706 """
4706 """
4707
4707
4708 source_branch = source_branch or ''
4708 source_branch = source_branch or ''
4709 target_branch = target_branch or ''
4709 target_branch = target_branch or ''
4710 files_changed = files_changed or []
4710 files_changed = files_changed or []
4711
4711
4712 branch_matches = True
4712 branch_matches = True
4713 if source_branch or target_branch:
4713 if source_branch or target_branch:
4714 if self.source_branch_pattern == '*':
4714 if self.source_branch_pattern == '*':
4715 source_branch_match = True
4715 source_branch_match = True
4716 else:
4716 else:
4717 if self.source_branch_pattern.startswith('re:'):
4717 if self.source_branch_pattern.startswith('re:'):
4718 source_pattern = self.source_branch_pattern[3:]
4718 source_pattern = self.source_branch_pattern[3:]
4719 else:
4719 else:
4720 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4720 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4721 source_branch_regex = re.compile(source_pattern)
4721 source_branch_regex = re.compile(source_pattern)
4722 source_branch_match = bool(source_branch_regex.search(source_branch))
4722 source_branch_match = bool(source_branch_regex.search(source_branch))
4723 if self.target_branch_pattern == '*':
4723 if self.target_branch_pattern == '*':
4724 target_branch_match = True
4724 target_branch_match = True
4725 else:
4725 else:
4726 if self.target_branch_pattern.startswith('re:'):
4726 if self.target_branch_pattern.startswith('re:'):
4727 target_pattern = self.target_branch_pattern[3:]
4727 target_pattern = self.target_branch_pattern[3:]
4728 else:
4728 else:
4729 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4729 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4730 target_branch_regex = re.compile(target_pattern)
4730 target_branch_regex = re.compile(target_pattern)
4731 target_branch_match = bool(target_branch_regex.search(target_branch))
4731 target_branch_match = bool(target_branch_regex.search(target_branch))
4732
4732
4733 branch_matches = source_branch_match and target_branch_match
4733 branch_matches = source_branch_match and target_branch_match
4734
4734
4735 files_matches = True
4735 files_matches = True
4736 if self.file_pattern != '*':
4736 if self.file_pattern != '*':
4737 files_matches = False
4737 files_matches = False
4738 if self.file_pattern.startswith('re:'):
4738 if self.file_pattern.startswith('re:'):
4739 file_pattern = self.file_pattern[3:]
4739 file_pattern = self.file_pattern[3:]
4740 else:
4740 else:
4741 file_pattern = glob2re(self.file_pattern)
4741 file_pattern = glob2re(self.file_pattern)
4742 file_regex = re.compile(file_pattern)
4742 file_regex = re.compile(file_pattern)
4743 for filename in files_changed:
4743 for filename in files_changed:
4744 if file_regex.search(filename):
4744 if file_regex.search(filename):
4745 files_matches = True
4745 files_matches = True
4746 break
4746 break
4747
4747
4748 return branch_matches and files_matches
4748 return branch_matches and files_matches
4749
4749
4750 @property
4750 @property
4751 def review_users(self):
4751 def review_users(self):
4752 """ Returns the users which this rule applies to """
4752 """ Returns the users which this rule applies to """
4753
4753
4754 users = collections.OrderedDict()
4754 users = collections.OrderedDict()
4755
4755
4756 for rule_user in self.rule_users:
4756 for rule_user in self.rule_users:
4757 if rule_user.user.active:
4757 if rule_user.user.active:
4758 if rule_user.user not in users:
4758 if rule_user.user not in users:
4759 users[rule_user.user.username] = {
4759 users[rule_user.user.username] = {
4760 'user': rule_user.user,
4760 'user': rule_user.user,
4761 'source': 'user',
4761 'source': 'user',
4762 'source_data': {},
4762 'source_data': {},
4763 'data': rule_user.rule_data()
4763 'data': rule_user.rule_data()
4764 }
4764 }
4765
4765
4766 for rule_user_group in self.rule_user_groups:
4766 for rule_user_group in self.rule_user_groups:
4767 source_data = {
4767 source_data = {
4768 'user_group_id': rule_user_group.users_group.users_group_id,
4768 'user_group_id': rule_user_group.users_group.users_group_id,
4769 'name': rule_user_group.users_group.users_group_name,
4769 'name': rule_user_group.users_group.users_group_name,
4770 'members': len(rule_user_group.users_group.members)
4770 'members': len(rule_user_group.users_group.members)
4771 }
4771 }
4772 for member in rule_user_group.users_group.members:
4772 for member in rule_user_group.users_group.members:
4773 if member.user.active:
4773 if member.user.active:
4774 key = member.user.username
4774 key = member.user.username
4775 if key in users:
4775 if key in users:
4776 # skip this member as we have him already
4776 # skip this member as we have him already
4777 # this prevents from override the "first" matched
4777 # this prevents from override the "first" matched
4778 # users with duplicates in multiple groups
4778 # users with duplicates in multiple groups
4779 continue
4779 continue
4780
4780
4781 users[key] = {
4781 users[key] = {
4782 'user': member.user,
4782 'user': member.user,
4783 'source': 'user_group',
4783 'source': 'user_group',
4784 'source_data': source_data,
4784 'source_data': source_data,
4785 'data': rule_user_group.rule_data()
4785 'data': rule_user_group.rule_data()
4786 }
4786 }
4787
4787
4788 return users
4788 return users
4789
4789
4790 def user_group_vote_rule(self, user_id):
4790 def user_group_vote_rule(self, user_id):
4791
4791
4792 rules = []
4792 rules = []
4793 if not self.rule_user_groups:
4793 if not self.rule_user_groups:
4794 return rules
4794 return rules
4795
4795
4796 for user_group in self.rule_user_groups:
4796 for user_group in self.rule_user_groups:
4797 user_group_members = [x.user_id for x in user_group.users_group.members]
4797 user_group_members = [x.user_id for x in user_group.users_group.members]
4798 if user_id in user_group_members:
4798 if user_id in user_group_members:
4799 rules.append(user_group)
4799 rules.append(user_group)
4800 return rules
4800 return rules
4801
4801
4802 def __repr__(self):
4802 def __repr__(self):
4803 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4803 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4804 self.repo_review_rule_id, self.repo)
4804 self.repo_review_rule_id, self.repo)
4805
4805
4806
4806
4807 class ScheduleEntry(Base, BaseModel):
4807 class ScheduleEntry(Base, BaseModel):
4808 __tablename__ = 'schedule_entries'
4808 __tablename__ = 'schedule_entries'
4809 __table_args__ = (
4809 __table_args__ = (
4810 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4810 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4811 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4811 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4812 base_table_args,
4812 base_table_args,
4813 )
4813 )
4814
4814
4815 schedule_types = ['crontab', 'timedelta', 'integer']
4815 schedule_types = ['crontab', 'timedelta', 'integer']
4816 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4816 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4817
4817
4818 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4818 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4819 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4819 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4820 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4820 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4821
4821
4822 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4822 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4823 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4823 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4824
4824
4825 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4825 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4826 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4826 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4827
4827
4828 # task
4828 # task
4829 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4829 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4830 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4830 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4831 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4831 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4832 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4832 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4833
4833
4834 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4834 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4835 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4835 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4836
4836
4837 @hybrid_property
4837 @hybrid_property
4838 def schedule_type(self):
4838 def schedule_type(self):
4839 return self._schedule_type
4839 return self._schedule_type
4840
4840
4841 @schedule_type.setter
4841 @schedule_type.setter
4842 def schedule_type(self, val):
4842 def schedule_type(self, val):
4843 if val not in self.schedule_types:
4843 if val not in self.schedule_types:
4844 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4844 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4845 val, self.schedule_type))
4845 val, self.schedule_type))
4846
4846
4847 self._schedule_type = val
4847 self._schedule_type = val
4848
4848
4849 @classmethod
4849 @classmethod
4850 def get_uid(cls, obj):
4850 def get_uid(cls, obj):
4851 args = obj.task_args
4851 args = obj.task_args
4852 kwargs = obj.task_kwargs
4852 kwargs = obj.task_kwargs
4853 if isinstance(args, JsonRaw):
4853 if isinstance(args, JsonRaw):
4854 try:
4854 try:
4855 args = json.loads(args)
4855 args = json.loads(args)
4856 except ValueError:
4856 except ValueError:
4857 args = tuple()
4857 args = tuple()
4858
4858
4859 if isinstance(kwargs, JsonRaw):
4859 if isinstance(kwargs, JsonRaw):
4860 try:
4860 try:
4861 kwargs = json.loads(kwargs)
4861 kwargs = json.loads(kwargs)
4862 except ValueError:
4862 except ValueError:
4863 kwargs = dict()
4863 kwargs = dict()
4864
4864
4865 dot_notation = obj.task_dot_notation
4865 dot_notation = obj.task_dot_notation
4866 val = '.'.join(map(safe_str, [
4866 val = '.'.join(map(safe_str, [
4867 sorted(dot_notation), args, sorted(kwargs.items())]))
4867 sorted(dot_notation), args, sorted(kwargs.items())]))
4868 return hashlib.sha1(val).hexdigest()
4868 return hashlib.sha1(val).hexdigest()
4869
4869
4870 @classmethod
4870 @classmethod
4871 def get_by_schedule_name(cls, schedule_name):
4871 def get_by_schedule_name(cls, schedule_name):
4872 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4872 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4873
4873
4874 @classmethod
4874 @classmethod
4875 def get_by_schedule_id(cls, schedule_id):
4875 def get_by_schedule_id(cls, schedule_id):
4876 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4876 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4877
4877
4878 @property
4878 @property
4879 def task(self):
4879 def task(self):
4880 return self.task_dot_notation
4880 return self.task_dot_notation
4881
4881
4882 @property
4882 @property
4883 def schedule(self):
4883 def schedule(self):
4884 from rhodecode.lib.celerylib.utils import raw_2_schedule
4884 from rhodecode.lib.celerylib.utils import raw_2_schedule
4885 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4885 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4886 return schedule
4886 return schedule
4887
4887
4888 @property
4888 @property
4889 def args(self):
4889 def args(self):
4890 try:
4890 try:
4891 return list(self.task_args or [])
4891 return list(self.task_args or [])
4892 except ValueError:
4892 except ValueError:
4893 return list()
4893 return list()
4894
4894
4895 @property
4895 @property
4896 def kwargs(self):
4896 def kwargs(self):
4897 try:
4897 try:
4898 return dict(self.task_kwargs or {})
4898 return dict(self.task_kwargs or {})
4899 except ValueError:
4899 except ValueError:
4900 return dict()
4900 return dict()
4901
4901
4902 def _as_raw(self, val):
4902 def _as_raw(self, val):
4903 if hasattr(val, 'de_coerce'):
4903 if hasattr(val, 'de_coerce'):
4904 val = val.de_coerce()
4904 val = val.de_coerce()
4905 if val:
4905 if val:
4906 val = json.dumps(val)
4906 val = json.dumps(val)
4907
4907
4908 return val
4908 return val
4909
4909
4910 @property
4910 @property
4911 def schedule_definition_raw(self):
4911 def schedule_definition_raw(self):
4912 return self._as_raw(self.schedule_definition)
4912 return self._as_raw(self.schedule_definition)
4913
4913
4914 @property
4914 @property
4915 def args_raw(self):
4915 def args_raw(self):
4916 return self._as_raw(self.task_args)
4916 return self._as_raw(self.task_args)
4917
4917
4918 @property
4918 @property
4919 def kwargs_raw(self):
4919 def kwargs_raw(self):
4920 return self._as_raw(self.task_kwargs)
4920 return self._as_raw(self.task_kwargs)
4921
4921
4922 def __repr__(self):
4922 def __repr__(self):
4923 return '<DB:ScheduleEntry({}:{})>'.format(
4923 return '<DB:ScheduleEntry({}:{})>'.format(
4924 self.schedule_entry_id, self.schedule_name)
4924 self.schedule_entry_id, self.schedule_name)
4925
4925
4926
4926
4927 @event.listens_for(ScheduleEntry, 'before_update')
4927 @event.listens_for(ScheduleEntry, 'before_update')
4928 def update_task_uid(mapper, connection, target):
4928 def update_task_uid(mapper, connection, target):
4929 target.task_uid = ScheduleEntry.get_uid(target)
4929 target.task_uid = ScheduleEntry.get_uid(target)
4930
4930
4931
4931
4932 @event.listens_for(ScheduleEntry, 'before_insert')
4932 @event.listens_for(ScheduleEntry, 'before_insert')
4933 def set_task_uid(mapper, connection, target):
4933 def set_task_uid(mapper, connection, target):
4934 target.task_uid = ScheduleEntry.get_uid(target)
4934 target.task_uid = ScheduleEntry.get_uid(target)
4935
4935
4936
4936
4937 class _BaseBranchPerms(BaseModel):
4937 class _BaseBranchPerms(BaseModel):
4938 @classmethod
4938 @classmethod
4939 def compute_hash(cls, value):
4939 def compute_hash(cls, value):
4940 return sha1_safe(value)
4940 return sha1_safe(value)
4941
4941
4942 @hybrid_property
4942 @hybrid_property
4943 def branch_pattern(self):
4943 def branch_pattern(self):
4944 return self._branch_pattern or '*'
4944 return self._branch_pattern or '*'
4945
4945
4946 @hybrid_property
4946 @hybrid_property
4947 def branch_hash(self):
4947 def branch_hash(self):
4948 return self._branch_hash
4948 return self._branch_hash
4949
4949
4950 def _validate_glob(self, value):
4950 def _validate_glob(self, value):
4951 re.compile('^' + glob2re(value) + '$')
4951 re.compile('^' + glob2re(value) + '$')
4952
4952
4953 @branch_pattern.setter
4953 @branch_pattern.setter
4954 def branch_pattern(self, value):
4954 def branch_pattern(self, value):
4955 self._validate_glob(value)
4955 self._validate_glob(value)
4956 self._branch_pattern = value or '*'
4956 self._branch_pattern = value or '*'
4957 # set the Hash when setting the branch pattern
4957 # set the Hash when setting the branch pattern
4958 self._branch_hash = self.compute_hash(self._branch_pattern)
4958 self._branch_hash = self.compute_hash(self._branch_pattern)
4959
4959
4960 def matches(self, branch):
4960 def matches(self, branch):
4961 """
4961 """
4962 Check if this the branch matches entry
4962 Check if this the branch matches entry
4963
4963
4964 :param branch: branch name for the commit
4964 :param branch: branch name for the commit
4965 """
4965 """
4966
4966
4967 branch = branch or ''
4967 branch = branch or ''
4968
4968
4969 branch_matches = True
4969 branch_matches = True
4970 if branch:
4970 if branch:
4971 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4971 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4972 branch_matches = bool(branch_regex.search(branch))
4972 branch_matches = bool(branch_regex.search(branch))
4973
4973
4974 return branch_matches
4974 return branch_matches
4975
4975
4976
4976
4977 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4977 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4978 __tablename__ = 'user_to_repo_branch_permissions'
4978 __tablename__ = 'user_to_repo_branch_permissions'
4979 __table_args__ = (
4979 __table_args__ = (
4980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4982 )
4982 )
4983
4983
4984 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4984 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4985
4985
4986 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4986 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4987 repo = relationship('Repository', backref='user_branch_perms')
4987 repo = relationship('Repository', backref='user_branch_perms')
4988
4988
4989 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4989 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4990 permission = relationship('Permission')
4990 permission = relationship('Permission')
4991
4991
4992 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4992 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4993 user_repo_to_perm = relationship('UserRepoToPerm')
4993 user_repo_to_perm = relationship('UserRepoToPerm')
4994
4994
4995 rule_order = Column('rule_order', Integer(), nullable=False)
4995 rule_order = Column('rule_order', Integer(), nullable=False)
4996 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4996 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4997 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4997 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4998
4998
4999 def __unicode__(self):
4999 def __unicode__(self):
5000 return u'<UserBranchPermission(%s => %r)>' % (
5000 return u'<UserBranchPermission(%s => %r)>' % (
5001 self.user_repo_to_perm, self.branch_pattern)
5001 self.user_repo_to_perm, self.branch_pattern)
5002
5002
5003
5003
5004 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5004 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5005 __tablename__ = 'user_group_to_repo_branch_permissions'
5005 __tablename__ = 'user_group_to_repo_branch_permissions'
5006 __table_args__ = (
5006 __table_args__ = (
5007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
5007 {'extend_existing': True, 'mysql_engine': 'InnoDB',
5008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5008 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5009 )
5009 )
5010
5010
5011 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5011 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5012
5012
5013 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5013 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5014 repo = relationship('Repository', backref='user_group_branch_perms')
5014 repo = relationship('Repository', backref='user_group_branch_perms')
5015
5015
5016 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5016 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5017 permission = relationship('Permission')
5017 permission = relationship('Permission')
5018
5018
5019 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5019 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5020 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5020 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5021
5021
5022 rule_order = Column('rule_order', Integer(), nullable=False)
5022 rule_order = Column('rule_order', Integer(), nullable=False)
5023 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5023 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5024 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5024 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5025
5025
5026 def __unicode__(self):
5026 def __unicode__(self):
5027 return u'<UserBranchPermission(%s => %r)>' % (
5027 return u'<UserBranchPermission(%s => %r)>' % (
5028 self.user_group_repo_to_perm, self.branch_pattern)
5028 self.user_group_repo_to_perm, self.branch_pattern)
5029
5029
5030
5030
5031 class UserBookmark(Base, BaseModel):
5031 class UserBookmark(Base, BaseModel):
5032 __tablename__ = 'user_bookmarks'
5032 __tablename__ = 'user_bookmarks'
5033 __table_args__ = (
5033 __table_args__ = (
5034 UniqueConstraint('user_id', 'bookmark_repo_id'),
5034 UniqueConstraint('user_id', 'bookmark_repo_id'),
5035 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5035 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5036 UniqueConstraint('user_id', 'bookmark_position'),
5036 UniqueConstraint('user_id', 'bookmark_position'),
5037 base_table_args
5037 base_table_args
5038 )
5038 )
5039
5039
5040 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5040 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5042 position = Column("bookmark_position", Integer(), nullable=False)
5042 position = Column("bookmark_position", Integer(), nullable=False)
5043 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5043 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5044 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5044 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5045 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5045 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5046
5046
5047 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5047 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5048 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5048 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5049
5049
5050 user = relationship("User")
5050 user = relationship("User")
5051
5051
5052 repository = relationship("Repository")
5052 repository = relationship("Repository")
5053 repository_group = relationship("RepoGroup")
5053 repository_group = relationship("RepoGroup")
5054
5054
5055 @classmethod
5055 @classmethod
5056 def get_by_position_for_user(cls, position, user_id):
5056 def get_by_position_for_user(cls, position, user_id):
5057 return cls.query() \
5057 return cls.query() \
5058 .filter(UserBookmark.user_id == user_id) \
5058 .filter(UserBookmark.user_id == user_id) \
5059 .filter(UserBookmark.position == position).scalar()
5059 .filter(UserBookmark.position == position).scalar()
5060
5060
5061 @classmethod
5061 @classmethod
5062 def get_bookmarks_for_user(cls, user_id):
5062 def get_bookmarks_for_user(cls, user_id):
5063 return cls.query() \
5063 return cls.query() \
5064 .filter(UserBookmark.user_id == user_id) \
5064 .filter(UserBookmark.user_id == user_id) \
5065 .options(joinedload(UserBookmark.repository)) \
5065 .options(joinedload(UserBookmark.repository)) \
5066 .options(joinedload(UserBookmark.repository_group)) \
5066 .options(joinedload(UserBookmark.repository_group)) \
5067 .order_by(UserBookmark.position.asc()) \
5067 .order_by(UserBookmark.position.asc()) \
5068 .all()
5068 .all()
5069
5069
5070 def __unicode__(self):
5070 def __unicode__(self):
5071 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5071 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5072
5072
5073
5073
5074 class FileStore(Base, BaseModel):
5074 class FileStore(Base, BaseModel):
5075 __tablename__ = 'file_store'
5075 __tablename__ = 'file_store'
5076 __table_args__ = (
5076 __table_args__ = (
5077 base_table_args
5077 base_table_args
5078 )
5078 )
5079
5079
5080 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5080 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5081 file_uid = Column('file_uid', String(1024), nullable=False)
5081 file_uid = Column('file_uid', String(1024), nullable=False)
5082 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5082 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5083 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5083 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5084 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5084 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5085
5085
5086 # sha256 hash
5086 # sha256 hash
5087 file_hash = Column('file_hash', String(512), nullable=False)
5087 file_hash = Column('file_hash', String(512), nullable=False)
5088 file_size = Column('file_size', Integer(), nullable=False)
5088 file_size = Column('file_size', Integer(), nullable=False)
5089
5089
5090 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5090 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5091 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5091 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5092 accessed_count = Column('accessed_count', Integer(), default=0)
5092 accessed_count = Column('accessed_count', Integer(), default=0)
5093
5093
5094 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5094 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5095
5095
5096 # if repo/repo_group reference is set, check for permissions
5096 # if repo/repo_group reference is set, check for permissions
5097 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5097 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5098
5098
5099 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5099 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5100 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5100 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5101
5101
5102 # scope limited to user, which requester have access to
5102 # scope limited to user, which requester have access to
5103 scope_user_id = Column(
5103 scope_user_id = Column(
5104 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5104 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5105 nullable=True, unique=None, default=None)
5105 nullable=True, unique=None, default=None)
5106 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5106 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5107
5107
5108 # scope limited to user group, which requester have access to
5108 # scope limited to user group, which requester have access to
5109 scope_user_group_id = Column(
5109 scope_user_group_id = Column(
5110 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5110 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5111 nullable=True, unique=None, default=None)
5111 nullable=True, unique=None, default=None)
5112 user_group = relationship('UserGroup', lazy='joined')
5112 user_group = relationship('UserGroup', lazy='joined')
5113
5113
5114 # scope limited to repo, which requester have access to
5114 # scope limited to repo, which requester have access to
5115 scope_repo_id = Column(
5115 scope_repo_id = Column(
5116 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5116 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5117 nullable=True, unique=None, default=None)
5117 nullable=True, unique=None, default=None)
5118 repo = relationship('Repository', lazy='joined')
5118 repo = relationship('Repository', lazy='joined')
5119
5119
5120 # scope limited to repo group, which requester have access to
5120 # scope limited to repo group, which requester have access to
5121 scope_repo_group_id = Column(
5121 scope_repo_group_id = Column(
5122 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5122 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5123 nullable=True, unique=None, default=None)
5123 nullable=True, unique=None, default=None)
5124 repo_group = relationship('RepoGroup', lazy='joined')
5124 repo_group = relationship('RepoGroup', lazy='joined')
5125
5125
5126 @classmethod
5126 @classmethod
5127 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5127 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5128 file_description='', enabled=True, check_acl=True, user_id=None,
5128 file_description='', enabled=True, check_acl=True, user_id=None,
5129 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5129 scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5130
5130
5131 store_entry = FileStore()
5131 store_entry = FileStore()
5132 store_entry.file_uid = file_uid
5132 store_entry.file_uid = file_uid
5133 store_entry.file_display_name = file_display_name
5133 store_entry.file_display_name = file_display_name
5134 store_entry.file_org_name = filename
5134 store_entry.file_org_name = filename
5135 store_entry.file_size = file_size
5135 store_entry.file_size = file_size
5136 store_entry.file_hash = file_hash
5136 store_entry.file_hash = file_hash
5137 store_entry.file_description = file_description
5137 store_entry.file_description = file_description
5138
5138
5139 store_entry.check_acl = check_acl
5139 store_entry.check_acl = check_acl
5140 store_entry.enabled = enabled
5140 store_entry.enabled = enabled
5141
5141
5142 store_entry.user_id = user_id
5142 store_entry.user_id = user_id
5143 store_entry.scope_user_id = scope_user_id
5143 store_entry.scope_user_id = scope_user_id
5144 store_entry.scope_repo_id = scope_repo_id
5144 store_entry.scope_repo_id = scope_repo_id
5145 store_entry.scope_repo_group_id = scope_repo_group_id
5145 store_entry.scope_repo_group_id = scope_repo_group_id
5146 return store_entry
5146 return store_entry
5147
5147
5148 @classmethod
5148 @classmethod
5149 def bump_access_counter(cls, file_uid, commit=True):
5149 def bump_access_counter(cls, file_uid, commit=True):
5150 FileStore().query()\
5150 FileStore().query()\
5151 .filter(FileStore.file_uid == file_uid)\
5151 .filter(FileStore.file_uid == file_uid)\
5152 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5152 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5153 FileStore.accessed_on: datetime.datetime.now()})
5153 FileStore.accessed_on: datetime.datetime.now()})
5154 if commit:
5154 if commit:
5155 Session().commit()
5155 Session().commit()
5156
5156
5157 def __repr__(self):
5157 def __repr__(self):
5158 return '<FileStore({})>'.format(self.file_store_id)
5158 return '<FileStore({})>'.format(self.file_store_id)
5159
5159
5160
5160
5161 class DbMigrateVersion(Base, BaseModel):
5161 class DbMigrateVersion(Base, BaseModel):
5162 __tablename__ = 'db_migrate_version'
5162 __tablename__ = 'db_migrate_version'
5163 __table_args__ = (
5163 __table_args__ = (
5164 base_table_args,
5164 base_table_args,
5165 )
5165 )
5166
5166
5167 repository_id = Column('repository_id', String(250), primary_key=True)
5167 repository_id = Column('repository_id', String(250), primary_key=True)
5168 repository_path = Column('repository_path', Text)
5168 repository_path = Column('repository_path', Text)
5169 version = Column('version', Integer)
5169 version = Column('version', Integer)
5170
5170
5171 @classmethod
5171 @classmethod
5172 def set_version(cls, version):
5172 def set_version(cls, version):
5173 """
5173 """
5174 Helper for forcing a different version, usually for debugging purposes via ishell.
5174 Helper for forcing a different version, usually for debugging purposes via ishell.
5175 """
5175 """
5176 ver = DbMigrateVersion.query().first()
5176 ver = DbMigrateVersion.query().first()
5177 ver.version = version
5177 ver.version = version
5178 Session().commit()
5178 Session().commit()
5179
5179
5180
5180
5181 class DbSession(Base, BaseModel):
5181 class DbSession(Base, BaseModel):
5182 __tablename__ = 'db_session'
5182 __tablename__ = 'db_session'
5183 __table_args__ = (
5183 __table_args__ = (
5184 base_table_args,
5184 base_table_args,
5185 )
5185 )
5186
5186
5187 def __repr__(self):
5187 def __repr__(self):
5188 return '<DB:DbSession({})>'.format(self.id)
5188 return '<DB:DbSession({})>'.format(self.id)
5189
5189
5190 id = Column('id', Integer())
5190 id = Column('id', Integer())
5191 namespace = Column('namespace', String(255), primary_key=True)
5191 namespace = Column('namespace', String(255), primary_key=True)
5192 accessed = Column('accessed', DateTime, nullable=False)
5192 accessed = Column('accessed', DateTime, nullable=False)
5193 created = Column('created', DateTime, nullable=False)
5193 created = Column('created', DateTime, nullable=False)
5194 data = Column('data', PickleType, nullable=False)
5194 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now