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

Auto status change to "Under Review"

You need to be logged in to leave comments. Login now