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