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