##// END OF EJS Templates
permissions: explain better what is inactive duplicate in permissions, sort them to last positions, and make them less visible.
dan -
r4417:524f9e6a default
parent child Browse files
Show More
@@ -1,5640 +1,5645 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 extra_sort_num = '1' # default
107
108 # NOTE(dan): inactive duplicates goes last
109 if getattr(obj, 'duplicate_perm', None):
110 extra_sort_num = '9'
111 return prefix + extra_sort_num + obj.username
107
112
108
113
109 def display_user_group_sort(obj):
114 def display_user_group_sort(obj):
110 """
115 """
111 Sort function used to sort permissions in .permissions() function of
116 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
117 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
118 of all other resources
114 """
119 """
115
120
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
121 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
122 return prefix + obj.users_group_name
118
123
119
124
120 def _hash_key(k):
125 def _hash_key(k):
121 return sha1_safe(k)
126 return sha1_safe(k)
122
127
123
128
124 def in_filter_generator(qry, items, limit=500):
129 def in_filter_generator(qry, items, limit=500):
125 """
130 """
126 Splits IN() into multiple with OR
131 Splits IN() into multiple with OR
127 e.g.::
132 e.g.::
128 cnt = Repository.query().filter(
133 cnt = Repository.query().filter(
129 or_(
134 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
135 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
136 )).count()
132 """
137 """
133 if not items:
138 if not items:
134 # empty list will cause empty query which might cause security issues
139 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
140 # this can lead to hidden unpleasant results
136 items = [-1]
141 items = [-1]
137
142
138 parts = []
143 parts = []
139 for chunk in xrange(0, len(items), limit):
144 for chunk in xrange(0, len(items), limit):
140 parts.append(
145 parts.append(
141 qry.in_(items[chunk: chunk + limit])
146 qry.in_(items[chunk: chunk + limit])
142 )
147 )
143
148
144 return parts
149 return parts
145
150
146
151
147 base_table_args = {
152 base_table_args = {
148 'extend_existing': True,
153 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
154 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
155 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
156 'sqlite_autoincrement': True
152 }
157 }
153
158
154
159
155 class EncryptedTextValue(TypeDecorator):
160 class EncryptedTextValue(TypeDecorator):
156 """
161 """
157 Special column for encrypted long text data, use like::
162 Special column for encrypted long text data, use like::
158
163
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
164 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
165
161 This column is intelligent so if value is in unencrypted form it return
166 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
167 unencrypted form, but on save it always encrypts
163 """
168 """
164 impl = Text
169 impl = Text
165
170
166 def process_bind_param(self, value, dialect):
171 def process_bind_param(self, value, dialect):
167 """
172 """
168 Setter for storing value
173 Setter for storing value
169 """
174 """
170 import rhodecode
175 import rhodecode
171 if not value:
176 if not value:
172 return value
177 return value
173
178
174 # protect against double encrypting if values is already encrypted
179 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
180 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
181 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
182 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
183 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
184 'ie. not starting with enc$ or enc2$')
180
185
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
186 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
187 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
188 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
189 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
190 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
191 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
192 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
193
189 def process_result_value(self, value, dialect):
194 def process_result_value(self, value, dialect):
190 """
195 """
191 Getter for retrieving value
196 Getter for retrieving value
192 """
197 """
193
198
194 import rhodecode
199 import rhodecode
195 if not value:
200 if not value:
196 return value
201 return value
197
202
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
203 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
204 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
205 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
206 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
207 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
208 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
209 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
210 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
211 return decrypted_data
207
212
208
213
209 class BaseModel(object):
214 class BaseModel(object):
210 """
215 """
211 Base Model for all classes
216 Base Model for all classes
212 """
217 """
213
218
214 @classmethod
219 @classmethod
215 def _get_keys(cls):
220 def _get_keys(cls):
216 """return column names for this model """
221 """return column names for this model """
217 return class_mapper(cls).c.keys()
222 return class_mapper(cls).c.keys()
218
223
219 def get_dict(self):
224 def get_dict(self):
220 """
225 """
221 return dict with keys and values corresponding
226 return dict with keys and values corresponding
222 to this model data """
227 to this model data """
223
228
224 d = {}
229 d = {}
225 for k in self._get_keys():
230 for k in self._get_keys():
226 d[k] = getattr(self, k)
231 d[k] = getattr(self, k)
227
232
228 # also use __json__() if present to get additional fields
233 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
234 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
235 if _json_attr:
231 # update with attributes from __json__
236 # update with attributes from __json__
232 if callable(_json_attr):
237 if callable(_json_attr):
233 _json_attr = _json_attr()
238 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
239 for k, val in _json_attr.iteritems():
235 d[k] = val
240 d[k] = val
236 return d
241 return d
237
242
238 def get_appstruct(self):
243 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
244 """return list with keys and values tuples corresponding
240 to this model data """
245 to this model data """
241
246
242 lst = []
247 lst = []
243 for k in self._get_keys():
248 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
249 lst.append((k, getattr(self, k),))
245 return lst
250 return lst
246
251
247 def populate_obj(self, populate_dict):
252 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
253 """populate model with data from given populate_dict"""
249
254
250 for k in self._get_keys():
255 for k in self._get_keys():
251 if k in populate_dict:
256 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
257 setattr(self, k, populate_dict[k])
253
258
254 @classmethod
259 @classmethod
255 def query(cls):
260 def query(cls):
256 return Session().query(cls)
261 return Session().query(cls)
257
262
258 @classmethod
263 @classmethod
259 def get(cls, id_):
264 def get(cls, id_):
260 if id_:
265 if id_:
261 return cls.query().get(id_)
266 return cls.query().get(id_)
262
267
263 @classmethod
268 @classmethod
264 def get_or_404(cls, id_):
269 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
270 from pyramid.httpexceptions import HTTPNotFound
266
271
267 try:
272 try:
268 id_ = int(id_)
273 id_ = int(id_)
269 except (TypeError, ValueError):
274 except (TypeError, ValueError):
270 raise HTTPNotFound()
275 raise HTTPNotFound()
271
276
272 res = cls.query().get(id_)
277 res = cls.query().get(id_)
273 if not res:
278 if not res:
274 raise HTTPNotFound()
279 raise HTTPNotFound()
275 return res
280 return res
276
281
277 @classmethod
282 @classmethod
278 def getAll(cls):
283 def getAll(cls):
279 # deprecated and left for backward compatibility
284 # deprecated and left for backward compatibility
280 return cls.get_all()
285 return cls.get_all()
281
286
282 @classmethod
287 @classmethod
283 def get_all(cls):
288 def get_all(cls):
284 return cls.query().all()
289 return cls.query().all()
285
290
286 @classmethod
291 @classmethod
287 def delete(cls, id_):
292 def delete(cls, id_):
288 obj = cls.query().get(id_)
293 obj = cls.query().get(id_)
289 Session().delete(obj)
294 Session().delete(obj)
290
295
291 @classmethod
296 @classmethod
292 def identity_cache(cls, session, attr_name, value):
297 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
298 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
299 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
300 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
301 exist_in_session.append(instance)
297 if exist_in_session:
302 if exist_in_session:
298 if len(exist_in_session) == 1:
303 if len(exist_in_session) == 1:
299 return exist_in_session[0]
304 return exist_in_session[0]
300 log.exception(
305 log.exception(
301 'multiple objects with attr %s and '
306 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
307 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
308 attr_name, value, exist_in_session)
304
309
305 def __repr__(self):
310 def __repr__(self):
306 if hasattr(self, '__unicode__'):
311 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
312 # python repr needs to return str
308 try:
313 try:
309 return safe_str(self.__unicode__())
314 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
315 except UnicodeDecodeError:
311 pass
316 pass
312 return '<DB:%s>' % (self.__class__.__name__)
317 return '<DB:%s>' % (self.__class__.__name__)
313
318
314
319
315 class RhodeCodeSetting(Base, BaseModel):
320 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
321 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
322 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
323 UniqueConstraint('app_settings_name'),
319 base_table_args
324 base_table_args
320 )
325 )
321
326
322 SETTINGS_TYPES = {
327 SETTINGS_TYPES = {
323 'str': safe_str,
328 'str': safe_str,
324 'int': safe_int,
329 'int': safe_int,
325 'unicode': safe_unicode,
330 'unicode': safe_unicode,
326 'bool': str2bool,
331 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
332 'list': functools.partial(aslist, sep=',')
328 }
333 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
334 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
335 GLOBAL_CONF_KEY = 'app_settings'
331
336
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
337 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
338 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
339 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
340 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
341
337 def __init__(self, key='', val='', type='unicode'):
342 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
343 self.app_settings_name = key
339 self.app_settings_type = type
344 self.app_settings_type = type
340 self.app_settings_value = val
345 self.app_settings_value = val
341
346
342 @validates('_app_settings_value')
347 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
348 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
349 assert type(val) == unicode
345 return val
350 return val
346
351
347 @hybrid_property
352 @hybrid_property
348 def app_settings_value(self):
353 def app_settings_value(self):
349 v = self._app_settings_value
354 v = self._app_settings_value
350 _type = self.app_settings_type
355 _type = self.app_settings_type
351 if _type:
356 if _type:
352 _type = self.app_settings_type.split('.')[0]
357 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
358 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
359 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
360 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
361 v = safe_unicode(cipher.process_result_value(v, None))
357
362
358 converter = self.SETTINGS_TYPES.get(_type) or \
363 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
364 self.SETTINGS_TYPES['unicode']
360 return converter(v)
365 return converter(v)
361
366
362 @app_settings_value.setter
367 @app_settings_value.setter
363 def app_settings_value(self, val):
368 def app_settings_value(self, val):
364 """
369 """
365 Setter that will always make sure we use unicode in app_settings_value
370 Setter that will always make sure we use unicode in app_settings_value
366
371
367 :param val:
372 :param val:
368 """
373 """
369 val = safe_unicode(val)
374 val = safe_unicode(val)
370 # encode the encrypted value
375 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
376 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
377 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
378 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
379 self._app_settings_value = val
375
380
376 @hybrid_property
381 @hybrid_property
377 def app_settings_type(self):
382 def app_settings_type(self):
378 return self._app_settings_type
383 return self._app_settings_type
379
384
380 @app_settings_type.setter
385 @app_settings_type.setter
381 def app_settings_type(self, val):
386 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
387 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
388 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
389 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
390 self._app_settings_type = val
386
391
387 @classmethod
392 @classmethod
388 def get_by_prefix(cls, prefix):
393 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
394 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
395 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
396 .all()
392
397
393 def __unicode__(self):
398 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
399 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
400 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
401 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
402 self.app_settings_type
398 )
403 )
399
404
400
405
401 class RhodeCodeUi(Base, BaseModel):
406 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
407 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
408 __table_args__ = (
404 UniqueConstraint('ui_key'),
409 UniqueConstraint('ui_key'),
405 base_table_args
410 base_table_args
406 )
411 )
407
412
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
413 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
414 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
415 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
416 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
417 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
418 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
419 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
420 HOOK_PUSH_KEY = 'pushkey.key_push'
416
421
417 HOOKS_BUILTIN = [
422 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
423 HOOK_PRE_PULL,
419 HOOK_PULL,
424 HOOK_PULL,
420 HOOK_PRE_PUSH,
425 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
426 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
427 HOOK_PUSH,
423 HOOK_PUSH_KEY,
428 HOOK_PUSH_KEY,
424 ]
429 ]
425
430
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
431 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
432 # git part is currently hardcoded.
428
433
429 # SVN PATTERNS
434 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
435 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
436 SVN_TAG_ID = 'vcs_svn_tag'
432
437
433 ui_id = Column(
438 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
439 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
440 primary_key=True)
436 ui_section = Column(
441 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
442 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
443 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
444 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
445 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
446 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
447 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
448 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
449
445 def __repr__(self):
450 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
451 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
452 self.ui_key, self.ui_value)
448
453
449
454
450 class RepoRhodeCodeSetting(Base, BaseModel):
455 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
456 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
457 __table_args__ = (
453 UniqueConstraint(
458 UniqueConstraint(
454 'app_settings_name', 'repository_id',
459 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
460 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
461 base_table_args
457 )
462 )
458
463
459 repository_id = Column(
464 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
465 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
466 nullable=False)
462 app_settings_id = Column(
467 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
468 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
469 default=None, primary_key=True)
465 app_settings_name = Column(
470 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
471 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
472 default=None)
468 _app_settings_value = Column(
473 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
474 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
475 default=None)
471 _app_settings_type = Column(
476 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
477 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
478 default=None)
474
479
475 repository = relationship('Repository')
480 repository = relationship('Repository')
476
481
477 def __init__(self, repository_id, key='', val='', type='unicode'):
482 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
483 self.repository_id = repository_id
479 self.app_settings_name = key
484 self.app_settings_name = key
480 self.app_settings_type = type
485 self.app_settings_type = type
481 self.app_settings_value = val
486 self.app_settings_value = val
482
487
483 @validates('_app_settings_value')
488 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
489 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
490 assert type(val) == unicode
486 return val
491 return val
487
492
488 @hybrid_property
493 @hybrid_property
489 def app_settings_value(self):
494 def app_settings_value(self):
490 v = self._app_settings_value
495 v = self._app_settings_value
491 type_ = self.app_settings_type
496 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
498 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
499 return converter(v)
495
500
496 @app_settings_value.setter
501 @app_settings_value.setter
497 def app_settings_value(self, val):
502 def app_settings_value(self, val):
498 """
503 """
499 Setter that will always make sure we use unicode in app_settings_value
504 Setter that will always make sure we use unicode in app_settings_value
500
505
501 :param val:
506 :param val:
502 """
507 """
503 self._app_settings_value = safe_unicode(val)
508 self._app_settings_value = safe_unicode(val)
504
509
505 @hybrid_property
510 @hybrid_property
506 def app_settings_type(self):
511 def app_settings_type(self):
507 return self._app_settings_type
512 return self._app_settings_type
508
513
509 @app_settings_type.setter
514 @app_settings_type.setter
510 def app_settings_type(self, val):
515 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
516 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
517 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
518 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
519 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
520 self._app_settings_type = val
516
521
517 def __unicode__(self):
522 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
523 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
524 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
525 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
526 self.app_settings_type
522 )
527 )
523
528
524
529
525 class RepoRhodeCodeUi(Base, BaseModel):
530 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
531 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
532 __table_args__ = (
528 UniqueConstraint(
533 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
534 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
535 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
536 base_table_args
532 )
537 )
533
538
534 repository_id = Column(
539 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
540 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
541 nullable=False)
537 ui_id = Column(
542 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
543 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
544 primary_key=True)
540 ui_section = Column(
545 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
546 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
547 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
548 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
549 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
550 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
551 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
552 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
553
549 repository = relationship('Repository')
554 repository = relationship('Repository')
550
555
551 def __repr__(self):
556 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
557 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
558 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
559 self.ui_section, self.ui_key, self.ui_value)
555
560
556
561
557 class User(Base, BaseModel):
562 class User(Base, BaseModel):
558 __tablename__ = 'users'
563 __tablename__ = 'users'
559 __table_args__ = (
564 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
565 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
566 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
567 Index('u_email_idx', 'email'),
563 base_table_args
568 base_table_args
564 )
569 )
565
570
566 DEFAULT_USER = 'default'
571 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
572 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
573 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
574
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
575 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
576 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
577 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
578 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
579 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
580 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
581 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
582 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
583 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
584 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
585 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
586
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
587 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
588 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
589 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
590 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
591 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
592 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
593
589 user_log = relationship('UserLog')
594 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
595 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
596
592 repositories = relationship('Repository')
597 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
598 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
599 user_groups = relationship('UserGroup')
595
600
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
601 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
602 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
603
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
604 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
605 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
607
603 group_member = relationship('UserGroupMember', cascade='all')
608 group_member = relationship('UserGroupMember', cascade='all')
604
609
605 notifications = relationship('UserNotification', cascade='all')
610 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
611 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
612 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
613 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
614 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
615 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
616 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
617 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
618 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
619 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
620
616 # gists
621 # gists
617 user_gists = relationship('Gist', cascade='all')
622 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
623 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
624 user_pull_requests = relationship('PullRequest', cascade='all')
620
625
621 # external identities
626 # external identities
622 external_identities = relationship(
627 external_identities = relationship(
623 'ExternalIdentity',
628 'ExternalIdentity',
624 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
629 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
625 cascade='all')
630 cascade='all')
626 # review rules
631 # review rules
627 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
632 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
628
633
629 # artifacts owned
634 # artifacts owned
630 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
635 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
631
636
632 # no cascade, set NULL
637 # no cascade, set NULL
633 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
638 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
634
639
635 def __unicode__(self):
640 def __unicode__(self):
636 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
641 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
637 self.user_id, self.username)
642 self.user_id, self.username)
638
643
639 @hybrid_property
644 @hybrid_property
640 def email(self):
645 def email(self):
641 return self._email
646 return self._email
642
647
643 @email.setter
648 @email.setter
644 def email(self, val):
649 def email(self, val):
645 self._email = val.lower() if val else None
650 self._email = val.lower() if val else None
646
651
647 @hybrid_property
652 @hybrid_property
648 def first_name(self):
653 def first_name(self):
649 from rhodecode.lib import helpers as h
654 from rhodecode.lib import helpers as h
650 if self.name:
655 if self.name:
651 return h.escape(self.name)
656 return h.escape(self.name)
652 return self.name
657 return self.name
653
658
654 @hybrid_property
659 @hybrid_property
655 def last_name(self):
660 def last_name(self):
656 from rhodecode.lib import helpers as h
661 from rhodecode.lib import helpers as h
657 if self.lastname:
662 if self.lastname:
658 return h.escape(self.lastname)
663 return h.escape(self.lastname)
659 return self.lastname
664 return self.lastname
660
665
661 @hybrid_property
666 @hybrid_property
662 def api_key(self):
667 def api_key(self):
663 """
668 """
664 Fetch if exist an auth-token with role ALL connected to this user
669 Fetch if exist an auth-token with role ALL connected to this user
665 """
670 """
666 user_auth_token = UserApiKeys.query()\
671 user_auth_token = UserApiKeys.query()\
667 .filter(UserApiKeys.user_id == self.user_id)\
672 .filter(UserApiKeys.user_id == self.user_id)\
668 .filter(or_(UserApiKeys.expires == -1,
673 .filter(or_(UserApiKeys.expires == -1,
669 UserApiKeys.expires >= time.time()))\
674 UserApiKeys.expires >= time.time()))\
670 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
675 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
671 if user_auth_token:
676 if user_auth_token:
672 user_auth_token = user_auth_token.api_key
677 user_auth_token = user_auth_token.api_key
673
678
674 return user_auth_token
679 return user_auth_token
675
680
676 @api_key.setter
681 @api_key.setter
677 def api_key(self, val):
682 def api_key(self, val):
678 # don't allow to set API key this is deprecated for now
683 # don't allow to set API key this is deprecated for now
679 self._api_key = None
684 self._api_key = None
680
685
681 @property
686 @property
682 def reviewer_pull_requests(self):
687 def reviewer_pull_requests(self):
683 return PullRequestReviewers.query() \
688 return PullRequestReviewers.query() \
684 .options(joinedload(PullRequestReviewers.pull_request)) \
689 .options(joinedload(PullRequestReviewers.pull_request)) \
685 .filter(PullRequestReviewers.user_id == self.user_id) \
690 .filter(PullRequestReviewers.user_id == self.user_id) \
686 .all()
691 .all()
687
692
688 @property
693 @property
689 def firstname(self):
694 def firstname(self):
690 # alias for future
695 # alias for future
691 return self.name
696 return self.name
692
697
693 @property
698 @property
694 def emails(self):
699 def emails(self):
695 other = UserEmailMap.query()\
700 other = UserEmailMap.query()\
696 .filter(UserEmailMap.user == self) \
701 .filter(UserEmailMap.user == self) \
697 .order_by(UserEmailMap.email_id.asc()) \
702 .order_by(UserEmailMap.email_id.asc()) \
698 .all()
703 .all()
699 return [self.email] + [x.email for x in other]
704 return [self.email] + [x.email for x in other]
700
705
701 def emails_cached(self):
706 def emails_cached(self):
702 emails = UserEmailMap.query()\
707 emails = UserEmailMap.query()\
703 .filter(UserEmailMap.user == self) \
708 .filter(UserEmailMap.user == self) \
704 .order_by(UserEmailMap.email_id.asc())
709 .order_by(UserEmailMap.email_id.asc())
705
710
706 emails = emails.options(
711 emails = emails.options(
707 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
712 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
708 )
713 )
709
714
710 return [self.email] + [x.email for x in emails]
715 return [self.email] + [x.email for x in emails]
711
716
712 @property
717 @property
713 def auth_tokens(self):
718 def auth_tokens(self):
714 auth_tokens = self.get_auth_tokens()
719 auth_tokens = self.get_auth_tokens()
715 return [x.api_key for x in auth_tokens]
720 return [x.api_key for x in auth_tokens]
716
721
717 def get_auth_tokens(self):
722 def get_auth_tokens(self):
718 return UserApiKeys.query()\
723 return UserApiKeys.query()\
719 .filter(UserApiKeys.user == self)\
724 .filter(UserApiKeys.user == self)\
720 .order_by(UserApiKeys.user_api_key_id.asc())\
725 .order_by(UserApiKeys.user_api_key_id.asc())\
721 .all()
726 .all()
722
727
723 @LazyProperty
728 @LazyProperty
724 def feed_token(self):
729 def feed_token(self):
725 return self.get_feed_token()
730 return self.get_feed_token()
726
731
727 def get_feed_token(self, cache=True):
732 def get_feed_token(self, cache=True):
728 feed_tokens = UserApiKeys.query()\
733 feed_tokens = UserApiKeys.query()\
729 .filter(UserApiKeys.user == self)\
734 .filter(UserApiKeys.user == self)\
730 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
735 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
731 if cache:
736 if cache:
732 feed_tokens = feed_tokens.options(
737 feed_tokens = feed_tokens.options(
733 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
738 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
734
739
735 feed_tokens = feed_tokens.all()
740 feed_tokens = feed_tokens.all()
736 if feed_tokens:
741 if feed_tokens:
737 return feed_tokens[0].api_key
742 return feed_tokens[0].api_key
738 return 'NO_FEED_TOKEN_AVAILABLE'
743 return 'NO_FEED_TOKEN_AVAILABLE'
739
744
740 @LazyProperty
745 @LazyProperty
741 def artifact_token(self):
746 def artifact_token(self):
742 return self.get_artifact_token()
747 return self.get_artifact_token()
743
748
744 def get_artifact_token(self, cache=True):
749 def get_artifact_token(self, cache=True):
745 artifacts_tokens = UserApiKeys.query()\
750 artifacts_tokens = UserApiKeys.query()\
746 .filter(UserApiKeys.user == self)\
751 .filter(UserApiKeys.user == self)\
747 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
752 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
748 if cache:
753 if cache:
749 artifacts_tokens = artifacts_tokens.options(
754 artifacts_tokens = artifacts_tokens.options(
750 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
755 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
751
756
752 artifacts_tokens = artifacts_tokens.all()
757 artifacts_tokens = artifacts_tokens.all()
753 if artifacts_tokens:
758 if artifacts_tokens:
754 return artifacts_tokens[0].api_key
759 return artifacts_tokens[0].api_key
755 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
760 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
756
761
757 @classmethod
762 @classmethod
758 def get(cls, user_id, cache=False):
763 def get(cls, user_id, cache=False):
759 if not user_id:
764 if not user_id:
760 return
765 return
761
766
762 user = cls.query()
767 user = cls.query()
763 if cache:
768 if cache:
764 user = user.options(
769 user = user.options(
765 FromCache("sql_cache_short", "get_users_%s" % user_id))
770 FromCache("sql_cache_short", "get_users_%s" % user_id))
766 return user.get(user_id)
771 return user.get(user_id)
767
772
768 @classmethod
773 @classmethod
769 def extra_valid_auth_tokens(cls, user, role=None):
774 def extra_valid_auth_tokens(cls, user, role=None):
770 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
775 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
771 .filter(or_(UserApiKeys.expires == -1,
776 .filter(or_(UserApiKeys.expires == -1,
772 UserApiKeys.expires >= time.time()))
777 UserApiKeys.expires >= time.time()))
773 if role:
778 if role:
774 tokens = tokens.filter(or_(UserApiKeys.role == role,
779 tokens = tokens.filter(or_(UserApiKeys.role == role,
775 UserApiKeys.role == UserApiKeys.ROLE_ALL))
780 UserApiKeys.role == UserApiKeys.ROLE_ALL))
776 return tokens.all()
781 return tokens.all()
777
782
778 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
783 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
779 from rhodecode.lib import auth
784 from rhodecode.lib import auth
780
785
781 log.debug('Trying to authenticate user: %s via auth-token, '
786 log.debug('Trying to authenticate user: %s via auth-token, '
782 'and roles: %s', self, roles)
787 'and roles: %s', self, roles)
783
788
784 if not auth_token:
789 if not auth_token:
785 return False
790 return False
786
791
787 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
792 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
788 tokens_q = UserApiKeys.query()\
793 tokens_q = UserApiKeys.query()\
789 .filter(UserApiKeys.user_id == self.user_id)\
794 .filter(UserApiKeys.user_id == self.user_id)\
790 .filter(or_(UserApiKeys.expires == -1,
795 .filter(or_(UserApiKeys.expires == -1,
791 UserApiKeys.expires >= time.time()))
796 UserApiKeys.expires >= time.time()))
792
797
793 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
798 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
794
799
795 crypto_backend = auth.crypto_backend()
800 crypto_backend = auth.crypto_backend()
796 enc_token_map = {}
801 enc_token_map = {}
797 plain_token_map = {}
802 plain_token_map = {}
798 for token in tokens_q:
803 for token in tokens_q:
799 if token.api_key.startswith(crypto_backend.ENC_PREF):
804 if token.api_key.startswith(crypto_backend.ENC_PREF):
800 enc_token_map[token.api_key] = token
805 enc_token_map[token.api_key] = token
801 else:
806 else:
802 plain_token_map[token.api_key] = token
807 plain_token_map[token.api_key] = token
803 log.debug(
808 log.debug(
804 'Found %s plain and %s encrypted tokens to check for authentication for this user',
809 'Found %s plain and %s encrypted tokens to check for authentication for this user',
805 len(plain_token_map), len(enc_token_map))
810 len(plain_token_map), len(enc_token_map))
806
811
807 # plain token match comes first
812 # plain token match comes first
808 match = plain_token_map.get(auth_token)
813 match = plain_token_map.get(auth_token)
809
814
810 # check encrypted tokens now
815 # check encrypted tokens now
811 if not match:
816 if not match:
812 for token_hash, token in enc_token_map.items():
817 for token_hash, token in enc_token_map.items():
813 # NOTE(marcink): this is expensive to calculate, but most secure
818 # NOTE(marcink): this is expensive to calculate, but most secure
814 if crypto_backend.hash_check(auth_token, token_hash):
819 if crypto_backend.hash_check(auth_token, token_hash):
815 match = token
820 match = token
816 break
821 break
817
822
818 if match:
823 if match:
819 log.debug('Found matching token %s', match)
824 log.debug('Found matching token %s', match)
820 if match.repo_id:
825 if match.repo_id:
821 log.debug('Found scope, checking for scope match of token %s', match)
826 log.debug('Found scope, checking for scope match of token %s', match)
822 if match.repo_id == scope_repo_id:
827 if match.repo_id == scope_repo_id:
823 return True
828 return True
824 else:
829 else:
825 log.debug(
830 log.debug(
826 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
831 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
827 'and calling scope is:%s, skipping further checks',
832 'and calling scope is:%s, skipping further checks',
828 match.repo, scope_repo_id)
833 match.repo, scope_repo_id)
829 return False
834 return False
830 else:
835 else:
831 return True
836 return True
832
837
833 return False
838 return False
834
839
835 @property
840 @property
836 def ip_addresses(self):
841 def ip_addresses(self):
837 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
842 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
838 return [x.ip_addr for x in ret]
843 return [x.ip_addr for x in ret]
839
844
840 @property
845 @property
841 def username_and_name(self):
846 def username_and_name(self):
842 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
847 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
843
848
844 @property
849 @property
845 def username_or_name_or_email(self):
850 def username_or_name_or_email(self):
846 full_name = self.full_name if self.full_name is not ' ' else None
851 full_name = self.full_name if self.full_name is not ' ' else None
847 return self.username or full_name or self.email
852 return self.username or full_name or self.email
848
853
849 @property
854 @property
850 def full_name(self):
855 def full_name(self):
851 return '%s %s' % (self.first_name, self.last_name)
856 return '%s %s' % (self.first_name, self.last_name)
852
857
853 @property
858 @property
854 def full_name_or_username(self):
859 def full_name_or_username(self):
855 return ('%s %s' % (self.first_name, self.last_name)
860 return ('%s %s' % (self.first_name, self.last_name)
856 if (self.first_name and self.last_name) else self.username)
861 if (self.first_name and self.last_name) else self.username)
857
862
858 @property
863 @property
859 def full_contact(self):
864 def full_contact(self):
860 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
865 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
861
866
862 @property
867 @property
863 def short_contact(self):
868 def short_contact(self):
864 return '%s %s' % (self.first_name, self.last_name)
869 return '%s %s' % (self.first_name, self.last_name)
865
870
866 @property
871 @property
867 def is_admin(self):
872 def is_admin(self):
868 return self.admin
873 return self.admin
869
874
870 @property
875 @property
871 def language(self):
876 def language(self):
872 return self.user_data.get('language')
877 return self.user_data.get('language')
873
878
874 def AuthUser(self, **kwargs):
879 def AuthUser(self, **kwargs):
875 """
880 """
876 Returns instance of AuthUser for this user
881 Returns instance of AuthUser for this user
877 """
882 """
878 from rhodecode.lib.auth import AuthUser
883 from rhodecode.lib.auth import AuthUser
879 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
884 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
880
885
881 @hybrid_property
886 @hybrid_property
882 def user_data(self):
887 def user_data(self):
883 if not self._user_data:
888 if not self._user_data:
884 return {}
889 return {}
885
890
886 try:
891 try:
887 return json.loads(self._user_data)
892 return json.loads(self._user_data)
888 except TypeError:
893 except TypeError:
889 return {}
894 return {}
890
895
891 @user_data.setter
896 @user_data.setter
892 def user_data(self, val):
897 def user_data(self, val):
893 if not isinstance(val, dict):
898 if not isinstance(val, dict):
894 raise Exception('user_data must be dict, got %s' % type(val))
899 raise Exception('user_data must be dict, got %s' % type(val))
895 try:
900 try:
896 self._user_data = json.dumps(val)
901 self._user_data = json.dumps(val)
897 except Exception:
902 except Exception:
898 log.error(traceback.format_exc())
903 log.error(traceback.format_exc())
899
904
900 @classmethod
905 @classmethod
901 def get_by_username(cls, username, case_insensitive=False,
906 def get_by_username(cls, username, case_insensitive=False,
902 cache=False, identity_cache=False):
907 cache=False, identity_cache=False):
903 session = Session()
908 session = Session()
904
909
905 if case_insensitive:
910 if case_insensitive:
906 q = cls.query().filter(
911 q = cls.query().filter(
907 func.lower(cls.username) == func.lower(username))
912 func.lower(cls.username) == func.lower(username))
908 else:
913 else:
909 q = cls.query().filter(cls.username == username)
914 q = cls.query().filter(cls.username == username)
910
915
911 if cache:
916 if cache:
912 if identity_cache:
917 if identity_cache:
913 val = cls.identity_cache(session, 'username', username)
918 val = cls.identity_cache(session, 'username', username)
914 if val:
919 if val:
915 return val
920 return val
916 else:
921 else:
917 cache_key = "get_user_by_name_%s" % _hash_key(username)
922 cache_key = "get_user_by_name_%s" % _hash_key(username)
918 q = q.options(
923 q = q.options(
919 FromCache("sql_cache_short", cache_key))
924 FromCache("sql_cache_short", cache_key))
920
925
921 return q.scalar()
926 return q.scalar()
922
927
923 @classmethod
928 @classmethod
924 def get_by_auth_token(cls, auth_token, cache=False):
929 def get_by_auth_token(cls, auth_token, cache=False):
925 q = UserApiKeys.query()\
930 q = UserApiKeys.query()\
926 .filter(UserApiKeys.api_key == auth_token)\
931 .filter(UserApiKeys.api_key == auth_token)\
927 .filter(or_(UserApiKeys.expires == -1,
932 .filter(or_(UserApiKeys.expires == -1,
928 UserApiKeys.expires >= time.time()))
933 UserApiKeys.expires >= time.time()))
929 if cache:
934 if cache:
930 q = q.options(
935 q = q.options(
931 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
936 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
932
937
933 match = q.first()
938 match = q.first()
934 if match:
939 if match:
935 return match.user
940 return match.user
936
941
937 @classmethod
942 @classmethod
938 def get_by_email(cls, email, case_insensitive=False, cache=False):
943 def get_by_email(cls, email, case_insensitive=False, cache=False):
939
944
940 if case_insensitive:
945 if case_insensitive:
941 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
946 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
942
947
943 else:
948 else:
944 q = cls.query().filter(cls.email == email)
949 q = cls.query().filter(cls.email == email)
945
950
946 email_key = _hash_key(email)
951 email_key = _hash_key(email)
947 if cache:
952 if cache:
948 q = q.options(
953 q = q.options(
949 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
954 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
950
955
951 ret = q.scalar()
956 ret = q.scalar()
952 if ret is None:
957 if ret is None:
953 q = UserEmailMap.query()
958 q = UserEmailMap.query()
954 # try fetching in alternate email map
959 # try fetching in alternate email map
955 if case_insensitive:
960 if case_insensitive:
956 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
961 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
957 else:
962 else:
958 q = q.filter(UserEmailMap.email == email)
963 q = q.filter(UserEmailMap.email == email)
959 q = q.options(joinedload(UserEmailMap.user))
964 q = q.options(joinedload(UserEmailMap.user))
960 if cache:
965 if cache:
961 q = q.options(
966 q = q.options(
962 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
967 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
963 ret = getattr(q.scalar(), 'user', None)
968 ret = getattr(q.scalar(), 'user', None)
964
969
965 return ret
970 return ret
966
971
967 @classmethod
972 @classmethod
968 def get_from_cs_author(cls, author):
973 def get_from_cs_author(cls, author):
969 """
974 """
970 Tries to get User objects out of commit author string
975 Tries to get User objects out of commit author string
971
976
972 :param author:
977 :param author:
973 """
978 """
974 from rhodecode.lib.helpers import email, author_name
979 from rhodecode.lib.helpers import email, author_name
975 # Valid email in the attribute passed, see if they're in the system
980 # Valid email in the attribute passed, see if they're in the system
976 _email = email(author)
981 _email = email(author)
977 if _email:
982 if _email:
978 user = cls.get_by_email(_email, case_insensitive=True)
983 user = cls.get_by_email(_email, case_insensitive=True)
979 if user:
984 if user:
980 return user
985 return user
981 # Maybe we can match by username?
986 # Maybe we can match by username?
982 _author = author_name(author)
987 _author = author_name(author)
983 user = cls.get_by_username(_author, case_insensitive=True)
988 user = cls.get_by_username(_author, case_insensitive=True)
984 if user:
989 if user:
985 return user
990 return user
986
991
987 def update_userdata(self, **kwargs):
992 def update_userdata(self, **kwargs):
988 usr = self
993 usr = self
989 old = usr.user_data
994 old = usr.user_data
990 old.update(**kwargs)
995 old.update(**kwargs)
991 usr.user_data = old
996 usr.user_data = old
992 Session().add(usr)
997 Session().add(usr)
993 log.debug('updated userdata with %s', kwargs)
998 log.debug('updated userdata with %s', kwargs)
994
999
995 def update_lastlogin(self):
1000 def update_lastlogin(self):
996 """Update user lastlogin"""
1001 """Update user lastlogin"""
997 self.last_login = datetime.datetime.now()
1002 self.last_login = datetime.datetime.now()
998 Session().add(self)
1003 Session().add(self)
999 log.debug('updated user %s lastlogin', self.username)
1004 log.debug('updated user %s lastlogin', self.username)
1000
1005
1001 def update_password(self, new_password):
1006 def update_password(self, new_password):
1002 from rhodecode.lib.auth import get_crypt_password
1007 from rhodecode.lib.auth import get_crypt_password
1003
1008
1004 self.password = get_crypt_password(new_password)
1009 self.password = get_crypt_password(new_password)
1005 Session().add(self)
1010 Session().add(self)
1006
1011
1007 @classmethod
1012 @classmethod
1008 def get_first_super_admin(cls):
1013 def get_first_super_admin(cls):
1009 user = User.query()\
1014 user = User.query()\
1010 .filter(User.admin == true()) \
1015 .filter(User.admin == true()) \
1011 .order_by(User.user_id.asc()) \
1016 .order_by(User.user_id.asc()) \
1012 .first()
1017 .first()
1013
1018
1014 if user is None:
1019 if user is None:
1015 raise Exception('FATAL: Missing administrative account!')
1020 raise Exception('FATAL: Missing administrative account!')
1016 return user
1021 return user
1017
1022
1018 @classmethod
1023 @classmethod
1019 def get_all_super_admins(cls, only_active=False):
1024 def get_all_super_admins(cls, only_active=False):
1020 """
1025 """
1021 Returns all admin accounts sorted by username
1026 Returns all admin accounts sorted by username
1022 """
1027 """
1023 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1028 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1024 if only_active:
1029 if only_active:
1025 qry = qry.filter(User.active == true())
1030 qry = qry.filter(User.active == true())
1026 return qry.all()
1031 return qry.all()
1027
1032
1028 @classmethod
1033 @classmethod
1029 def get_all_user_ids(cls, only_active=True):
1034 def get_all_user_ids(cls, only_active=True):
1030 """
1035 """
1031 Returns all users IDs
1036 Returns all users IDs
1032 """
1037 """
1033 qry = Session().query(User.user_id)
1038 qry = Session().query(User.user_id)
1034
1039
1035 if only_active:
1040 if only_active:
1036 qry = qry.filter(User.active == true())
1041 qry = qry.filter(User.active == true())
1037 return [x.user_id for x in qry]
1042 return [x.user_id for x in qry]
1038
1043
1039 @classmethod
1044 @classmethod
1040 def get_default_user(cls, cache=False, refresh=False):
1045 def get_default_user(cls, cache=False, refresh=False):
1041 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1046 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1042 if user is None:
1047 if user is None:
1043 raise Exception('FATAL: Missing default account!')
1048 raise Exception('FATAL: Missing default account!')
1044 if refresh:
1049 if refresh:
1045 # The default user might be based on outdated state which
1050 # The default user might be based on outdated state which
1046 # has been loaded from the cache.
1051 # has been loaded from the cache.
1047 # A call to refresh() ensures that the
1052 # A call to refresh() ensures that the
1048 # latest state from the database is used.
1053 # latest state from the database is used.
1049 Session().refresh(user)
1054 Session().refresh(user)
1050 return user
1055 return user
1051
1056
1052 @classmethod
1057 @classmethod
1053 def get_default_user_id(cls):
1058 def get_default_user_id(cls):
1054 import rhodecode
1059 import rhodecode
1055 return rhodecode.CONFIG['default_user_id']
1060 return rhodecode.CONFIG['default_user_id']
1056
1061
1057 def _get_default_perms(self, user, suffix=''):
1062 def _get_default_perms(self, user, suffix=''):
1058 from rhodecode.model.permission import PermissionModel
1063 from rhodecode.model.permission import PermissionModel
1059 return PermissionModel().get_default_perms(user.user_perms, suffix)
1064 return PermissionModel().get_default_perms(user.user_perms, suffix)
1060
1065
1061 def get_default_perms(self, suffix=''):
1066 def get_default_perms(self, suffix=''):
1062 return self._get_default_perms(self, suffix)
1067 return self._get_default_perms(self, suffix)
1063
1068
1064 def get_api_data(self, include_secrets=False, details='full'):
1069 def get_api_data(self, include_secrets=False, details='full'):
1065 """
1070 """
1066 Common function for generating user related data for API
1071 Common function for generating user related data for API
1067
1072
1068 :param include_secrets: By default secrets in the API data will be replaced
1073 :param include_secrets: By default secrets in the API data will be replaced
1069 by a placeholder value to prevent exposing this data by accident. In case
1074 by a placeholder value to prevent exposing this data by accident. In case
1070 this data shall be exposed, set this flag to ``True``.
1075 this data shall be exposed, set this flag to ``True``.
1071
1076
1072 :param details: details can be 'basic|full' basic gives only a subset of
1077 :param details: details can be 'basic|full' basic gives only a subset of
1073 the available user information that includes user_id, name and emails.
1078 the available user information that includes user_id, name and emails.
1074 """
1079 """
1075 user = self
1080 user = self
1076 user_data = self.user_data
1081 user_data = self.user_data
1077 data = {
1082 data = {
1078 'user_id': user.user_id,
1083 'user_id': user.user_id,
1079 'username': user.username,
1084 'username': user.username,
1080 'firstname': user.name,
1085 'firstname': user.name,
1081 'lastname': user.lastname,
1086 'lastname': user.lastname,
1082 'description': user.description,
1087 'description': user.description,
1083 'email': user.email,
1088 'email': user.email,
1084 'emails': user.emails,
1089 'emails': user.emails,
1085 }
1090 }
1086 if details == 'basic':
1091 if details == 'basic':
1087 return data
1092 return data
1088
1093
1089 auth_token_length = 40
1094 auth_token_length = 40
1090 auth_token_replacement = '*' * auth_token_length
1095 auth_token_replacement = '*' * auth_token_length
1091
1096
1092 extras = {
1097 extras = {
1093 'auth_tokens': [auth_token_replacement],
1098 'auth_tokens': [auth_token_replacement],
1094 'active': user.active,
1099 'active': user.active,
1095 'admin': user.admin,
1100 'admin': user.admin,
1096 'extern_type': user.extern_type,
1101 'extern_type': user.extern_type,
1097 'extern_name': user.extern_name,
1102 'extern_name': user.extern_name,
1098 'last_login': user.last_login,
1103 'last_login': user.last_login,
1099 'last_activity': user.last_activity,
1104 'last_activity': user.last_activity,
1100 'ip_addresses': user.ip_addresses,
1105 'ip_addresses': user.ip_addresses,
1101 'language': user_data.get('language')
1106 'language': user_data.get('language')
1102 }
1107 }
1103 data.update(extras)
1108 data.update(extras)
1104
1109
1105 if include_secrets:
1110 if include_secrets:
1106 data['auth_tokens'] = user.auth_tokens
1111 data['auth_tokens'] = user.auth_tokens
1107 return data
1112 return data
1108
1113
1109 def __json__(self):
1114 def __json__(self):
1110 data = {
1115 data = {
1111 'full_name': self.full_name,
1116 'full_name': self.full_name,
1112 'full_name_or_username': self.full_name_or_username,
1117 'full_name_or_username': self.full_name_or_username,
1113 'short_contact': self.short_contact,
1118 'short_contact': self.short_contact,
1114 'full_contact': self.full_contact,
1119 'full_contact': self.full_contact,
1115 }
1120 }
1116 data.update(self.get_api_data())
1121 data.update(self.get_api_data())
1117 return data
1122 return data
1118
1123
1119
1124
1120 class UserApiKeys(Base, BaseModel):
1125 class UserApiKeys(Base, BaseModel):
1121 __tablename__ = 'user_api_keys'
1126 __tablename__ = 'user_api_keys'
1122 __table_args__ = (
1127 __table_args__ = (
1123 Index('uak_api_key_idx', 'api_key'),
1128 Index('uak_api_key_idx', 'api_key'),
1124 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1129 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1125 base_table_args
1130 base_table_args
1126 )
1131 )
1127 __mapper_args__ = {}
1132 __mapper_args__ = {}
1128
1133
1129 # ApiKey role
1134 # ApiKey role
1130 ROLE_ALL = 'token_role_all'
1135 ROLE_ALL = 'token_role_all'
1131 ROLE_HTTP = 'token_role_http'
1136 ROLE_HTTP = 'token_role_http'
1132 ROLE_VCS = 'token_role_vcs'
1137 ROLE_VCS = 'token_role_vcs'
1133 ROLE_API = 'token_role_api'
1138 ROLE_API = 'token_role_api'
1134 ROLE_FEED = 'token_role_feed'
1139 ROLE_FEED = 'token_role_feed'
1135 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1140 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1136 ROLE_PASSWORD_RESET = 'token_password_reset'
1141 ROLE_PASSWORD_RESET = 'token_password_reset'
1137
1142
1138 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1143 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1139
1144
1140 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1141 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1142 api_key = Column("api_key", String(255), nullable=False, unique=True)
1147 api_key = Column("api_key", String(255), nullable=False, unique=True)
1143 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1148 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1144 expires = Column('expires', Float(53), nullable=False)
1149 expires = Column('expires', Float(53), nullable=False)
1145 role = Column('role', String(255), nullable=True)
1150 role = Column('role', String(255), nullable=True)
1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1151 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1147
1152
1148 # scope columns
1153 # scope columns
1149 repo_id = Column(
1154 repo_id = Column(
1150 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1155 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1151 nullable=True, unique=None, default=None)
1156 nullable=True, unique=None, default=None)
1152 repo = relationship('Repository', lazy='joined')
1157 repo = relationship('Repository', lazy='joined')
1153
1158
1154 repo_group_id = Column(
1159 repo_group_id = Column(
1155 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1160 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1156 nullable=True, unique=None, default=None)
1161 nullable=True, unique=None, default=None)
1157 repo_group = relationship('RepoGroup', lazy='joined')
1162 repo_group = relationship('RepoGroup', lazy='joined')
1158
1163
1159 user = relationship('User', lazy='joined')
1164 user = relationship('User', lazy='joined')
1160
1165
1161 def __unicode__(self):
1166 def __unicode__(self):
1162 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1167 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1163
1168
1164 def __json__(self):
1169 def __json__(self):
1165 data = {
1170 data = {
1166 'auth_token': self.api_key,
1171 'auth_token': self.api_key,
1167 'role': self.role,
1172 'role': self.role,
1168 'scope': self.scope_humanized,
1173 'scope': self.scope_humanized,
1169 'expired': self.expired
1174 'expired': self.expired
1170 }
1175 }
1171 return data
1176 return data
1172
1177
1173 def get_api_data(self, include_secrets=False):
1178 def get_api_data(self, include_secrets=False):
1174 data = self.__json__()
1179 data = self.__json__()
1175 if include_secrets:
1180 if include_secrets:
1176 return data
1181 return data
1177 else:
1182 else:
1178 data['auth_token'] = self.token_obfuscated
1183 data['auth_token'] = self.token_obfuscated
1179 return data
1184 return data
1180
1185
1181 @hybrid_property
1186 @hybrid_property
1182 def description_safe(self):
1187 def description_safe(self):
1183 from rhodecode.lib import helpers as h
1188 from rhodecode.lib import helpers as h
1184 return h.escape(self.description)
1189 return h.escape(self.description)
1185
1190
1186 @property
1191 @property
1187 def expired(self):
1192 def expired(self):
1188 if self.expires == -1:
1193 if self.expires == -1:
1189 return False
1194 return False
1190 return time.time() > self.expires
1195 return time.time() > self.expires
1191
1196
1192 @classmethod
1197 @classmethod
1193 def _get_role_name(cls, role):
1198 def _get_role_name(cls, role):
1194 return {
1199 return {
1195 cls.ROLE_ALL: _('all'),
1200 cls.ROLE_ALL: _('all'),
1196 cls.ROLE_HTTP: _('http/web interface'),
1201 cls.ROLE_HTTP: _('http/web interface'),
1197 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1202 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1198 cls.ROLE_API: _('api calls'),
1203 cls.ROLE_API: _('api calls'),
1199 cls.ROLE_FEED: _('feed access'),
1204 cls.ROLE_FEED: _('feed access'),
1200 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1205 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1201 }.get(role, role)
1206 }.get(role, role)
1202
1207
1203 @property
1208 @property
1204 def role_humanized(self):
1209 def role_humanized(self):
1205 return self._get_role_name(self.role)
1210 return self._get_role_name(self.role)
1206
1211
1207 def _get_scope(self):
1212 def _get_scope(self):
1208 if self.repo:
1213 if self.repo:
1209 return 'Repository: {}'.format(self.repo.repo_name)
1214 return 'Repository: {}'.format(self.repo.repo_name)
1210 if self.repo_group:
1215 if self.repo_group:
1211 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1216 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1212 return 'Global'
1217 return 'Global'
1213
1218
1214 @property
1219 @property
1215 def scope_humanized(self):
1220 def scope_humanized(self):
1216 return self._get_scope()
1221 return self._get_scope()
1217
1222
1218 @property
1223 @property
1219 def token_obfuscated(self):
1224 def token_obfuscated(self):
1220 if self.api_key:
1225 if self.api_key:
1221 return self.api_key[:4] + "****"
1226 return self.api_key[:4] + "****"
1222
1227
1223
1228
1224 class UserEmailMap(Base, BaseModel):
1229 class UserEmailMap(Base, BaseModel):
1225 __tablename__ = 'user_email_map'
1230 __tablename__ = 'user_email_map'
1226 __table_args__ = (
1231 __table_args__ = (
1227 Index('uem_email_idx', 'email'),
1232 Index('uem_email_idx', 'email'),
1228 UniqueConstraint('email'),
1233 UniqueConstraint('email'),
1229 base_table_args
1234 base_table_args
1230 )
1235 )
1231 __mapper_args__ = {}
1236 __mapper_args__ = {}
1232
1237
1233 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1234 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1239 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1235 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1240 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1236 user = relationship('User', lazy='joined')
1241 user = relationship('User', lazy='joined')
1237
1242
1238 @validates('_email')
1243 @validates('_email')
1239 def validate_email(self, key, email):
1244 def validate_email(self, key, email):
1240 # check if this email is not main one
1245 # check if this email is not main one
1241 main_email = Session().query(User).filter(User.email == email).scalar()
1246 main_email = Session().query(User).filter(User.email == email).scalar()
1242 if main_email is not None:
1247 if main_email is not None:
1243 raise AttributeError('email %s is present is user table' % email)
1248 raise AttributeError('email %s is present is user table' % email)
1244 return email
1249 return email
1245
1250
1246 @hybrid_property
1251 @hybrid_property
1247 def email(self):
1252 def email(self):
1248 return self._email
1253 return self._email
1249
1254
1250 @email.setter
1255 @email.setter
1251 def email(self, val):
1256 def email(self, val):
1252 self._email = val.lower() if val else None
1257 self._email = val.lower() if val else None
1253
1258
1254
1259
1255 class UserIpMap(Base, BaseModel):
1260 class UserIpMap(Base, BaseModel):
1256 __tablename__ = 'user_ip_map'
1261 __tablename__ = 'user_ip_map'
1257 __table_args__ = (
1262 __table_args__ = (
1258 UniqueConstraint('user_id', 'ip_addr'),
1263 UniqueConstraint('user_id', 'ip_addr'),
1259 base_table_args
1264 base_table_args
1260 )
1265 )
1261 __mapper_args__ = {}
1266 __mapper_args__ = {}
1262
1267
1263 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1268 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1269 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1265 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1270 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1266 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1271 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1267 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1272 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1268 user = relationship('User', lazy='joined')
1273 user = relationship('User', lazy='joined')
1269
1274
1270 @hybrid_property
1275 @hybrid_property
1271 def description_safe(self):
1276 def description_safe(self):
1272 from rhodecode.lib import helpers as h
1277 from rhodecode.lib import helpers as h
1273 return h.escape(self.description)
1278 return h.escape(self.description)
1274
1279
1275 @classmethod
1280 @classmethod
1276 def _get_ip_range(cls, ip_addr):
1281 def _get_ip_range(cls, ip_addr):
1277 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1282 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1278 return [str(net.network_address), str(net.broadcast_address)]
1283 return [str(net.network_address), str(net.broadcast_address)]
1279
1284
1280 def __json__(self):
1285 def __json__(self):
1281 return {
1286 return {
1282 'ip_addr': self.ip_addr,
1287 'ip_addr': self.ip_addr,
1283 'ip_range': self._get_ip_range(self.ip_addr),
1288 'ip_range': self._get_ip_range(self.ip_addr),
1284 }
1289 }
1285
1290
1286 def __unicode__(self):
1291 def __unicode__(self):
1287 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1292 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1288 self.user_id, self.ip_addr)
1293 self.user_id, self.ip_addr)
1289
1294
1290
1295
1291 class UserSshKeys(Base, BaseModel):
1296 class UserSshKeys(Base, BaseModel):
1292 __tablename__ = 'user_ssh_keys'
1297 __tablename__ = 'user_ssh_keys'
1293 __table_args__ = (
1298 __table_args__ = (
1294 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1299 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1295
1300
1296 UniqueConstraint('ssh_key_fingerprint'),
1301 UniqueConstraint('ssh_key_fingerprint'),
1297
1302
1298 base_table_args
1303 base_table_args
1299 )
1304 )
1300 __mapper_args__ = {}
1305 __mapper_args__ = {}
1301
1306
1302 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1307 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1303 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1308 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1304 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1309 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1305
1310
1306 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1311 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1307
1312
1308 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1313 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1309 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1314 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1310 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1315 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1311
1316
1312 user = relationship('User', lazy='joined')
1317 user = relationship('User', lazy='joined')
1313
1318
1314 def __json__(self):
1319 def __json__(self):
1315 data = {
1320 data = {
1316 'ssh_fingerprint': self.ssh_key_fingerprint,
1321 'ssh_fingerprint': self.ssh_key_fingerprint,
1317 'description': self.description,
1322 'description': self.description,
1318 'created_on': self.created_on
1323 'created_on': self.created_on
1319 }
1324 }
1320 return data
1325 return data
1321
1326
1322 def get_api_data(self):
1327 def get_api_data(self):
1323 data = self.__json__()
1328 data = self.__json__()
1324 return data
1329 return data
1325
1330
1326
1331
1327 class UserLog(Base, BaseModel):
1332 class UserLog(Base, BaseModel):
1328 __tablename__ = 'user_logs'
1333 __tablename__ = 'user_logs'
1329 __table_args__ = (
1334 __table_args__ = (
1330 base_table_args,
1335 base_table_args,
1331 )
1336 )
1332
1337
1333 VERSION_1 = 'v1'
1338 VERSION_1 = 'v1'
1334 VERSION_2 = 'v2'
1339 VERSION_2 = 'v2'
1335 VERSIONS = [VERSION_1, VERSION_2]
1340 VERSIONS = [VERSION_1, VERSION_2]
1336
1341
1337 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1342 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1338 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1343 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1339 username = Column("username", String(255), nullable=True, unique=None, default=None)
1344 username = Column("username", String(255), nullable=True, unique=None, default=None)
1340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1345 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1341 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1346 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1342 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1347 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1343 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1348 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1349 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1345
1350
1346 version = Column("version", String(255), nullable=True, default=VERSION_1)
1351 version = Column("version", String(255), nullable=True, default=VERSION_1)
1347 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1352 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1348 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1353 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1349
1354
1350 def __unicode__(self):
1355 def __unicode__(self):
1351 return u"<%s('id:%s:%s')>" % (
1356 return u"<%s('id:%s:%s')>" % (
1352 self.__class__.__name__, self.repository_name, self.action)
1357 self.__class__.__name__, self.repository_name, self.action)
1353
1358
1354 def __json__(self):
1359 def __json__(self):
1355 return {
1360 return {
1356 'user_id': self.user_id,
1361 'user_id': self.user_id,
1357 'username': self.username,
1362 'username': self.username,
1358 'repository_id': self.repository_id,
1363 'repository_id': self.repository_id,
1359 'repository_name': self.repository_name,
1364 'repository_name': self.repository_name,
1360 'user_ip': self.user_ip,
1365 'user_ip': self.user_ip,
1361 'action_date': self.action_date,
1366 'action_date': self.action_date,
1362 'action': self.action,
1367 'action': self.action,
1363 }
1368 }
1364
1369
1365 @hybrid_property
1370 @hybrid_property
1366 def entry_id(self):
1371 def entry_id(self):
1367 return self.user_log_id
1372 return self.user_log_id
1368
1373
1369 @property
1374 @property
1370 def action_as_day(self):
1375 def action_as_day(self):
1371 return datetime.date(*self.action_date.timetuple()[:3])
1376 return datetime.date(*self.action_date.timetuple()[:3])
1372
1377
1373 user = relationship('User')
1378 user = relationship('User')
1374 repository = relationship('Repository', cascade='')
1379 repository = relationship('Repository', cascade='')
1375
1380
1376
1381
1377 class UserGroup(Base, BaseModel):
1382 class UserGroup(Base, BaseModel):
1378 __tablename__ = 'users_groups'
1383 __tablename__ = 'users_groups'
1379 __table_args__ = (
1384 __table_args__ = (
1380 base_table_args,
1385 base_table_args,
1381 )
1386 )
1382
1387
1383 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1388 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1384 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1389 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1385 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1390 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1386 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1391 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1387 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1392 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1389 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1394 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1390 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1395 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1391
1396
1392 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1397 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1393 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1398 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1394 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1399 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1395 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1400 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1396 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1401 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1397 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1402 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1398
1403
1399 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1404 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1400 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1405 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1401
1406
1402 @classmethod
1407 @classmethod
1403 def _load_group_data(cls, column):
1408 def _load_group_data(cls, column):
1404 if not column:
1409 if not column:
1405 return {}
1410 return {}
1406
1411
1407 try:
1412 try:
1408 return json.loads(column) or {}
1413 return json.loads(column) or {}
1409 except TypeError:
1414 except TypeError:
1410 return {}
1415 return {}
1411
1416
1412 @hybrid_property
1417 @hybrid_property
1413 def description_safe(self):
1418 def description_safe(self):
1414 from rhodecode.lib import helpers as h
1419 from rhodecode.lib import helpers as h
1415 return h.escape(self.user_group_description)
1420 return h.escape(self.user_group_description)
1416
1421
1417 @hybrid_property
1422 @hybrid_property
1418 def group_data(self):
1423 def group_data(self):
1419 return self._load_group_data(self._group_data)
1424 return self._load_group_data(self._group_data)
1420
1425
1421 @group_data.expression
1426 @group_data.expression
1422 def group_data(self, **kwargs):
1427 def group_data(self, **kwargs):
1423 return self._group_data
1428 return self._group_data
1424
1429
1425 @group_data.setter
1430 @group_data.setter
1426 def group_data(self, val):
1431 def group_data(self, val):
1427 try:
1432 try:
1428 self._group_data = json.dumps(val)
1433 self._group_data = json.dumps(val)
1429 except Exception:
1434 except Exception:
1430 log.error(traceback.format_exc())
1435 log.error(traceback.format_exc())
1431
1436
1432 @classmethod
1437 @classmethod
1433 def _load_sync(cls, group_data):
1438 def _load_sync(cls, group_data):
1434 if group_data:
1439 if group_data:
1435 return group_data.get('extern_type')
1440 return group_data.get('extern_type')
1436
1441
1437 @property
1442 @property
1438 def sync(self):
1443 def sync(self):
1439 return self._load_sync(self.group_data)
1444 return self._load_sync(self.group_data)
1440
1445
1441 def __unicode__(self):
1446 def __unicode__(self):
1442 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1447 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1443 self.users_group_id,
1448 self.users_group_id,
1444 self.users_group_name)
1449 self.users_group_name)
1445
1450
1446 @classmethod
1451 @classmethod
1447 def get_by_group_name(cls, group_name, cache=False,
1452 def get_by_group_name(cls, group_name, cache=False,
1448 case_insensitive=False):
1453 case_insensitive=False):
1449 if case_insensitive:
1454 if case_insensitive:
1450 q = cls.query().filter(func.lower(cls.users_group_name) ==
1455 q = cls.query().filter(func.lower(cls.users_group_name) ==
1451 func.lower(group_name))
1456 func.lower(group_name))
1452
1457
1453 else:
1458 else:
1454 q = cls.query().filter(cls.users_group_name == group_name)
1459 q = cls.query().filter(cls.users_group_name == group_name)
1455 if cache:
1460 if cache:
1456 q = q.options(
1461 q = q.options(
1457 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1462 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1458 return q.scalar()
1463 return q.scalar()
1459
1464
1460 @classmethod
1465 @classmethod
1461 def get(cls, user_group_id, cache=False):
1466 def get(cls, user_group_id, cache=False):
1462 if not user_group_id:
1467 if not user_group_id:
1463 return
1468 return
1464
1469
1465 user_group = cls.query()
1470 user_group = cls.query()
1466 if cache:
1471 if cache:
1467 user_group = user_group.options(
1472 user_group = user_group.options(
1468 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1473 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1469 return user_group.get(user_group_id)
1474 return user_group.get(user_group_id)
1470
1475
1471 def permissions(self, with_admins=True, with_owner=True,
1476 def permissions(self, with_admins=True, with_owner=True,
1472 expand_from_user_groups=False):
1477 expand_from_user_groups=False):
1473 """
1478 """
1474 Permissions for user groups
1479 Permissions for user groups
1475 """
1480 """
1476 _admin_perm = 'usergroup.admin'
1481 _admin_perm = 'usergroup.admin'
1477
1482
1478 owner_row = []
1483 owner_row = []
1479 if with_owner:
1484 if with_owner:
1480 usr = AttributeDict(self.user.get_dict())
1485 usr = AttributeDict(self.user.get_dict())
1481 usr.owner_row = True
1486 usr.owner_row = True
1482 usr.permission = _admin_perm
1487 usr.permission = _admin_perm
1483 owner_row.append(usr)
1488 owner_row.append(usr)
1484
1489
1485 super_admin_ids = []
1490 super_admin_ids = []
1486 super_admin_rows = []
1491 super_admin_rows = []
1487 if with_admins:
1492 if with_admins:
1488 for usr in User.get_all_super_admins():
1493 for usr in User.get_all_super_admins():
1489 super_admin_ids.append(usr.user_id)
1494 super_admin_ids.append(usr.user_id)
1490 # if this admin is also owner, don't double the record
1495 # if this admin is also owner, don't double the record
1491 if usr.user_id == owner_row[0].user_id:
1496 if usr.user_id == owner_row[0].user_id:
1492 owner_row[0].admin_row = True
1497 owner_row[0].admin_row = True
1493 else:
1498 else:
1494 usr = AttributeDict(usr.get_dict())
1499 usr = AttributeDict(usr.get_dict())
1495 usr.admin_row = True
1500 usr.admin_row = True
1496 usr.permission = _admin_perm
1501 usr.permission = _admin_perm
1497 super_admin_rows.append(usr)
1502 super_admin_rows.append(usr)
1498
1503
1499 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1504 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1500 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1505 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1501 joinedload(UserUserGroupToPerm.user),
1506 joinedload(UserUserGroupToPerm.user),
1502 joinedload(UserUserGroupToPerm.permission),)
1507 joinedload(UserUserGroupToPerm.permission),)
1503
1508
1504 # get owners and admins and permissions. We do a trick of re-writing
1509 # get owners and admins and permissions. We do a trick of re-writing
1505 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1510 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1506 # has a global reference and changing one object propagates to all
1511 # has a global reference and changing one object propagates to all
1507 # others. This means if admin is also an owner admin_row that change
1512 # others. This means if admin is also an owner admin_row that change
1508 # would propagate to both objects
1513 # would propagate to both objects
1509 perm_rows = []
1514 perm_rows = []
1510 for _usr in q.all():
1515 for _usr in q.all():
1511 usr = AttributeDict(_usr.user.get_dict())
1516 usr = AttributeDict(_usr.user.get_dict())
1512 # if this user is also owner/admin, mark as duplicate record
1517 # if this user is also owner/admin, mark as duplicate record
1513 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1518 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1514 usr.duplicate_perm = True
1519 usr.duplicate_perm = True
1515 usr.permission = _usr.permission.permission_name
1520 usr.permission = _usr.permission.permission_name
1516 perm_rows.append(usr)
1521 perm_rows.append(usr)
1517
1522
1518 # filter the perm rows by 'default' first and then sort them by
1523 # filter the perm rows by 'default' first and then sort them by
1519 # admin,write,read,none permissions sorted again alphabetically in
1524 # admin,write,read,none permissions sorted again alphabetically in
1520 # each group
1525 # each group
1521 perm_rows = sorted(perm_rows, key=display_user_sort)
1526 perm_rows = sorted(perm_rows, key=display_user_sort)
1522
1527
1523 user_groups_rows = []
1528 user_groups_rows = []
1524 if expand_from_user_groups:
1529 if expand_from_user_groups:
1525 for ug in self.permission_user_groups(with_members=True):
1530 for ug in self.permission_user_groups(with_members=True):
1526 for user_data in ug.members:
1531 for user_data in ug.members:
1527 user_groups_rows.append(user_data)
1532 user_groups_rows.append(user_data)
1528
1533
1529 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1534 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1530
1535
1531 def permission_user_groups(self, with_members=False):
1536 def permission_user_groups(self, with_members=False):
1532 q = UserGroupUserGroupToPerm.query()\
1537 q = UserGroupUserGroupToPerm.query()\
1533 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1538 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1534 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1539 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1535 joinedload(UserGroupUserGroupToPerm.target_user_group),
1540 joinedload(UserGroupUserGroupToPerm.target_user_group),
1536 joinedload(UserGroupUserGroupToPerm.permission),)
1541 joinedload(UserGroupUserGroupToPerm.permission),)
1537
1542
1538 perm_rows = []
1543 perm_rows = []
1539 for _user_group in q.all():
1544 for _user_group in q.all():
1540 entry = AttributeDict(_user_group.user_group.get_dict())
1545 entry = AttributeDict(_user_group.user_group.get_dict())
1541 entry.permission = _user_group.permission.permission_name
1546 entry.permission = _user_group.permission.permission_name
1542 if with_members:
1547 if with_members:
1543 entry.members = [x.user.get_dict()
1548 entry.members = [x.user.get_dict()
1544 for x in _user_group.user_group.members]
1549 for x in _user_group.user_group.members]
1545 perm_rows.append(entry)
1550 perm_rows.append(entry)
1546
1551
1547 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1552 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1548 return perm_rows
1553 return perm_rows
1549
1554
1550 def _get_default_perms(self, user_group, suffix=''):
1555 def _get_default_perms(self, user_group, suffix=''):
1551 from rhodecode.model.permission import PermissionModel
1556 from rhodecode.model.permission import PermissionModel
1552 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1557 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1553
1558
1554 def get_default_perms(self, suffix=''):
1559 def get_default_perms(self, suffix=''):
1555 return self._get_default_perms(self, suffix)
1560 return self._get_default_perms(self, suffix)
1556
1561
1557 def get_api_data(self, with_group_members=True, include_secrets=False):
1562 def get_api_data(self, with_group_members=True, include_secrets=False):
1558 """
1563 """
1559 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1564 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1560 basically forwarded.
1565 basically forwarded.
1561
1566
1562 """
1567 """
1563 user_group = self
1568 user_group = self
1564 data = {
1569 data = {
1565 'users_group_id': user_group.users_group_id,
1570 'users_group_id': user_group.users_group_id,
1566 'group_name': user_group.users_group_name,
1571 'group_name': user_group.users_group_name,
1567 'group_description': user_group.user_group_description,
1572 'group_description': user_group.user_group_description,
1568 'active': user_group.users_group_active,
1573 'active': user_group.users_group_active,
1569 'owner': user_group.user.username,
1574 'owner': user_group.user.username,
1570 'sync': user_group.sync,
1575 'sync': user_group.sync,
1571 'owner_email': user_group.user.email,
1576 'owner_email': user_group.user.email,
1572 }
1577 }
1573
1578
1574 if with_group_members:
1579 if with_group_members:
1575 users = []
1580 users = []
1576 for user in user_group.members:
1581 for user in user_group.members:
1577 user = user.user
1582 user = user.user
1578 users.append(user.get_api_data(include_secrets=include_secrets))
1583 users.append(user.get_api_data(include_secrets=include_secrets))
1579 data['users'] = users
1584 data['users'] = users
1580
1585
1581 return data
1586 return data
1582
1587
1583
1588
1584 class UserGroupMember(Base, BaseModel):
1589 class UserGroupMember(Base, BaseModel):
1585 __tablename__ = 'users_groups_members'
1590 __tablename__ = 'users_groups_members'
1586 __table_args__ = (
1591 __table_args__ = (
1587 base_table_args,
1592 base_table_args,
1588 )
1593 )
1589
1594
1590 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1595 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1596 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1593
1598
1594 user = relationship('User', lazy='joined')
1599 user = relationship('User', lazy='joined')
1595 users_group = relationship('UserGroup')
1600 users_group = relationship('UserGroup')
1596
1601
1597 def __init__(self, gr_id='', u_id=''):
1602 def __init__(self, gr_id='', u_id=''):
1598 self.users_group_id = gr_id
1603 self.users_group_id = gr_id
1599 self.user_id = u_id
1604 self.user_id = u_id
1600
1605
1601
1606
1602 class RepositoryField(Base, BaseModel):
1607 class RepositoryField(Base, BaseModel):
1603 __tablename__ = 'repositories_fields'
1608 __tablename__ = 'repositories_fields'
1604 __table_args__ = (
1609 __table_args__ = (
1605 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1610 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1606 base_table_args,
1611 base_table_args,
1607 )
1612 )
1608
1613
1609 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1614 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1610
1615
1611 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1616 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1612 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1617 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1613 field_key = Column("field_key", String(250))
1618 field_key = Column("field_key", String(250))
1614 field_label = Column("field_label", String(1024), nullable=False)
1619 field_label = Column("field_label", String(1024), nullable=False)
1615 field_value = Column("field_value", String(10000), nullable=False)
1620 field_value = Column("field_value", String(10000), nullable=False)
1616 field_desc = Column("field_desc", String(1024), nullable=False)
1621 field_desc = Column("field_desc", String(1024), nullable=False)
1617 field_type = Column("field_type", String(255), nullable=False, unique=None)
1622 field_type = Column("field_type", String(255), nullable=False, unique=None)
1618 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1623 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1619
1624
1620 repository = relationship('Repository')
1625 repository = relationship('Repository')
1621
1626
1622 @property
1627 @property
1623 def field_key_prefixed(self):
1628 def field_key_prefixed(self):
1624 return 'ex_%s' % self.field_key
1629 return 'ex_%s' % self.field_key
1625
1630
1626 @classmethod
1631 @classmethod
1627 def un_prefix_key(cls, key):
1632 def un_prefix_key(cls, key):
1628 if key.startswith(cls.PREFIX):
1633 if key.startswith(cls.PREFIX):
1629 return key[len(cls.PREFIX):]
1634 return key[len(cls.PREFIX):]
1630 return key
1635 return key
1631
1636
1632 @classmethod
1637 @classmethod
1633 def get_by_key_name(cls, key, repo):
1638 def get_by_key_name(cls, key, repo):
1634 row = cls.query()\
1639 row = cls.query()\
1635 .filter(cls.repository == repo)\
1640 .filter(cls.repository == repo)\
1636 .filter(cls.field_key == key).scalar()
1641 .filter(cls.field_key == key).scalar()
1637 return row
1642 return row
1638
1643
1639
1644
1640 class Repository(Base, BaseModel):
1645 class Repository(Base, BaseModel):
1641 __tablename__ = 'repositories'
1646 __tablename__ = 'repositories'
1642 __table_args__ = (
1647 __table_args__ = (
1643 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1648 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1644 base_table_args,
1649 base_table_args,
1645 )
1650 )
1646 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1651 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1647 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1652 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1648 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1653 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1649
1654
1650 STATE_CREATED = 'repo_state_created'
1655 STATE_CREATED = 'repo_state_created'
1651 STATE_PENDING = 'repo_state_pending'
1656 STATE_PENDING = 'repo_state_pending'
1652 STATE_ERROR = 'repo_state_error'
1657 STATE_ERROR = 'repo_state_error'
1653
1658
1654 LOCK_AUTOMATIC = 'lock_auto'
1659 LOCK_AUTOMATIC = 'lock_auto'
1655 LOCK_API = 'lock_api'
1660 LOCK_API = 'lock_api'
1656 LOCK_WEB = 'lock_web'
1661 LOCK_WEB = 'lock_web'
1657 LOCK_PULL = 'lock_pull'
1662 LOCK_PULL = 'lock_pull'
1658
1663
1659 NAME_SEP = URL_SEP
1664 NAME_SEP = URL_SEP
1660
1665
1661 repo_id = Column(
1666 repo_id = Column(
1662 "repo_id", Integer(), nullable=False, unique=True, default=None,
1667 "repo_id", Integer(), nullable=False, unique=True, default=None,
1663 primary_key=True)
1668 primary_key=True)
1664 _repo_name = Column(
1669 _repo_name = Column(
1665 "repo_name", Text(), nullable=False, default=None)
1670 "repo_name", Text(), nullable=False, default=None)
1666 repo_name_hash = Column(
1671 repo_name_hash = Column(
1667 "repo_name_hash", String(255), nullable=False, unique=True)
1672 "repo_name_hash", String(255), nullable=False, unique=True)
1668 repo_state = Column("repo_state", String(255), nullable=True)
1673 repo_state = Column("repo_state", String(255), nullable=True)
1669
1674
1670 clone_uri = Column(
1675 clone_uri = Column(
1671 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1676 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1672 default=None)
1677 default=None)
1673 push_uri = Column(
1678 push_uri = Column(
1674 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1679 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1675 default=None)
1680 default=None)
1676 repo_type = Column(
1681 repo_type = Column(
1677 "repo_type", String(255), nullable=False, unique=False, default=None)
1682 "repo_type", String(255), nullable=False, unique=False, default=None)
1678 user_id = Column(
1683 user_id = Column(
1679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1684 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1680 unique=False, default=None)
1685 unique=False, default=None)
1681 private = Column(
1686 private = Column(
1682 "private", Boolean(), nullable=True, unique=None, default=None)
1687 "private", Boolean(), nullable=True, unique=None, default=None)
1683 archived = Column(
1688 archived = Column(
1684 "archived", Boolean(), nullable=True, unique=None, default=None)
1689 "archived", Boolean(), nullable=True, unique=None, default=None)
1685 enable_statistics = Column(
1690 enable_statistics = Column(
1686 "statistics", Boolean(), nullable=True, unique=None, default=True)
1691 "statistics", Boolean(), nullable=True, unique=None, default=True)
1687 enable_downloads = Column(
1692 enable_downloads = Column(
1688 "downloads", Boolean(), nullable=True, unique=None, default=True)
1693 "downloads", Boolean(), nullable=True, unique=None, default=True)
1689 description = Column(
1694 description = Column(
1690 "description", String(10000), nullable=True, unique=None, default=None)
1695 "description", String(10000), nullable=True, unique=None, default=None)
1691 created_on = Column(
1696 created_on = Column(
1692 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1697 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1693 default=datetime.datetime.now)
1698 default=datetime.datetime.now)
1694 updated_on = Column(
1699 updated_on = Column(
1695 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1700 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1696 default=datetime.datetime.now)
1701 default=datetime.datetime.now)
1697 _landing_revision = Column(
1702 _landing_revision = Column(
1698 "landing_revision", String(255), nullable=False, unique=False,
1703 "landing_revision", String(255), nullable=False, unique=False,
1699 default=None)
1704 default=None)
1700 enable_locking = Column(
1705 enable_locking = Column(
1701 "enable_locking", Boolean(), nullable=False, unique=None,
1706 "enable_locking", Boolean(), nullable=False, unique=None,
1702 default=False)
1707 default=False)
1703 _locked = Column(
1708 _locked = Column(
1704 "locked", String(255), nullable=True, unique=False, default=None)
1709 "locked", String(255), nullable=True, unique=False, default=None)
1705 _changeset_cache = Column(
1710 _changeset_cache = Column(
1706 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1711 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1707
1712
1708 fork_id = Column(
1713 fork_id = Column(
1709 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1714 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1710 nullable=True, unique=False, default=None)
1715 nullable=True, unique=False, default=None)
1711 group_id = Column(
1716 group_id = Column(
1712 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1717 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1713 unique=False, default=None)
1718 unique=False, default=None)
1714
1719
1715 user = relationship('User', lazy='joined')
1720 user = relationship('User', lazy='joined')
1716 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1721 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1717 group = relationship('RepoGroup', lazy='joined')
1722 group = relationship('RepoGroup', lazy='joined')
1718 repo_to_perm = relationship(
1723 repo_to_perm = relationship(
1719 'UserRepoToPerm', cascade='all',
1724 'UserRepoToPerm', cascade='all',
1720 order_by='UserRepoToPerm.repo_to_perm_id')
1725 order_by='UserRepoToPerm.repo_to_perm_id')
1721 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1726 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1722 stats = relationship('Statistics', cascade='all', uselist=False)
1727 stats = relationship('Statistics', cascade='all', uselist=False)
1723
1728
1724 followers = relationship(
1729 followers = relationship(
1725 'UserFollowing',
1730 'UserFollowing',
1726 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1731 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1727 cascade='all')
1732 cascade='all')
1728 extra_fields = relationship(
1733 extra_fields = relationship(
1729 'RepositoryField', cascade="all, delete-orphan")
1734 'RepositoryField', cascade="all, delete-orphan")
1730 logs = relationship('UserLog')
1735 logs = relationship('UserLog')
1731 comments = relationship(
1736 comments = relationship(
1732 'ChangesetComment', cascade="all, delete-orphan")
1737 'ChangesetComment', cascade="all, delete-orphan")
1733 pull_requests_source = relationship(
1738 pull_requests_source = relationship(
1734 'PullRequest',
1739 'PullRequest',
1735 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1740 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1736 cascade="all, delete-orphan")
1741 cascade="all, delete-orphan")
1737 pull_requests_target = relationship(
1742 pull_requests_target = relationship(
1738 'PullRequest',
1743 'PullRequest',
1739 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1744 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1740 cascade="all, delete-orphan")
1745 cascade="all, delete-orphan")
1741 ui = relationship('RepoRhodeCodeUi', cascade="all")
1746 ui = relationship('RepoRhodeCodeUi', cascade="all")
1742 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1747 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1743 integrations = relationship('Integration', cascade="all, delete-orphan")
1748 integrations = relationship('Integration', cascade="all, delete-orphan")
1744
1749
1745 scoped_tokens = relationship('UserApiKeys', cascade="all")
1750 scoped_tokens = relationship('UserApiKeys', cascade="all")
1746
1751
1747 # no cascade, set NULL
1752 # no cascade, set NULL
1748 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1753 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1749
1754
1750 def __unicode__(self):
1755 def __unicode__(self):
1751 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1756 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1752 safe_unicode(self.repo_name))
1757 safe_unicode(self.repo_name))
1753
1758
1754 @hybrid_property
1759 @hybrid_property
1755 def description_safe(self):
1760 def description_safe(self):
1756 from rhodecode.lib import helpers as h
1761 from rhodecode.lib import helpers as h
1757 return h.escape(self.description)
1762 return h.escape(self.description)
1758
1763
1759 @hybrid_property
1764 @hybrid_property
1760 def landing_rev(self):
1765 def landing_rev(self):
1761 # always should return [rev_type, rev], e.g ['branch', 'master']
1766 # always should return [rev_type, rev], e.g ['branch', 'master']
1762 if self._landing_revision:
1767 if self._landing_revision:
1763 _rev_info = self._landing_revision.split(':')
1768 _rev_info = self._landing_revision.split(':')
1764 if len(_rev_info) < 2:
1769 if len(_rev_info) < 2:
1765 _rev_info.insert(0, 'rev')
1770 _rev_info.insert(0, 'rev')
1766 return [_rev_info[0], _rev_info[1]]
1771 return [_rev_info[0], _rev_info[1]]
1767 return [None, None]
1772 return [None, None]
1768
1773
1769 @property
1774 @property
1770 def landing_ref_type(self):
1775 def landing_ref_type(self):
1771 return self.landing_rev[0]
1776 return self.landing_rev[0]
1772
1777
1773 @property
1778 @property
1774 def landing_ref_name(self):
1779 def landing_ref_name(self):
1775 return self.landing_rev[1]
1780 return self.landing_rev[1]
1776
1781
1777 @landing_rev.setter
1782 @landing_rev.setter
1778 def landing_rev(self, val):
1783 def landing_rev(self, val):
1779 if ':' not in val:
1784 if ':' not in val:
1780 raise ValueError('value must be delimited with `:` and consist '
1785 raise ValueError('value must be delimited with `:` and consist '
1781 'of <rev_type>:<rev>, got %s instead' % val)
1786 'of <rev_type>:<rev>, got %s instead' % val)
1782 self._landing_revision = val
1787 self._landing_revision = val
1783
1788
1784 @hybrid_property
1789 @hybrid_property
1785 def locked(self):
1790 def locked(self):
1786 if self._locked:
1791 if self._locked:
1787 user_id, timelocked, reason = self._locked.split(':')
1792 user_id, timelocked, reason = self._locked.split(':')
1788 lock_values = int(user_id), timelocked, reason
1793 lock_values = int(user_id), timelocked, reason
1789 else:
1794 else:
1790 lock_values = [None, None, None]
1795 lock_values = [None, None, None]
1791 return lock_values
1796 return lock_values
1792
1797
1793 @locked.setter
1798 @locked.setter
1794 def locked(self, val):
1799 def locked(self, val):
1795 if val and isinstance(val, (list, tuple)):
1800 if val and isinstance(val, (list, tuple)):
1796 self._locked = ':'.join(map(str, val))
1801 self._locked = ':'.join(map(str, val))
1797 else:
1802 else:
1798 self._locked = None
1803 self._locked = None
1799
1804
1800 @classmethod
1805 @classmethod
1801 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1806 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1802 from rhodecode.lib.vcs.backends.base import EmptyCommit
1807 from rhodecode.lib.vcs.backends.base import EmptyCommit
1803 dummy = EmptyCommit().__json__()
1808 dummy = EmptyCommit().__json__()
1804 if not changeset_cache_raw:
1809 if not changeset_cache_raw:
1805 dummy['source_repo_id'] = repo_id
1810 dummy['source_repo_id'] = repo_id
1806 return json.loads(json.dumps(dummy))
1811 return json.loads(json.dumps(dummy))
1807
1812
1808 try:
1813 try:
1809 return json.loads(changeset_cache_raw)
1814 return json.loads(changeset_cache_raw)
1810 except TypeError:
1815 except TypeError:
1811 return dummy
1816 return dummy
1812 except Exception:
1817 except Exception:
1813 log.error(traceback.format_exc())
1818 log.error(traceback.format_exc())
1814 return dummy
1819 return dummy
1815
1820
1816 @hybrid_property
1821 @hybrid_property
1817 def changeset_cache(self):
1822 def changeset_cache(self):
1818 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1823 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1819
1824
1820 @changeset_cache.setter
1825 @changeset_cache.setter
1821 def changeset_cache(self, val):
1826 def changeset_cache(self, val):
1822 try:
1827 try:
1823 self._changeset_cache = json.dumps(val)
1828 self._changeset_cache = json.dumps(val)
1824 except Exception:
1829 except Exception:
1825 log.error(traceback.format_exc())
1830 log.error(traceback.format_exc())
1826
1831
1827 @hybrid_property
1832 @hybrid_property
1828 def repo_name(self):
1833 def repo_name(self):
1829 return self._repo_name
1834 return self._repo_name
1830
1835
1831 @repo_name.setter
1836 @repo_name.setter
1832 def repo_name(self, value):
1837 def repo_name(self, value):
1833 self._repo_name = value
1838 self._repo_name = value
1834 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1839 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1835
1840
1836 @classmethod
1841 @classmethod
1837 def normalize_repo_name(cls, repo_name):
1842 def normalize_repo_name(cls, repo_name):
1838 """
1843 """
1839 Normalizes os specific repo_name to the format internally stored inside
1844 Normalizes os specific repo_name to the format internally stored inside
1840 database using URL_SEP
1845 database using URL_SEP
1841
1846
1842 :param cls:
1847 :param cls:
1843 :param repo_name:
1848 :param repo_name:
1844 """
1849 """
1845 return cls.NAME_SEP.join(repo_name.split(os.sep))
1850 return cls.NAME_SEP.join(repo_name.split(os.sep))
1846
1851
1847 @classmethod
1852 @classmethod
1848 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1853 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1849 session = Session()
1854 session = Session()
1850 q = session.query(cls).filter(cls.repo_name == repo_name)
1855 q = session.query(cls).filter(cls.repo_name == repo_name)
1851
1856
1852 if cache:
1857 if cache:
1853 if identity_cache:
1858 if identity_cache:
1854 val = cls.identity_cache(session, 'repo_name', repo_name)
1859 val = cls.identity_cache(session, 'repo_name', repo_name)
1855 if val:
1860 if val:
1856 return val
1861 return val
1857 else:
1862 else:
1858 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1863 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1859 q = q.options(
1864 q = q.options(
1860 FromCache("sql_cache_short", cache_key))
1865 FromCache("sql_cache_short", cache_key))
1861
1866
1862 return q.scalar()
1867 return q.scalar()
1863
1868
1864 @classmethod
1869 @classmethod
1865 def get_by_id_or_repo_name(cls, repoid):
1870 def get_by_id_or_repo_name(cls, repoid):
1866 if isinstance(repoid, (int, long)):
1871 if isinstance(repoid, (int, long)):
1867 try:
1872 try:
1868 repo = cls.get(repoid)
1873 repo = cls.get(repoid)
1869 except ValueError:
1874 except ValueError:
1870 repo = None
1875 repo = None
1871 else:
1876 else:
1872 repo = cls.get_by_repo_name(repoid)
1877 repo = cls.get_by_repo_name(repoid)
1873 return repo
1878 return repo
1874
1879
1875 @classmethod
1880 @classmethod
1876 def get_by_full_path(cls, repo_full_path):
1881 def get_by_full_path(cls, repo_full_path):
1877 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1882 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1878 repo_name = cls.normalize_repo_name(repo_name)
1883 repo_name = cls.normalize_repo_name(repo_name)
1879 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1884 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1880
1885
1881 @classmethod
1886 @classmethod
1882 def get_repo_forks(cls, repo_id):
1887 def get_repo_forks(cls, repo_id):
1883 return cls.query().filter(Repository.fork_id == repo_id)
1888 return cls.query().filter(Repository.fork_id == repo_id)
1884
1889
1885 @classmethod
1890 @classmethod
1886 def base_path(cls):
1891 def base_path(cls):
1887 """
1892 """
1888 Returns base path when all repos are stored
1893 Returns base path when all repos are stored
1889
1894
1890 :param cls:
1895 :param cls:
1891 """
1896 """
1892 q = Session().query(RhodeCodeUi)\
1897 q = Session().query(RhodeCodeUi)\
1893 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1898 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1894 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1899 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1895 return q.one().ui_value
1900 return q.one().ui_value
1896
1901
1897 @classmethod
1902 @classmethod
1898 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1903 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1899 case_insensitive=True, archived=False):
1904 case_insensitive=True, archived=False):
1900 q = Repository.query()
1905 q = Repository.query()
1901
1906
1902 if not archived:
1907 if not archived:
1903 q = q.filter(Repository.archived.isnot(true()))
1908 q = q.filter(Repository.archived.isnot(true()))
1904
1909
1905 if not isinstance(user_id, Optional):
1910 if not isinstance(user_id, Optional):
1906 q = q.filter(Repository.user_id == user_id)
1911 q = q.filter(Repository.user_id == user_id)
1907
1912
1908 if not isinstance(group_id, Optional):
1913 if not isinstance(group_id, Optional):
1909 q = q.filter(Repository.group_id == group_id)
1914 q = q.filter(Repository.group_id == group_id)
1910
1915
1911 if case_insensitive:
1916 if case_insensitive:
1912 q = q.order_by(func.lower(Repository.repo_name))
1917 q = q.order_by(func.lower(Repository.repo_name))
1913 else:
1918 else:
1914 q = q.order_by(Repository.repo_name)
1919 q = q.order_by(Repository.repo_name)
1915
1920
1916 return q.all()
1921 return q.all()
1917
1922
1918 @property
1923 @property
1919 def repo_uid(self):
1924 def repo_uid(self):
1920 return '_{}'.format(self.repo_id)
1925 return '_{}'.format(self.repo_id)
1921
1926
1922 @property
1927 @property
1923 def forks(self):
1928 def forks(self):
1924 """
1929 """
1925 Return forks of this repo
1930 Return forks of this repo
1926 """
1931 """
1927 return Repository.get_repo_forks(self.repo_id)
1932 return Repository.get_repo_forks(self.repo_id)
1928
1933
1929 @property
1934 @property
1930 def parent(self):
1935 def parent(self):
1931 """
1936 """
1932 Returns fork parent
1937 Returns fork parent
1933 """
1938 """
1934 return self.fork
1939 return self.fork
1935
1940
1936 @property
1941 @property
1937 def just_name(self):
1942 def just_name(self):
1938 return self.repo_name.split(self.NAME_SEP)[-1]
1943 return self.repo_name.split(self.NAME_SEP)[-1]
1939
1944
1940 @property
1945 @property
1941 def groups_with_parents(self):
1946 def groups_with_parents(self):
1942 groups = []
1947 groups = []
1943 if self.group is None:
1948 if self.group is None:
1944 return groups
1949 return groups
1945
1950
1946 cur_gr = self.group
1951 cur_gr = self.group
1947 groups.insert(0, cur_gr)
1952 groups.insert(0, cur_gr)
1948 while 1:
1953 while 1:
1949 gr = getattr(cur_gr, 'parent_group', None)
1954 gr = getattr(cur_gr, 'parent_group', None)
1950 cur_gr = cur_gr.parent_group
1955 cur_gr = cur_gr.parent_group
1951 if gr is None:
1956 if gr is None:
1952 break
1957 break
1953 groups.insert(0, gr)
1958 groups.insert(0, gr)
1954
1959
1955 return groups
1960 return groups
1956
1961
1957 @property
1962 @property
1958 def groups_and_repo(self):
1963 def groups_and_repo(self):
1959 return self.groups_with_parents, self
1964 return self.groups_with_parents, self
1960
1965
1961 @LazyProperty
1966 @LazyProperty
1962 def repo_path(self):
1967 def repo_path(self):
1963 """
1968 """
1964 Returns base full path for that repository means where it actually
1969 Returns base full path for that repository means where it actually
1965 exists on a filesystem
1970 exists on a filesystem
1966 """
1971 """
1967 q = Session().query(RhodeCodeUi).filter(
1972 q = Session().query(RhodeCodeUi).filter(
1968 RhodeCodeUi.ui_key == self.NAME_SEP)
1973 RhodeCodeUi.ui_key == self.NAME_SEP)
1969 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1974 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1970 return q.one().ui_value
1975 return q.one().ui_value
1971
1976
1972 @property
1977 @property
1973 def repo_full_path(self):
1978 def repo_full_path(self):
1974 p = [self.repo_path]
1979 p = [self.repo_path]
1975 # we need to split the name by / since this is how we store the
1980 # we need to split the name by / since this is how we store the
1976 # names in the database, but that eventually needs to be converted
1981 # names in the database, but that eventually needs to be converted
1977 # into a valid system path
1982 # into a valid system path
1978 p += self.repo_name.split(self.NAME_SEP)
1983 p += self.repo_name.split(self.NAME_SEP)
1979 return os.path.join(*map(safe_unicode, p))
1984 return os.path.join(*map(safe_unicode, p))
1980
1985
1981 @property
1986 @property
1982 def cache_keys(self):
1987 def cache_keys(self):
1983 """
1988 """
1984 Returns associated cache keys for that repo
1989 Returns associated cache keys for that repo
1985 """
1990 """
1986 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1991 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1987 repo_id=self.repo_id)
1992 repo_id=self.repo_id)
1988 return CacheKey.query()\
1993 return CacheKey.query()\
1989 .filter(CacheKey.cache_args == invalidation_namespace)\
1994 .filter(CacheKey.cache_args == invalidation_namespace)\
1990 .order_by(CacheKey.cache_key)\
1995 .order_by(CacheKey.cache_key)\
1991 .all()
1996 .all()
1992
1997
1993 @property
1998 @property
1994 def cached_diffs_relative_dir(self):
1999 def cached_diffs_relative_dir(self):
1995 """
2000 """
1996 Return a relative to the repository store path of cached diffs
2001 Return a relative to the repository store path of cached diffs
1997 used for safe display for users, who shouldn't know the absolute store
2002 used for safe display for users, who shouldn't know the absolute store
1998 path
2003 path
1999 """
2004 """
2000 return os.path.join(
2005 return os.path.join(
2001 os.path.dirname(self.repo_name),
2006 os.path.dirname(self.repo_name),
2002 self.cached_diffs_dir.split(os.path.sep)[-1])
2007 self.cached_diffs_dir.split(os.path.sep)[-1])
2003
2008
2004 @property
2009 @property
2005 def cached_diffs_dir(self):
2010 def cached_diffs_dir(self):
2006 path = self.repo_full_path
2011 path = self.repo_full_path
2007 return os.path.join(
2012 return os.path.join(
2008 os.path.dirname(path),
2013 os.path.dirname(path),
2009 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2014 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2010
2015
2011 def cached_diffs(self):
2016 def cached_diffs(self):
2012 diff_cache_dir = self.cached_diffs_dir
2017 diff_cache_dir = self.cached_diffs_dir
2013 if os.path.isdir(diff_cache_dir):
2018 if os.path.isdir(diff_cache_dir):
2014 return os.listdir(diff_cache_dir)
2019 return os.listdir(diff_cache_dir)
2015 return []
2020 return []
2016
2021
2017 def shadow_repos(self):
2022 def shadow_repos(self):
2018 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2023 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2019 return [
2024 return [
2020 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2025 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2021 if x.startswith(shadow_repos_pattern)]
2026 if x.startswith(shadow_repos_pattern)]
2022
2027
2023 def get_new_name(self, repo_name):
2028 def get_new_name(self, repo_name):
2024 """
2029 """
2025 returns new full repository name based on assigned group and new new
2030 returns new full repository name based on assigned group and new new
2026
2031
2027 :param group_name:
2032 :param group_name:
2028 """
2033 """
2029 path_prefix = self.group.full_path_splitted if self.group else []
2034 path_prefix = self.group.full_path_splitted if self.group else []
2030 return self.NAME_SEP.join(path_prefix + [repo_name])
2035 return self.NAME_SEP.join(path_prefix + [repo_name])
2031
2036
2032 @property
2037 @property
2033 def _config(self):
2038 def _config(self):
2034 """
2039 """
2035 Returns db based config object.
2040 Returns db based config object.
2036 """
2041 """
2037 from rhodecode.lib.utils import make_db_config
2042 from rhodecode.lib.utils import make_db_config
2038 return make_db_config(clear_session=False, repo=self)
2043 return make_db_config(clear_session=False, repo=self)
2039
2044
2040 def permissions(self, with_admins=True, with_owner=True,
2045 def permissions(self, with_admins=True, with_owner=True,
2041 expand_from_user_groups=False):
2046 expand_from_user_groups=False):
2042 """
2047 """
2043 Permissions for repositories
2048 Permissions for repositories
2044 """
2049 """
2045 _admin_perm = 'repository.admin'
2050 _admin_perm = 'repository.admin'
2046
2051
2047 owner_row = []
2052 owner_row = []
2048 if with_owner:
2053 if with_owner:
2049 usr = AttributeDict(self.user.get_dict())
2054 usr = AttributeDict(self.user.get_dict())
2050 usr.owner_row = True
2055 usr.owner_row = True
2051 usr.permission = _admin_perm
2056 usr.permission = _admin_perm
2052 usr.permission_id = None
2057 usr.permission_id = None
2053 owner_row.append(usr)
2058 owner_row.append(usr)
2054
2059
2055 super_admin_ids = []
2060 super_admin_ids = []
2056 super_admin_rows = []
2061 super_admin_rows = []
2057 if with_admins:
2062 if with_admins:
2058 for usr in User.get_all_super_admins():
2063 for usr in User.get_all_super_admins():
2059 super_admin_ids.append(usr.user_id)
2064 super_admin_ids.append(usr.user_id)
2060 # if this admin is also owner, don't double the record
2065 # if this admin is also owner, don't double the record
2061 if usr.user_id == owner_row[0].user_id:
2066 if usr.user_id == owner_row[0].user_id:
2062 owner_row[0].admin_row = True
2067 owner_row[0].admin_row = True
2063 else:
2068 else:
2064 usr = AttributeDict(usr.get_dict())
2069 usr = AttributeDict(usr.get_dict())
2065 usr.admin_row = True
2070 usr.admin_row = True
2066 usr.permission = _admin_perm
2071 usr.permission = _admin_perm
2067 usr.permission_id = None
2072 usr.permission_id = None
2068 super_admin_rows.append(usr)
2073 super_admin_rows.append(usr)
2069
2074
2070 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2075 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2071 q = q.options(joinedload(UserRepoToPerm.repository),
2076 q = q.options(joinedload(UserRepoToPerm.repository),
2072 joinedload(UserRepoToPerm.user),
2077 joinedload(UserRepoToPerm.user),
2073 joinedload(UserRepoToPerm.permission),)
2078 joinedload(UserRepoToPerm.permission),)
2074
2079
2075 # get owners and admins and permissions. We do a trick of re-writing
2080 # get owners and admins and permissions. We do a trick of re-writing
2076 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2081 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2077 # has a global reference and changing one object propagates to all
2082 # has a global reference and changing one object propagates to all
2078 # others. This means if admin is also an owner admin_row that change
2083 # others. This means if admin is also an owner admin_row that change
2079 # would propagate to both objects
2084 # would propagate to both objects
2080 perm_rows = []
2085 perm_rows = []
2081 for _usr in q.all():
2086 for _usr in q.all():
2082 usr = AttributeDict(_usr.user.get_dict())
2087 usr = AttributeDict(_usr.user.get_dict())
2083 # if this user is also owner/admin, mark as duplicate record
2088 # if this user is also owner/admin, mark as duplicate record
2084 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2089 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2085 usr.duplicate_perm = True
2090 usr.duplicate_perm = True
2086 # also check if this permission is maybe used by branch_permissions
2091 # also check if this permission is maybe used by branch_permissions
2087 if _usr.branch_perm_entry:
2092 if _usr.branch_perm_entry:
2088 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2093 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2089
2094
2090 usr.permission = _usr.permission.permission_name
2095 usr.permission = _usr.permission.permission_name
2091 usr.permission_id = _usr.repo_to_perm_id
2096 usr.permission_id = _usr.repo_to_perm_id
2092 perm_rows.append(usr)
2097 perm_rows.append(usr)
2093
2098
2094 # filter the perm rows by 'default' first and then sort them by
2099 # filter the perm rows by 'default' first and then sort them by
2095 # admin,write,read,none permissions sorted again alphabetically in
2100 # admin,write,read,none permissions sorted again alphabetically in
2096 # each group
2101 # each group
2097 perm_rows = sorted(perm_rows, key=display_user_sort)
2102 perm_rows = sorted(perm_rows, key=display_user_sort)
2098
2103
2099 user_groups_rows = []
2104 user_groups_rows = []
2100 if expand_from_user_groups:
2105 if expand_from_user_groups:
2101 for ug in self.permission_user_groups(with_members=True):
2106 for ug in self.permission_user_groups(with_members=True):
2102 for user_data in ug.members:
2107 for user_data in ug.members:
2103 user_groups_rows.append(user_data)
2108 user_groups_rows.append(user_data)
2104
2109
2105 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2110 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2106
2111
2107 def permission_user_groups(self, with_members=True):
2112 def permission_user_groups(self, with_members=True):
2108 q = UserGroupRepoToPerm.query()\
2113 q = UserGroupRepoToPerm.query()\
2109 .filter(UserGroupRepoToPerm.repository == self)
2114 .filter(UserGroupRepoToPerm.repository == self)
2110 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2115 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2111 joinedload(UserGroupRepoToPerm.users_group),
2116 joinedload(UserGroupRepoToPerm.users_group),
2112 joinedload(UserGroupRepoToPerm.permission),)
2117 joinedload(UserGroupRepoToPerm.permission),)
2113
2118
2114 perm_rows = []
2119 perm_rows = []
2115 for _user_group in q.all():
2120 for _user_group in q.all():
2116 entry = AttributeDict(_user_group.users_group.get_dict())
2121 entry = AttributeDict(_user_group.users_group.get_dict())
2117 entry.permission = _user_group.permission.permission_name
2122 entry.permission = _user_group.permission.permission_name
2118 if with_members:
2123 if with_members:
2119 entry.members = [x.user.get_dict()
2124 entry.members = [x.user.get_dict()
2120 for x in _user_group.users_group.members]
2125 for x in _user_group.users_group.members]
2121 perm_rows.append(entry)
2126 perm_rows.append(entry)
2122
2127
2123 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2128 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2124 return perm_rows
2129 return perm_rows
2125
2130
2126 def get_api_data(self, include_secrets=False):
2131 def get_api_data(self, include_secrets=False):
2127 """
2132 """
2128 Common function for generating repo api data
2133 Common function for generating repo api data
2129
2134
2130 :param include_secrets: See :meth:`User.get_api_data`.
2135 :param include_secrets: See :meth:`User.get_api_data`.
2131
2136
2132 """
2137 """
2133 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2138 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2134 # move this methods on models level.
2139 # move this methods on models level.
2135 from rhodecode.model.settings import SettingsModel
2140 from rhodecode.model.settings import SettingsModel
2136 from rhodecode.model.repo import RepoModel
2141 from rhodecode.model.repo import RepoModel
2137
2142
2138 repo = self
2143 repo = self
2139 _user_id, _time, _reason = self.locked
2144 _user_id, _time, _reason = self.locked
2140
2145
2141 data = {
2146 data = {
2142 'repo_id': repo.repo_id,
2147 'repo_id': repo.repo_id,
2143 'repo_name': repo.repo_name,
2148 'repo_name': repo.repo_name,
2144 'repo_type': repo.repo_type,
2149 'repo_type': repo.repo_type,
2145 'clone_uri': repo.clone_uri or '',
2150 'clone_uri': repo.clone_uri or '',
2146 'push_uri': repo.push_uri or '',
2151 'push_uri': repo.push_uri or '',
2147 'url': RepoModel().get_url(self),
2152 'url': RepoModel().get_url(self),
2148 'private': repo.private,
2153 'private': repo.private,
2149 'created_on': repo.created_on,
2154 'created_on': repo.created_on,
2150 'description': repo.description_safe,
2155 'description': repo.description_safe,
2151 'landing_rev': repo.landing_rev,
2156 'landing_rev': repo.landing_rev,
2152 'owner': repo.user.username,
2157 'owner': repo.user.username,
2153 'fork_of': repo.fork.repo_name if repo.fork else None,
2158 'fork_of': repo.fork.repo_name if repo.fork else None,
2154 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2159 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2155 'enable_statistics': repo.enable_statistics,
2160 'enable_statistics': repo.enable_statistics,
2156 'enable_locking': repo.enable_locking,
2161 'enable_locking': repo.enable_locking,
2157 'enable_downloads': repo.enable_downloads,
2162 'enable_downloads': repo.enable_downloads,
2158 'last_changeset': repo.changeset_cache,
2163 'last_changeset': repo.changeset_cache,
2159 'locked_by': User.get(_user_id).get_api_data(
2164 'locked_by': User.get(_user_id).get_api_data(
2160 include_secrets=include_secrets) if _user_id else None,
2165 include_secrets=include_secrets) if _user_id else None,
2161 'locked_date': time_to_datetime(_time) if _time else None,
2166 'locked_date': time_to_datetime(_time) if _time else None,
2162 'lock_reason': _reason if _reason else None,
2167 'lock_reason': _reason if _reason else None,
2163 }
2168 }
2164
2169
2165 # TODO: mikhail: should be per-repo settings here
2170 # TODO: mikhail: should be per-repo settings here
2166 rc_config = SettingsModel().get_all_settings()
2171 rc_config = SettingsModel().get_all_settings()
2167 repository_fields = str2bool(
2172 repository_fields = str2bool(
2168 rc_config.get('rhodecode_repository_fields'))
2173 rc_config.get('rhodecode_repository_fields'))
2169 if repository_fields:
2174 if repository_fields:
2170 for f in self.extra_fields:
2175 for f in self.extra_fields:
2171 data[f.field_key_prefixed] = f.field_value
2176 data[f.field_key_prefixed] = f.field_value
2172
2177
2173 return data
2178 return data
2174
2179
2175 @classmethod
2180 @classmethod
2176 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2181 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2177 if not lock_time:
2182 if not lock_time:
2178 lock_time = time.time()
2183 lock_time = time.time()
2179 if not lock_reason:
2184 if not lock_reason:
2180 lock_reason = cls.LOCK_AUTOMATIC
2185 lock_reason = cls.LOCK_AUTOMATIC
2181 repo.locked = [user_id, lock_time, lock_reason]
2186 repo.locked = [user_id, lock_time, lock_reason]
2182 Session().add(repo)
2187 Session().add(repo)
2183 Session().commit()
2188 Session().commit()
2184
2189
2185 @classmethod
2190 @classmethod
2186 def unlock(cls, repo):
2191 def unlock(cls, repo):
2187 repo.locked = None
2192 repo.locked = None
2188 Session().add(repo)
2193 Session().add(repo)
2189 Session().commit()
2194 Session().commit()
2190
2195
2191 @classmethod
2196 @classmethod
2192 def getlock(cls, repo):
2197 def getlock(cls, repo):
2193 return repo.locked
2198 return repo.locked
2194
2199
2195 def is_user_lock(self, user_id):
2200 def is_user_lock(self, user_id):
2196 if self.lock[0]:
2201 if self.lock[0]:
2197 lock_user_id = safe_int(self.lock[0])
2202 lock_user_id = safe_int(self.lock[0])
2198 user_id = safe_int(user_id)
2203 user_id = safe_int(user_id)
2199 # both are ints, and they are equal
2204 # both are ints, and they are equal
2200 return all([lock_user_id, user_id]) and lock_user_id == user_id
2205 return all([lock_user_id, user_id]) and lock_user_id == user_id
2201
2206
2202 return False
2207 return False
2203
2208
2204 def get_locking_state(self, action, user_id, only_when_enabled=True):
2209 def get_locking_state(self, action, user_id, only_when_enabled=True):
2205 """
2210 """
2206 Checks locking on this repository, if locking is enabled and lock is
2211 Checks locking on this repository, if locking is enabled and lock is
2207 present returns a tuple of make_lock, locked, locked_by.
2212 present returns a tuple of make_lock, locked, locked_by.
2208 make_lock can have 3 states None (do nothing) True, make lock
2213 make_lock can have 3 states None (do nothing) True, make lock
2209 False release lock, This value is later propagated to hooks, which
2214 False release lock, This value is later propagated to hooks, which
2210 do the locking. Think about this as signals passed to hooks what to do.
2215 do the locking. Think about this as signals passed to hooks what to do.
2211
2216
2212 """
2217 """
2213 # TODO: johbo: This is part of the business logic and should be moved
2218 # TODO: johbo: This is part of the business logic and should be moved
2214 # into the RepositoryModel.
2219 # into the RepositoryModel.
2215
2220
2216 if action not in ('push', 'pull'):
2221 if action not in ('push', 'pull'):
2217 raise ValueError("Invalid action value: %s" % repr(action))
2222 raise ValueError("Invalid action value: %s" % repr(action))
2218
2223
2219 # defines if locked error should be thrown to user
2224 # defines if locked error should be thrown to user
2220 currently_locked = False
2225 currently_locked = False
2221 # defines if new lock should be made, tri-state
2226 # defines if new lock should be made, tri-state
2222 make_lock = None
2227 make_lock = None
2223 repo = self
2228 repo = self
2224 user = User.get(user_id)
2229 user = User.get(user_id)
2225
2230
2226 lock_info = repo.locked
2231 lock_info = repo.locked
2227
2232
2228 if repo and (repo.enable_locking or not only_when_enabled):
2233 if repo and (repo.enable_locking or not only_when_enabled):
2229 if action == 'push':
2234 if action == 'push':
2230 # check if it's already locked !, if it is compare users
2235 # check if it's already locked !, if it is compare users
2231 locked_by_user_id = lock_info[0]
2236 locked_by_user_id = lock_info[0]
2232 if user.user_id == locked_by_user_id:
2237 if user.user_id == locked_by_user_id:
2233 log.debug(
2238 log.debug(
2234 'Got `push` action from user %s, now unlocking', user)
2239 'Got `push` action from user %s, now unlocking', user)
2235 # unlock if we have push from user who locked
2240 # unlock if we have push from user who locked
2236 make_lock = False
2241 make_lock = False
2237 else:
2242 else:
2238 # we're not the same user who locked, ban with
2243 # we're not the same user who locked, ban with
2239 # code defined in settings (default is 423 HTTP Locked) !
2244 # code defined in settings (default is 423 HTTP Locked) !
2240 log.debug('Repo %s is currently locked by %s', repo, user)
2245 log.debug('Repo %s is currently locked by %s', repo, user)
2241 currently_locked = True
2246 currently_locked = True
2242 elif action == 'pull':
2247 elif action == 'pull':
2243 # [0] user [1] date
2248 # [0] user [1] date
2244 if lock_info[0] and lock_info[1]:
2249 if lock_info[0] and lock_info[1]:
2245 log.debug('Repo %s is currently locked by %s', repo, user)
2250 log.debug('Repo %s is currently locked by %s', repo, user)
2246 currently_locked = True
2251 currently_locked = True
2247 else:
2252 else:
2248 log.debug('Setting lock on repo %s by %s', repo, user)
2253 log.debug('Setting lock on repo %s by %s', repo, user)
2249 make_lock = True
2254 make_lock = True
2250
2255
2251 else:
2256 else:
2252 log.debug('Repository %s do not have locking enabled', repo)
2257 log.debug('Repository %s do not have locking enabled', repo)
2253
2258
2254 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2259 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2255 make_lock, currently_locked, lock_info)
2260 make_lock, currently_locked, lock_info)
2256
2261
2257 from rhodecode.lib.auth import HasRepoPermissionAny
2262 from rhodecode.lib.auth import HasRepoPermissionAny
2258 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2263 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2259 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2264 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2260 # if we don't have at least write permission we cannot make a lock
2265 # if we don't have at least write permission we cannot make a lock
2261 log.debug('lock state reset back to FALSE due to lack '
2266 log.debug('lock state reset back to FALSE due to lack '
2262 'of at least read permission')
2267 'of at least read permission')
2263 make_lock = False
2268 make_lock = False
2264
2269
2265 return make_lock, currently_locked, lock_info
2270 return make_lock, currently_locked, lock_info
2266
2271
2267 @property
2272 @property
2268 def last_commit_cache_update_diff(self):
2273 def last_commit_cache_update_diff(self):
2269 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2274 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2270
2275
2271 @classmethod
2276 @classmethod
2272 def _load_commit_change(cls, last_commit_cache):
2277 def _load_commit_change(cls, last_commit_cache):
2273 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2278 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2274 empty_date = datetime.datetime.fromtimestamp(0)
2279 empty_date = datetime.datetime.fromtimestamp(0)
2275 date_latest = last_commit_cache.get('date', empty_date)
2280 date_latest = last_commit_cache.get('date', empty_date)
2276 try:
2281 try:
2277 return parse_datetime(date_latest)
2282 return parse_datetime(date_latest)
2278 except Exception:
2283 except Exception:
2279 return empty_date
2284 return empty_date
2280
2285
2281 @property
2286 @property
2282 def last_commit_change(self):
2287 def last_commit_change(self):
2283 return self._load_commit_change(self.changeset_cache)
2288 return self._load_commit_change(self.changeset_cache)
2284
2289
2285 @property
2290 @property
2286 def last_db_change(self):
2291 def last_db_change(self):
2287 return self.updated_on
2292 return self.updated_on
2288
2293
2289 @property
2294 @property
2290 def clone_uri_hidden(self):
2295 def clone_uri_hidden(self):
2291 clone_uri = self.clone_uri
2296 clone_uri = self.clone_uri
2292 if clone_uri:
2297 if clone_uri:
2293 import urlobject
2298 import urlobject
2294 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2299 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2295 if url_obj.password:
2300 if url_obj.password:
2296 clone_uri = url_obj.with_password('*****')
2301 clone_uri = url_obj.with_password('*****')
2297 return clone_uri
2302 return clone_uri
2298
2303
2299 @property
2304 @property
2300 def push_uri_hidden(self):
2305 def push_uri_hidden(self):
2301 push_uri = self.push_uri
2306 push_uri = self.push_uri
2302 if push_uri:
2307 if push_uri:
2303 import urlobject
2308 import urlobject
2304 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2309 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2305 if url_obj.password:
2310 if url_obj.password:
2306 push_uri = url_obj.with_password('*****')
2311 push_uri = url_obj.with_password('*****')
2307 return push_uri
2312 return push_uri
2308
2313
2309 def clone_url(self, **override):
2314 def clone_url(self, **override):
2310 from rhodecode.model.settings import SettingsModel
2315 from rhodecode.model.settings import SettingsModel
2311
2316
2312 uri_tmpl = None
2317 uri_tmpl = None
2313 if 'with_id' in override:
2318 if 'with_id' in override:
2314 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2319 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2315 del override['with_id']
2320 del override['with_id']
2316
2321
2317 if 'uri_tmpl' in override:
2322 if 'uri_tmpl' in override:
2318 uri_tmpl = override['uri_tmpl']
2323 uri_tmpl = override['uri_tmpl']
2319 del override['uri_tmpl']
2324 del override['uri_tmpl']
2320
2325
2321 ssh = False
2326 ssh = False
2322 if 'ssh' in override:
2327 if 'ssh' in override:
2323 ssh = True
2328 ssh = True
2324 del override['ssh']
2329 del override['ssh']
2325
2330
2326 # we didn't override our tmpl from **overrides
2331 # we didn't override our tmpl from **overrides
2327 request = get_current_request()
2332 request = get_current_request()
2328 if not uri_tmpl:
2333 if not uri_tmpl:
2329 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2334 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2330 rc_config = request.call_context.rc_config
2335 rc_config = request.call_context.rc_config
2331 else:
2336 else:
2332 rc_config = SettingsModel().get_all_settings(cache=True)
2337 rc_config = SettingsModel().get_all_settings(cache=True)
2333
2338
2334 if ssh:
2339 if ssh:
2335 uri_tmpl = rc_config.get(
2340 uri_tmpl = rc_config.get(
2336 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2341 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2337
2342
2338 else:
2343 else:
2339 uri_tmpl = rc_config.get(
2344 uri_tmpl = rc_config.get(
2340 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2345 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2341
2346
2342 return get_clone_url(request=request,
2347 return get_clone_url(request=request,
2343 uri_tmpl=uri_tmpl,
2348 uri_tmpl=uri_tmpl,
2344 repo_name=self.repo_name,
2349 repo_name=self.repo_name,
2345 repo_id=self.repo_id,
2350 repo_id=self.repo_id,
2346 repo_type=self.repo_type,
2351 repo_type=self.repo_type,
2347 **override)
2352 **override)
2348
2353
2349 def set_state(self, state):
2354 def set_state(self, state):
2350 self.repo_state = state
2355 self.repo_state = state
2351 Session().add(self)
2356 Session().add(self)
2352 #==========================================================================
2357 #==========================================================================
2353 # SCM PROPERTIES
2358 # SCM PROPERTIES
2354 #==========================================================================
2359 #==========================================================================
2355
2360
2356 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2361 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2357 return get_commit_safe(
2362 return get_commit_safe(
2358 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2363 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2359 maybe_unreachable=maybe_unreachable)
2364 maybe_unreachable=maybe_unreachable)
2360
2365
2361 def get_changeset(self, rev=None, pre_load=None):
2366 def get_changeset(self, rev=None, pre_load=None):
2362 warnings.warn("Use get_commit", DeprecationWarning)
2367 warnings.warn("Use get_commit", DeprecationWarning)
2363 commit_id = None
2368 commit_id = None
2364 commit_idx = None
2369 commit_idx = None
2365 if isinstance(rev, compat.string_types):
2370 if isinstance(rev, compat.string_types):
2366 commit_id = rev
2371 commit_id = rev
2367 else:
2372 else:
2368 commit_idx = rev
2373 commit_idx = rev
2369 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2374 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2370 pre_load=pre_load)
2375 pre_load=pre_load)
2371
2376
2372 def get_landing_commit(self):
2377 def get_landing_commit(self):
2373 """
2378 """
2374 Returns landing commit, or if that doesn't exist returns the tip
2379 Returns landing commit, or if that doesn't exist returns the tip
2375 """
2380 """
2376 _rev_type, _rev = self.landing_rev
2381 _rev_type, _rev = self.landing_rev
2377 commit = self.get_commit(_rev)
2382 commit = self.get_commit(_rev)
2378 if isinstance(commit, EmptyCommit):
2383 if isinstance(commit, EmptyCommit):
2379 return self.get_commit()
2384 return self.get_commit()
2380 return commit
2385 return commit
2381
2386
2382 def flush_commit_cache(self):
2387 def flush_commit_cache(self):
2383 self.update_commit_cache(cs_cache={'raw_id':'0'})
2388 self.update_commit_cache(cs_cache={'raw_id':'0'})
2384 self.update_commit_cache()
2389 self.update_commit_cache()
2385
2390
2386 def update_commit_cache(self, cs_cache=None, config=None):
2391 def update_commit_cache(self, cs_cache=None, config=None):
2387 """
2392 """
2388 Update cache of last commit for repository
2393 Update cache of last commit for repository
2389 cache_keys should be::
2394 cache_keys should be::
2390
2395
2391 source_repo_id
2396 source_repo_id
2392 short_id
2397 short_id
2393 raw_id
2398 raw_id
2394 revision
2399 revision
2395 parents
2400 parents
2396 message
2401 message
2397 date
2402 date
2398 author
2403 author
2399 updated_on
2404 updated_on
2400
2405
2401 """
2406 """
2402 from rhodecode.lib.vcs.backends.base import BaseChangeset
2407 from rhodecode.lib.vcs.backends.base import BaseChangeset
2403 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2408 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2404 empty_date = datetime.datetime.fromtimestamp(0)
2409 empty_date = datetime.datetime.fromtimestamp(0)
2405
2410
2406 if cs_cache is None:
2411 if cs_cache is None:
2407 # use no-cache version here
2412 # use no-cache version here
2408 try:
2413 try:
2409 scm_repo = self.scm_instance(cache=False, config=config)
2414 scm_repo = self.scm_instance(cache=False, config=config)
2410 except VCSError:
2415 except VCSError:
2411 scm_repo = None
2416 scm_repo = None
2412 empty = scm_repo is None or scm_repo.is_empty()
2417 empty = scm_repo is None or scm_repo.is_empty()
2413
2418
2414 if not empty:
2419 if not empty:
2415 cs_cache = scm_repo.get_commit(
2420 cs_cache = scm_repo.get_commit(
2416 pre_load=["author", "date", "message", "parents", "branch"])
2421 pre_load=["author", "date", "message", "parents", "branch"])
2417 else:
2422 else:
2418 cs_cache = EmptyCommit()
2423 cs_cache = EmptyCommit()
2419
2424
2420 if isinstance(cs_cache, BaseChangeset):
2425 if isinstance(cs_cache, BaseChangeset):
2421 cs_cache = cs_cache.__json__()
2426 cs_cache = cs_cache.__json__()
2422
2427
2423 def is_outdated(new_cs_cache):
2428 def is_outdated(new_cs_cache):
2424 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2429 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2425 new_cs_cache['revision'] != self.changeset_cache['revision']):
2430 new_cs_cache['revision'] != self.changeset_cache['revision']):
2426 return True
2431 return True
2427 return False
2432 return False
2428
2433
2429 # check if we have maybe already latest cached revision
2434 # check if we have maybe already latest cached revision
2430 if is_outdated(cs_cache) or not self.changeset_cache:
2435 if is_outdated(cs_cache) or not self.changeset_cache:
2431 _current_datetime = datetime.datetime.utcnow()
2436 _current_datetime = datetime.datetime.utcnow()
2432 last_change = cs_cache.get('date') or _current_datetime
2437 last_change = cs_cache.get('date') or _current_datetime
2433 # we check if last update is newer than the new value
2438 # we check if last update is newer than the new value
2434 # if yes, we use the current timestamp instead. Imagine you get
2439 # if yes, we use the current timestamp instead. Imagine you get
2435 # old commit pushed 1y ago, we'd set last update 1y to ago.
2440 # old commit pushed 1y ago, we'd set last update 1y to ago.
2436 last_change_timestamp = datetime_to_time(last_change)
2441 last_change_timestamp = datetime_to_time(last_change)
2437 current_timestamp = datetime_to_time(last_change)
2442 current_timestamp = datetime_to_time(last_change)
2438 if last_change_timestamp > current_timestamp and not empty:
2443 if last_change_timestamp > current_timestamp and not empty:
2439 cs_cache['date'] = _current_datetime
2444 cs_cache['date'] = _current_datetime
2440
2445
2441 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2446 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2442 cs_cache['updated_on'] = time.time()
2447 cs_cache['updated_on'] = time.time()
2443 self.changeset_cache = cs_cache
2448 self.changeset_cache = cs_cache
2444 self.updated_on = last_change
2449 self.updated_on = last_change
2445 Session().add(self)
2450 Session().add(self)
2446 Session().commit()
2451 Session().commit()
2447
2452
2448 else:
2453 else:
2449 if empty:
2454 if empty:
2450 cs_cache = EmptyCommit().__json__()
2455 cs_cache = EmptyCommit().__json__()
2451 else:
2456 else:
2452 cs_cache = self.changeset_cache
2457 cs_cache = self.changeset_cache
2453
2458
2454 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2459 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2455
2460
2456 cs_cache['updated_on'] = time.time()
2461 cs_cache['updated_on'] = time.time()
2457 self.changeset_cache = cs_cache
2462 self.changeset_cache = cs_cache
2458 self.updated_on = _date_latest
2463 self.updated_on = _date_latest
2459 Session().add(self)
2464 Session().add(self)
2460 Session().commit()
2465 Session().commit()
2461
2466
2462 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2467 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2463 self.repo_name, cs_cache, _date_latest)
2468 self.repo_name, cs_cache, _date_latest)
2464
2469
2465 @property
2470 @property
2466 def tip(self):
2471 def tip(self):
2467 return self.get_commit('tip')
2472 return self.get_commit('tip')
2468
2473
2469 @property
2474 @property
2470 def author(self):
2475 def author(self):
2471 return self.tip.author
2476 return self.tip.author
2472
2477
2473 @property
2478 @property
2474 def last_change(self):
2479 def last_change(self):
2475 return self.scm_instance().last_change
2480 return self.scm_instance().last_change
2476
2481
2477 def get_comments(self, revisions=None):
2482 def get_comments(self, revisions=None):
2478 """
2483 """
2479 Returns comments for this repository grouped by revisions
2484 Returns comments for this repository grouped by revisions
2480
2485
2481 :param revisions: filter query by revisions only
2486 :param revisions: filter query by revisions only
2482 """
2487 """
2483 cmts = ChangesetComment.query()\
2488 cmts = ChangesetComment.query()\
2484 .filter(ChangesetComment.repo == self)
2489 .filter(ChangesetComment.repo == self)
2485 if revisions:
2490 if revisions:
2486 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2491 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2487 grouped = collections.defaultdict(list)
2492 grouped = collections.defaultdict(list)
2488 for cmt in cmts.all():
2493 for cmt in cmts.all():
2489 grouped[cmt.revision].append(cmt)
2494 grouped[cmt.revision].append(cmt)
2490 return grouped
2495 return grouped
2491
2496
2492 def statuses(self, revisions=None):
2497 def statuses(self, revisions=None):
2493 """
2498 """
2494 Returns statuses for this repository
2499 Returns statuses for this repository
2495
2500
2496 :param revisions: list of revisions to get statuses for
2501 :param revisions: list of revisions to get statuses for
2497 """
2502 """
2498 statuses = ChangesetStatus.query()\
2503 statuses = ChangesetStatus.query()\
2499 .filter(ChangesetStatus.repo == self)\
2504 .filter(ChangesetStatus.repo == self)\
2500 .filter(ChangesetStatus.version == 0)
2505 .filter(ChangesetStatus.version == 0)
2501
2506
2502 if revisions:
2507 if revisions:
2503 # Try doing the filtering in chunks to avoid hitting limits
2508 # Try doing the filtering in chunks to avoid hitting limits
2504 size = 500
2509 size = 500
2505 status_results = []
2510 status_results = []
2506 for chunk in xrange(0, len(revisions), size):
2511 for chunk in xrange(0, len(revisions), size):
2507 status_results += statuses.filter(
2512 status_results += statuses.filter(
2508 ChangesetStatus.revision.in_(
2513 ChangesetStatus.revision.in_(
2509 revisions[chunk: chunk+size])
2514 revisions[chunk: chunk+size])
2510 ).all()
2515 ).all()
2511 else:
2516 else:
2512 status_results = statuses.all()
2517 status_results = statuses.all()
2513
2518
2514 grouped = {}
2519 grouped = {}
2515
2520
2516 # maybe we have open new pullrequest without a status?
2521 # maybe we have open new pullrequest without a status?
2517 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2522 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2518 status_lbl = ChangesetStatus.get_status_lbl(stat)
2523 status_lbl = ChangesetStatus.get_status_lbl(stat)
2519 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2524 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2520 for rev in pr.revisions:
2525 for rev in pr.revisions:
2521 pr_id = pr.pull_request_id
2526 pr_id = pr.pull_request_id
2522 pr_repo = pr.target_repo.repo_name
2527 pr_repo = pr.target_repo.repo_name
2523 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2528 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2524
2529
2525 for stat in status_results:
2530 for stat in status_results:
2526 pr_id = pr_repo = None
2531 pr_id = pr_repo = None
2527 if stat.pull_request:
2532 if stat.pull_request:
2528 pr_id = stat.pull_request.pull_request_id
2533 pr_id = stat.pull_request.pull_request_id
2529 pr_repo = stat.pull_request.target_repo.repo_name
2534 pr_repo = stat.pull_request.target_repo.repo_name
2530 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2535 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2531 pr_id, pr_repo]
2536 pr_id, pr_repo]
2532 return grouped
2537 return grouped
2533
2538
2534 # ==========================================================================
2539 # ==========================================================================
2535 # SCM CACHE INSTANCE
2540 # SCM CACHE INSTANCE
2536 # ==========================================================================
2541 # ==========================================================================
2537
2542
2538 def scm_instance(self, **kwargs):
2543 def scm_instance(self, **kwargs):
2539 import rhodecode
2544 import rhodecode
2540
2545
2541 # Passing a config will not hit the cache currently only used
2546 # Passing a config will not hit the cache currently only used
2542 # for repo2dbmapper
2547 # for repo2dbmapper
2543 config = kwargs.pop('config', None)
2548 config = kwargs.pop('config', None)
2544 cache = kwargs.pop('cache', None)
2549 cache = kwargs.pop('cache', None)
2545 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2550 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2546 if vcs_full_cache is not None:
2551 if vcs_full_cache is not None:
2547 # allows override global config
2552 # allows override global config
2548 full_cache = vcs_full_cache
2553 full_cache = vcs_full_cache
2549 else:
2554 else:
2550 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2555 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2551 # if cache is NOT defined use default global, else we have a full
2556 # if cache is NOT defined use default global, else we have a full
2552 # control over cache behaviour
2557 # control over cache behaviour
2553 if cache is None and full_cache and not config:
2558 if cache is None and full_cache and not config:
2554 log.debug('Initializing pure cached instance for %s', self.repo_path)
2559 log.debug('Initializing pure cached instance for %s', self.repo_path)
2555 return self._get_instance_cached()
2560 return self._get_instance_cached()
2556
2561
2557 # cache here is sent to the "vcs server"
2562 # cache here is sent to the "vcs server"
2558 return self._get_instance(cache=bool(cache), config=config)
2563 return self._get_instance(cache=bool(cache), config=config)
2559
2564
2560 def _get_instance_cached(self):
2565 def _get_instance_cached(self):
2561 from rhodecode.lib import rc_cache
2566 from rhodecode.lib import rc_cache
2562
2567
2563 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2568 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2564 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2569 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2565 repo_id=self.repo_id)
2570 repo_id=self.repo_id)
2566 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2571 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2567
2572
2568 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2573 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2569 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2574 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2570 return self._get_instance(repo_state_uid=_cache_state_uid)
2575 return self._get_instance(repo_state_uid=_cache_state_uid)
2571
2576
2572 # we must use thread scoped cache here,
2577 # we must use thread scoped cache here,
2573 # because each thread of gevent needs it's own not shared connection and cache
2578 # because each thread of gevent needs it's own not shared connection and cache
2574 # we also alter `args` so the cache key is individual for every green thread.
2579 # we also alter `args` so the cache key is individual for every green thread.
2575 inv_context_manager = rc_cache.InvalidationContext(
2580 inv_context_manager = rc_cache.InvalidationContext(
2576 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2581 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2577 thread_scoped=True)
2582 thread_scoped=True)
2578 with inv_context_manager as invalidation_context:
2583 with inv_context_manager as invalidation_context:
2579 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2584 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2580 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2585 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2581
2586
2582 # re-compute and store cache if we get invalidate signal
2587 # re-compute and store cache if we get invalidate signal
2583 if invalidation_context.should_invalidate():
2588 if invalidation_context.should_invalidate():
2584 instance = get_instance_cached.refresh(*args)
2589 instance = get_instance_cached.refresh(*args)
2585 else:
2590 else:
2586 instance = get_instance_cached(*args)
2591 instance = get_instance_cached(*args)
2587
2592
2588 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2593 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2589 return instance
2594 return instance
2590
2595
2591 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2596 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2592 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2597 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2593 self.repo_type, self.repo_path, cache)
2598 self.repo_type, self.repo_path, cache)
2594 config = config or self._config
2599 config = config or self._config
2595 custom_wire = {
2600 custom_wire = {
2596 'cache': cache, # controls the vcs.remote cache
2601 'cache': cache, # controls the vcs.remote cache
2597 'repo_state_uid': repo_state_uid
2602 'repo_state_uid': repo_state_uid
2598 }
2603 }
2599 repo = get_vcs_instance(
2604 repo = get_vcs_instance(
2600 repo_path=safe_str(self.repo_full_path),
2605 repo_path=safe_str(self.repo_full_path),
2601 config=config,
2606 config=config,
2602 with_wire=custom_wire,
2607 with_wire=custom_wire,
2603 create=False,
2608 create=False,
2604 _vcs_alias=self.repo_type)
2609 _vcs_alias=self.repo_type)
2605 if repo is not None:
2610 if repo is not None:
2606 repo.count() # cache rebuild
2611 repo.count() # cache rebuild
2607 return repo
2612 return repo
2608
2613
2609 def get_shadow_repository_path(self, workspace_id):
2614 def get_shadow_repository_path(self, workspace_id):
2610 from rhodecode.lib.vcs.backends.base import BaseRepository
2615 from rhodecode.lib.vcs.backends.base import BaseRepository
2611 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2616 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2612 self.repo_full_path, self.repo_id, workspace_id)
2617 self.repo_full_path, self.repo_id, workspace_id)
2613 return shadow_repo_path
2618 return shadow_repo_path
2614
2619
2615 def __json__(self):
2620 def __json__(self):
2616 return {'landing_rev': self.landing_rev}
2621 return {'landing_rev': self.landing_rev}
2617
2622
2618 def get_dict(self):
2623 def get_dict(self):
2619
2624
2620 # Since we transformed `repo_name` to a hybrid property, we need to
2625 # Since we transformed `repo_name` to a hybrid property, we need to
2621 # keep compatibility with the code which uses `repo_name` field.
2626 # keep compatibility with the code which uses `repo_name` field.
2622
2627
2623 result = super(Repository, self).get_dict()
2628 result = super(Repository, self).get_dict()
2624 result['repo_name'] = result.pop('_repo_name', None)
2629 result['repo_name'] = result.pop('_repo_name', None)
2625 return result
2630 return result
2626
2631
2627
2632
2628 class RepoGroup(Base, BaseModel):
2633 class RepoGroup(Base, BaseModel):
2629 __tablename__ = 'groups'
2634 __tablename__ = 'groups'
2630 __table_args__ = (
2635 __table_args__ = (
2631 UniqueConstraint('group_name', 'group_parent_id'),
2636 UniqueConstraint('group_name', 'group_parent_id'),
2632 base_table_args,
2637 base_table_args,
2633 )
2638 )
2634 __mapper_args__ = {'order_by': 'group_name'}
2639 __mapper_args__ = {'order_by': 'group_name'}
2635
2640
2636 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2641 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2637
2642
2638 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2643 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2639 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2644 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2640 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2645 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2641 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2646 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2642 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2647 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2643 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2648 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2644 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2649 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2645 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2650 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2646 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2651 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2647 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2652 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2648 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2653 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2649
2654
2650 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2655 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2651 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2656 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2652 parent_group = relationship('RepoGroup', remote_side=group_id)
2657 parent_group = relationship('RepoGroup', remote_side=group_id)
2653 user = relationship('User')
2658 user = relationship('User')
2654 integrations = relationship('Integration', cascade="all, delete-orphan")
2659 integrations = relationship('Integration', cascade="all, delete-orphan")
2655
2660
2656 # no cascade, set NULL
2661 # no cascade, set NULL
2657 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2662 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2658
2663
2659 def __init__(self, group_name='', parent_group=None):
2664 def __init__(self, group_name='', parent_group=None):
2660 self.group_name = group_name
2665 self.group_name = group_name
2661 self.parent_group = parent_group
2666 self.parent_group = parent_group
2662
2667
2663 def __unicode__(self):
2668 def __unicode__(self):
2664 return u"<%s('id:%s:%s')>" % (
2669 return u"<%s('id:%s:%s')>" % (
2665 self.__class__.__name__, self.group_id, self.group_name)
2670 self.__class__.__name__, self.group_id, self.group_name)
2666
2671
2667 @hybrid_property
2672 @hybrid_property
2668 def group_name(self):
2673 def group_name(self):
2669 return self._group_name
2674 return self._group_name
2670
2675
2671 @group_name.setter
2676 @group_name.setter
2672 def group_name(self, value):
2677 def group_name(self, value):
2673 self._group_name = value
2678 self._group_name = value
2674 self.group_name_hash = self.hash_repo_group_name(value)
2679 self.group_name_hash = self.hash_repo_group_name(value)
2675
2680
2676 @classmethod
2681 @classmethod
2677 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2682 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2678 from rhodecode.lib.vcs.backends.base import EmptyCommit
2683 from rhodecode.lib.vcs.backends.base import EmptyCommit
2679 dummy = EmptyCommit().__json__()
2684 dummy = EmptyCommit().__json__()
2680 if not changeset_cache_raw:
2685 if not changeset_cache_raw:
2681 dummy['source_repo_id'] = repo_id
2686 dummy['source_repo_id'] = repo_id
2682 return json.loads(json.dumps(dummy))
2687 return json.loads(json.dumps(dummy))
2683
2688
2684 try:
2689 try:
2685 return json.loads(changeset_cache_raw)
2690 return json.loads(changeset_cache_raw)
2686 except TypeError:
2691 except TypeError:
2687 return dummy
2692 return dummy
2688 except Exception:
2693 except Exception:
2689 log.error(traceback.format_exc())
2694 log.error(traceback.format_exc())
2690 return dummy
2695 return dummy
2691
2696
2692 @hybrid_property
2697 @hybrid_property
2693 def changeset_cache(self):
2698 def changeset_cache(self):
2694 return self._load_changeset_cache('', self._changeset_cache)
2699 return self._load_changeset_cache('', self._changeset_cache)
2695
2700
2696 @changeset_cache.setter
2701 @changeset_cache.setter
2697 def changeset_cache(self, val):
2702 def changeset_cache(self, val):
2698 try:
2703 try:
2699 self._changeset_cache = json.dumps(val)
2704 self._changeset_cache = json.dumps(val)
2700 except Exception:
2705 except Exception:
2701 log.error(traceback.format_exc())
2706 log.error(traceback.format_exc())
2702
2707
2703 @validates('group_parent_id')
2708 @validates('group_parent_id')
2704 def validate_group_parent_id(self, key, val):
2709 def validate_group_parent_id(self, key, val):
2705 """
2710 """
2706 Check cycle references for a parent group to self
2711 Check cycle references for a parent group to self
2707 """
2712 """
2708 if self.group_id and val:
2713 if self.group_id and val:
2709 assert val != self.group_id
2714 assert val != self.group_id
2710
2715
2711 return val
2716 return val
2712
2717
2713 @hybrid_property
2718 @hybrid_property
2714 def description_safe(self):
2719 def description_safe(self):
2715 from rhodecode.lib import helpers as h
2720 from rhodecode.lib import helpers as h
2716 return h.escape(self.group_description)
2721 return h.escape(self.group_description)
2717
2722
2718 @classmethod
2723 @classmethod
2719 def hash_repo_group_name(cls, repo_group_name):
2724 def hash_repo_group_name(cls, repo_group_name):
2720 val = remove_formatting(repo_group_name)
2725 val = remove_formatting(repo_group_name)
2721 val = safe_str(val).lower()
2726 val = safe_str(val).lower()
2722 chars = []
2727 chars = []
2723 for c in val:
2728 for c in val:
2724 if c not in string.ascii_letters:
2729 if c not in string.ascii_letters:
2725 c = str(ord(c))
2730 c = str(ord(c))
2726 chars.append(c)
2731 chars.append(c)
2727
2732
2728 return ''.join(chars)
2733 return ''.join(chars)
2729
2734
2730 @classmethod
2735 @classmethod
2731 def _generate_choice(cls, repo_group):
2736 def _generate_choice(cls, repo_group):
2732 from webhelpers2.html import literal as _literal
2737 from webhelpers2.html import literal as _literal
2733 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2738 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2734 return repo_group.group_id, _name(repo_group.full_path_splitted)
2739 return repo_group.group_id, _name(repo_group.full_path_splitted)
2735
2740
2736 @classmethod
2741 @classmethod
2737 def groups_choices(cls, groups=None, show_empty_group=True):
2742 def groups_choices(cls, groups=None, show_empty_group=True):
2738 if not groups:
2743 if not groups:
2739 groups = cls.query().all()
2744 groups = cls.query().all()
2740
2745
2741 repo_groups = []
2746 repo_groups = []
2742 if show_empty_group:
2747 if show_empty_group:
2743 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2748 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2744
2749
2745 repo_groups.extend([cls._generate_choice(x) for x in groups])
2750 repo_groups.extend([cls._generate_choice(x) for x in groups])
2746
2751
2747 repo_groups = sorted(
2752 repo_groups = sorted(
2748 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2753 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2749 return repo_groups
2754 return repo_groups
2750
2755
2751 @classmethod
2756 @classmethod
2752 def url_sep(cls):
2757 def url_sep(cls):
2753 return URL_SEP
2758 return URL_SEP
2754
2759
2755 @classmethod
2760 @classmethod
2756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2761 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2757 if case_insensitive:
2762 if case_insensitive:
2758 gr = cls.query().filter(func.lower(cls.group_name)
2763 gr = cls.query().filter(func.lower(cls.group_name)
2759 == func.lower(group_name))
2764 == func.lower(group_name))
2760 else:
2765 else:
2761 gr = cls.query().filter(cls.group_name == group_name)
2766 gr = cls.query().filter(cls.group_name == group_name)
2762 if cache:
2767 if cache:
2763 name_key = _hash_key(group_name)
2768 name_key = _hash_key(group_name)
2764 gr = gr.options(
2769 gr = gr.options(
2765 FromCache("sql_cache_short", "get_group_%s" % name_key))
2770 FromCache("sql_cache_short", "get_group_%s" % name_key))
2766 return gr.scalar()
2771 return gr.scalar()
2767
2772
2768 @classmethod
2773 @classmethod
2769 def get_user_personal_repo_group(cls, user_id):
2774 def get_user_personal_repo_group(cls, user_id):
2770 user = User.get(user_id)
2775 user = User.get(user_id)
2771 if user.username == User.DEFAULT_USER:
2776 if user.username == User.DEFAULT_USER:
2772 return None
2777 return None
2773
2778
2774 return cls.query()\
2779 return cls.query()\
2775 .filter(cls.personal == true()) \
2780 .filter(cls.personal == true()) \
2776 .filter(cls.user == user) \
2781 .filter(cls.user == user) \
2777 .order_by(cls.group_id.asc()) \
2782 .order_by(cls.group_id.asc()) \
2778 .first()
2783 .first()
2779
2784
2780 @classmethod
2785 @classmethod
2781 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2786 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2782 case_insensitive=True):
2787 case_insensitive=True):
2783 q = RepoGroup.query()
2788 q = RepoGroup.query()
2784
2789
2785 if not isinstance(user_id, Optional):
2790 if not isinstance(user_id, Optional):
2786 q = q.filter(RepoGroup.user_id == user_id)
2791 q = q.filter(RepoGroup.user_id == user_id)
2787
2792
2788 if not isinstance(group_id, Optional):
2793 if not isinstance(group_id, Optional):
2789 q = q.filter(RepoGroup.group_parent_id == group_id)
2794 q = q.filter(RepoGroup.group_parent_id == group_id)
2790
2795
2791 if case_insensitive:
2796 if case_insensitive:
2792 q = q.order_by(func.lower(RepoGroup.group_name))
2797 q = q.order_by(func.lower(RepoGroup.group_name))
2793 else:
2798 else:
2794 q = q.order_by(RepoGroup.group_name)
2799 q = q.order_by(RepoGroup.group_name)
2795 return q.all()
2800 return q.all()
2796
2801
2797 @property
2802 @property
2798 def parents(self, parents_recursion_limit=10):
2803 def parents(self, parents_recursion_limit=10):
2799 groups = []
2804 groups = []
2800 if self.parent_group is None:
2805 if self.parent_group is None:
2801 return groups
2806 return groups
2802 cur_gr = self.parent_group
2807 cur_gr = self.parent_group
2803 groups.insert(0, cur_gr)
2808 groups.insert(0, cur_gr)
2804 cnt = 0
2809 cnt = 0
2805 while 1:
2810 while 1:
2806 cnt += 1
2811 cnt += 1
2807 gr = getattr(cur_gr, 'parent_group', None)
2812 gr = getattr(cur_gr, 'parent_group', None)
2808 cur_gr = cur_gr.parent_group
2813 cur_gr = cur_gr.parent_group
2809 if gr is None:
2814 if gr is None:
2810 break
2815 break
2811 if cnt == parents_recursion_limit:
2816 if cnt == parents_recursion_limit:
2812 # this will prevent accidental infinit loops
2817 # this will prevent accidental infinit loops
2813 log.error('more than %s parents found for group %s, stopping '
2818 log.error('more than %s parents found for group %s, stopping '
2814 'recursive parent fetching', parents_recursion_limit, self)
2819 'recursive parent fetching', parents_recursion_limit, self)
2815 break
2820 break
2816
2821
2817 groups.insert(0, gr)
2822 groups.insert(0, gr)
2818 return groups
2823 return groups
2819
2824
2820 @property
2825 @property
2821 def last_commit_cache_update_diff(self):
2826 def last_commit_cache_update_diff(self):
2822 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2827 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2823
2828
2824 @classmethod
2829 @classmethod
2825 def _load_commit_change(cls, last_commit_cache):
2830 def _load_commit_change(cls, last_commit_cache):
2826 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2831 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2827 empty_date = datetime.datetime.fromtimestamp(0)
2832 empty_date = datetime.datetime.fromtimestamp(0)
2828 date_latest = last_commit_cache.get('date', empty_date)
2833 date_latest = last_commit_cache.get('date', empty_date)
2829 try:
2834 try:
2830 return parse_datetime(date_latest)
2835 return parse_datetime(date_latest)
2831 except Exception:
2836 except Exception:
2832 return empty_date
2837 return empty_date
2833
2838
2834 @property
2839 @property
2835 def last_commit_change(self):
2840 def last_commit_change(self):
2836 return self._load_commit_change(self.changeset_cache)
2841 return self._load_commit_change(self.changeset_cache)
2837
2842
2838 @property
2843 @property
2839 def last_db_change(self):
2844 def last_db_change(self):
2840 return self.updated_on
2845 return self.updated_on
2841
2846
2842 @property
2847 @property
2843 def children(self):
2848 def children(self):
2844 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2849 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2845
2850
2846 @property
2851 @property
2847 def name(self):
2852 def name(self):
2848 return self.group_name.split(RepoGroup.url_sep())[-1]
2853 return self.group_name.split(RepoGroup.url_sep())[-1]
2849
2854
2850 @property
2855 @property
2851 def full_path(self):
2856 def full_path(self):
2852 return self.group_name
2857 return self.group_name
2853
2858
2854 @property
2859 @property
2855 def full_path_splitted(self):
2860 def full_path_splitted(self):
2856 return self.group_name.split(RepoGroup.url_sep())
2861 return self.group_name.split(RepoGroup.url_sep())
2857
2862
2858 @property
2863 @property
2859 def repositories(self):
2864 def repositories(self):
2860 return Repository.query()\
2865 return Repository.query()\
2861 .filter(Repository.group == self)\
2866 .filter(Repository.group == self)\
2862 .order_by(Repository.repo_name)
2867 .order_by(Repository.repo_name)
2863
2868
2864 @property
2869 @property
2865 def repositories_recursive_count(self):
2870 def repositories_recursive_count(self):
2866 cnt = self.repositories.count()
2871 cnt = self.repositories.count()
2867
2872
2868 def children_count(group):
2873 def children_count(group):
2869 cnt = 0
2874 cnt = 0
2870 for child in group.children:
2875 for child in group.children:
2871 cnt += child.repositories.count()
2876 cnt += child.repositories.count()
2872 cnt += children_count(child)
2877 cnt += children_count(child)
2873 return cnt
2878 return cnt
2874
2879
2875 return cnt + children_count(self)
2880 return cnt + children_count(self)
2876
2881
2877 def _recursive_objects(self, include_repos=True, include_groups=True):
2882 def _recursive_objects(self, include_repos=True, include_groups=True):
2878 all_ = []
2883 all_ = []
2879
2884
2880 def _get_members(root_gr):
2885 def _get_members(root_gr):
2881 if include_repos:
2886 if include_repos:
2882 for r in root_gr.repositories:
2887 for r in root_gr.repositories:
2883 all_.append(r)
2888 all_.append(r)
2884 childs = root_gr.children.all()
2889 childs = root_gr.children.all()
2885 if childs:
2890 if childs:
2886 for gr in childs:
2891 for gr in childs:
2887 if include_groups:
2892 if include_groups:
2888 all_.append(gr)
2893 all_.append(gr)
2889 _get_members(gr)
2894 _get_members(gr)
2890
2895
2891 root_group = []
2896 root_group = []
2892 if include_groups:
2897 if include_groups:
2893 root_group = [self]
2898 root_group = [self]
2894
2899
2895 _get_members(self)
2900 _get_members(self)
2896 return root_group + all_
2901 return root_group + all_
2897
2902
2898 def recursive_groups_and_repos(self):
2903 def recursive_groups_and_repos(self):
2899 """
2904 """
2900 Recursive return all groups, with repositories in those groups
2905 Recursive return all groups, with repositories in those groups
2901 """
2906 """
2902 return self._recursive_objects()
2907 return self._recursive_objects()
2903
2908
2904 def recursive_groups(self):
2909 def recursive_groups(self):
2905 """
2910 """
2906 Returns all children groups for this group including children of children
2911 Returns all children groups for this group including children of children
2907 """
2912 """
2908 return self._recursive_objects(include_repos=False)
2913 return self._recursive_objects(include_repos=False)
2909
2914
2910 def recursive_repos(self):
2915 def recursive_repos(self):
2911 """
2916 """
2912 Returns all children repositories for this group
2917 Returns all children repositories for this group
2913 """
2918 """
2914 return self._recursive_objects(include_groups=False)
2919 return self._recursive_objects(include_groups=False)
2915
2920
2916 def get_new_name(self, group_name):
2921 def get_new_name(self, group_name):
2917 """
2922 """
2918 returns new full group name based on parent and new name
2923 returns new full group name based on parent and new name
2919
2924
2920 :param group_name:
2925 :param group_name:
2921 """
2926 """
2922 path_prefix = (self.parent_group.full_path_splitted if
2927 path_prefix = (self.parent_group.full_path_splitted if
2923 self.parent_group else [])
2928 self.parent_group else [])
2924 return RepoGroup.url_sep().join(path_prefix + [group_name])
2929 return RepoGroup.url_sep().join(path_prefix + [group_name])
2925
2930
2926 def update_commit_cache(self, config=None):
2931 def update_commit_cache(self, config=None):
2927 """
2932 """
2928 Update cache of last commit for newest repository inside this repository group.
2933 Update cache of last commit for newest repository inside this repository group.
2929 cache_keys should be::
2934 cache_keys should be::
2930
2935
2931 source_repo_id
2936 source_repo_id
2932 short_id
2937 short_id
2933 raw_id
2938 raw_id
2934 revision
2939 revision
2935 parents
2940 parents
2936 message
2941 message
2937 date
2942 date
2938 author
2943 author
2939
2944
2940 """
2945 """
2941 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2946 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2942 empty_date = datetime.datetime.fromtimestamp(0)
2947 empty_date = datetime.datetime.fromtimestamp(0)
2943
2948
2944 def repo_groups_and_repos(root_gr):
2949 def repo_groups_and_repos(root_gr):
2945 for _repo in root_gr.repositories:
2950 for _repo in root_gr.repositories:
2946 yield _repo
2951 yield _repo
2947 for child_group in root_gr.children.all():
2952 for child_group in root_gr.children.all():
2948 yield child_group
2953 yield child_group
2949
2954
2950 latest_repo_cs_cache = {}
2955 latest_repo_cs_cache = {}
2951 for obj in repo_groups_and_repos(self):
2956 for obj in repo_groups_and_repos(self):
2952 repo_cs_cache = obj.changeset_cache
2957 repo_cs_cache = obj.changeset_cache
2953 date_latest = latest_repo_cs_cache.get('date', empty_date)
2958 date_latest = latest_repo_cs_cache.get('date', empty_date)
2954 date_current = repo_cs_cache.get('date', empty_date)
2959 date_current = repo_cs_cache.get('date', empty_date)
2955 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2960 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2956 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2961 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2957 latest_repo_cs_cache = repo_cs_cache
2962 latest_repo_cs_cache = repo_cs_cache
2958 if hasattr(obj, 'repo_id'):
2963 if hasattr(obj, 'repo_id'):
2959 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2964 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2960 else:
2965 else:
2961 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2966 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2962
2967
2963 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2968 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2964
2969
2965 latest_repo_cs_cache['updated_on'] = time.time()
2970 latest_repo_cs_cache['updated_on'] = time.time()
2966 self.changeset_cache = latest_repo_cs_cache
2971 self.changeset_cache = latest_repo_cs_cache
2967 self.updated_on = _date_latest
2972 self.updated_on = _date_latest
2968 Session().add(self)
2973 Session().add(self)
2969 Session().commit()
2974 Session().commit()
2970
2975
2971 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2976 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2972 self.group_name, latest_repo_cs_cache, _date_latest)
2977 self.group_name, latest_repo_cs_cache, _date_latest)
2973
2978
2974 def permissions(self, with_admins=True, with_owner=True,
2979 def permissions(self, with_admins=True, with_owner=True,
2975 expand_from_user_groups=False):
2980 expand_from_user_groups=False):
2976 """
2981 """
2977 Permissions for repository groups
2982 Permissions for repository groups
2978 """
2983 """
2979 _admin_perm = 'group.admin'
2984 _admin_perm = 'group.admin'
2980
2985
2981 owner_row = []
2986 owner_row = []
2982 if with_owner:
2987 if with_owner:
2983 usr = AttributeDict(self.user.get_dict())
2988 usr = AttributeDict(self.user.get_dict())
2984 usr.owner_row = True
2989 usr.owner_row = True
2985 usr.permission = _admin_perm
2990 usr.permission = _admin_perm
2986 owner_row.append(usr)
2991 owner_row.append(usr)
2987
2992
2988 super_admin_ids = []
2993 super_admin_ids = []
2989 super_admin_rows = []
2994 super_admin_rows = []
2990 if with_admins:
2995 if with_admins:
2991 for usr in User.get_all_super_admins():
2996 for usr in User.get_all_super_admins():
2992 super_admin_ids.append(usr.user_id)
2997 super_admin_ids.append(usr.user_id)
2993 # if this admin is also owner, don't double the record
2998 # if this admin is also owner, don't double the record
2994 if usr.user_id == owner_row[0].user_id:
2999 if usr.user_id == owner_row[0].user_id:
2995 owner_row[0].admin_row = True
3000 owner_row[0].admin_row = True
2996 else:
3001 else:
2997 usr = AttributeDict(usr.get_dict())
3002 usr = AttributeDict(usr.get_dict())
2998 usr.admin_row = True
3003 usr.admin_row = True
2999 usr.permission = _admin_perm
3004 usr.permission = _admin_perm
3000 super_admin_rows.append(usr)
3005 super_admin_rows.append(usr)
3001
3006
3002 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3007 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3003 q = q.options(joinedload(UserRepoGroupToPerm.group),
3008 q = q.options(joinedload(UserRepoGroupToPerm.group),
3004 joinedload(UserRepoGroupToPerm.user),
3009 joinedload(UserRepoGroupToPerm.user),
3005 joinedload(UserRepoGroupToPerm.permission),)
3010 joinedload(UserRepoGroupToPerm.permission),)
3006
3011
3007 # get owners and admins and permissions. We do a trick of re-writing
3012 # get owners and admins and permissions. We do a trick of re-writing
3008 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3013 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3009 # has a global reference and changing one object propagates to all
3014 # has a global reference and changing one object propagates to all
3010 # others. This means if admin is also an owner admin_row that change
3015 # others. This means if admin is also an owner admin_row that change
3011 # would propagate to both objects
3016 # would propagate to both objects
3012 perm_rows = []
3017 perm_rows = []
3013 for _usr in q.all():
3018 for _usr in q.all():
3014 usr = AttributeDict(_usr.user.get_dict())
3019 usr = AttributeDict(_usr.user.get_dict())
3015 # if this user is also owner/admin, mark as duplicate record
3020 # if this user is also owner/admin, mark as duplicate record
3016 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3021 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3017 usr.duplicate_perm = True
3022 usr.duplicate_perm = True
3018 usr.permission = _usr.permission.permission_name
3023 usr.permission = _usr.permission.permission_name
3019 perm_rows.append(usr)
3024 perm_rows.append(usr)
3020
3025
3021 # filter the perm rows by 'default' first and then sort them by
3026 # filter the perm rows by 'default' first and then sort them by
3022 # admin,write,read,none permissions sorted again alphabetically in
3027 # admin,write,read,none permissions sorted again alphabetically in
3023 # each group
3028 # each group
3024 perm_rows = sorted(perm_rows, key=display_user_sort)
3029 perm_rows = sorted(perm_rows, key=display_user_sort)
3025
3030
3026 user_groups_rows = []
3031 user_groups_rows = []
3027 if expand_from_user_groups:
3032 if expand_from_user_groups:
3028 for ug in self.permission_user_groups(with_members=True):
3033 for ug in self.permission_user_groups(with_members=True):
3029 for user_data in ug.members:
3034 for user_data in ug.members:
3030 user_groups_rows.append(user_data)
3035 user_groups_rows.append(user_data)
3031
3036
3032 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3037 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3033
3038
3034 def permission_user_groups(self, with_members=False):
3039 def permission_user_groups(self, with_members=False):
3035 q = UserGroupRepoGroupToPerm.query()\
3040 q = UserGroupRepoGroupToPerm.query()\
3036 .filter(UserGroupRepoGroupToPerm.group == self)
3041 .filter(UserGroupRepoGroupToPerm.group == self)
3037 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3042 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3038 joinedload(UserGroupRepoGroupToPerm.users_group),
3043 joinedload(UserGroupRepoGroupToPerm.users_group),
3039 joinedload(UserGroupRepoGroupToPerm.permission),)
3044 joinedload(UserGroupRepoGroupToPerm.permission),)
3040
3045
3041 perm_rows = []
3046 perm_rows = []
3042 for _user_group in q.all():
3047 for _user_group in q.all():
3043 entry = AttributeDict(_user_group.users_group.get_dict())
3048 entry = AttributeDict(_user_group.users_group.get_dict())
3044 entry.permission = _user_group.permission.permission_name
3049 entry.permission = _user_group.permission.permission_name
3045 if with_members:
3050 if with_members:
3046 entry.members = [x.user.get_dict()
3051 entry.members = [x.user.get_dict()
3047 for x in _user_group.users_group.members]
3052 for x in _user_group.users_group.members]
3048 perm_rows.append(entry)
3053 perm_rows.append(entry)
3049
3054
3050 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3055 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3051 return perm_rows
3056 return perm_rows
3052
3057
3053 def get_api_data(self):
3058 def get_api_data(self):
3054 """
3059 """
3055 Common function for generating api data
3060 Common function for generating api data
3056
3061
3057 """
3062 """
3058 group = self
3063 group = self
3059 data = {
3064 data = {
3060 'group_id': group.group_id,
3065 'group_id': group.group_id,
3061 'group_name': group.group_name,
3066 'group_name': group.group_name,
3062 'group_description': group.description_safe,
3067 'group_description': group.description_safe,
3063 'parent_group': group.parent_group.group_name if group.parent_group else None,
3068 'parent_group': group.parent_group.group_name if group.parent_group else None,
3064 'repositories': [x.repo_name for x in group.repositories],
3069 'repositories': [x.repo_name for x in group.repositories],
3065 'owner': group.user.username,
3070 'owner': group.user.username,
3066 }
3071 }
3067 return data
3072 return data
3068
3073
3069 def get_dict(self):
3074 def get_dict(self):
3070 # Since we transformed `group_name` to a hybrid property, we need to
3075 # Since we transformed `group_name` to a hybrid property, we need to
3071 # keep compatibility with the code which uses `group_name` field.
3076 # keep compatibility with the code which uses `group_name` field.
3072 result = super(RepoGroup, self).get_dict()
3077 result = super(RepoGroup, self).get_dict()
3073 result['group_name'] = result.pop('_group_name', None)
3078 result['group_name'] = result.pop('_group_name', None)
3074 return result
3079 return result
3075
3080
3076
3081
3077 class Permission(Base, BaseModel):
3082 class Permission(Base, BaseModel):
3078 __tablename__ = 'permissions'
3083 __tablename__ = 'permissions'
3079 __table_args__ = (
3084 __table_args__ = (
3080 Index('p_perm_name_idx', 'permission_name'),
3085 Index('p_perm_name_idx', 'permission_name'),
3081 base_table_args,
3086 base_table_args,
3082 )
3087 )
3083
3088
3084 PERMS = [
3089 PERMS = [
3085 ('hg.admin', _('RhodeCode Super Administrator')),
3090 ('hg.admin', _('RhodeCode Super Administrator')),
3086
3091
3087 ('repository.none', _('Repository no access')),
3092 ('repository.none', _('Repository no access')),
3088 ('repository.read', _('Repository read access')),
3093 ('repository.read', _('Repository read access')),
3089 ('repository.write', _('Repository write access')),
3094 ('repository.write', _('Repository write access')),
3090 ('repository.admin', _('Repository admin access')),
3095 ('repository.admin', _('Repository admin access')),
3091
3096
3092 ('group.none', _('Repository group no access')),
3097 ('group.none', _('Repository group no access')),
3093 ('group.read', _('Repository group read access')),
3098 ('group.read', _('Repository group read access')),
3094 ('group.write', _('Repository group write access')),
3099 ('group.write', _('Repository group write access')),
3095 ('group.admin', _('Repository group admin access')),
3100 ('group.admin', _('Repository group admin access')),
3096
3101
3097 ('usergroup.none', _('User group no access')),
3102 ('usergroup.none', _('User group no access')),
3098 ('usergroup.read', _('User group read access')),
3103 ('usergroup.read', _('User group read access')),
3099 ('usergroup.write', _('User group write access')),
3104 ('usergroup.write', _('User group write access')),
3100 ('usergroup.admin', _('User group admin access')),
3105 ('usergroup.admin', _('User group admin access')),
3101
3106
3102 ('branch.none', _('Branch no permissions')),
3107 ('branch.none', _('Branch no permissions')),
3103 ('branch.merge', _('Branch access by web merge')),
3108 ('branch.merge', _('Branch access by web merge')),
3104 ('branch.push', _('Branch access by push')),
3109 ('branch.push', _('Branch access by push')),
3105 ('branch.push_force', _('Branch access by push with force')),
3110 ('branch.push_force', _('Branch access by push with force')),
3106
3111
3107 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3112 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3108 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3113 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3109
3114
3110 ('hg.usergroup.create.false', _('User Group creation disabled')),
3115 ('hg.usergroup.create.false', _('User Group creation disabled')),
3111 ('hg.usergroup.create.true', _('User Group creation enabled')),
3116 ('hg.usergroup.create.true', _('User Group creation enabled')),
3112
3117
3113 ('hg.create.none', _('Repository creation disabled')),
3118 ('hg.create.none', _('Repository creation disabled')),
3114 ('hg.create.repository', _('Repository creation enabled')),
3119 ('hg.create.repository', _('Repository creation enabled')),
3115 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3120 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3116 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3121 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3117
3122
3118 ('hg.fork.none', _('Repository forking disabled')),
3123 ('hg.fork.none', _('Repository forking disabled')),
3119 ('hg.fork.repository', _('Repository forking enabled')),
3124 ('hg.fork.repository', _('Repository forking enabled')),
3120
3125
3121 ('hg.register.none', _('Registration disabled')),
3126 ('hg.register.none', _('Registration disabled')),
3122 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3127 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3123 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3128 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3124
3129
3125 ('hg.password_reset.enabled', _('Password reset enabled')),
3130 ('hg.password_reset.enabled', _('Password reset enabled')),
3126 ('hg.password_reset.hidden', _('Password reset hidden')),
3131 ('hg.password_reset.hidden', _('Password reset hidden')),
3127 ('hg.password_reset.disabled', _('Password reset disabled')),
3132 ('hg.password_reset.disabled', _('Password reset disabled')),
3128
3133
3129 ('hg.extern_activate.manual', _('Manual activation of external account')),
3134 ('hg.extern_activate.manual', _('Manual activation of external account')),
3130 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3135 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3131
3136
3132 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3137 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3133 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3138 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3134 ]
3139 ]
3135
3140
3136 # definition of system default permissions for DEFAULT user, created on
3141 # definition of system default permissions for DEFAULT user, created on
3137 # system setup
3142 # system setup
3138 DEFAULT_USER_PERMISSIONS = [
3143 DEFAULT_USER_PERMISSIONS = [
3139 # object perms
3144 # object perms
3140 'repository.read',
3145 'repository.read',
3141 'group.read',
3146 'group.read',
3142 'usergroup.read',
3147 'usergroup.read',
3143 # branch, for backward compat we need same value as before so forced pushed
3148 # branch, for backward compat we need same value as before so forced pushed
3144 'branch.push_force',
3149 'branch.push_force',
3145 # global
3150 # global
3146 'hg.create.repository',
3151 'hg.create.repository',
3147 'hg.repogroup.create.false',
3152 'hg.repogroup.create.false',
3148 'hg.usergroup.create.false',
3153 'hg.usergroup.create.false',
3149 'hg.create.write_on_repogroup.true',
3154 'hg.create.write_on_repogroup.true',
3150 'hg.fork.repository',
3155 'hg.fork.repository',
3151 'hg.register.manual_activate',
3156 'hg.register.manual_activate',
3152 'hg.password_reset.enabled',
3157 'hg.password_reset.enabled',
3153 'hg.extern_activate.auto',
3158 'hg.extern_activate.auto',
3154 'hg.inherit_default_perms.true',
3159 'hg.inherit_default_perms.true',
3155 ]
3160 ]
3156
3161
3157 # defines which permissions are more important higher the more important
3162 # defines which permissions are more important higher the more important
3158 # Weight defines which permissions are more important.
3163 # Weight defines which permissions are more important.
3159 # The higher number the more important.
3164 # The higher number the more important.
3160 PERM_WEIGHTS = {
3165 PERM_WEIGHTS = {
3161 'repository.none': 0,
3166 'repository.none': 0,
3162 'repository.read': 1,
3167 'repository.read': 1,
3163 'repository.write': 3,
3168 'repository.write': 3,
3164 'repository.admin': 4,
3169 'repository.admin': 4,
3165
3170
3166 'group.none': 0,
3171 'group.none': 0,
3167 'group.read': 1,
3172 'group.read': 1,
3168 'group.write': 3,
3173 'group.write': 3,
3169 'group.admin': 4,
3174 'group.admin': 4,
3170
3175
3171 'usergroup.none': 0,
3176 'usergroup.none': 0,
3172 'usergroup.read': 1,
3177 'usergroup.read': 1,
3173 'usergroup.write': 3,
3178 'usergroup.write': 3,
3174 'usergroup.admin': 4,
3179 'usergroup.admin': 4,
3175
3180
3176 'branch.none': 0,
3181 'branch.none': 0,
3177 'branch.merge': 1,
3182 'branch.merge': 1,
3178 'branch.push': 3,
3183 'branch.push': 3,
3179 'branch.push_force': 4,
3184 'branch.push_force': 4,
3180
3185
3181 'hg.repogroup.create.false': 0,
3186 'hg.repogroup.create.false': 0,
3182 'hg.repogroup.create.true': 1,
3187 'hg.repogroup.create.true': 1,
3183
3188
3184 'hg.usergroup.create.false': 0,
3189 'hg.usergroup.create.false': 0,
3185 'hg.usergroup.create.true': 1,
3190 'hg.usergroup.create.true': 1,
3186
3191
3187 'hg.fork.none': 0,
3192 'hg.fork.none': 0,
3188 'hg.fork.repository': 1,
3193 'hg.fork.repository': 1,
3189 'hg.create.none': 0,
3194 'hg.create.none': 0,
3190 'hg.create.repository': 1
3195 'hg.create.repository': 1
3191 }
3196 }
3192
3197
3193 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3198 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3194 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3199 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3195 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3200 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3196
3201
3197 def __unicode__(self):
3202 def __unicode__(self):
3198 return u"<%s('%s:%s')>" % (
3203 return u"<%s('%s:%s')>" % (
3199 self.__class__.__name__, self.permission_id, self.permission_name
3204 self.__class__.__name__, self.permission_id, self.permission_name
3200 )
3205 )
3201
3206
3202 @classmethod
3207 @classmethod
3203 def get_by_key(cls, key):
3208 def get_by_key(cls, key):
3204 return cls.query().filter(cls.permission_name == key).scalar()
3209 return cls.query().filter(cls.permission_name == key).scalar()
3205
3210
3206 @classmethod
3211 @classmethod
3207 def get_default_repo_perms(cls, user_id, repo_id=None):
3212 def get_default_repo_perms(cls, user_id, repo_id=None):
3208 q = Session().query(UserRepoToPerm, Repository, Permission)\
3213 q = Session().query(UserRepoToPerm, Repository, Permission)\
3209 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3214 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3210 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3215 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3211 .filter(UserRepoToPerm.user_id == user_id)
3216 .filter(UserRepoToPerm.user_id == user_id)
3212 if repo_id:
3217 if repo_id:
3213 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3218 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3214 return q.all()
3219 return q.all()
3215
3220
3216 @classmethod
3221 @classmethod
3217 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3222 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3218 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3223 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3219 .join(
3224 .join(
3220 Permission,
3225 Permission,
3221 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3226 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3222 .join(
3227 .join(
3223 UserRepoToPerm,
3228 UserRepoToPerm,
3224 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3229 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3225 .filter(UserRepoToPerm.user_id == user_id)
3230 .filter(UserRepoToPerm.user_id == user_id)
3226
3231
3227 if repo_id:
3232 if repo_id:
3228 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3233 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3229 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3234 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3230
3235
3231 @classmethod
3236 @classmethod
3232 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3237 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3233 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3238 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3234 .join(
3239 .join(
3235 Permission,
3240 Permission,
3236 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3241 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3237 .join(
3242 .join(
3238 Repository,
3243 Repository,
3239 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3244 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3240 .join(
3245 .join(
3241 UserGroup,
3246 UserGroup,
3242 UserGroupRepoToPerm.users_group_id ==
3247 UserGroupRepoToPerm.users_group_id ==
3243 UserGroup.users_group_id)\
3248 UserGroup.users_group_id)\
3244 .join(
3249 .join(
3245 UserGroupMember,
3250 UserGroupMember,
3246 UserGroupRepoToPerm.users_group_id ==
3251 UserGroupRepoToPerm.users_group_id ==
3247 UserGroupMember.users_group_id)\
3252 UserGroupMember.users_group_id)\
3248 .filter(
3253 .filter(
3249 UserGroupMember.user_id == user_id,
3254 UserGroupMember.user_id == user_id,
3250 UserGroup.users_group_active == true())
3255 UserGroup.users_group_active == true())
3251 if repo_id:
3256 if repo_id:
3252 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3257 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3253 return q.all()
3258 return q.all()
3254
3259
3255 @classmethod
3260 @classmethod
3256 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3261 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3257 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3262 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3258 .join(
3263 .join(
3259 Permission,
3264 Permission,
3260 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3265 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3261 .join(
3266 .join(
3262 UserGroupRepoToPerm,
3267 UserGroupRepoToPerm,
3263 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3268 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3264 .join(
3269 .join(
3265 UserGroup,
3270 UserGroup,
3266 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3271 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3267 .join(
3272 .join(
3268 UserGroupMember,
3273 UserGroupMember,
3269 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3274 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3270 .filter(
3275 .filter(
3271 UserGroupMember.user_id == user_id,
3276 UserGroupMember.user_id == user_id,
3272 UserGroup.users_group_active == true())
3277 UserGroup.users_group_active == true())
3273
3278
3274 if repo_id:
3279 if repo_id:
3275 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3280 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3276 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3281 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3277
3282
3278 @classmethod
3283 @classmethod
3279 def get_default_group_perms(cls, user_id, repo_group_id=None):
3284 def get_default_group_perms(cls, user_id, repo_group_id=None):
3280 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3285 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3281 .join(
3286 .join(
3282 Permission,
3287 Permission,
3283 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3288 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3284 .join(
3289 .join(
3285 RepoGroup,
3290 RepoGroup,
3286 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3291 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3287 .filter(UserRepoGroupToPerm.user_id == user_id)
3292 .filter(UserRepoGroupToPerm.user_id == user_id)
3288 if repo_group_id:
3293 if repo_group_id:
3289 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3294 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3290 return q.all()
3295 return q.all()
3291
3296
3292 @classmethod
3297 @classmethod
3293 def get_default_group_perms_from_user_group(
3298 def get_default_group_perms_from_user_group(
3294 cls, user_id, repo_group_id=None):
3299 cls, user_id, repo_group_id=None):
3295 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3300 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3296 .join(
3301 .join(
3297 Permission,
3302 Permission,
3298 UserGroupRepoGroupToPerm.permission_id ==
3303 UserGroupRepoGroupToPerm.permission_id ==
3299 Permission.permission_id)\
3304 Permission.permission_id)\
3300 .join(
3305 .join(
3301 RepoGroup,
3306 RepoGroup,
3302 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3307 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3303 .join(
3308 .join(
3304 UserGroup,
3309 UserGroup,
3305 UserGroupRepoGroupToPerm.users_group_id ==
3310 UserGroupRepoGroupToPerm.users_group_id ==
3306 UserGroup.users_group_id)\
3311 UserGroup.users_group_id)\
3307 .join(
3312 .join(
3308 UserGroupMember,
3313 UserGroupMember,
3309 UserGroupRepoGroupToPerm.users_group_id ==
3314 UserGroupRepoGroupToPerm.users_group_id ==
3310 UserGroupMember.users_group_id)\
3315 UserGroupMember.users_group_id)\
3311 .filter(
3316 .filter(
3312 UserGroupMember.user_id == user_id,
3317 UserGroupMember.user_id == user_id,
3313 UserGroup.users_group_active == true())
3318 UserGroup.users_group_active == true())
3314 if repo_group_id:
3319 if repo_group_id:
3315 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3320 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3316 return q.all()
3321 return q.all()
3317
3322
3318 @classmethod
3323 @classmethod
3319 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3324 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3320 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3325 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3321 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3326 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3322 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3327 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3323 .filter(UserUserGroupToPerm.user_id == user_id)
3328 .filter(UserUserGroupToPerm.user_id == user_id)
3324 if user_group_id:
3329 if user_group_id:
3325 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3330 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3326 return q.all()
3331 return q.all()
3327
3332
3328 @classmethod
3333 @classmethod
3329 def get_default_user_group_perms_from_user_group(
3334 def get_default_user_group_perms_from_user_group(
3330 cls, user_id, user_group_id=None):
3335 cls, user_id, user_group_id=None):
3331 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3336 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3332 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3337 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3333 .join(
3338 .join(
3334 Permission,
3339 Permission,
3335 UserGroupUserGroupToPerm.permission_id ==
3340 UserGroupUserGroupToPerm.permission_id ==
3336 Permission.permission_id)\
3341 Permission.permission_id)\
3337 .join(
3342 .join(
3338 TargetUserGroup,
3343 TargetUserGroup,
3339 UserGroupUserGroupToPerm.target_user_group_id ==
3344 UserGroupUserGroupToPerm.target_user_group_id ==
3340 TargetUserGroup.users_group_id)\
3345 TargetUserGroup.users_group_id)\
3341 .join(
3346 .join(
3342 UserGroup,
3347 UserGroup,
3343 UserGroupUserGroupToPerm.user_group_id ==
3348 UserGroupUserGroupToPerm.user_group_id ==
3344 UserGroup.users_group_id)\
3349 UserGroup.users_group_id)\
3345 .join(
3350 .join(
3346 UserGroupMember,
3351 UserGroupMember,
3347 UserGroupUserGroupToPerm.user_group_id ==
3352 UserGroupUserGroupToPerm.user_group_id ==
3348 UserGroupMember.users_group_id)\
3353 UserGroupMember.users_group_id)\
3349 .filter(
3354 .filter(
3350 UserGroupMember.user_id == user_id,
3355 UserGroupMember.user_id == user_id,
3351 UserGroup.users_group_active == true())
3356 UserGroup.users_group_active == true())
3352 if user_group_id:
3357 if user_group_id:
3353 q = q.filter(
3358 q = q.filter(
3354 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3359 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3355
3360
3356 return q.all()
3361 return q.all()
3357
3362
3358
3363
3359 class UserRepoToPerm(Base, BaseModel):
3364 class UserRepoToPerm(Base, BaseModel):
3360 __tablename__ = 'repo_to_perm'
3365 __tablename__ = 'repo_to_perm'
3361 __table_args__ = (
3366 __table_args__ = (
3362 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3367 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3363 base_table_args
3368 base_table_args
3364 )
3369 )
3365
3370
3366 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3371 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3372 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3368 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3373 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3369 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3374 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3370
3375
3371 user = relationship('User')
3376 user = relationship('User')
3372 repository = relationship('Repository')
3377 repository = relationship('Repository')
3373 permission = relationship('Permission')
3378 permission = relationship('Permission')
3374
3379
3375 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3380 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3376
3381
3377 @classmethod
3382 @classmethod
3378 def create(cls, user, repository, permission):
3383 def create(cls, user, repository, permission):
3379 n = cls()
3384 n = cls()
3380 n.user = user
3385 n.user = user
3381 n.repository = repository
3386 n.repository = repository
3382 n.permission = permission
3387 n.permission = permission
3383 Session().add(n)
3388 Session().add(n)
3384 return n
3389 return n
3385
3390
3386 def __unicode__(self):
3391 def __unicode__(self):
3387 return u'<%s => %s >' % (self.user, self.repository)
3392 return u'<%s => %s >' % (self.user, self.repository)
3388
3393
3389
3394
3390 class UserUserGroupToPerm(Base, BaseModel):
3395 class UserUserGroupToPerm(Base, BaseModel):
3391 __tablename__ = 'user_user_group_to_perm'
3396 __tablename__ = 'user_user_group_to_perm'
3392 __table_args__ = (
3397 __table_args__ = (
3393 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3398 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3394 base_table_args
3399 base_table_args
3395 )
3400 )
3396
3401
3397 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3402 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3398 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3399 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3400 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3405 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3401
3406
3402 user = relationship('User')
3407 user = relationship('User')
3403 user_group = relationship('UserGroup')
3408 user_group = relationship('UserGroup')
3404 permission = relationship('Permission')
3409 permission = relationship('Permission')
3405
3410
3406 @classmethod
3411 @classmethod
3407 def create(cls, user, user_group, permission):
3412 def create(cls, user, user_group, permission):
3408 n = cls()
3413 n = cls()
3409 n.user = user
3414 n.user = user
3410 n.user_group = user_group
3415 n.user_group = user_group
3411 n.permission = permission
3416 n.permission = permission
3412 Session().add(n)
3417 Session().add(n)
3413 return n
3418 return n
3414
3419
3415 def __unicode__(self):
3420 def __unicode__(self):
3416 return u'<%s => %s >' % (self.user, self.user_group)
3421 return u'<%s => %s >' % (self.user, self.user_group)
3417
3422
3418
3423
3419 class UserToPerm(Base, BaseModel):
3424 class UserToPerm(Base, BaseModel):
3420 __tablename__ = 'user_to_perm'
3425 __tablename__ = 'user_to_perm'
3421 __table_args__ = (
3426 __table_args__ = (
3422 UniqueConstraint('user_id', 'permission_id'),
3427 UniqueConstraint('user_id', 'permission_id'),
3423 base_table_args
3428 base_table_args
3424 )
3429 )
3425
3430
3426 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3431 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3432 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3433 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3429
3434
3430 user = relationship('User')
3435 user = relationship('User')
3431 permission = relationship('Permission', lazy='joined')
3436 permission = relationship('Permission', lazy='joined')
3432
3437
3433 def __unicode__(self):
3438 def __unicode__(self):
3434 return u'<%s => %s >' % (self.user, self.permission)
3439 return u'<%s => %s >' % (self.user, self.permission)
3435
3440
3436
3441
3437 class UserGroupRepoToPerm(Base, BaseModel):
3442 class UserGroupRepoToPerm(Base, BaseModel):
3438 __tablename__ = 'users_group_repo_to_perm'
3443 __tablename__ = 'users_group_repo_to_perm'
3439 __table_args__ = (
3444 __table_args__ = (
3440 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3445 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3441 base_table_args
3446 base_table_args
3442 )
3447 )
3443
3448
3444 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3449 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3450 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3451 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3447 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3452 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3448
3453
3449 users_group = relationship('UserGroup')
3454 users_group = relationship('UserGroup')
3450 permission = relationship('Permission')
3455 permission = relationship('Permission')
3451 repository = relationship('Repository')
3456 repository = relationship('Repository')
3452 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3457 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3453
3458
3454 @classmethod
3459 @classmethod
3455 def create(cls, users_group, repository, permission):
3460 def create(cls, users_group, repository, permission):
3456 n = cls()
3461 n = cls()
3457 n.users_group = users_group
3462 n.users_group = users_group
3458 n.repository = repository
3463 n.repository = repository
3459 n.permission = permission
3464 n.permission = permission
3460 Session().add(n)
3465 Session().add(n)
3461 return n
3466 return n
3462
3467
3463 def __unicode__(self):
3468 def __unicode__(self):
3464 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3469 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3465
3470
3466
3471
3467 class UserGroupUserGroupToPerm(Base, BaseModel):
3472 class UserGroupUserGroupToPerm(Base, BaseModel):
3468 __tablename__ = 'user_group_user_group_to_perm'
3473 __tablename__ = 'user_group_user_group_to_perm'
3469 __table_args__ = (
3474 __table_args__ = (
3470 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3475 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3471 CheckConstraint('target_user_group_id != user_group_id'),
3476 CheckConstraint('target_user_group_id != user_group_id'),
3472 base_table_args
3477 base_table_args
3473 )
3478 )
3474
3479
3475 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)
3480 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)
3476 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3481 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3477 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3482 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3478 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3483 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3479
3484
3480 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3485 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3481 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3486 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3482 permission = relationship('Permission')
3487 permission = relationship('Permission')
3483
3488
3484 @classmethod
3489 @classmethod
3485 def create(cls, target_user_group, user_group, permission):
3490 def create(cls, target_user_group, user_group, permission):
3486 n = cls()
3491 n = cls()
3487 n.target_user_group = target_user_group
3492 n.target_user_group = target_user_group
3488 n.user_group = user_group
3493 n.user_group = user_group
3489 n.permission = permission
3494 n.permission = permission
3490 Session().add(n)
3495 Session().add(n)
3491 return n
3496 return n
3492
3497
3493 def __unicode__(self):
3498 def __unicode__(self):
3494 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3499 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3495
3500
3496
3501
3497 class UserGroupToPerm(Base, BaseModel):
3502 class UserGroupToPerm(Base, BaseModel):
3498 __tablename__ = 'users_group_to_perm'
3503 __tablename__ = 'users_group_to_perm'
3499 __table_args__ = (
3504 __table_args__ = (
3500 UniqueConstraint('users_group_id', 'permission_id',),
3505 UniqueConstraint('users_group_id', 'permission_id',),
3501 base_table_args
3506 base_table_args
3502 )
3507 )
3503
3508
3504 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3509 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3510 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3511 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3507
3512
3508 users_group = relationship('UserGroup')
3513 users_group = relationship('UserGroup')
3509 permission = relationship('Permission')
3514 permission = relationship('Permission')
3510
3515
3511
3516
3512 class UserRepoGroupToPerm(Base, BaseModel):
3517 class UserRepoGroupToPerm(Base, BaseModel):
3513 __tablename__ = 'user_repo_group_to_perm'
3518 __tablename__ = 'user_repo_group_to_perm'
3514 __table_args__ = (
3519 __table_args__ = (
3515 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3520 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3516 base_table_args
3521 base_table_args
3517 )
3522 )
3518
3523
3519 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3524 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3525 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3521 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3526 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3527 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3523
3528
3524 user = relationship('User')
3529 user = relationship('User')
3525 group = relationship('RepoGroup')
3530 group = relationship('RepoGroup')
3526 permission = relationship('Permission')
3531 permission = relationship('Permission')
3527
3532
3528 @classmethod
3533 @classmethod
3529 def create(cls, user, repository_group, permission):
3534 def create(cls, user, repository_group, permission):
3530 n = cls()
3535 n = cls()
3531 n.user = user
3536 n.user = user
3532 n.group = repository_group
3537 n.group = repository_group
3533 n.permission = permission
3538 n.permission = permission
3534 Session().add(n)
3539 Session().add(n)
3535 return n
3540 return n
3536
3541
3537
3542
3538 class UserGroupRepoGroupToPerm(Base, BaseModel):
3543 class UserGroupRepoGroupToPerm(Base, BaseModel):
3539 __tablename__ = 'users_group_repo_group_to_perm'
3544 __tablename__ = 'users_group_repo_group_to_perm'
3540 __table_args__ = (
3545 __table_args__ = (
3541 UniqueConstraint('users_group_id', 'group_id'),
3546 UniqueConstraint('users_group_id', 'group_id'),
3542 base_table_args
3547 base_table_args
3543 )
3548 )
3544
3549
3545 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)
3550 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)
3546 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3547 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3552 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3548 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3553 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3549
3554
3550 users_group = relationship('UserGroup')
3555 users_group = relationship('UserGroup')
3551 permission = relationship('Permission')
3556 permission = relationship('Permission')
3552 group = relationship('RepoGroup')
3557 group = relationship('RepoGroup')
3553
3558
3554 @classmethod
3559 @classmethod
3555 def create(cls, user_group, repository_group, permission):
3560 def create(cls, user_group, repository_group, permission):
3556 n = cls()
3561 n = cls()
3557 n.users_group = user_group
3562 n.users_group = user_group
3558 n.group = repository_group
3563 n.group = repository_group
3559 n.permission = permission
3564 n.permission = permission
3560 Session().add(n)
3565 Session().add(n)
3561 return n
3566 return n
3562
3567
3563 def __unicode__(self):
3568 def __unicode__(self):
3564 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3569 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3565
3570
3566
3571
3567 class Statistics(Base, BaseModel):
3572 class Statistics(Base, BaseModel):
3568 __tablename__ = 'statistics'
3573 __tablename__ = 'statistics'
3569 __table_args__ = (
3574 __table_args__ = (
3570 base_table_args
3575 base_table_args
3571 )
3576 )
3572
3577
3573 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3578 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3574 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3579 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3575 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3580 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3576 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3581 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3577 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3582 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3578 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3583 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3579
3584
3580 repository = relationship('Repository', single_parent=True)
3585 repository = relationship('Repository', single_parent=True)
3581
3586
3582
3587
3583 class UserFollowing(Base, BaseModel):
3588 class UserFollowing(Base, BaseModel):
3584 __tablename__ = 'user_followings'
3589 __tablename__ = 'user_followings'
3585 __table_args__ = (
3590 __table_args__ = (
3586 UniqueConstraint('user_id', 'follows_repository_id'),
3591 UniqueConstraint('user_id', 'follows_repository_id'),
3587 UniqueConstraint('user_id', 'follows_user_id'),
3592 UniqueConstraint('user_id', 'follows_user_id'),
3588 base_table_args
3593 base_table_args
3589 )
3594 )
3590
3595
3591 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3596 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3597 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3593 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3598 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3594 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3599 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3595 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3600 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3596
3601
3597 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3602 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3598
3603
3599 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3604 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3600 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3605 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3601
3606
3602 @classmethod
3607 @classmethod
3603 def get_repo_followers(cls, repo_id):
3608 def get_repo_followers(cls, repo_id):
3604 return cls.query().filter(cls.follows_repo_id == repo_id)
3609 return cls.query().filter(cls.follows_repo_id == repo_id)
3605
3610
3606
3611
3607 class CacheKey(Base, BaseModel):
3612 class CacheKey(Base, BaseModel):
3608 __tablename__ = 'cache_invalidation'
3613 __tablename__ = 'cache_invalidation'
3609 __table_args__ = (
3614 __table_args__ = (
3610 UniqueConstraint('cache_key'),
3615 UniqueConstraint('cache_key'),
3611 Index('key_idx', 'cache_key'),
3616 Index('key_idx', 'cache_key'),
3612 base_table_args,
3617 base_table_args,
3613 )
3618 )
3614
3619
3615 CACHE_TYPE_FEED = 'FEED'
3620 CACHE_TYPE_FEED = 'FEED'
3616
3621
3617 # namespaces used to register process/thread aware caches
3622 # namespaces used to register process/thread aware caches
3618 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3623 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3619 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3624 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3620
3625
3621 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3626 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3622 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3627 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3623 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3628 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3624 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3629 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3625 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3630 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3626
3631
3627 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3632 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3628 self.cache_key = cache_key
3633 self.cache_key = cache_key
3629 self.cache_args = cache_args
3634 self.cache_args = cache_args
3630 self.cache_active = False
3635 self.cache_active = False
3631 # first key should be same for all entries, since all workers should share it
3636 # first key should be same for all entries, since all workers should share it
3632 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3637 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3633
3638
3634 def __unicode__(self):
3639 def __unicode__(self):
3635 return u"<%s('%s:%s[%s]')>" % (
3640 return u"<%s('%s:%s[%s]')>" % (
3636 self.__class__.__name__,
3641 self.__class__.__name__,
3637 self.cache_id, self.cache_key, self.cache_active)
3642 self.cache_id, self.cache_key, self.cache_active)
3638
3643
3639 def _cache_key_partition(self):
3644 def _cache_key_partition(self):
3640 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3645 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3641 return prefix, repo_name, suffix
3646 return prefix, repo_name, suffix
3642
3647
3643 def get_prefix(self):
3648 def get_prefix(self):
3644 """
3649 """
3645 Try to extract prefix from existing cache key. The key could consist
3650 Try to extract prefix from existing cache key. The key could consist
3646 of prefix, repo_name, suffix
3651 of prefix, repo_name, suffix
3647 """
3652 """
3648 # this returns prefix, repo_name, suffix
3653 # this returns prefix, repo_name, suffix
3649 return self._cache_key_partition()[0]
3654 return self._cache_key_partition()[0]
3650
3655
3651 def get_suffix(self):
3656 def get_suffix(self):
3652 """
3657 """
3653 get suffix that might have been used in _get_cache_key to
3658 get suffix that might have been used in _get_cache_key to
3654 generate self.cache_key. Only used for informational purposes
3659 generate self.cache_key. Only used for informational purposes
3655 in repo_edit.mako.
3660 in repo_edit.mako.
3656 """
3661 """
3657 # prefix, repo_name, suffix
3662 # prefix, repo_name, suffix
3658 return self._cache_key_partition()[2]
3663 return self._cache_key_partition()[2]
3659
3664
3660 @classmethod
3665 @classmethod
3661 def generate_new_state_uid(cls, based_on=None):
3666 def generate_new_state_uid(cls, based_on=None):
3662 if based_on:
3667 if based_on:
3663 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3668 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3664 else:
3669 else:
3665 return str(uuid.uuid4())
3670 return str(uuid.uuid4())
3666
3671
3667 @classmethod
3672 @classmethod
3668 def delete_all_cache(cls):
3673 def delete_all_cache(cls):
3669 """
3674 """
3670 Delete all cache keys from database.
3675 Delete all cache keys from database.
3671 Should only be run when all instances are down and all entries
3676 Should only be run when all instances are down and all entries
3672 thus stale.
3677 thus stale.
3673 """
3678 """
3674 cls.query().delete()
3679 cls.query().delete()
3675 Session().commit()
3680 Session().commit()
3676
3681
3677 @classmethod
3682 @classmethod
3678 def set_invalidate(cls, cache_uid, delete=False):
3683 def set_invalidate(cls, cache_uid, delete=False):
3679 """
3684 """
3680 Mark all caches of a repo as invalid in the database.
3685 Mark all caches of a repo as invalid in the database.
3681 """
3686 """
3682
3687
3683 try:
3688 try:
3684 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3689 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3685 if delete:
3690 if delete:
3686 qry.delete()
3691 qry.delete()
3687 log.debug('cache objects deleted for cache args %s',
3692 log.debug('cache objects deleted for cache args %s',
3688 safe_str(cache_uid))
3693 safe_str(cache_uid))
3689 else:
3694 else:
3690 qry.update({"cache_active": False,
3695 qry.update({"cache_active": False,
3691 "cache_state_uid": cls.generate_new_state_uid()})
3696 "cache_state_uid": cls.generate_new_state_uid()})
3692 log.debug('cache objects marked as invalid for cache args %s',
3697 log.debug('cache objects marked as invalid for cache args %s',
3693 safe_str(cache_uid))
3698 safe_str(cache_uid))
3694
3699
3695 Session().commit()
3700 Session().commit()
3696 except Exception:
3701 except Exception:
3697 log.exception(
3702 log.exception(
3698 'Cache key invalidation failed for cache args %s',
3703 'Cache key invalidation failed for cache args %s',
3699 safe_str(cache_uid))
3704 safe_str(cache_uid))
3700 Session().rollback()
3705 Session().rollback()
3701
3706
3702 @classmethod
3707 @classmethod
3703 def get_active_cache(cls, cache_key):
3708 def get_active_cache(cls, cache_key):
3704 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3709 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3705 if inv_obj:
3710 if inv_obj:
3706 return inv_obj
3711 return inv_obj
3707 return None
3712 return None
3708
3713
3709 @classmethod
3714 @classmethod
3710 def get_namespace_map(cls, namespace):
3715 def get_namespace_map(cls, namespace):
3711 return {
3716 return {
3712 x.cache_key: x
3717 x.cache_key: x
3713 for x in cls.query().filter(cls.cache_args == namespace)}
3718 for x in cls.query().filter(cls.cache_args == namespace)}
3714
3719
3715
3720
3716 class ChangesetComment(Base, BaseModel):
3721 class ChangesetComment(Base, BaseModel):
3717 __tablename__ = 'changeset_comments'
3722 __tablename__ = 'changeset_comments'
3718 __table_args__ = (
3723 __table_args__ = (
3719 Index('cc_revision_idx', 'revision'),
3724 Index('cc_revision_idx', 'revision'),
3720 base_table_args,
3725 base_table_args,
3721 )
3726 )
3722
3727
3723 COMMENT_OUTDATED = u'comment_outdated'
3728 COMMENT_OUTDATED = u'comment_outdated'
3724 COMMENT_TYPE_NOTE = u'note'
3729 COMMENT_TYPE_NOTE = u'note'
3725 COMMENT_TYPE_TODO = u'todo'
3730 COMMENT_TYPE_TODO = u'todo'
3726 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3731 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3727
3732
3728 OP_IMMUTABLE = u'immutable'
3733 OP_IMMUTABLE = u'immutable'
3729 OP_CHANGEABLE = u'changeable'
3734 OP_CHANGEABLE = u'changeable'
3730
3735
3731 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3736 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3732 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3737 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3733 revision = Column('revision', String(40), nullable=True)
3738 revision = Column('revision', String(40), nullable=True)
3734 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3739 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3735 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3740 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3736 line_no = Column('line_no', Unicode(10), nullable=True)
3741 line_no = Column('line_no', Unicode(10), nullable=True)
3737 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3742 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3738 f_path = Column('f_path', Unicode(1000), nullable=True)
3743 f_path = Column('f_path', Unicode(1000), nullable=True)
3739 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3744 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3740 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3745 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3741 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3746 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3742 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3747 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3743 renderer = Column('renderer', Unicode(64), nullable=True)
3748 renderer = Column('renderer', Unicode(64), nullable=True)
3744 display_state = Column('display_state', Unicode(128), nullable=True)
3749 display_state = Column('display_state', Unicode(128), nullable=True)
3745 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3750 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3746
3751
3747 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3752 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3748 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3753 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3749
3754
3750 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3755 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3751 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3756 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3752
3757
3753 author = relationship('User', lazy='joined')
3758 author = relationship('User', lazy='joined')
3754 repo = relationship('Repository')
3759 repo = relationship('Repository')
3755 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3760 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3756 pull_request = relationship('PullRequest', lazy='joined')
3761 pull_request = relationship('PullRequest', lazy='joined')
3757 pull_request_version = relationship('PullRequestVersion')
3762 pull_request_version = relationship('PullRequestVersion')
3758 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
3763 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
3759
3764
3760 @classmethod
3765 @classmethod
3761 def get_users(cls, revision=None, pull_request_id=None):
3766 def get_users(cls, revision=None, pull_request_id=None):
3762 """
3767 """
3763 Returns user associated with this ChangesetComment. ie those
3768 Returns user associated with this ChangesetComment. ie those
3764 who actually commented
3769 who actually commented
3765
3770
3766 :param cls:
3771 :param cls:
3767 :param revision:
3772 :param revision:
3768 """
3773 """
3769 q = Session().query(User)\
3774 q = Session().query(User)\
3770 .join(ChangesetComment.author)
3775 .join(ChangesetComment.author)
3771 if revision:
3776 if revision:
3772 q = q.filter(cls.revision == revision)
3777 q = q.filter(cls.revision == revision)
3773 elif pull_request_id:
3778 elif pull_request_id:
3774 q = q.filter(cls.pull_request_id == pull_request_id)
3779 q = q.filter(cls.pull_request_id == pull_request_id)
3775 return q.all()
3780 return q.all()
3776
3781
3777 @classmethod
3782 @classmethod
3778 def get_index_from_version(cls, pr_version, versions):
3783 def get_index_from_version(cls, pr_version, versions):
3779 num_versions = [x.pull_request_version_id for x in versions]
3784 num_versions = [x.pull_request_version_id for x in versions]
3780 try:
3785 try:
3781 return num_versions.index(pr_version) +1
3786 return num_versions.index(pr_version) +1
3782 except (IndexError, ValueError):
3787 except (IndexError, ValueError):
3783 return
3788 return
3784
3789
3785 @property
3790 @property
3786 def outdated(self):
3791 def outdated(self):
3787 return self.display_state == self.COMMENT_OUTDATED
3792 return self.display_state == self.COMMENT_OUTDATED
3788
3793
3789 @property
3794 @property
3790 def immutable(self):
3795 def immutable(self):
3791 return self.immutable_state == self.OP_IMMUTABLE
3796 return self.immutable_state == self.OP_IMMUTABLE
3792
3797
3793 def outdated_at_version(self, version):
3798 def outdated_at_version(self, version):
3794 """
3799 """
3795 Checks if comment is outdated for given pull request version
3800 Checks if comment is outdated for given pull request version
3796 """
3801 """
3797 return self.outdated and self.pull_request_version_id != version
3802 return self.outdated and self.pull_request_version_id != version
3798
3803
3799 def older_than_version(self, version):
3804 def older_than_version(self, version):
3800 """
3805 """
3801 Checks if comment is made from previous version than given
3806 Checks if comment is made from previous version than given
3802 """
3807 """
3803 if version is None:
3808 if version is None:
3804 return self.pull_request_version_id is not None
3809 return self.pull_request_version_id is not None
3805
3810
3806 return self.pull_request_version_id < version
3811 return self.pull_request_version_id < version
3807
3812
3808 @property
3813 @property
3809 def commit_id(self):
3814 def commit_id(self):
3810 """New style naming to stop using .revision"""
3815 """New style naming to stop using .revision"""
3811 return self.revision
3816 return self.revision
3812
3817
3813 @property
3818 @property
3814 def resolved(self):
3819 def resolved(self):
3815 return self.resolved_by[0] if self.resolved_by else None
3820 return self.resolved_by[0] if self.resolved_by else None
3816
3821
3817 @property
3822 @property
3818 def is_todo(self):
3823 def is_todo(self):
3819 return self.comment_type == self.COMMENT_TYPE_TODO
3824 return self.comment_type == self.COMMENT_TYPE_TODO
3820
3825
3821 @property
3826 @property
3822 def is_inline(self):
3827 def is_inline(self):
3823 return self.line_no and self.f_path
3828 return self.line_no and self.f_path
3824
3829
3825 def get_index_version(self, versions):
3830 def get_index_version(self, versions):
3826 return self.get_index_from_version(
3831 return self.get_index_from_version(
3827 self.pull_request_version_id, versions)
3832 self.pull_request_version_id, versions)
3828
3833
3829 def __repr__(self):
3834 def __repr__(self):
3830 if self.comment_id:
3835 if self.comment_id:
3831 return '<DB:Comment #%s>' % self.comment_id
3836 return '<DB:Comment #%s>' % self.comment_id
3832 else:
3837 else:
3833 return '<DB:Comment at %#x>' % id(self)
3838 return '<DB:Comment at %#x>' % id(self)
3834
3839
3835 def get_api_data(self):
3840 def get_api_data(self):
3836 comment = self
3841 comment = self
3837 data = {
3842 data = {
3838 'comment_id': comment.comment_id,
3843 'comment_id': comment.comment_id,
3839 'comment_type': comment.comment_type,
3844 'comment_type': comment.comment_type,
3840 'comment_text': comment.text,
3845 'comment_text': comment.text,
3841 'comment_status': comment.status_change,
3846 'comment_status': comment.status_change,
3842 'comment_f_path': comment.f_path,
3847 'comment_f_path': comment.f_path,
3843 'comment_lineno': comment.line_no,
3848 'comment_lineno': comment.line_no,
3844 'comment_author': comment.author,
3849 'comment_author': comment.author,
3845 'comment_created_on': comment.created_on,
3850 'comment_created_on': comment.created_on,
3846 'comment_resolved_by': self.resolved,
3851 'comment_resolved_by': self.resolved,
3847 'comment_commit_id': comment.revision,
3852 'comment_commit_id': comment.revision,
3848 'comment_pull_request_id': comment.pull_request_id,
3853 'comment_pull_request_id': comment.pull_request_id,
3849 }
3854 }
3850 return data
3855 return data
3851
3856
3852 def __json__(self):
3857 def __json__(self):
3853 data = dict()
3858 data = dict()
3854 data.update(self.get_api_data())
3859 data.update(self.get_api_data())
3855 return data
3860 return data
3856
3861
3857
3862
3858 class ChangesetCommentHistory(Base, BaseModel):
3863 class ChangesetCommentHistory(Base, BaseModel):
3859 __tablename__ = 'changeset_comments_history'
3864 __tablename__ = 'changeset_comments_history'
3860 __table_args__ = (
3865 __table_args__ = (
3861 Index('cch_comment_id_idx', 'comment_id'),
3866 Index('cch_comment_id_idx', 'comment_id'),
3862 base_table_args,
3867 base_table_args,
3863 )
3868 )
3864
3869
3865 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3870 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3866 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3871 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3867 version = Column("version", Integer(), nullable=False, default=0)
3872 version = Column("version", Integer(), nullable=False, default=0)
3868 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3873 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3869 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3874 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3870 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3875 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3871 deleted = Column('deleted', Boolean(), default=False)
3876 deleted = Column('deleted', Boolean(), default=False)
3872
3877
3873 author = relationship('User', lazy='joined')
3878 author = relationship('User', lazy='joined')
3874 comment = relationship('ChangesetComment', cascade="all, delete")
3879 comment = relationship('ChangesetComment', cascade="all, delete")
3875
3880
3876 @classmethod
3881 @classmethod
3877 def get_version(cls, comment_id):
3882 def get_version(cls, comment_id):
3878 q = Session().query(ChangesetCommentHistory).filter(
3883 q = Session().query(ChangesetCommentHistory).filter(
3879 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3884 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3880 if q.count() == 0:
3885 if q.count() == 0:
3881 return 1
3886 return 1
3882 elif q.count() >= q[0].version:
3887 elif q.count() >= q[0].version:
3883 return q.count() + 1
3888 return q.count() + 1
3884 else:
3889 else:
3885 return q[0].version + 1
3890 return q[0].version + 1
3886
3891
3887
3892
3888 class ChangesetStatus(Base, BaseModel):
3893 class ChangesetStatus(Base, BaseModel):
3889 __tablename__ = 'changeset_statuses'
3894 __tablename__ = 'changeset_statuses'
3890 __table_args__ = (
3895 __table_args__ = (
3891 Index('cs_revision_idx', 'revision'),
3896 Index('cs_revision_idx', 'revision'),
3892 Index('cs_version_idx', 'version'),
3897 Index('cs_version_idx', 'version'),
3893 UniqueConstraint('repo_id', 'revision', 'version'),
3898 UniqueConstraint('repo_id', 'revision', 'version'),
3894 base_table_args
3899 base_table_args
3895 )
3900 )
3896
3901
3897 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3902 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3898 STATUS_APPROVED = 'approved'
3903 STATUS_APPROVED = 'approved'
3899 STATUS_REJECTED = 'rejected'
3904 STATUS_REJECTED = 'rejected'
3900 STATUS_UNDER_REVIEW = 'under_review'
3905 STATUS_UNDER_REVIEW = 'under_review'
3901
3906
3902 STATUSES = [
3907 STATUSES = [
3903 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3908 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3904 (STATUS_APPROVED, _("Approved")),
3909 (STATUS_APPROVED, _("Approved")),
3905 (STATUS_REJECTED, _("Rejected")),
3910 (STATUS_REJECTED, _("Rejected")),
3906 (STATUS_UNDER_REVIEW, _("Under Review")),
3911 (STATUS_UNDER_REVIEW, _("Under Review")),
3907 ]
3912 ]
3908
3913
3909 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3914 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3910 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3915 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3911 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3916 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3912 revision = Column('revision', String(40), nullable=False)
3917 revision = Column('revision', String(40), nullable=False)
3913 status = Column('status', String(128), nullable=False, default=DEFAULT)
3918 status = Column('status', String(128), nullable=False, default=DEFAULT)
3914 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3919 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3915 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3920 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3916 version = Column('version', Integer(), nullable=False, default=0)
3921 version = Column('version', Integer(), nullable=False, default=0)
3917 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3922 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3918
3923
3919 author = relationship('User', lazy='joined')
3924 author = relationship('User', lazy='joined')
3920 repo = relationship('Repository')
3925 repo = relationship('Repository')
3921 comment = relationship('ChangesetComment', lazy='joined')
3926 comment = relationship('ChangesetComment', lazy='joined')
3922 pull_request = relationship('PullRequest', lazy='joined')
3927 pull_request = relationship('PullRequest', lazy='joined')
3923
3928
3924 def __unicode__(self):
3929 def __unicode__(self):
3925 return u"<%s('%s[v%s]:%s')>" % (
3930 return u"<%s('%s[v%s]:%s')>" % (
3926 self.__class__.__name__,
3931 self.__class__.__name__,
3927 self.status, self.version, self.author
3932 self.status, self.version, self.author
3928 )
3933 )
3929
3934
3930 @classmethod
3935 @classmethod
3931 def get_status_lbl(cls, value):
3936 def get_status_lbl(cls, value):
3932 return dict(cls.STATUSES).get(value)
3937 return dict(cls.STATUSES).get(value)
3933
3938
3934 @property
3939 @property
3935 def status_lbl(self):
3940 def status_lbl(self):
3936 return ChangesetStatus.get_status_lbl(self.status)
3941 return ChangesetStatus.get_status_lbl(self.status)
3937
3942
3938 def get_api_data(self):
3943 def get_api_data(self):
3939 status = self
3944 status = self
3940 data = {
3945 data = {
3941 'status_id': status.changeset_status_id,
3946 'status_id': status.changeset_status_id,
3942 'status': status.status,
3947 'status': status.status,
3943 }
3948 }
3944 return data
3949 return data
3945
3950
3946 def __json__(self):
3951 def __json__(self):
3947 data = dict()
3952 data = dict()
3948 data.update(self.get_api_data())
3953 data.update(self.get_api_data())
3949 return data
3954 return data
3950
3955
3951
3956
3952 class _SetState(object):
3957 class _SetState(object):
3953 """
3958 """
3954 Context processor allowing changing state for sensitive operation such as
3959 Context processor allowing changing state for sensitive operation such as
3955 pull request update or merge
3960 pull request update or merge
3956 """
3961 """
3957
3962
3958 def __init__(self, pull_request, pr_state, back_state=None):
3963 def __init__(self, pull_request, pr_state, back_state=None):
3959 self._pr = pull_request
3964 self._pr = pull_request
3960 self._org_state = back_state or pull_request.pull_request_state
3965 self._org_state = back_state or pull_request.pull_request_state
3961 self._pr_state = pr_state
3966 self._pr_state = pr_state
3962 self._current_state = None
3967 self._current_state = None
3963
3968
3964 def __enter__(self):
3969 def __enter__(self):
3965 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3970 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3966 self._pr, self._pr_state)
3971 self._pr, self._pr_state)
3967 self.set_pr_state(self._pr_state)
3972 self.set_pr_state(self._pr_state)
3968 return self
3973 return self
3969
3974
3970 def __exit__(self, exc_type, exc_val, exc_tb):
3975 def __exit__(self, exc_type, exc_val, exc_tb):
3971 if exc_val is not None:
3976 if exc_val is not None:
3972 log.error(traceback.format_exc(exc_tb))
3977 log.error(traceback.format_exc(exc_tb))
3973 return None
3978 return None
3974
3979
3975 self.set_pr_state(self._org_state)
3980 self.set_pr_state(self._org_state)
3976 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3981 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3977 self._pr, self._org_state)
3982 self._pr, self._org_state)
3978
3983
3979 @property
3984 @property
3980 def state(self):
3985 def state(self):
3981 return self._current_state
3986 return self._current_state
3982
3987
3983 def set_pr_state(self, pr_state):
3988 def set_pr_state(self, pr_state):
3984 try:
3989 try:
3985 self._pr.pull_request_state = pr_state
3990 self._pr.pull_request_state = pr_state
3986 Session().add(self._pr)
3991 Session().add(self._pr)
3987 Session().commit()
3992 Session().commit()
3988 self._current_state = pr_state
3993 self._current_state = pr_state
3989 except Exception:
3994 except Exception:
3990 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3995 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3991 raise
3996 raise
3992
3997
3993
3998
3994 class _PullRequestBase(BaseModel):
3999 class _PullRequestBase(BaseModel):
3995 """
4000 """
3996 Common attributes of pull request and version entries.
4001 Common attributes of pull request and version entries.
3997 """
4002 """
3998
4003
3999 # .status values
4004 # .status values
4000 STATUS_NEW = u'new'
4005 STATUS_NEW = u'new'
4001 STATUS_OPEN = u'open'
4006 STATUS_OPEN = u'open'
4002 STATUS_CLOSED = u'closed'
4007 STATUS_CLOSED = u'closed'
4003
4008
4004 # available states
4009 # available states
4005 STATE_CREATING = u'creating'
4010 STATE_CREATING = u'creating'
4006 STATE_UPDATING = u'updating'
4011 STATE_UPDATING = u'updating'
4007 STATE_MERGING = u'merging'
4012 STATE_MERGING = u'merging'
4008 STATE_CREATED = u'created'
4013 STATE_CREATED = u'created'
4009
4014
4010 title = Column('title', Unicode(255), nullable=True)
4015 title = Column('title', Unicode(255), nullable=True)
4011 description = Column(
4016 description = Column(
4012 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4017 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4013 nullable=True)
4018 nullable=True)
4014 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4019 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4015
4020
4016 # new/open/closed status of pull request (not approve/reject/etc)
4021 # new/open/closed status of pull request (not approve/reject/etc)
4017 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4022 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4018 created_on = Column(
4023 created_on = Column(
4019 'created_on', DateTime(timezone=False), nullable=False,
4024 'created_on', DateTime(timezone=False), nullable=False,
4020 default=datetime.datetime.now)
4025 default=datetime.datetime.now)
4021 updated_on = Column(
4026 updated_on = Column(
4022 'updated_on', DateTime(timezone=False), nullable=False,
4027 'updated_on', DateTime(timezone=False), nullable=False,
4023 default=datetime.datetime.now)
4028 default=datetime.datetime.now)
4024
4029
4025 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4030 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4026
4031
4027 @declared_attr
4032 @declared_attr
4028 def user_id(cls):
4033 def user_id(cls):
4029 return Column(
4034 return Column(
4030 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4035 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4031 unique=None)
4036 unique=None)
4032
4037
4033 # 500 revisions max
4038 # 500 revisions max
4034 _revisions = Column(
4039 _revisions = Column(
4035 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4040 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4036
4041
4037 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4042 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4038
4043
4039 @declared_attr
4044 @declared_attr
4040 def source_repo_id(cls):
4045 def source_repo_id(cls):
4041 # TODO: dan: rename column to source_repo_id
4046 # TODO: dan: rename column to source_repo_id
4042 return Column(
4047 return Column(
4043 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4048 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4044 nullable=False)
4049 nullable=False)
4045
4050
4046 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4051 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4047
4052
4048 @hybrid_property
4053 @hybrid_property
4049 def source_ref(self):
4054 def source_ref(self):
4050 return self._source_ref
4055 return self._source_ref
4051
4056
4052 @source_ref.setter
4057 @source_ref.setter
4053 def source_ref(self, val):
4058 def source_ref(self, val):
4054 parts = (val or '').split(':')
4059 parts = (val or '').split(':')
4055 if len(parts) != 3:
4060 if len(parts) != 3:
4056 raise ValueError(
4061 raise ValueError(
4057 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4062 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4058 self._source_ref = safe_unicode(val)
4063 self._source_ref = safe_unicode(val)
4059
4064
4060 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4065 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4061
4066
4062 @hybrid_property
4067 @hybrid_property
4063 def target_ref(self):
4068 def target_ref(self):
4064 return self._target_ref
4069 return self._target_ref
4065
4070
4066 @target_ref.setter
4071 @target_ref.setter
4067 def target_ref(self, val):
4072 def target_ref(self, val):
4068 parts = (val or '').split(':')
4073 parts = (val or '').split(':')
4069 if len(parts) != 3:
4074 if len(parts) != 3:
4070 raise ValueError(
4075 raise ValueError(
4071 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4076 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4072 self._target_ref = safe_unicode(val)
4077 self._target_ref = safe_unicode(val)
4073
4078
4074 @declared_attr
4079 @declared_attr
4075 def target_repo_id(cls):
4080 def target_repo_id(cls):
4076 # TODO: dan: rename column to target_repo_id
4081 # TODO: dan: rename column to target_repo_id
4077 return Column(
4082 return Column(
4078 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4083 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4079 nullable=False)
4084 nullable=False)
4080
4085
4081 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4086 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4082
4087
4083 # TODO: dan: rename column to last_merge_source_rev
4088 # TODO: dan: rename column to last_merge_source_rev
4084 _last_merge_source_rev = Column(
4089 _last_merge_source_rev = Column(
4085 'last_merge_org_rev', String(40), nullable=True)
4090 'last_merge_org_rev', String(40), nullable=True)
4086 # TODO: dan: rename column to last_merge_target_rev
4091 # TODO: dan: rename column to last_merge_target_rev
4087 _last_merge_target_rev = Column(
4092 _last_merge_target_rev = Column(
4088 'last_merge_other_rev', String(40), nullable=True)
4093 'last_merge_other_rev', String(40), nullable=True)
4089 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4094 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4090 last_merge_metadata = Column(
4095 last_merge_metadata = Column(
4091 'last_merge_metadata', MutationObj.as_mutable(
4096 'last_merge_metadata', MutationObj.as_mutable(
4092 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4097 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4093
4098
4094 merge_rev = Column('merge_rev', String(40), nullable=True)
4099 merge_rev = Column('merge_rev', String(40), nullable=True)
4095
4100
4096 reviewer_data = Column(
4101 reviewer_data = Column(
4097 'reviewer_data_json', MutationObj.as_mutable(
4102 'reviewer_data_json', MutationObj.as_mutable(
4098 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4103 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4099
4104
4100 @property
4105 @property
4101 def reviewer_data_json(self):
4106 def reviewer_data_json(self):
4102 return json.dumps(self.reviewer_data)
4107 return json.dumps(self.reviewer_data)
4103
4108
4104 @property
4109 @property
4105 def work_in_progress(self):
4110 def work_in_progress(self):
4106 """checks if pull request is work in progress by checking the title"""
4111 """checks if pull request is work in progress by checking the title"""
4107 title = self.title.upper()
4112 title = self.title.upper()
4108 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4113 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4109 return True
4114 return True
4110 return False
4115 return False
4111
4116
4112 @hybrid_property
4117 @hybrid_property
4113 def description_safe(self):
4118 def description_safe(self):
4114 from rhodecode.lib import helpers as h
4119 from rhodecode.lib import helpers as h
4115 return h.escape(self.description)
4120 return h.escape(self.description)
4116
4121
4117 @hybrid_property
4122 @hybrid_property
4118 def revisions(self):
4123 def revisions(self):
4119 return self._revisions.split(':') if self._revisions else []
4124 return self._revisions.split(':') if self._revisions else []
4120
4125
4121 @revisions.setter
4126 @revisions.setter
4122 def revisions(self, val):
4127 def revisions(self, val):
4123 self._revisions = u':'.join(val)
4128 self._revisions = u':'.join(val)
4124
4129
4125 @hybrid_property
4130 @hybrid_property
4126 def last_merge_status(self):
4131 def last_merge_status(self):
4127 return safe_int(self._last_merge_status)
4132 return safe_int(self._last_merge_status)
4128
4133
4129 @last_merge_status.setter
4134 @last_merge_status.setter
4130 def last_merge_status(self, val):
4135 def last_merge_status(self, val):
4131 self._last_merge_status = val
4136 self._last_merge_status = val
4132
4137
4133 @declared_attr
4138 @declared_attr
4134 def author(cls):
4139 def author(cls):
4135 return relationship('User', lazy='joined')
4140 return relationship('User', lazy='joined')
4136
4141
4137 @declared_attr
4142 @declared_attr
4138 def source_repo(cls):
4143 def source_repo(cls):
4139 return relationship(
4144 return relationship(
4140 'Repository',
4145 'Repository',
4141 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4146 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4142
4147
4143 @property
4148 @property
4144 def source_ref_parts(self):
4149 def source_ref_parts(self):
4145 return self.unicode_to_reference(self.source_ref)
4150 return self.unicode_to_reference(self.source_ref)
4146
4151
4147 @declared_attr
4152 @declared_attr
4148 def target_repo(cls):
4153 def target_repo(cls):
4149 return relationship(
4154 return relationship(
4150 'Repository',
4155 'Repository',
4151 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4156 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4152
4157
4153 @property
4158 @property
4154 def target_ref_parts(self):
4159 def target_ref_parts(self):
4155 return self.unicode_to_reference(self.target_ref)
4160 return self.unicode_to_reference(self.target_ref)
4156
4161
4157 @property
4162 @property
4158 def shadow_merge_ref(self):
4163 def shadow_merge_ref(self):
4159 return self.unicode_to_reference(self._shadow_merge_ref)
4164 return self.unicode_to_reference(self._shadow_merge_ref)
4160
4165
4161 @shadow_merge_ref.setter
4166 @shadow_merge_ref.setter
4162 def shadow_merge_ref(self, ref):
4167 def shadow_merge_ref(self, ref):
4163 self._shadow_merge_ref = self.reference_to_unicode(ref)
4168 self._shadow_merge_ref = self.reference_to_unicode(ref)
4164
4169
4165 @staticmethod
4170 @staticmethod
4166 def unicode_to_reference(raw):
4171 def unicode_to_reference(raw):
4167 """
4172 """
4168 Convert a unicode (or string) to a reference object.
4173 Convert a unicode (or string) to a reference object.
4169 If unicode evaluates to False it returns None.
4174 If unicode evaluates to False it returns None.
4170 """
4175 """
4171 if raw:
4176 if raw:
4172 refs = raw.split(':')
4177 refs = raw.split(':')
4173 return Reference(*refs)
4178 return Reference(*refs)
4174 else:
4179 else:
4175 return None
4180 return None
4176
4181
4177 @staticmethod
4182 @staticmethod
4178 def reference_to_unicode(ref):
4183 def reference_to_unicode(ref):
4179 """
4184 """
4180 Convert a reference object to unicode.
4185 Convert a reference object to unicode.
4181 If reference is None it returns None.
4186 If reference is None it returns None.
4182 """
4187 """
4183 if ref:
4188 if ref:
4184 return u':'.join(ref)
4189 return u':'.join(ref)
4185 else:
4190 else:
4186 return None
4191 return None
4187
4192
4188 def get_api_data(self, with_merge_state=True):
4193 def get_api_data(self, with_merge_state=True):
4189 from rhodecode.model.pull_request import PullRequestModel
4194 from rhodecode.model.pull_request import PullRequestModel
4190
4195
4191 pull_request = self
4196 pull_request = self
4192 if with_merge_state:
4197 if with_merge_state:
4193 merge_response, merge_status, msg = \
4198 merge_response, merge_status, msg = \
4194 PullRequestModel().merge_status(pull_request)
4199 PullRequestModel().merge_status(pull_request)
4195 merge_state = {
4200 merge_state = {
4196 'status': merge_status,
4201 'status': merge_status,
4197 'message': safe_unicode(msg),
4202 'message': safe_unicode(msg),
4198 }
4203 }
4199 else:
4204 else:
4200 merge_state = {'status': 'not_available',
4205 merge_state = {'status': 'not_available',
4201 'message': 'not_available'}
4206 'message': 'not_available'}
4202
4207
4203 merge_data = {
4208 merge_data = {
4204 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4209 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4205 'reference': (
4210 'reference': (
4206 pull_request.shadow_merge_ref._asdict()
4211 pull_request.shadow_merge_ref._asdict()
4207 if pull_request.shadow_merge_ref else None),
4212 if pull_request.shadow_merge_ref else None),
4208 }
4213 }
4209
4214
4210 data = {
4215 data = {
4211 'pull_request_id': pull_request.pull_request_id,
4216 'pull_request_id': pull_request.pull_request_id,
4212 'url': PullRequestModel().get_url(pull_request),
4217 'url': PullRequestModel().get_url(pull_request),
4213 'title': pull_request.title,
4218 'title': pull_request.title,
4214 'description': pull_request.description,
4219 'description': pull_request.description,
4215 'status': pull_request.status,
4220 'status': pull_request.status,
4216 'state': pull_request.pull_request_state,
4221 'state': pull_request.pull_request_state,
4217 'created_on': pull_request.created_on,
4222 'created_on': pull_request.created_on,
4218 'updated_on': pull_request.updated_on,
4223 'updated_on': pull_request.updated_on,
4219 'commit_ids': pull_request.revisions,
4224 'commit_ids': pull_request.revisions,
4220 'review_status': pull_request.calculated_review_status(),
4225 'review_status': pull_request.calculated_review_status(),
4221 'mergeable': merge_state,
4226 'mergeable': merge_state,
4222 'source': {
4227 'source': {
4223 'clone_url': pull_request.source_repo.clone_url(),
4228 'clone_url': pull_request.source_repo.clone_url(),
4224 'repository': pull_request.source_repo.repo_name,
4229 'repository': pull_request.source_repo.repo_name,
4225 'reference': {
4230 'reference': {
4226 'name': pull_request.source_ref_parts.name,
4231 'name': pull_request.source_ref_parts.name,
4227 'type': pull_request.source_ref_parts.type,
4232 'type': pull_request.source_ref_parts.type,
4228 'commit_id': pull_request.source_ref_parts.commit_id,
4233 'commit_id': pull_request.source_ref_parts.commit_id,
4229 },
4234 },
4230 },
4235 },
4231 'target': {
4236 'target': {
4232 'clone_url': pull_request.target_repo.clone_url(),
4237 'clone_url': pull_request.target_repo.clone_url(),
4233 'repository': pull_request.target_repo.repo_name,
4238 'repository': pull_request.target_repo.repo_name,
4234 'reference': {
4239 'reference': {
4235 'name': pull_request.target_ref_parts.name,
4240 'name': pull_request.target_ref_parts.name,
4236 'type': pull_request.target_ref_parts.type,
4241 'type': pull_request.target_ref_parts.type,
4237 'commit_id': pull_request.target_ref_parts.commit_id,
4242 'commit_id': pull_request.target_ref_parts.commit_id,
4238 },
4243 },
4239 },
4244 },
4240 'merge': merge_data,
4245 'merge': merge_data,
4241 'author': pull_request.author.get_api_data(include_secrets=False,
4246 'author': pull_request.author.get_api_data(include_secrets=False,
4242 details='basic'),
4247 details='basic'),
4243 'reviewers': [
4248 'reviewers': [
4244 {
4249 {
4245 'user': reviewer.get_api_data(include_secrets=False,
4250 'user': reviewer.get_api_data(include_secrets=False,
4246 details='basic'),
4251 details='basic'),
4247 'reasons': reasons,
4252 'reasons': reasons,
4248 'review_status': st[0][1].status if st else 'not_reviewed',
4253 'review_status': st[0][1].status if st else 'not_reviewed',
4249 }
4254 }
4250 for obj, reviewer, reasons, mandatory, st in
4255 for obj, reviewer, reasons, mandatory, st in
4251 pull_request.reviewers_statuses()
4256 pull_request.reviewers_statuses()
4252 ]
4257 ]
4253 }
4258 }
4254
4259
4255 return data
4260 return data
4256
4261
4257 def set_state(self, pull_request_state, final_state=None):
4262 def set_state(self, pull_request_state, final_state=None):
4258 """
4263 """
4259 # goes from initial state to updating to initial state.
4264 # goes from initial state to updating to initial state.
4260 # initial state can be changed by specifying back_state=
4265 # initial state can be changed by specifying back_state=
4261 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4266 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4262 pull_request.merge()
4267 pull_request.merge()
4263
4268
4264 :param pull_request_state:
4269 :param pull_request_state:
4265 :param final_state:
4270 :param final_state:
4266
4271
4267 """
4272 """
4268
4273
4269 return _SetState(self, pull_request_state, back_state=final_state)
4274 return _SetState(self, pull_request_state, back_state=final_state)
4270
4275
4271
4276
4272 class PullRequest(Base, _PullRequestBase):
4277 class PullRequest(Base, _PullRequestBase):
4273 __tablename__ = 'pull_requests'
4278 __tablename__ = 'pull_requests'
4274 __table_args__ = (
4279 __table_args__ = (
4275 base_table_args,
4280 base_table_args,
4276 )
4281 )
4277
4282
4278 pull_request_id = Column(
4283 pull_request_id = Column(
4279 'pull_request_id', Integer(), nullable=False, primary_key=True)
4284 'pull_request_id', Integer(), nullable=False, primary_key=True)
4280
4285
4281 def __repr__(self):
4286 def __repr__(self):
4282 if self.pull_request_id:
4287 if self.pull_request_id:
4283 return '<DB:PullRequest #%s>' % self.pull_request_id
4288 return '<DB:PullRequest #%s>' % self.pull_request_id
4284 else:
4289 else:
4285 return '<DB:PullRequest at %#x>' % id(self)
4290 return '<DB:PullRequest at %#x>' % id(self)
4286
4291
4287 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4292 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4288 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4293 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4289 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4294 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4290 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4295 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4291 lazy='dynamic')
4296 lazy='dynamic')
4292
4297
4293 @classmethod
4298 @classmethod
4294 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4299 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4295 internal_methods=None):
4300 internal_methods=None):
4296
4301
4297 class PullRequestDisplay(object):
4302 class PullRequestDisplay(object):
4298 """
4303 """
4299 Special object wrapper for showing PullRequest data via Versions
4304 Special object wrapper for showing PullRequest data via Versions
4300 It mimics PR object as close as possible. This is read only object
4305 It mimics PR object as close as possible. This is read only object
4301 just for display
4306 just for display
4302 """
4307 """
4303
4308
4304 def __init__(self, attrs, internal=None):
4309 def __init__(self, attrs, internal=None):
4305 self.attrs = attrs
4310 self.attrs = attrs
4306 # internal have priority over the given ones via attrs
4311 # internal have priority over the given ones via attrs
4307 self.internal = internal or ['versions']
4312 self.internal = internal or ['versions']
4308
4313
4309 def __getattr__(self, item):
4314 def __getattr__(self, item):
4310 if item in self.internal:
4315 if item in self.internal:
4311 return getattr(self, item)
4316 return getattr(self, item)
4312 try:
4317 try:
4313 return self.attrs[item]
4318 return self.attrs[item]
4314 except KeyError:
4319 except KeyError:
4315 raise AttributeError(
4320 raise AttributeError(
4316 '%s object has no attribute %s' % (self, item))
4321 '%s object has no attribute %s' % (self, item))
4317
4322
4318 def __repr__(self):
4323 def __repr__(self):
4319 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4324 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4320
4325
4321 def versions(self):
4326 def versions(self):
4322 return pull_request_obj.versions.order_by(
4327 return pull_request_obj.versions.order_by(
4323 PullRequestVersion.pull_request_version_id).all()
4328 PullRequestVersion.pull_request_version_id).all()
4324
4329
4325 def is_closed(self):
4330 def is_closed(self):
4326 return pull_request_obj.is_closed()
4331 return pull_request_obj.is_closed()
4327
4332
4328 def is_state_changing(self):
4333 def is_state_changing(self):
4329 return pull_request_obj.is_state_changing()
4334 return pull_request_obj.is_state_changing()
4330
4335
4331 @property
4336 @property
4332 def pull_request_version_id(self):
4337 def pull_request_version_id(self):
4333 return getattr(pull_request_obj, 'pull_request_version_id', None)
4338 return getattr(pull_request_obj, 'pull_request_version_id', None)
4334
4339
4335 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4340 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4336
4341
4337 attrs.author = StrictAttributeDict(
4342 attrs.author = StrictAttributeDict(
4338 pull_request_obj.author.get_api_data())
4343 pull_request_obj.author.get_api_data())
4339 if pull_request_obj.target_repo:
4344 if pull_request_obj.target_repo:
4340 attrs.target_repo = StrictAttributeDict(
4345 attrs.target_repo = StrictAttributeDict(
4341 pull_request_obj.target_repo.get_api_data())
4346 pull_request_obj.target_repo.get_api_data())
4342 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4347 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4343
4348
4344 if pull_request_obj.source_repo:
4349 if pull_request_obj.source_repo:
4345 attrs.source_repo = StrictAttributeDict(
4350 attrs.source_repo = StrictAttributeDict(
4346 pull_request_obj.source_repo.get_api_data())
4351 pull_request_obj.source_repo.get_api_data())
4347 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4352 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4348
4353
4349 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4354 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4350 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4355 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4351 attrs.revisions = pull_request_obj.revisions
4356 attrs.revisions = pull_request_obj.revisions
4352 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4357 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4353 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4358 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4354 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4359 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4355 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4360 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4356
4361
4357 return PullRequestDisplay(attrs, internal=internal_methods)
4362 return PullRequestDisplay(attrs, internal=internal_methods)
4358
4363
4359 def is_closed(self):
4364 def is_closed(self):
4360 return self.status == self.STATUS_CLOSED
4365 return self.status == self.STATUS_CLOSED
4361
4366
4362 def is_state_changing(self):
4367 def is_state_changing(self):
4363 return self.pull_request_state != PullRequest.STATE_CREATED
4368 return self.pull_request_state != PullRequest.STATE_CREATED
4364
4369
4365 def __json__(self):
4370 def __json__(self):
4366 return {
4371 return {
4367 'revisions': self.revisions,
4372 'revisions': self.revisions,
4368 'versions': self.versions_count
4373 'versions': self.versions_count
4369 }
4374 }
4370
4375
4371 def calculated_review_status(self):
4376 def calculated_review_status(self):
4372 from rhodecode.model.changeset_status import ChangesetStatusModel
4377 from rhodecode.model.changeset_status import ChangesetStatusModel
4373 return ChangesetStatusModel().calculated_review_status(self)
4378 return ChangesetStatusModel().calculated_review_status(self)
4374
4379
4375 def reviewers_statuses(self):
4380 def reviewers_statuses(self):
4376 from rhodecode.model.changeset_status import ChangesetStatusModel
4381 from rhodecode.model.changeset_status import ChangesetStatusModel
4377 return ChangesetStatusModel().reviewers_statuses(self)
4382 return ChangesetStatusModel().reviewers_statuses(self)
4378
4383
4379 @property
4384 @property
4380 def workspace_id(self):
4385 def workspace_id(self):
4381 from rhodecode.model.pull_request import PullRequestModel
4386 from rhodecode.model.pull_request import PullRequestModel
4382 return PullRequestModel()._workspace_id(self)
4387 return PullRequestModel()._workspace_id(self)
4383
4388
4384 def get_shadow_repo(self):
4389 def get_shadow_repo(self):
4385 workspace_id = self.workspace_id
4390 workspace_id = self.workspace_id
4386 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4391 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4387 if os.path.isdir(shadow_repository_path):
4392 if os.path.isdir(shadow_repository_path):
4388 vcs_obj = self.target_repo.scm_instance()
4393 vcs_obj = self.target_repo.scm_instance()
4389 return vcs_obj.get_shadow_instance(shadow_repository_path)
4394 return vcs_obj.get_shadow_instance(shadow_repository_path)
4390
4395
4391 @property
4396 @property
4392 def versions_count(self):
4397 def versions_count(self):
4393 """
4398 """
4394 return number of versions this PR have, e.g a PR that once been
4399 return number of versions this PR have, e.g a PR that once been
4395 updated will have 2 versions
4400 updated will have 2 versions
4396 """
4401 """
4397 return self.versions.count() + 1
4402 return self.versions.count() + 1
4398
4403
4399
4404
4400 class PullRequestVersion(Base, _PullRequestBase):
4405 class PullRequestVersion(Base, _PullRequestBase):
4401 __tablename__ = 'pull_request_versions'
4406 __tablename__ = 'pull_request_versions'
4402 __table_args__ = (
4407 __table_args__ = (
4403 base_table_args,
4408 base_table_args,
4404 )
4409 )
4405
4410
4406 pull_request_version_id = Column(
4411 pull_request_version_id = Column(
4407 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4412 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4408 pull_request_id = Column(
4413 pull_request_id = Column(
4409 'pull_request_id', Integer(),
4414 'pull_request_id', Integer(),
4410 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4415 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4411 pull_request = relationship('PullRequest')
4416 pull_request = relationship('PullRequest')
4412
4417
4413 def __repr__(self):
4418 def __repr__(self):
4414 if self.pull_request_version_id:
4419 if self.pull_request_version_id:
4415 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4420 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4416 else:
4421 else:
4417 return '<DB:PullRequestVersion at %#x>' % id(self)
4422 return '<DB:PullRequestVersion at %#x>' % id(self)
4418
4423
4419 @property
4424 @property
4420 def reviewers(self):
4425 def reviewers(self):
4421 return self.pull_request.reviewers
4426 return self.pull_request.reviewers
4422
4427
4423 @property
4428 @property
4424 def versions(self):
4429 def versions(self):
4425 return self.pull_request.versions
4430 return self.pull_request.versions
4426
4431
4427 def is_closed(self):
4432 def is_closed(self):
4428 # calculate from original
4433 # calculate from original
4429 return self.pull_request.status == self.STATUS_CLOSED
4434 return self.pull_request.status == self.STATUS_CLOSED
4430
4435
4431 def is_state_changing(self):
4436 def is_state_changing(self):
4432 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4437 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4433
4438
4434 def calculated_review_status(self):
4439 def calculated_review_status(self):
4435 return self.pull_request.calculated_review_status()
4440 return self.pull_request.calculated_review_status()
4436
4441
4437 def reviewers_statuses(self):
4442 def reviewers_statuses(self):
4438 return self.pull_request.reviewers_statuses()
4443 return self.pull_request.reviewers_statuses()
4439
4444
4440
4445
4441 class PullRequestReviewers(Base, BaseModel):
4446 class PullRequestReviewers(Base, BaseModel):
4442 __tablename__ = 'pull_request_reviewers'
4447 __tablename__ = 'pull_request_reviewers'
4443 __table_args__ = (
4448 __table_args__ = (
4444 base_table_args,
4449 base_table_args,
4445 )
4450 )
4446
4451
4447 @hybrid_property
4452 @hybrid_property
4448 def reasons(self):
4453 def reasons(self):
4449 if not self._reasons:
4454 if not self._reasons:
4450 return []
4455 return []
4451 return self._reasons
4456 return self._reasons
4452
4457
4453 @reasons.setter
4458 @reasons.setter
4454 def reasons(self, val):
4459 def reasons(self, val):
4455 val = val or []
4460 val = val or []
4456 if any(not isinstance(x, compat.string_types) for x in val):
4461 if any(not isinstance(x, compat.string_types) for x in val):
4457 raise Exception('invalid reasons type, must be list of strings')
4462 raise Exception('invalid reasons type, must be list of strings')
4458 self._reasons = val
4463 self._reasons = val
4459
4464
4460 pull_requests_reviewers_id = Column(
4465 pull_requests_reviewers_id = Column(
4461 'pull_requests_reviewers_id', Integer(), nullable=False,
4466 'pull_requests_reviewers_id', Integer(), nullable=False,
4462 primary_key=True)
4467 primary_key=True)
4463 pull_request_id = Column(
4468 pull_request_id = Column(
4464 "pull_request_id", Integer(),
4469 "pull_request_id", Integer(),
4465 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4470 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4466 user_id = Column(
4471 user_id = Column(
4467 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4472 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4468 _reasons = Column(
4473 _reasons = Column(
4469 'reason', MutationList.as_mutable(
4474 'reason', MutationList.as_mutable(
4470 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4475 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4471
4476
4472 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4477 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4473 user = relationship('User')
4478 user = relationship('User')
4474 pull_request = relationship('PullRequest')
4479 pull_request = relationship('PullRequest')
4475
4480
4476 rule_data = Column(
4481 rule_data = Column(
4477 'rule_data_json',
4482 'rule_data_json',
4478 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4483 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4479
4484
4480 def rule_user_group_data(self):
4485 def rule_user_group_data(self):
4481 """
4486 """
4482 Returns the voting user group rule data for this reviewer
4487 Returns the voting user group rule data for this reviewer
4483 """
4488 """
4484
4489
4485 if self.rule_data and 'vote_rule' in self.rule_data:
4490 if self.rule_data and 'vote_rule' in self.rule_data:
4486 user_group_data = {}
4491 user_group_data = {}
4487 if 'rule_user_group_entry_id' in self.rule_data:
4492 if 'rule_user_group_entry_id' in self.rule_data:
4488 # means a group with voting rules !
4493 # means a group with voting rules !
4489 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4494 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4490 user_group_data['name'] = self.rule_data['rule_name']
4495 user_group_data['name'] = self.rule_data['rule_name']
4491 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4496 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4492
4497
4493 return user_group_data
4498 return user_group_data
4494
4499
4495 def __unicode__(self):
4500 def __unicode__(self):
4496 return u"<%s('id:%s')>" % (self.__class__.__name__,
4501 return u"<%s('id:%s')>" % (self.__class__.__name__,
4497 self.pull_requests_reviewers_id)
4502 self.pull_requests_reviewers_id)
4498
4503
4499
4504
4500 class Notification(Base, BaseModel):
4505 class Notification(Base, BaseModel):
4501 __tablename__ = 'notifications'
4506 __tablename__ = 'notifications'
4502 __table_args__ = (
4507 __table_args__ = (
4503 Index('notification_type_idx', 'type'),
4508 Index('notification_type_idx', 'type'),
4504 base_table_args,
4509 base_table_args,
4505 )
4510 )
4506
4511
4507 TYPE_CHANGESET_COMMENT = u'cs_comment'
4512 TYPE_CHANGESET_COMMENT = u'cs_comment'
4508 TYPE_MESSAGE = u'message'
4513 TYPE_MESSAGE = u'message'
4509 TYPE_MENTION = u'mention'
4514 TYPE_MENTION = u'mention'
4510 TYPE_REGISTRATION = u'registration'
4515 TYPE_REGISTRATION = u'registration'
4511 TYPE_PULL_REQUEST = u'pull_request'
4516 TYPE_PULL_REQUEST = u'pull_request'
4512 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4517 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4513 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4518 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4514
4519
4515 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4520 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4516 subject = Column('subject', Unicode(512), nullable=True)
4521 subject = Column('subject', Unicode(512), nullable=True)
4517 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4522 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4518 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4523 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4519 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4520 type_ = Column('type', Unicode(255))
4525 type_ = Column('type', Unicode(255))
4521
4526
4522 created_by_user = relationship('User')
4527 created_by_user = relationship('User')
4523 notifications_to_users = relationship('UserNotification', lazy='joined',
4528 notifications_to_users = relationship('UserNotification', lazy='joined',
4524 cascade="all, delete-orphan")
4529 cascade="all, delete-orphan")
4525
4530
4526 @property
4531 @property
4527 def recipients(self):
4532 def recipients(self):
4528 return [x.user for x in UserNotification.query()\
4533 return [x.user for x in UserNotification.query()\
4529 .filter(UserNotification.notification == self)\
4534 .filter(UserNotification.notification == self)\
4530 .order_by(UserNotification.user_id.asc()).all()]
4535 .order_by(UserNotification.user_id.asc()).all()]
4531
4536
4532 @classmethod
4537 @classmethod
4533 def create(cls, created_by, subject, body, recipients, type_=None):
4538 def create(cls, created_by, subject, body, recipients, type_=None):
4534 if type_ is None:
4539 if type_ is None:
4535 type_ = Notification.TYPE_MESSAGE
4540 type_ = Notification.TYPE_MESSAGE
4536
4541
4537 notification = cls()
4542 notification = cls()
4538 notification.created_by_user = created_by
4543 notification.created_by_user = created_by
4539 notification.subject = subject
4544 notification.subject = subject
4540 notification.body = body
4545 notification.body = body
4541 notification.type_ = type_
4546 notification.type_ = type_
4542 notification.created_on = datetime.datetime.now()
4547 notification.created_on = datetime.datetime.now()
4543
4548
4544 # For each recipient link the created notification to his account
4549 # For each recipient link the created notification to his account
4545 for u in recipients:
4550 for u in recipients:
4546 assoc = UserNotification()
4551 assoc = UserNotification()
4547 assoc.user_id = u.user_id
4552 assoc.user_id = u.user_id
4548 assoc.notification = notification
4553 assoc.notification = notification
4549
4554
4550 # if created_by is inside recipients mark his notification
4555 # if created_by is inside recipients mark his notification
4551 # as read
4556 # as read
4552 if u.user_id == created_by.user_id:
4557 if u.user_id == created_by.user_id:
4553 assoc.read = True
4558 assoc.read = True
4554 Session().add(assoc)
4559 Session().add(assoc)
4555
4560
4556 Session().add(notification)
4561 Session().add(notification)
4557
4562
4558 return notification
4563 return notification
4559
4564
4560
4565
4561 class UserNotification(Base, BaseModel):
4566 class UserNotification(Base, BaseModel):
4562 __tablename__ = 'user_to_notification'
4567 __tablename__ = 'user_to_notification'
4563 __table_args__ = (
4568 __table_args__ = (
4564 UniqueConstraint('user_id', 'notification_id'),
4569 UniqueConstraint('user_id', 'notification_id'),
4565 base_table_args
4570 base_table_args
4566 )
4571 )
4567
4572
4568 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4573 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4569 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4574 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4570 read = Column('read', Boolean, default=False)
4575 read = Column('read', Boolean, default=False)
4571 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4576 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4572
4577
4573 user = relationship('User', lazy="joined")
4578 user = relationship('User', lazy="joined")
4574 notification = relationship('Notification', lazy="joined",
4579 notification = relationship('Notification', lazy="joined",
4575 order_by=lambda: Notification.created_on.desc(),)
4580 order_by=lambda: Notification.created_on.desc(),)
4576
4581
4577 def mark_as_read(self):
4582 def mark_as_read(self):
4578 self.read = True
4583 self.read = True
4579 Session().add(self)
4584 Session().add(self)
4580
4585
4581
4586
4582 class UserNotice(Base, BaseModel):
4587 class UserNotice(Base, BaseModel):
4583 __tablename__ = 'user_notices'
4588 __tablename__ = 'user_notices'
4584 __table_args__ = (
4589 __table_args__ = (
4585 base_table_args
4590 base_table_args
4586 )
4591 )
4587
4592
4588 NOTIFICATION_TYPE_MESSAGE = 'message'
4593 NOTIFICATION_TYPE_MESSAGE = 'message'
4589 NOTIFICATION_TYPE_NOTICE = 'notice'
4594 NOTIFICATION_TYPE_NOTICE = 'notice'
4590
4595
4591 NOTIFICATION_LEVEL_INFO = 'info'
4596 NOTIFICATION_LEVEL_INFO = 'info'
4592 NOTIFICATION_LEVEL_WARNING = 'warning'
4597 NOTIFICATION_LEVEL_WARNING = 'warning'
4593 NOTIFICATION_LEVEL_ERROR = 'error'
4598 NOTIFICATION_LEVEL_ERROR = 'error'
4594
4599
4595 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4600 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4596
4601
4597 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4602 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4598 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4603 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4599
4604
4600 notice_read = Column('notice_read', Boolean, default=False)
4605 notice_read = Column('notice_read', Boolean, default=False)
4601
4606
4602 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4607 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4603 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4608 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4604
4609
4605 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4610 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4606 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4611 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4607
4612
4608 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4613 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4609 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4614 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4610
4615
4611 @classmethod
4616 @classmethod
4612 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4617 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4613
4618
4614 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4619 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4615 cls.NOTIFICATION_LEVEL_WARNING,
4620 cls.NOTIFICATION_LEVEL_WARNING,
4616 cls.NOTIFICATION_LEVEL_INFO]:
4621 cls.NOTIFICATION_LEVEL_INFO]:
4617 return
4622 return
4618
4623
4619 from rhodecode.model.user import UserModel
4624 from rhodecode.model.user import UserModel
4620 user = UserModel().get_user(user)
4625 user = UserModel().get_user(user)
4621
4626
4622 new_notice = UserNotice()
4627 new_notice = UserNotice()
4623 if not allow_duplicate:
4628 if not allow_duplicate:
4624 existing_msg = UserNotice().query() \
4629 existing_msg = UserNotice().query() \
4625 .filter(UserNotice.user == user) \
4630 .filter(UserNotice.user == user) \
4626 .filter(UserNotice.notice_body == body) \
4631 .filter(UserNotice.notice_body == body) \
4627 .filter(UserNotice.notice_read == false()) \
4632 .filter(UserNotice.notice_read == false()) \
4628 .scalar()
4633 .scalar()
4629 if existing_msg:
4634 if existing_msg:
4630 log.warning('Ignoring duplicate notice for user %s', user)
4635 log.warning('Ignoring duplicate notice for user %s', user)
4631 return
4636 return
4632
4637
4633 new_notice.user = user
4638 new_notice.user = user
4634 new_notice.notice_subject = subject
4639 new_notice.notice_subject = subject
4635 new_notice.notice_body = body
4640 new_notice.notice_body = body
4636 new_notice.notification_level = notice_level
4641 new_notice.notification_level = notice_level
4637 Session().add(new_notice)
4642 Session().add(new_notice)
4638 Session().commit()
4643 Session().commit()
4639
4644
4640
4645
4641 class Gist(Base, BaseModel):
4646 class Gist(Base, BaseModel):
4642 __tablename__ = 'gists'
4647 __tablename__ = 'gists'
4643 __table_args__ = (
4648 __table_args__ = (
4644 Index('g_gist_access_id_idx', 'gist_access_id'),
4649 Index('g_gist_access_id_idx', 'gist_access_id'),
4645 Index('g_created_on_idx', 'created_on'),
4650 Index('g_created_on_idx', 'created_on'),
4646 base_table_args
4651 base_table_args
4647 )
4652 )
4648
4653
4649 GIST_PUBLIC = u'public'
4654 GIST_PUBLIC = u'public'
4650 GIST_PRIVATE = u'private'
4655 GIST_PRIVATE = u'private'
4651 DEFAULT_FILENAME = u'gistfile1.txt'
4656 DEFAULT_FILENAME = u'gistfile1.txt'
4652
4657
4653 ACL_LEVEL_PUBLIC = u'acl_public'
4658 ACL_LEVEL_PUBLIC = u'acl_public'
4654 ACL_LEVEL_PRIVATE = u'acl_private'
4659 ACL_LEVEL_PRIVATE = u'acl_private'
4655
4660
4656 gist_id = Column('gist_id', Integer(), primary_key=True)
4661 gist_id = Column('gist_id', Integer(), primary_key=True)
4657 gist_access_id = Column('gist_access_id', Unicode(250))
4662 gist_access_id = Column('gist_access_id', Unicode(250))
4658 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4663 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4659 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4664 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4660 gist_expires = Column('gist_expires', Float(53), nullable=False)
4665 gist_expires = Column('gist_expires', Float(53), nullable=False)
4661 gist_type = Column('gist_type', Unicode(128), nullable=False)
4666 gist_type = Column('gist_type', Unicode(128), nullable=False)
4662 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4667 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4663 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4668 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4664 acl_level = Column('acl_level', Unicode(128), nullable=True)
4669 acl_level = Column('acl_level', Unicode(128), nullable=True)
4665
4670
4666 owner = relationship('User')
4671 owner = relationship('User')
4667
4672
4668 def __repr__(self):
4673 def __repr__(self):
4669 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4674 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4670
4675
4671 @hybrid_property
4676 @hybrid_property
4672 def description_safe(self):
4677 def description_safe(self):
4673 from rhodecode.lib import helpers as h
4678 from rhodecode.lib import helpers as h
4674 return h.escape(self.gist_description)
4679 return h.escape(self.gist_description)
4675
4680
4676 @classmethod
4681 @classmethod
4677 def get_or_404(cls, id_):
4682 def get_or_404(cls, id_):
4678 from pyramid.httpexceptions import HTTPNotFound
4683 from pyramid.httpexceptions import HTTPNotFound
4679
4684
4680 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4685 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4681 if not res:
4686 if not res:
4682 raise HTTPNotFound()
4687 raise HTTPNotFound()
4683 return res
4688 return res
4684
4689
4685 @classmethod
4690 @classmethod
4686 def get_by_access_id(cls, gist_access_id):
4691 def get_by_access_id(cls, gist_access_id):
4687 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4692 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4688
4693
4689 def gist_url(self):
4694 def gist_url(self):
4690 from rhodecode.model.gist import GistModel
4695 from rhodecode.model.gist import GistModel
4691 return GistModel().get_url(self)
4696 return GistModel().get_url(self)
4692
4697
4693 @classmethod
4698 @classmethod
4694 def base_path(cls):
4699 def base_path(cls):
4695 """
4700 """
4696 Returns base path when all gists are stored
4701 Returns base path when all gists are stored
4697
4702
4698 :param cls:
4703 :param cls:
4699 """
4704 """
4700 from rhodecode.model.gist import GIST_STORE_LOC
4705 from rhodecode.model.gist import GIST_STORE_LOC
4701 q = Session().query(RhodeCodeUi)\
4706 q = Session().query(RhodeCodeUi)\
4702 .filter(RhodeCodeUi.ui_key == URL_SEP)
4707 .filter(RhodeCodeUi.ui_key == URL_SEP)
4703 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4708 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4704 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4709 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4705
4710
4706 def get_api_data(self):
4711 def get_api_data(self):
4707 """
4712 """
4708 Common function for generating gist related data for API
4713 Common function for generating gist related data for API
4709 """
4714 """
4710 gist = self
4715 gist = self
4711 data = {
4716 data = {
4712 'gist_id': gist.gist_id,
4717 'gist_id': gist.gist_id,
4713 'type': gist.gist_type,
4718 'type': gist.gist_type,
4714 'access_id': gist.gist_access_id,
4719 'access_id': gist.gist_access_id,
4715 'description': gist.gist_description,
4720 'description': gist.gist_description,
4716 'url': gist.gist_url(),
4721 'url': gist.gist_url(),
4717 'expires': gist.gist_expires,
4722 'expires': gist.gist_expires,
4718 'created_on': gist.created_on,
4723 'created_on': gist.created_on,
4719 'modified_at': gist.modified_at,
4724 'modified_at': gist.modified_at,
4720 'content': None,
4725 'content': None,
4721 'acl_level': gist.acl_level,
4726 'acl_level': gist.acl_level,
4722 }
4727 }
4723 return data
4728 return data
4724
4729
4725 def __json__(self):
4730 def __json__(self):
4726 data = dict(
4731 data = dict(
4727 )
4732 )
4728 data.update(self.get_api_data())
4733 data.update(self.get_api_data())
4729 return data
4734 return data
4730 # SCM functions
4735 # SCM functions
4731
4736
4732 def scm_instance(self, **kwargs):
4737 def scm_instance(self, **kwargs):
4733 """
4738 """
4734 Get an instance of VCS Repository
4739 Get an instance of VCS Repository
4735
4740
4736 :param kwargs:
4741 :param kwargs:
4737 """
4742 """
4738 from rhodecode.model.gist import GistModel
4743 from rhodecode.model.gist import GistModel
4739 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4744 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4740 return get_vcs_instance(
4745 return get_vcs_instance(
4741 repo_path=safe_str(full_repo_path), create=False,
4746 repo_path=safe_str(full_repo_path), create=False,
4742 _vcs_alias=GistModel.vcs_backend)
4747 _vcs_alias=GistModel.vcs_backend)
4743
4748
4744
4749
4745 class ExternalIdentity(Base, BaseModel):
4750 class ExternalIdentity(Base, BaseModel):
4746 __tablename__ = 'external_identities'
4751 __tablename__ = 'external_identities'
4747 __table_args__ = (
4752 __table_args__ = (
4748 Index('local_user_id_idx', 'local_user_id'),
4753 Index('local_user_id_idx', 'local_user_id'),
4749 Index('external_id_idx', 'external_id'),
4754 Index('external_id_idx', 'external_id'),
4750 base_table_args
4755 base_table_args
4751 )
4756 )
4752
4757
4753 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4758 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4754 external_username = Column('external_username', Unicode(1024), default=u'')
4759 external_username = Column('external_username', Unicode(1024), default=u'')
4755 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4760 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4756 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4761 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4757 access_token = Column('access_token', String(1024), default=u'')
4762 access_token = Column('access_token', String(1024), default=u'')
4758 alt_token = Column('alt_token', String(1024), default=u'')
4763 alt_token = Column('alt_token', String(1024), default=u'')
4759 token_secret = Column('token_secret', String(1024), default=u'')
4764 token_secret = Column('token_secret', String(1024), default=u'')
4760
4765
4761 @classmethod
4766 @classmethod
4762 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4767 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4763 """
4768 """
4764 Returns ExternalIdentity instance based on search params
4769 Returns ExternalIdentity instance based on search params
4765
4770
4766 :param external_id:
4771 :param external_id:
4767 :param provider_name:
4772 :param provider_name:
4768 :return: ExternalIdentity
4773 :return: ExternalIdentity
4769 """
4774 """
4770 query = cls.query()
4775 query = cls.query()
4771 query = query.filter(cls.external_id == external_id)
4776 query = query.filter(cls.external_id == external_id)
4772 query = query.filter(cls.provider_name == provider_name)
4777 query = query.filter(cls.provider_name == provider_name)
4773 if local_user_id:
4778 if local_user_id:
4774 query = query.filter(cls.local_user_id == local_user_id)
4779 query = query.filter(cls.local_user_id == local_user_id)
4775 return query.first()
4780 return query.first()
4776
4781
4777 @classmethod
4782 @classmethod
4778 def user_by_external_id_and_provider(cls, external_id, provider_name):
4783 def user_by_external_id_and_provider(cls, external_id, provider_name):
4779 """
4784 """
4780 Returns User instance based on search params
4785 Returns User instance based on search params
4781
4786
4782 :param external_id:
4787 :param external_id:
4783 :param provider_name:
4788 :param provider_name:
4784 :return: User
4789 :return: User
4785 """
4790 """
4786 query = User.query()
4791 query = User.query()
4787 query = query.filter(cls.external_id == external_id)
4792 query = query.filter(cls.external_id == external_id)
4788 query = query.filter(cls.provider_name == provider_name)
4793 query = query.filter(cls.provider_name == provider_name)
4789 query = query.filter(User.user_id == cls.local_user_id)
4794 query = query.filter(User.user_id == cls.local_user_id)
4790 return query.first()
4795 return query.first()
4791
4796
4792 @classmethod
4797 @classmethod
4793 def by_local_user_id(cls, local_user_id):
4798 def by_local_user_id(cls, local_user_id):
4794 """
4799 """
4795 Returns all tokens for user
4800 Returns all tokens for user
4796
4801
4797 :param local_user_id:
4802 :param local_user_id:
4798 :return: ExternalIdentity
4803 :return: ExternalIdentity
4799 """
4804 """
4800 query = cls.query()
4805 query = cls.query()
4801 query = query.filter(cls.local_user_id == local_user_id)
4806 query = query.filter(cls.local_user_id == local_user_id)
4802 return query
4807 return query
4803
4808
4804 @classmethod
4809 @classmethod
4805 def load_provider_plugin(cls, plugin_id):
4810 def load_provider_plugin(cls, plugin_id):
4806 from rhodecode.authentication.base import loadplugin
4811 from rhodecode.authentication.base import loadplugin
4807 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4812 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4808 auth_plugin = loadplugin(_plugin_id)
4813 auth_plugin = loadplugin(_plugin_id)
4809 return auth_plugin
4814 return auth_plugin
4810
4815
4811
4816
4812 class Integration(Base, BaseModel):
4817 class Integration(Base, BaseModel):
4813 __tablename__ = 'integrations'
4818 __tablename__ = 'integrations'
4814 __table_args__ = (
4819 __table_args__ = (
4815 base_table_args
4820 base_table_args
4816 )
4821 )
4817
4822
4818 integration_id = Column('integration_id', Integer(), primary_key=True)
4823 integration_id = Column('integration_id', Integer(), primary_key=True)
4819 integration_type = Column('integration_type', String(255))
4824 integration_type = Column('integration_type', String(255))
4820 enabled = Column('enabled', Boolean(), nullable=False)
4825 enabled = Column('enabled', Boolean(), nullable=False)
4821 name = Column('name', String(255), nullable=False)
4826 name = Column('name', String(255), nullable=False)
4822 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4827 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4823 default=False)
4828 default=False)
4824
4829
4825 settings = Column(
4830 settings = Column(
4826 'settings_json', MutationObj.as_mutable(
4831 'settings_json', MutationObj.as_mutable(
4827 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4832 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4828 repo_id = Column(
4833 repo_id = Column(
4829 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4834 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4830 nullable=True, unique=None, default=None)
4835 nullable=True, unique=None, default=None)
4831 repo = relationship('Repository', lazy='joined')
4836 repo = relationship('Repository', lazy='joined')
4832
4837
4833 repo_group_id = Column(
4838 repo_group_id = Column(
4834 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4839 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4835 nullable=True, unique=None, default=None)
4840 nullable=True, unique=None, default=None)
4836 repo_group = relationship('RepoGroup', lazy='joined')
4841 repo_group = relationship('RepoGroup', lazy='joined')
4837
4842
4838 @property
4843 @property
4839 def scope(self):
4844 def scope(self):
4840 if self.repo:
4845 if self.repo:
4841 return repr(self.repo)
4846 return repr(self.repo)
4842 if self.repo_group:
4847 if self.repo_group:
4843 if self.child_repos_only:
4848 if self.child_repos_only:
4844 return repr(self.repo_group) + ' (child repos only)'
4849 return repr(self.repo_group) + ' (child repos only)'
4845 else:
4850 else:
4846 return repr(self.repo_group) + ' (recursive)'
4851 return repr(self.repo_group) + ' (recursive)'
4847 if self.child_repos_only:
4852 if self.child_repos_only:
4848 return 'root_repos'
4853 return 'root_repos'
4849 return 'global'
4854 return 'global'
4850
4855
4851 def __repr__(self):
4856 def __repr__(self):
4852 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4857 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4853
4858
4854
4859
4855 class RepoReviewRuleUser(Base, BaseModel):
4860 class RepoReviewRuleUser(Base, BaseModel):
4856 __tablename__ = 'repo_review_rules_users'
4861 __tablename__ = 'repo_review_rules_users'
4857 __table_args__ = (
4862 __table_args__ = (
4858 base_table_args
4863 base_table_args
4859 )
4864 )
4860
4865
4861 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4866 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4862 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4867 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4863 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4864 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4869 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4865 user = relationship('User')
4870 user = relationship('User')
4866
4871
4867 def rule_data(self):
4872 def rule_data(self):
4868 return {
4873 return {
4869 'mandatory': self.mandatory
4874 'mandatory': self.mandatory
4870 }
4875 }
4871
4876
4872
4877
4873 class RepoReviewRuleUserGroup(Base, BaseModel):
4878 class RepoReviewRuleUserGroup(Base, BaseModel):
4874 __tablename__ = 'repo_review_rules_users_groups'
4879 __tablename__ = 'repo_review_rules_users_groups'
4875 __table_args__ = (
4880 __table_args__ = (
4876 base_table_args
4881 base_table_args
4877 )
4882 )
4878
4883
4879 VOTE_RULE_ALL = -1
4884 VOTE_RULE_ALL = -1
4880
4885
4881 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4886 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4882 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4887 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4883 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4888 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4884 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4889 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4885 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4890 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4886 users_group = relationship('UserGroup')
4891 users_group = relationship('UserGroup')
4887
4892
4888 def rule_data(self):
4893 def rule_data(self):
4889 return {
4894 return {
4890 'mandatory': self.mandatory,
4895 'mandatory': self.mandatory,
4891 'vote_rule': self.vote_rule
4896 'vote_rule': self.vote_rule
4892 }
4897 }
4893
4898
4894 @property
4899 @property
4895 def vote_rule_label(self):
4900 def vote_rule_label(self):
4896 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4901 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4897 return 'all must vote'
4902 return 'all must vote'
4898 else:
4903 else:
4899 return 'min. vote {}'.format(self.vote_rule)
4904 return 'min. vote {}'.format(self.vote_rule)
4900
4905
4901
4906
4902 class RepoReviewRule(Base, BaseModel):
4907 class RepoReviewRule(Base, BaseModel):
4903 __tablename__ = 'repo_review_rules'
4908 __tablename__ = 'repo_review_rules'
4904 __table_args__ = (
4909 __table_args__ = (
4905 base_table_args
4910 base_table_args
4906 )
4911 )
4907
4912
4908 repo_review_rule_id = Column(
4913 repo_review_rule_id = Column(
4909 'repo_review_rule_id', Integer(), primary_key=True)
4914 'repo_review_rule_id', Integer(), primary_key=True)
4910 repo_id = Column(
4915 repo_id = Column(
4911 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4916 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4912 repo = relationship('Repository', backref='review_rules')
4917 repo = relationship('Repository', backref='review_rules')
4913
4918
4914 review_rule_name = Column('review_rule_name', String(255))
4919 review_rule_name = Column('review_rule_name', String(255))
4915 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4920 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4916 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4921 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4917 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4922 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4918
4923
4919 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4924 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4920 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4925 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4921 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4926 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4922 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4927 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4923
4928
4924 rule_users = relationship('RepoReviewRuleUser')
4929 rule_users = relationship('RepoReviewRuleUser')
4925 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4930 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4926
4931
4927 def _validate_pattern(self, value):
4932 def _validate_pattern(self, value):
4928 re.compile('^' + glob2re(value) + '$')
4933 re.compile('^' + glob2re(value) + '$')
4929
4934
4930 @hybrid_property
4935 @hybrid_property
4931 def source_branch_pattern(self):
4936 def source_branch_pattern(self):
4932 return self._branch_pattern or '*'
4937 return self._branch_pattern or '*'
4933
4938
4934 @source_branch_pattern.setter
4939 @source_branch_pattern.setter
4935 def source_branch_pattern(self, value):
4940 def source_branch_pattern(self, value):
4936 self._validate_pattern(value)
4941 self._validate_pattern(value)
4937 self._branch_pattern = value or '*'
4942 self._branch_pattern = value or '*'
4938
4943
4939 @hybrid_property
4944 @hybrid_property
4940 def target_branch_pattern(self):
4945 def target_branch_pattern(self):
4941 return self._target_branch_pattern or '*'
4946 return self._target_branch_pattern or '*'
4942
4947
4943 @target_branch_pattern.setter
4948 @target_branch_pattern.setter
4944 def target_branch_pattern(self, value):
4949 def target_branch_pattern(self, value):
4945 self._validate_pattern(value)
4950 self._validate_pattern(value)
4946 self._target_branch_pattern = value or '*'
4951 self._target_branch_pattern = value or '*'
4947
4952
4948 @hybrid_property
4953 @hybrid_property
4949 def file_pattern(self):
4954 def file_pattern(self):
4950 return self._file_pattern or '*'
4955 return self._file_pattern or '*'
4951
4956
4952 @file_pattern.setter
4957 @file_pattern.setter
4953 def file_pattern(self, value):
4958 def file_pattern(self, value):
4954 self._validate_pattern(value)
4959 self._validate_pattern(value)
4955 self._file_pattern = value or '*'
4960 self._file_pattern = value or '*'
4956
4961
4957 def matches(self, source_branch, target_branch, files_changed):
4962 def matches(self, source_branch, target_branch, files_changed):
4958 """
4963 """
4959 Check if this review rule matches a branch/files in a pull request
4964 Check if this review rule matches a branch/files in a pull request
4960
4965
4961 :param source_branch: source branch name for the commit
4966 :param source_branch: source branch name for the commit
4962 :param target_branch: target branch name for the commit
4967 :param target_branch: target branch name for the commit
4963 :param files_changed: list of file paths changed in the pull request
4968 :param files_changed: list of file paths changed in the pull request
4964 """
4969 """
4965
4970
4966 source_branch = source_branch or ''
4971 source_branch = source_branch or ''
4967 target_branch = target_branch or ''
4972 target_branch = target_branch or ''
4968 files_changed = files_changed or []
4973 files_changed = files_changed or []
4969
4974
4970 branch_matches = True
4975 branch_matches = True
4971 if source_branch or target_branch:
4976 if source_branch or target_branch:
4972 if self.source_branch_pattern == '*':
4977 if self.source_branch_pattern == '*':
4973 source_branch_match = True
4978 source_branch_match = True
4974 else:
4979 else:
4975 if self.source_branch_pattern.startswith('re:'):
4980 if self.source_branch_pattern.startswith('re:'):
4976 source_pattern = self.source_branch_pattern[3:]
4981 source_pattern = self.source_branch_pattern[3:]
4977 else:
4982 else:
4978 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4983 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4979 source_branch_regex = re.compile(source_pattern)
4984 source_branch_regex = re.compile(source_pattern)
4980 source_branch_match = bool(source_branch_regex.search(source_branch))
4985 source_branch_match = bool(source_branch_regex.search(source_branch))
4981 if self.target_branch_pattern == '*':
4986 if self.target_branch_pattern == '*':
4982 target_branch_match = True
4987 target_branch_match = True
4983 else:
4988 else:
4984 if self.target_branch_pattern.startswith('re:'):
4989 if self.target_branch_pattern.startswith('re:'):
4985 target_pattern = self.target_branch_pattern[3:]
4990 target_pattern = self.target_branch_pattern[3:]
4986 else:
4991 else:
4987 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4992 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4988 target_branch_regex = re.compile(target_pattern)
4993 target_branch_regex = re.compile(target_pattern)
4989 target_branch_match = bool(target_branch_regex.search(target_branch))
4994 target_branch_match = bool(target_branch_regex.search(target_branch))
4990
4995
4991 branch_matches = source_branch_match and target_branch_match
4996 branch_matches = source_branch_match and target_branch_match
4992
4997
4993 files_matches = True
4998 files_matches = True
4994 if self.file_pattern != '*':
4999 if self.file_pattern != '*':
4995 files_matches = False
5000 files_matches = False
4996 if self.file_pattern.startswith('re:'):
5001 if self.file_pattern.startswith('re:'):
4997 file_pattern = self.file_pattern[3:]
5002 file_pattern = self.file_pattern[3:]
4998 else:
5003 else:
4999 file_pattern = glob2re(self.file_pattern)
5004 file_pattern = glob2re(self.file_pattern)
5000 file_regex = re.compile(file_pattern)
5005 file_regex = re.compile(file_pattern)
5001 for file_data in files_changed:
5006 for file_data in files_changed:
5002 filename = file_data.get('filename')
5007 filename = file_data.get('filename')
5003
5008
5004 if file_regex.search(filename):
5009 if file_regex.search(filename):
5005 files_matches = True
5010 files_matches = True
5006 break
5011 break
5007
5012
5008 return branch_matches and files_matches
5013 return branch_matches and files_matches
5009
5014
5010 @property
5015 @property
5011 def review_users(self):
5016 def review_users(self):
5012 """ Returns the users which this rule applies to """
5017 """ Returns the users which this rule applies to """
5013
5018
5014 users = collections.OrderedDict()
5019 users = collections.OrderedDict()
5015
5020
5016 for rule_user in self.rule_users:
5021 for rule_user in self.rule_users:
5017 if rule_user.user.active:
5022 if rule_user.user.active:
5018 if rule_user.user not in users:
5023 if rule_user.user not in users:
5019 users[rule_user.user.username] = {
5024 users[rule_user.user.username] = {
5020 'user': rule_user.user,
5025 'user': rule_user.user,
5021 'source': 'user',
5026 'source': 'user',
5022 'source_data': {},
5027 'source_data': {},
5023 'data': rule_user.rule_data()
5028 'data': rule_user.rule_data()
5024 }
5029 }
5025
5030
5026 for rule_user_group in self.rule_user_groups:
5031 for rule_user_group in self.rule_user_groups:
5027 source_data = {
5032 source_data = {
5028 'user_group_id': rule_user_group.users_group.users_group_id,
5033 'user_group_id': rule_user_group.users_group.users_group_id,
5029 'name': rule_user_group.users_group.users_group_name,
5034 'name': rule_user_group.users_group.users_group_name,
5030 'members': len(rule_user_group.users_group.members)
5035 'members': len(rule_user_group.users_group.members)
5031 }
5036 }
5032 for member in rule_user_group.users_group.members:
5037 for member in rule_user_group.users_group.members:
5033 if member.user.active:
5038 if member.user.active:
5034 key = member.user.username
5039 key = member.user.username
5035 if key in users:
5040 if key in users:
5036 # skip this member as we have him already
5041 # skip this member as we have him already
5037 # this prevents from override the "first" matched
5042 # this prevents from override the "first" matched
5038 # users with duplicates in multiple groups
5043 # users with duplicates in multiple groups
5039 continue
5044 continue
5040
5045
5041 users[key] = {
5046 users[key] = {
5042 'user': member.user,
5047 'user': member.user,
5043 'source': 'user_group',
5048 'source': 'user_group',
5044 'source_data': source_data,
5049 'source_data': source_data,
5045 'data': rule_user_group.rule_data()
5050 'data': rule_user_group.rule_data()
5046 }
5051 }
5047
5052
5048 return users
5053 return users
5049
5054
5050 def user_group_vote_rule(self, user_id):
5055 def user_group_vote_rule(self, user_id):
5051
5056
5052 rules = []
5057 rules = []
5053 if not self.rule_user_groups:
5058 if not self.rule_user_groups:
5054 return rules
5059 return rules
5055
5060
5056 for user_group in self.rule_user_groups:
5061 for user_group in self.rule_user_groups:
5057 user_group_members = [x.user_id for x in user_group.users_group.members]
5062 user_group_members = [x.user_id for x in user_group.users_group.members]
5058 if user_id in user_group_members:
5063 if user_id in user_group_members:
5059 rules.append(user_group)
5064 rules.append(user_group)
5060 return rules
5065 return rules
5061
5066
5062 def __repr__(self):
5067 def __repr__(self):
5063 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5068 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5064 self.repo_review_rule_id, self.repo)
5069 self.repo_review_rule_id, self.repo)
5065
5070
5066
5071
5067 class ScheduleEntry(Base, BaseModel):
5072 class ScheduleEntry(Base, BaseModel):
5068 __tablename__ = 'schedule_entries'
5073 __tablename__ = 'schedule_entries'
5069 __table_args__ = (
5074 __table_args__ = (
5070 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5075 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5071 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5076 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5072 base_table_args,
5077 base_table_args,
5073 )
5078 )
5074
5079
5075 schedule_types = ['crontab', 'timedelta', 'integer']
5080 schedule_types = ['crontab', 'timedelta', 'integer']
5076 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5081 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5077
5082
5078 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5083 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5079 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5084 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5080 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5085 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5081
5086
5082 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5087 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5083 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5088 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5084
5089
5085 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5090 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5086 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5091 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5087
5092
5088 # task
5093 # task
5089 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5094 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5090 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5095 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5091 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5096 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5092 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5097 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5093
5098
5094 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5099 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5095 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5100 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5096
5101
5097 @hybrid_property
5102 @hybrid_property
5098 def schedule_type(self):
5103 def schedule_type(self):
5099 return self._schedule_type
5104 return self._schedule_type
5100
5105
5101 @schedule_type.setter
5106 @schedule_type.setter
5102 def schedule_type(self, val):
5107 def schedule_type(self, val):
5103 if val not in self.schedule_types:
5108 if val not in self.schedule_types:
5104 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5109 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5105 val, self.schedule_type))
5110 val, self.schedule_type))
5106
5111
5107 self._schedule_type = val
5112 self._schedule_type = val
5108
5113
5109 @classmethod
5114 @classmethod
5110 def get_uid(cls, obj):
5115 def get_uid(cls, obj):
5111 args = obj.task_args
5116 args = obj.task_args
5112 kwargs = obj.task_kwargs
5117 kwargs = obj.task_kwargs
5113 if isinstance(args, JsonRaw):
5118 if isinstance(args, JsonRaw):
5114 try:
5119 try:
5115 args = json.loads(args)
5120 args = json.loads(args)
5116 except ValueError:
5121 except ValueError:
5117 args = tuple()
5122 args = tuple()
5118
5123
5119 if isinstance(kwargs, JsonRaw):
5124 if isinstance(kwargs, JsonRaw):
5120 try:
5125 try:
5121 kwargs = json.loads(kwargs)
5126 kwargs = json.loads(kwargs)
5122 except ValueError:
5127 except ValueError:
5123 kwargs = dict()
5128 kwargs = dict()
5124
5129
5125 dot_notation = obj.task_dot_notation
5130 dot_notation = obj.task_dot_notation
5126 val = '.'.join(map(safe_str, [
5131 val = '.'.join(map(safe_str, [
5127 sorted(dot_notation), args, sorted(kwargs.items())]))
5132 sorted(dot_notation), args, sorted(kwargs.items())]))
5128 return hashlib.sha1(val).hexdigest()
5133 return hashlib.sha1(val).hexdigest()
5129
5134
5130 @classmethod
5135 @classmethod
5131 def get_by_schedule_name(cls, schedule_name):
5136 def get_by_schedule_name(cls, schedule_name):
5132 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5137 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5133
5138
5134 @classmethod
5139 @classmethod
5135 def get_by_schedule_id(cls, schedule_id):
5140 def get_by_schedule_id(cls, schedule_id):
5136 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5141 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5137
5142
5138 @property
5143 @property
5139 def task(self):
5144 def task(self):
5140 return self.task_dot_notation
5145 return self.task_dot_notation
5141
5146
5142 @property
5147 @property
5143 def schedule(self):
5148 def schedule(self):
5144 from rhodecode.lib.celerylib.utils import raw_2_schedule
5149 from rhodecode.lib.celerylib.utils import raw_2_schedule
5145 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5150 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5146 return schedule
5151 return schedule
5147
5152
5148 @property
5153 @property
5149 def args(self):
5154 def args(self):
5150 try:
5155 try:
5151 return list(self.task_args or [])
5156 return list(self.task_args or [])
5152 except ValueError:
5157 except ValueError:
5153 return list()
5158 return list()
5154
5159
5155 @property
5160 @property
5156 def kwargs(self):
5161 def kwargs(self):
5157 try:
5162 try:
5158 return dict(self.task_kwargs or {})
5163 return dict(self.task_kwargs or {})
5159 except ValueError:
5164 except ValueError:
5160 return dict()
5165 return dict()
5161
5166
5162 def _as_raw(self, val):
5167 def _as_raw(self, val):
5163 if hasattr(val, 'de_coerce'):
5168 if hasattr(val, 'de_coerce'):
5164 val = val.de_coerce()
5169 val = val.de_coerce()
5165 if val:
5170 if val:
5166 val = json.dumps(val)
5171 val = json.dumps(val)
5167
5172
5168 return val
5173 return val
5169
5174
5170 @property
5175 @property
5171 def schedule_definition_raw(self):
5176 def schedule_definition_raw(self):
5172 return self._as_raw(self.schedule_definition)
5177 return self._as_raw(self.schedule_definition)
5173
5178
5174 @property
5179 @property
5175 def args_raw(self):
5180 def args_raw(self):
5176 return self._as_raw(self.task_args)
5181 return self._as_raw(self.task_args)
5177
5182
5178 @property
5183 @property
5179 def kwargs_raw(self):
5184 def kwargs_raw(self):
5180 return self._as_raw(self.task_kwargs)
5185 return self._as_raw(self.task_kwargs)
5181
5186
5182 def __repr__(self):
5187 def __repr__(self):
5183 return '<DB:ScheduleEntry({}:{})>'.format(
5188 return '<DB:ScheduleEntry({}:{})>'.format(
5184 self.schedule_entry_id, self.schedule_name)
5189 self.schedule_entry_id, self.schedule_name)
5185
5190
5186
5191
5187 @event.listens_for(ScheduleEntry, 'before_update')
5192 @event.listens_for(ScheduleEntry, 'before_update')
5188 def update_task_uid(mapper, connection, target):
5193 def update_task_uid(mapper, connection, target):
5189 target.task_uid = ScheduleEntry.get_uid(target)
5194 target.task_uid = ScheduleEntry.get_uid(target)
5190
5195
5191
5196
5192 @event.listens_for(ScheduleEntry, 'before_insert')
5197 @event.listens_for(ScheduleEntry, 'before_insert')
5193 def set_task_uid(mapper, connection, target):
5198 def set_task_uid(mapper, connection, target):
5194 target.task_uid = ScheduleEntry.get_uid(target)
5199 target.task_uid = ScheduleEntry.get_uid(target)
5195
5200
5196
5201
5197 class _BaseBranchPerms(BaseModel):
5202 class _BaseBranchPerms(BaseModel):
5198 @classmethod
5203 @classmethod
5199 def compute_hash(cls, value):
5204 def compute_hash(cls, value):
5200 return sha1_safe(value)
5205 return sha1_safe(value)
5201
5206
5202 @hybrid_property
5207 @hybrid_property
5203 def branch_pattern(self):
5208 def branch_pattern(self):
5204 return self._branch_pattern or '*'
5209 return self._branch_pattern or '*'
5205
5210
5206 @hybrid_property
5211 @hybrid_property
5207 def branch_hash(self):
5212 def branch_hash(self):
5208 return self._branch_hash
5213 return self._branch_hash
5209
5214
5210 def _validate_glob(self, value):
5215 def _validate_glob(self, value):
5211 re.compile('^' + glob2re(value) + '$')
5216 re.compile('^' + glob2re(value) + '$')
5212
5217
5213 @branch_pattern.setter
5218 @branch_pattern.setter
5214 def branch_pattern(self, value):
5219 def branch_pattern(self, value):
5215 self._validate_glob(value)
5220 self._validate_glob(value)
5216 self._branch_pattern = value or '*'
5221 self._branch_pattern = value or '*'
5217 # set the Hash when setting the branch pattern
5222 # set the Hash when setting the branch pattern
5218 self._branch_hash = self.compute_hash(self._branch_pattern)
5223 self._branch_hash = self.compute_hash(self._branch_pattern)
5219
5224
5220 def matches(self, branch):
5225 def matches(self, branch):
5221 """
5226 """
5222 Check if this the branch matches entry
5227 Check if this the branch matches entry
5223
5228
5224 :param branch: branch name for the commit
5229 :param branch: branch name for the commit
5225 """
5230 """
5226
5231
5227 branch = branch or ''
5232 branch = branch or ''
5228
5233
5229 branch_matches = True
5234 branch_matches = True
5230 if branch:
5235 if branch:
5231 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5236 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5232 branch_matches = bool(branch_regex.search(branch))
5237 branch_matches = bool(branch_regex.search(branch))
5233
5238
5234 return branch_matches
5239 return branch_matches
5235
5240
5236
5241
5237 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5242 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5238 __tablename__ = 'user_to_repo_branch_permissions'
5243 __tablename__ = 'user_to_repo_branch_permissions'
5239 __table_args__ = (
5244 __table_args__ = (
5240 base_table_args
5245 base_table_args
5241 )
5246 )
5242
5247
5243 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5248 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5244
5249
5245 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5250 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5246 repo = relationship('Repository', backref='user_branch_perms')
5251 repo = relationship('Repository', backref='user_branch_perms')
5247
5252
5248 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5253 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5249 permission = relationship('Permission')
5254 permission = relationship('Permission')
5250
5255
5251 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5256 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5252 user_repo_to_perm = relationship('UserRepoToPerm')
5257 user_repo_to_perm = relationship('UserRepoToPerm')
5253
5258
5254 rule_order = Column('rule_order', Integer(), nullable=False)
5259 rule_order = Column('rule_order', Integer(), nullable=False)
5255 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5260 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5256 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5261 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5257
5262
5258 def __unicode__(self):
5263 def __unicode__(self):
5259 return u'<UserBranchPermission(%s => %r)>' % (
5264 return u'<UserBranchPermission(%s => %r)>' % (
5260 self.user_repo_to_perm, self.branch_pattern)
5265 self.user_repo_to_perm, self.branch_pattern)
5261
5266
5262
5267
5263 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5268 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5264 __tablename__ = 'user_group_to_repo_branch_permissions'
5269 __tablename__ = 'user_group_to_repo_branch_permissions'
5265 __table_args__ = (
5270 __table_args__ = (
5266 base_table_args
5271 base_table_args
5267 )
5272 )
5268
5273
5269 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5274 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5270
5275
5271 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5276 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5272 repo = relationship('Repository', backref='user_group_branch_perms')
5277 repo = relationship('Repository', backref='user_group_branch_perms')
5273
5278
5274 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5279 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5275 permission = relationship('Permission')
5280 permission = relationship('Permission')
5276
5281
5277 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)
5282 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)
5278 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5283 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5279
5284
5280 rule_order = Column('rule_order', Integer(), nullable=False)
5285 rule_order = Column('rule_order', Integer(), nullable=False)
5281 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5286 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5282 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5287 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5283
5288
5284 def __unicode__(self):
5289 def __unicode__(self):
5285 return u'<UserBranchPermission(%s => %r)>' % (
5290 return u'<UserBranchPermission(%s => %r)>' % (
5286 self.user_group_repo_to_perm, self.branch_pattern)
5291 self.user_group_repo_to_perm, self.branch_pattern)
5287
5292
5288
5293
5289 class UserBookmark(Base, BaseModel):
5294 class UserBookmark(Base, BaseModel):
5290 __tablename__ = 'user_bookmarks'
5295 __tablename__ = 'user_bookmarks'
5291 __table_args__ = (
5296 __table_args__ = (
5292 UniqueConstraint('user_id', 'bookmark_repo_id'),
5297 UniqueConstraint('user_id', 'bookmark_repo_id'),
5293 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5298 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5294 UniqueConstraint('user_id', 'bookmark_position'),
5299 UniqueConstraint('user_id', 'bookmark_position'),
5295 base_table_args
5300 base_table_args
5296 )
5301 )
5297
5302
5298 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5303 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5299 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5300 position = Column("bookmark_position", Integer(), nullable=False)
5305 position = Column("bookmark_position", Integer(), nullable=False)
5301 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5306 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5302 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5307 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5303 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5308 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5304
5309
5305 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5310 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5306 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5311 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5307
5312
5308 user = relationship("User")
5313 user = relationship("User")
5309
5314
5310 repository = relationship("Repository")
5315 repository = relationship("Repository")
5311 repository_group = relationship("RepoGroup")
5316 repository_group = relationship("RepoGroup")
5312
5317
5313 @classmethod
5318 @classmethod
5314 def get_by_position_for_user(cls, position, user_id):
5319 def get_by_position_for_user(cls, position, user_id):
5315 return cls.query() \
5320 return cls.query() \
5316 .filter(UserBookmark.user_id == user_id) \
5321 .filter(UserBookmark.user_id == user_id) \
5317 .filter(UserBookmark.position == position).scalar()
5322 .filter(UserBookmark.position == position).scalar()
5318
5323
5319 @classmethod
5324 @classmethod
5320 def get_bookmarks_for_user(cls, user_id, cache=True):
5325 def get_bookmarks_for_user(cls, user_id, cache=True):
5321 bookmarks = cls.query() \
5326 bookmarks = cls.query() \
5322 .filter(UserBookmark.user_id == user_id) \
5327 .filter(UserBookmark.user_id == user_id) \
5323 .options(joinedload(UserBookmark.repository)) \
5328 .options(joinedload(UserBookmark.repository)) \
5324 .options(joinedload(UserBookmark.repository_group)) \
5329 .options(joinedload(UserBookmark.repository_group)) \
5325 .order_by(UserBookmark.position.asc())
5330 .order_by(UserBookmark.position.asc())
5326
5331
5327 if cache:
5332 if cache:
5328 bookmarks = bookmarks.options(
5333 bookmarks = bookmarks.options(
5329 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5334 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5330 )
5335 )
5331
5336
5332 return bookmarks.all()
5337 return bookmarks.all()
5333
5338
5334 def __unicode__(self):
5339 def __unicode__(self):
5335 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5340 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5336
5341
5337
5342
5338 class FileStore(Base, BaseModel):
5343 class FileStore(Base, BaseModel):
5339 __tablename__ = 'file_store'
5344 __tablename__ = 'file_store'
5340 __table_args__ = (
5345 __table_args__ = (
5341 base_table_args
5346 base_table_args
5342 )
5347 )
5343
5348
5344 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5349 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5345 file_uid = Column('file_uid', String(1024), nullable=False)
5350 file_uid = Column('file_uid', String(1024), nullable=False)
5346 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5351 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5347 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5352 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5348 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5353 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5349
5354
5350 # sha256 hash
5355 # sha256 hash
5351 file_hash = Column('file_hash', String(512), nullable=False)
5356 file_hash = Column('file_hash', String(512), nullable=False)
5352 file_size = Column('file_size', BigInteger(), nullable=False)
5357 file_size = Column('file_size', BigInteger(), nullable=False)
5353
5358
5354 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5359 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5355 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5360 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5356 accessed_count = Column('accessed_count', Integer(), default=0)
5361 accessed_count = Column('accessed_count', Integer(), default=0)
5357
5362
5358 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5363 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5359
5364
5360 # if repo/repo_group reference is set, check for permissions
5365 # if repo/repo_group reference is set, check for permissions
5361 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5366 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5362
5367
5363 # hidden defines an attachment that should be hidden from showing in artifact listing
5368 # hidden defines an attachment that should be hidden from showing in artifact listing
5364 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5369 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5365
5370
5366 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5371 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5367 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5372 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5368
5373
5369 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5374 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5370
5375
5371 # scope limited to user, which requester have access to
5376 # scope limited to user, which requester have access to
5372 scope_user_id = Column(
5377 scope_user_id = Column(
5373 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5378 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5374 nullable=True, unique=None, default=None)
5379 nullable=True, unique=None, default=None)
5375 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5380 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5376
5381
5377 # scope limited to user group, which requester have access to
5382 # scope limited to user group, which requester have access to
5378 scope_user_group_id = Column(
5383 scope_user_group_id = Column(
5379 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5384 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5380 nullable=True, unique=None, default=None)
5385 nullable=True, unique=None, default=None)
5381 user_group = relationship('UserGroup', lazy='joined')
5386 user_group = relationship('UserGroup', lazy='joined')
5382
5387
5383 # scope limited to repo, which requester have access to
5388 # scope limited to repo, which requester have access to
5384 scope_repo_id = Column(
5389 scope_repo_id = Column(
5385 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5390 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5386 nullable=True, unique=None, default=None)
5391 nullable=True, unique=None, default=None)
5387 repo = relationship('Repository', lazy='joined')
5392 repo = relationship('Repository', lazy='joined')
5388
5393
5389 # scope limited to repo group, which requester have access to
5394 # scope limited to repo group, which requester have access to
5390 scope_repo_group_id = Column(
5395 scope_repo_group_id = Column(
5391 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5396 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5392 nullable=True, unique=None, default=None)
5397 nullable=True, unique=None, default=None)
5393 repo_group = relationship('RepoGroup', lazy='joined')
5398 repo_group = relationship('RepoGroup', lazy='joined')
5394
5399
5395 @classmethod
5400 @classmethod
5396 def get_by_store_uid(cls, file_store_uid):
5401 def get_by_store_uid(cls, file_store_uid):
5397 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5402 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5398
5403
5399 @classmethod
5404 @classmethod
5400 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5405 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5401 file_description='', enabled=True, hidden=False, check_acl=True,
5406 file_description='', enabled=True, hidden=False, check_acl=True,
5402 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5407 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5403
5408
5404 store_entry = FileStore()
5409 store_entry = FileStore()
5405 store_entry.file_uid = file_uid
5410 store_entry.file_uid = file_uid
5406 store_entry.file_display_name = file_display_name
5411 store_entry.file_display_name = file_display_name
5407 store_entry.file_org_name = filename
5412 store_entry.file_org_name = filename
5408 store_entry.file_size = file_size
5413 store_entry.file_size = file_size
5409 store_entry.file_hash = file_hash
5414 store_entry.file_hash = file_hash
5410 store_entry.file_description = file_description
5415 store_entry.file_description = file_description
5411
5416
5412 store_entry.check_acl = check_acl
5417 store_entry.check_acl = check_acl
5413 store_entry.enabled = enabled
5418 store_entry.enabled = enabled
5414 store_entry.hidden = hidden
5419 store_entry.hidden = hidden
5415
5420
5416 store_entry.user_id = user_id
5421 store_entry.user_id = user_id
5417 store_entry.scope_user_id = scope_user_id
5422 store_entry.scope_user_id = scope_user_id
5418 store_entry.scope_repo_id = scope_repo_id
5423 store_entry.scope_repo_id = scope_repo_id
5419 store_entry.scope_repo_group_id = scope_repo_group_id
5424 store_entry.scope_repo_group_id = scope_repo_group_id
5420
5425
5421 return store_entry
5426 return store_entry
5422
5427
5423 @classmethod
5428 @classmethod
5424 def store_metadata(cls, file_store_id, args, commit=True):
5429 def store_metadata(cls, file_store_id, args, commit=True):
5425 file_store = FileStore.get(file_store_id)
5430 file_store = FileStore.get(file_store_id)
5426 if file_store is None:
5431 if file_store is None:
5427 return
5432 return
5428
5433
5429 for section, key, value, value_type in args:
5434 for section, key, value, value_type in args:
5430 has_key = FileStoreMetadata().query() \
5435 has_key = FileStoreMetadata().query() \
5431 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5436 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5432 .filter(FileStoreMetadata.file_store_meta_section == section) \
5437 .filter(FileStoreMetadata.file_store_meta_section == section) \
5433 .filter(FileStoreMetadata.file_store_meta_key == key) \
5438 .filter(FileStoreMetadata.file_store_meta_key == key) \
5434 .scalar()
5439 .scalar()
5435 if has_key:
5440 if has_key:
5436 msg = 'key `{}` already defined under section `{}` for this file.'\
5441 msg = 'key `{}` already defined under section `{}` for this file.'\
5437 .format(key, section)
5442 .format(key, section)
5438 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5443 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5439
5444
5440 # NOTE(marcink): raises ArtifactMetadataBadValueType
5445 # NOTE(marcink): raises ArtifactMetadataBadValueType
5441 FileStoreMetadata.valid_value_type(value_type)
5446 FileStoreMetadata.valid_value_type(value_type)
5442
5447
5443 meta_entry = FileStoreMetadata()
5448 meta_entry = FileStoreMetadata()
5444 meta_entry.file_store = file_store
5449 meta_entry.file_store = file_store
5445 meta_entry.file_store_meta_section = section
5450 meta_entry.file_store_meta_section = section
5446 meta_entry.file_store_meta_key = key
5451 meta_entry.file_store_meta_key = key
5447 meta_entry.file_store_meta_value_type = value_type
5452 meta_entry.file_store_meta_value_type = value_type
5448 meta_entry.file_store_meta_value = value
5453 meta_entry.file_store_meta_value = value
5449
5454
5450 Session().add(meta_entry)
5455 Session().add(meta_entry)
5451
5456
5452 try:
5457 try:
5453 if commit:
5458 if commit:
5454 Session().commit()
5459 Session().commit()
5455 except IntegrityError:
5460 except IntegrityError:
5456 Session().rollback()
5461 Session().rollback()
5457 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5462 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5458
5463
5459 @classmethod
5464 @classmethod
5460 def bump_access_counter(cls, file_uid, commit=True):
5465 def bump_access_counter(cls, file_uid, commit=True):
5461 FileStore().query()\
5466 FileStore().query()\
5462 .filter(FileStore.file_uid == file_uid)\
5467 .filter(FileStore.file_uid == file_uid)\
5463 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5468 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5464 FileStore.accessed_on: datetime.datetime.now()})
5469 FileStore.accessed_on: datetime.datetime.now()})
5465 if commit:
5470 if commit:
5466 Session().commit()
5471 Session().commit()
5467
5472
5468 def __json__(self):
5473 def __json__(self):
5469 data = {
5474 data = {
5470 'filename': self.file_display_name,
5475 'filename': self.file_display_name,
5471 'filename_org': self.file_org_name,
5476 'filename_org': self.file_org_name,
5472 'file_uid': self.file_uid,
5477 'file_uid': self.file_uid,
5473 'description': self.file_description,
5478 'description': self.file_description,
5474 'hidden': self.hidden,
5479 'hidden': self.hidden,
5475 'size': self.file_size,
5480 'size': self.file_size,
5476 'created_on': self.created_on,
5481 'created_on': self.created_on,
5477 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5482 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5478 'downloaded_times': self.accessed_count,
5483 'downloaded_times': self.accessed_count,
5479 'sha256': self.file_hash,
5484 'sha256': self.file_hash,
5480 'metadata': self.file_metadata,
5485 'metadata': self.file_metadata,
5481 }
5486 }
5482
5487
5483 return data
5488 return data
5484
5489
5485 def __repr__(self):
5490 def __repr__(self):
5486 return '<FileStore({})>'.format(self.file_store_id)
5491 return '<FileStore({})>'.format(self.file_store_id)
5487
5492
5488
5493
5489 class FileStoreMetadata(Base, BaseModel):
5494 class FileStoreMetadata(Base, BaseModel):
5490 __tablename__ = 'file_store_metadata'
5495 __tablename__ = 'file_store_metadata'
5491 __table_args__ = (
5496 __table_args__ = (
5492 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5497 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5493 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5498 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5494 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5499 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5495 base_table_args
5500 base_table_args
5496 )
5501 )
5497 SETTINGS_TYPES = {
5502 SETTINGS_TYPES = {
5498 'str': safe_str,
5503 'str': safe_str,
5499 'int': safe_int,
5504 'int': safe_int,
5500 'unicode': safe_unicode,
5505 'unicode': safe_unicode,
5501 'bool': str2bool,
5506 'bool': str2bool,
5502 'list': functools.partial(aslist, sep=',')
5507 'list': functools.partial(aslist, sep=',')
5503 }
5508 }
5504
5509
5505 file_store_meta_id = Column(
5510 file_store_meta_id = Column(
5506 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5511 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5507 primary_key=True)
5512 primary_key=True)
5508 _file_store_meta_section = Column(
5513 _file_store_meta_section = Column(
5509 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5514 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5510 nullable=True, unique=None, default=None)
5515 nullable=True, unique=None, default=None)
5511 _file_store_meta_section_hash = Column(
5516 _file_store_meta_section_hash = Column(
5512 "file_store_meta_section_hash", String(255),
5517 "file_store_meta_section_hash", String(255),
5513 nullable=True, unique=None, default=None)
5518 nullable=True, unique=None, default=None)
5514 _file_store_meta_key = Column(
5519 _file_store_meta_key = Column(
5515 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5520 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5516 nullable=True, unique=None, default=None)
5521 nullable=True, unique=None, default=None)
5517 _file_store_meta_key_hash = Column(
5522 _file_store_meta_key_hash = Column(
5518 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5523 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5519 _file_store_meta_value = Column(
5524 _file_store_meta_value = Column(
5520 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5525 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5521 nullable=True, unique=None, default=None)
5526 nullable=True, unique=None, default=None)
5522 _file_store_meta_value_type = Column(
5527 _file_store_meta_value_type = Column(
5523 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5528 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5524 default='unicode')
5529 default='unicode')
5525
5530
5526 file_store_id = Column(
5531 file_store_id = Column(
5527 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5532 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5528 nullable=True, unique=None, default=None)
5533 nullable=True, unique=None, default=None)
5529
5534
5530 file_store = relationship('FileStore', lazy='joined')
5535 file_store = relationship('FileStore', lazy='joined')
5531
5536
5532 @classmethod
5537 @classmethod
5533 def valid_value_type(cls, value):
5538 def valid_value_type(cls, value):
5534 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5539 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5535 raise ArtifactMetadataBadValueType(
5540 raise ArtifactMetadataBadValueType(
5536 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5541 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5537
5542
5538 @hybrid_property
5543 @hybrid_property
5539 def file_store_meta_section(self):
5544 def file_store_meta_section(self):
5540 return self._file_store_meta_section
5545 return self._file_store_meta_section
5541
5546
5542 @file_store_meta_section.setter
5547 @file_store_meta_section.setter
5543 def file_store_meta_section(self, value):
5548 def file_store_meta_section(self, value):
5544 self._file_store_meta_section = value
5549 self._file_store_meta_section = value
5545 self._file_store_meta_section_hash = _hash_key(value)
5550 self._file_store_meta_section_hash = _hash_key(value)
5546
5551
5547 @hybrid_property
5552 @hybrid_property
5548 def file_store_meta_key(self):
5553 def file_store_meta_key(self):
5549 return self._file_store_meta_key
5554 return self._file_store_meta_key
5550
5555
5551 @file_store_meta_key.setter
5556 @file_store_meta_key.setter
5552 def file_store_meta_key(self, value):
5557 def file_store_meta_key(self, value):
5553 self._file_store_meta_key = value
5558 self._file_store_meta_key = value
5554 self._file_store_meta_key_hash = _hash_key(value)
5559 self._file_store_meta_key_hash = _hash_key(value)
5555
5560
5556 @hybrid_property
5561 @hybrid_property
5557 def file_store_meta_value(self):
5562 def file_store_meta_value(self):
5558 val = self._file_store_meta_value
5563 val = self._file_store_meta_value
5559
5564
5560 if self._file_store_meta_value_type:
5565 if self._file_store_meta_value_type:
5561 # e.g unicode.encrypted == unicode
5566 # e.g unicode.encrypted == unicode
5562 _type = self._file_store_meta_value_type.split('.')[0]
5567 _type = self._file_store_meta_value_type.split('.')[0]
5563 # decode the encrypted value if it's encrypted field type
5568 # decode the encrypted value if it's encrypted field type
5564 if '.encrypted' in self._file_store_meta_value_type:
5569 if '.encrypted' in self._file_store_meta_value_type:
5565 cipher = EncryptedTextValue()
5570 cipher = EncryptedTextValue()
5566 val = safe_unicode(cipher.process_result_value(val, None))
5571 val = safe_unicode(cipher.process_result_value(val, None))
5567 # do final type conversion
5572 # do final type conversion
5568 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5573 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5569 val = converter(val)
5574 val = converter(val)
5570
5575
5571 return val
5576 return val
5572
5577
5573 @file_store_meta_value.setter
5578 @file_store_meta_value.setter
5574 def file_store_meta_value(self, val):
5579 def file_store_meta_value(self, val):
5575 val = safe_unicode(val)
5580 val = safe_unicode(val)
5576 # encode the encrypted value
5581 # encode the encrypted value
5577 if '.encrypted' in self.file_store_meta_value_type:
5582 if '.encrypted' in self.file_store_meta_value_type:
5578 cipher = EncryptedTextValue()
5583 cipher = EncryptedTextValue()
5579 val = safe_unicode(cipher.process_bind_param(val, None))
5584 val = safe_unicode(cipher.process_bind_param(val, None))
5580 self._file_store_meta_value = val
5585 self._file_store_meta_value = val
5581
5586
5582 @hybrid_property
5587 @hybrid_property
5583 def file_store_meta_value_type(self):
5588 def file_store_meta_value_type(self):
5584 return self._file_store_meta_value_type
5589 return self._file_store_meta_value_type
5585
5590
5586 @file_store_meta_value_type.setter
5591 @file_store_meta_value_type.setter
5587 def file_store_meta_value_type(self, val):
5592 def file_store_meta_value_type(self, val):
5588 # e.g unicode.encrypted
5593 # e.g unicode.encrypted
5589 self.valid_value_type(val)
5594 self.valid_value_type(val)
5590 self._file_store_meta_value_type = val
5595 self._file_store_meta_value_type = val
5591
5596
5592 def __json__(self):
5597 def __json__(self):
5593 data = {
5598 data = {
5594 'artifact': self.file_store.file_uid,
5599 'artifact': self.file_store.file_uid,
5595 'section': self.file_store_meta_section,
5600 'section': self.file_store_meta_section,
5596 'key': self.file_store_meta_key,
5601 'key': self.file_store_meta_key,
5597 'value': self.file_store_meta_value,
5602 'value': self.file_store_meta_value,
5598 }
5603 }
5599
5604
5600 return data
5605 return data
5601
5606
5602 def __repr__(self):
5607 def __repr__(self):
5603 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5608 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5604 self.file_store_meta_key, self.file_store_meta_value)
5609 self.file_store_meta_key, self.file_store_meta_value)
5605
5610
5606
5611
5607 class DbMigrateVersion(Base, BaseModel):
5612 class DbMigrateVersion(Base, BaseModel):
5608 __tablename__ = 'db_migrate_version'
5613 __tablename__ = 'db_migrate_version'
5609 __table_args__ = (
5614 __table_args__ = (
5610 base_table_args,
5615 base_table_args,
5611 )
5616 )
5612
5617
5613 repository_id = Column('repository_id', String(250), primary_key=True)
5618 repository_id = Column('repository_id', String(250), primary_key=True)
5614 repository_path = Column('repository_path', Text)
5619 repository_path = Column('repository_path', Text)
5615 version = Column('version', Integer)
5620 version = Column('version', Integer)
5616
5621
5617 @classmethod
5622 @classmethod
5618 def set_version(cls, version):
5623 def set_version(cls, version):
5619 """
5624 """
5620 Helper for forcing a different version, usually for debugging purposes via ishell.
5625 Helper for forcing a different version, usually for debugging purposes via ishell.
5621 """
5626 """
5622 ver = DbMigrateVersion.query().first()
5627 ver = DbMigrateVersion.query().first()
5623 ver.version = version
5628 ver.version = version
5624 Session().commit()
5629 Session().commit()
5625
5630
5626
5631
5627 class DbSession(Base, BaseModel):
5632 class DbSession(Base, BaseModel):
5628 __tablename__ = 'db_session'
5633 __tablename__ = 'db_session'
5629 __table_args__ = (
5634 __table_args__ = (
5630 base_table_args,
5635 base_table_args,
5631 )
5636 )
5632
5637
5633 def __repr__(self):
5638 def __repr__(self):
5634 return '<DB:DbSession({})>'.format(self.id)
5639 return '<DB:DbSession({})>'.format(self.id)
5635
5640
5636 id = Column('id', Integer())
5641 id = Column('id', Integer())
5637 namespace = Column('namespace', String(255), primary_key=True)
5642 namespace = Column('namespace', String(255), primary_key=True)
5638 accessed = Column('accessed', DateTime, nullable=False)
5643 accessed = Column('accessed', DateTime, nullable=False)
5639 created = Column('created', DateTime, nullable=False)
5644 created = Column('created', DateTime, nullable=False)
5640 data = Column('data', PickleType, nullable=False)
5645 data = Column('data', PickleType, nullable=False)
@@ -1,560 +1,566 b''
1
1
2 // tables.less
2 // tables.less
3 // For use in RhodeCode application tables;
3 // For use in RhodeCode application tables;
4 // see style guide documentation for guidelines.
4 // see style guide documentation for guidelines.
5
5
6 // TABLES
6 // TABLES
7
7
8 .rctable,
8 .rctable,
9 table.rctable,
9 table.rctable,
10 table.dataTable {
10 table.dataTable {
11 clear:both;
11 clear:both;
12 width: 100%;
12 width: 100%;
13 margin: 0 auto @padding;
13 margin: 0 auto @padding;
14 padding: 0;
14 padding: 0;
15 vertical-align: baseline;
15 vertical-align: baseline;
16 line-height:1.5em;
16 line-height:1.5em;
17 border: none;
17 border: none;
18 outline: none;
18 outline: none;
19 border-collapse: collapse;
19 border-collapse: collapse;
20 border-spacing: 0;
20 border-spacing: 0;
21 color: @grey2;
21 color: @grey2;
22
22
23 b {
23 b {
24 font-weight: normal;
24 font-weight: normal;
25 }
25 }
26
26
27 em {
27 em {
28 font-weight: bold;
28 font-weight: bold;
29 font-style: normal;
29 font-style: normal;
30 }
30 }
31
31
32 .td-user {
32 .td-user {
33 .rc-user {
33 .rc-user {
34 white-space: nowrap;
34 white-space: nowrap;
35 }
35 }
36 .user-perm-duplicate {
37 color: @grey4;
38 a {
39 color: @grey4;
40 }
41 }
36 }
42 }
37
43
38 .td-email {
44 .td-email {
39 white-space: nowrap;
45 white-space: nowrap;
40 }
46 }
41
47
42 th,
48 th,
43 td {
49 td {
44 height: auto;
50 height: auto;
45 max-width: 20%;
51 max-width: 20%;
46 padding: .65em 0 .65em 1em;
52 padding: .65em 0 .65em 1em;
47 vertical-align: middle;
53 vertical-align: middle;
48 border-bottom: @border-thickness solid @grey5;
54 border-bottom: @border-thickness solid @grey5;
49 white-space: normal;
55 white-space: normal;
50
56
51 &.td-radio,
57 &.td-radio,
52 &.td-checkbox {
58 &.td-checkbox {
53 padding-right: 0;
59 padding-right: 0;
54 text-align: center;
60 text-align: center;
55
61
56 input {
62 input {
57 margin: 0 1em;
63 margin: 0 1em;
58 }
64 }
59 }
65 }
60
66
61 &.truncate-wrap {
67 &.truncate-wrap {
62 white-space: nowrap !important;
68 white-space: nowrap !important;
63 }
69 }
64
70
65 pre {
71 pre {
66 margin: 0;
72 margin: 0;
67 }
73 }
68
74
69 .show_more {
75 .show_more {
70 height: inherit;
76 height: inherit;
71 }
77 }
72 }
78 }
73
79
74 .expired td {
80 .expired td {
75 background-color: @grey7;
81 background-color: @grey7;
76 }
82 }
77 .inactive td {
83 .inactive td {
78 background-color: @grey6;
84 background-color: @grey6;
79 }
85 }
80 th {
86 th {
81 text-align: left;
87 text-align: left;
82 font-weight: @text-semibold-weight;
88 font-weight: @text-semibold-weight;
83 font-family: @text-semibold;
89 font-family: @text-semibold;
84 }
90 }
85
91
86 .hl {
92 .hl {
87 td {
93 td {
88 background-color: lighten(@alert4,25%);
94 background-color: lighten(@alert4,25%);
89 }
95 }
90 }
96 }
91
97
92 // Special Data Cell Types
98 // Special Data Cell Types
93 // See style guide for desciptions and examples.
99 // See style guide for desciptions and examples.
94
100
95 td {
101 td {
96
102
97 &.user {
103 &.user {
98 padding-left: 1em;
104 padding-left: 1em;
99 }
105 }
100
106
101 &.td-rss {
107 &.td-rss {
102 width: 20px;
108 width: 20px;
103 min-width: 0;
109 min-width: 0;
104 margin: 0;
110 margin: 0;
105 }
111 }
106
112
107 &.quick_repo_menu {
113 &.quick_repo_menu {
108 width: 15px;
114 width: 15px;
109 text-align: center;
115 text-align: center;
110
116
111 &:hover {
117 &:hover {
112 background-color: @grey5;
118 background-color: @grey5;
113 }
119 }
114 }
120 }
115
121
116 &.td-icon {
122 &.td-icon {
117 min-width: 20px;
123 min-width: 20px;
118 width: 20px;
124 width: 20px;
119 }
125 }
120
126
121 &.td-hash {
127 &.td-hash {
122 min-width: 80px;
128 min-width: 80px;
123 width: 200px;
129 width: 200px;
124
130
125 .obsolete {
131 .obsolete {
126 text-decoration: line-through;
132 text-decoration: line-through;
127 color: lighten(@grey2,25%);
133 color: lighten(@grey2,25%);
128 }
134 }
129 }
135 }
130
136
131 &.td-sha {
137 &.td-sha {
132 white-space: nowrap;
138 white-space: nowrap;
133 }
139 }
134
140
135 &.td-graphbox {
141 &.td-graphbox {
136 width: 100px;
142 width: 100px;
137 max-width: 100px;
143 max-width: 100px;
138 min-width: 100px;
144 min-width: 100px;
139 }
145 }
140
146
141 &.td-time {
147 &.td-time {
142 width: 160px;
148 width: 160px;
143 white-space: nowrap;
149 white-space: nowrap;
144 }
150 }
145
151
146 &.annotate{
152 &.annotate{
147 padding-right: 0;
153 padding-right: 0;
148
154
149 div.annotatediv{
155 div.annotatediv{
150 margin: 0 0.7em;
156 margin: 0 0.7em;
151 }
157 }
152 }
158 }
153
159
154 &.tags-col {
160 &.tags-col {
155 padding-right: 0;
161 padding-right: 0;
156 }
162 }
157
163
158 &.td-description {
164 &.td-description {
159 min-width: 350px;
165 min-width: 350px;
160
166
161 &.truncate, .truncate-wrap {
167 &.truncate, .truncate-wrap {
162 white-space: nowrap;
168 white-space: nowrap;
163 overflow: hidden;
169 overflow: hidden;
164 text-overflow: ellipsis;
170 text-overflow: ellipsis;
165 max-width: 350px;
171 max-width: 350px;
166 }
172 }
167 }
173 }
168
174
169 &.td-grid-name {
175 &.td-grid-name {
170 white-space: nowrap;
176 white-space: nowrap;
171 min-width: 300px;
177 min-width: 300px;
172 }
178 }
173
179
174 &.td-componentname {
180 &.td-componentname {
175 white-space: nowrap;
181 white-space: nowrap;
176 }
182 }
177
183
178 &.td-name {
184 &.td-name {
179
185
180 }
186 }
181
187
182 &.td-journalaction {
188 &.td-journalaction {
183 min-width: 300px;
189 min-width: 300px;
184
190
185 .journal_action_params {
191 .journal_action_params {
186 // waiting for feedback
192 // waiting for feedback
187 }
193 }
188 }
194 }
189
195
190 &.td-active {
196 &.td-active {
191 padding-left: .65em;
197 padding-left: .65em;
192 }
198 }
193
199
194 &.td-issue-tracker-name {
200 &.td-issue-tracker-name {
195 width: 180px;
201 width: 180px;
196 input {
202 input {
197 width: 180px;
203 width: 180px;
198 }
204 }
199
205
200 }
206 }
201
207
202 &.td-issue-tracker-regex {
208 &.td-issue-tracker-regex {
203 white-space: nowrap;
209 white-space: nowrap;
204
210
205 min-width: 300px;
211 min-width: 300px;
206 input {
212 input {
207 min-width: 300px;
213 min-width: 300px;
208 }
214 }
209
215
210 }
216 }
211
217
212 &.td-url {
218 &.td-url {
213 white-space: nowrap;
219 white-space: nowrap;
214 }
220 }
215
221
216 &.td-comments {
222 &.td-comments {
217 min-width: 3em;
223 min-width: 3em;
218 }
224 }
219
225
220 &.td-buttons {
226 &.td-buttons {
221 padding: .3em 0;
227 padding: .3em 0;
222 }
228 }
223 &.td-align-top {
229 &.td-align-top {
224 vertical-align: text-top
230 vertical-align: text-top
225 }
231 }
226 &.td-action {
232 &.td-action {
227 // this is for the remove/delete/edit buttons
233 // this is for the remove/delete/edit buttons
228 padding-right: 0;
234 padding-right: 0;
229 min-width: 95px;
235 min-width: 95px;
230 text-transform: capitalize;
236 text-transform: capitalize;
231
237
232 i {
238 i {
233 display: none;
239 display: none;
234 }
240 }
235 }
241 }
236
242
237 // TODO: lisa: this needs to be cleaned up with the buttons
243 // TODO: lisa: this needs to be cleaned up with the buttons
238 .grid_edit,
244 .grid_edit,
239 .grid_delete {
245 .grid_delete {
240 display: inline-block;
246 display: inline-block;
241 margin: 0 @padding/3 0 0;
247 margin: 0 @padding/3 0 0;
242 font-family: @text-light;
248 font-family: @text-light;
243
249
244 i {
250 i {
245 display: none;
251 display: none;
246 }
252 }
247 }
253 }
248
254
249 .grid_edit + .grid_delete {
255 .grid_edit + .grid_delete {
250 border-left: @border-thickness solid @grey5;
256 border-left: @border-thickness solid @grey5;
251 padding-left: @padding/2;
257 padding-left: @padding/2;
252 }
258 }
253
259
254 &.td-compare {
260 &.td-compare {
255
261
256 input {
262 input {
257 margin-right: 1em;
263 margin-right: 1em;
258 }
264 }
259
265
260 .compare-radio-button {
266 .compare-radio-button {
261 margin: 0 1em 0 0;
267 margin: 0 1em 0 0;
262 }
268 }
263
269
264
270
265 }
271 }
266
272
267 &.td-tags {
273 &.td-tags {
268 padding: .5em 1em .5em 0;
274 padding: .5em 1em .5em 0;
269 width: 140px;
275 width: 140px;
270
276
271 .tag {
277 .tag {
272 margin: 1px;
278 margin: 1px;
273 float: left;
279 float: left;
274 }
280 }
275 }
281 }
276
282
277 .icon-svn, .icon-hg, .icon-git {
283 .icon-svn, .icon-hg, .icon-git {
278 font-size: 1.4em;
284 font-size: 1.4em;
279 }
285 }
280
286
281 &.collapse_commit,
287 &.collapse_commit,
282 &.expand_commit {
288 &.expand_commit {
283 padding-right: 0;
289 padding-right: 0;
284 padding-left: 1em;
290 padding-left: 1em;
285 cursor: pointer;
291 cursor: pointer;
286 width: 20px;
292 width: 20px;
287 }
293 }
288 }
294 }
289
295
290 .perm_admin_row {
296 .perm_admin_row {
291 color: @grey4;
297 color: @grey4;
292 background-color: @grey6;
298 background-color: @grey6;
293 }
299 }
294
300
295 .noborder {
301 .noborder {
296 border: none;
302 border: none;
297
303
298 td {
304 td {
299 border: none;
305 border: none;
300 }
306 }
301 }
307 }
302 }
308 }
303 .rctable.audit-log {
309 .rctable.audit-log {
304 td {
310 td {
305 vertical-align: top;
311 vertical-align: top;
306 }
312 }
307 }
313 }
308
314
309 // TRUNCATING
315 // TRUNCATING
310 // TODO: lisaq: should this possibly be moved out of tables.less?
316 // TODO: lisaq: should this possibly be moved out of tables.less?
311 // for truncated text
317 // for truncated text
312 // used inside of table cells and in code block headers
318 // used inside of table cells and in code block headers
313 .truncate-wrap {
319 .truncate-wrap {
314 white-space: nowrap !important;
320 white-space: nowrap !important;
315
321
316 //truncated text
322 //truncated text
317 .truncate {
323 .truncate {
318 max-width: 450px;
324 max-width: 450px;
319 width: 300px;
325 width: 300px;
320 overflow: hidden;
326 overflow: hidden;
321 text-overflow: ellipsis;
327 text-overflow: ellipsis;
322 -o-text-overflow: ellipsis;
328 -o-text-overflow: ellipsis;
323 -ms-text-overflow: ellipsis;
329 -ms-text-overflow: ellipsis;
324
330
325 &.autoexpand {
331 &.autoexpand {
326 width: 120px;
332 width: 120px;
327 margin-right: 200px;
333 margin-right: 200px;
328 }
334 }
329 }
335 }
330 &:hover .truncate.autoexpand {
336 &:hover .truncate.autoexpand {
331 overflow: visible;
337 overflow: visible;
332 }
338 }
333
339
334 .tags-truncate {
340 .tags-truncate {
335 width: 150px;
341 width: 150px;
336 height: 22px;
342 height: 22px;
337 overflow: hidden;
343 overflow: hidden;
338
344
339 .tag {
345 .tag {
340 display: inline-block;
346 display: inline-block;
341 }
347 }
342
348
343 &.truncate {
349 &.truncate {
344 height: 22px;
350 height: 22px;
345 max-height:2em;
351 max-height:2em;
346 width: 140px;
352 width: 140px;
347 }
353 }
348 }
354 }
349 }
355 }
350
356
351 .apikeys_wrap {
357 .apikeys_wrap {
352 margin-bottom: @padding;
358 margin-bottom: @padding;
353
359
354 table.rctable td:first-child {
360 table.rctable td:first-child {
355 width: 120px;
361 width: 120px;
356 }
362 }
357 }
363 }
358
364
359
365
360
366
361 // SPECIAL CASES
367 // SPECIAL CASES
362
368
363 // Repository Followers
369 // Repository Followers
364 table.rctable.followers_data {
370 table.rctable.followers_data {
365 width: 75%;
371 width: 75%;
366 margin: 0;
372 margin: 0;
367 }
373 }
368
374
369 // Repository List
375 // Repository List
370 // Group Members List
376 // Group Members List
371 table.rctable.group_members,
377 table.rctable.group_members,
372 table#repo_list_table {
378 table#repo_list_table {
373 min-width: 600px;
379 min-width: 600px;
374 }
380 }
375
381
376 #no_grid_data {
382 #no_grid_data {
377 text-align: center;
383 text-align: center;
378 }
384 }
379
385
380 #grid_data_loading {
386 #grid_data_loading {
381 text-align: center;
387 text-align: center;
382 font-weight: 600;
388 font-weight: 600;
383 font-size: 16px;
389 font-size: 16px;
384 padding: 80px 20px;
390 padding: 80px 20px;
385 }
391 }
386
392
387 // Keyboard mappings
393 // Keyboard mappings
388 table.keyboard-mappings {
394 table.keyboard-mappings {
389 th {
395 th {
390 text-align: left;
396 text-align: left;
391 font-weight: @text-semibold-weight;
397 font-weight: @text-semibold-weight;
392 font-family: @text-semibold;
398 font-family: @text-semibold;
393 }
399 }
394 }
400 }
395
401
396 // Branches, Tags, and Bookmarks
402 // Branches, Tags, and Bookmarks
397 #obj_list_table.dataTable {
403 #obj_list_table.dataTable {
398 td.td-time {
404 td.td-time {
399 padding-right: 1em;
405 padding-right: 1em;
400 }
406 }
401 }
407 }
402
408
403 // User Admin
409 // User Admin
404 .rctable.useremails,
410 .rctable.useremails,
405 .rctable.account_emails {
411 .rctable.account_emails {
406 .tag,
412 .tag,
407 .btn {
413 .btn {
408 float: right;
414 float: right;
409 }
415 }
410 .btn { //to line up with tags
416 .btn { //to line up with tags
411 margin-right: 1.65em;
417 margin-right: 1.65em;
412 }
418 }
413 }
419 }
414
420
415 // User List
421 // User List
416 #user_list_table {
422 #user_list_table {
417
423
418 td.td-user {
424 td.td-user {
419 min-width: 100px;
425 min-width: 100px;
420 }
426 }
421 }
427 }
422
428
423 // Pull Request List Table
429 // Pull Request List Table
424 #pull_request_list_table.dataTable {
430 #pull_request_list_table.dataTable {
425
431
426 //TODO: lisa: This needs to be removed once the description is adjusted
432 //TODO: lisa: This needs to be removed once the description is adjusted
427 // for using an expand_commit button (see issue 765)
433 // for using an expand_commit button (see issue 765)
428 td {
434 td {
429 vertical-align: middle;
435 vertical-align: middle;
430 }
436 }
431 }
437 }
432
438
433 // Settings (no border)
439 // Settings (no border)
434 table.rctable.dl-settings {
440 table.rctable.dl-settings {
435 td {
441 td {
436 border: none;
442 border: none;
437 vertical-align: baseline;
443 vertical-align: baseline;
438 }
444 }
439 }
445 }
440
446
441
447
442 // Statistics
448 // Statistics
443 table.trending_language_tbl {
449 table.trending_language_tbl {
444 width: 100%;
450 width: 100%;
445 line-height: 1em;
451 line-height: 1em;
446
452
447 td div {
453 td div {
448 overflow: visible;
454 overflow: visible;
449 }
455 }
450 }
456 }
451
457
452 .trending_language_tbl, .trending_language_tbl td {
458 .trending_language_tbl, .trending_language_tbl td {
453 border: 0;
459 border: 0;
454 margin: 0;
460 margin: 0;
455 padding: 0;
461 padding: 0;
456 background: transparent;
462 background: transparent;
457 }
463 }
458
464
459 .trending_language_tbl, .trending_language_tbl tr {
465 .trending_language_tbl, .trending_language_tbl tr {
460 border-spacing: 0 3px;
466 border-spacing: 0 3px;
461 }
467 }
462
468
463 .trending_language {
469 .trending_language {
464 position: relative;
470 position: relative;
465 overflow: hidden;
471 overflow: hidden;
466 color: @text-color;
472 color: @text-color;
467 width: 400px;
473 width: 400px;
468
474
469 .lang-bar {
475 .lang-bar {
470 z-index: 1;
476 z-index: 1;
471 overflow: hidden;
477 overflow: hidden;
472 background-color: @rcblue;
478 background-color: @rcblue;
473 color: #FFF;
479 color: #FFF;
474 text-decoration: none;
480 text-decoration: none;
475 }
481 }
476
482
477 }
483 }
478
484
479 // Changesets
485 // Changesets
480 #changesets.rctable {
486 #changesets.rctable {
481 th {
487 th {
482 padding: 0 1em 0.65em 0;
488 padding: 0 1em 0.65em 0;
483 }
489 }
484
490
485 // td must be fixed height for graph
491 // td must be fixed height for graph
486 td {
492 td {
487 height: 32px;
493 height: 32px;
488 padding: 0 1em 0 0;
494 padding: 0 1em 0 0;
489 vertical-align: middle;
495 vertical-align: middle;
490 white-space: nowrap;
496 white-space: nowrap;
491
497
492 &.td-description {
498 &.td-description {
493 white-space: normal;
499 white-space: normal;
494 }
500 }
495
501
496 &.expand_commit {
502 &.expand_commit {
497 padding-right: 0;
503 padding-right: 0;
498 cursor: pointer;
504 cursor: pointer;
499 width: 20px;
505 width: 20px;
500 }
506 }
501 }
507 }
502 }
508 }
503
509
504 // Compare
510 // Compare
505 table.compare_view_commits {
511 table.compare_view_commits {
506 margin-top: @space;
512 margin-top: @space;
507
513
508 td.td-time {
514 td.td-time {
509 padding-left: .5em;
515 padding-left: .5em;
510 }
516 }
511
517
512 // special case to not show hover actions on hidden indicator
518 // special case to not show hover actions on hidden indicator
513 tr.compare_select_hidden:hover {
519 tr.compare_select_hidden:hover {
514 cursor: inherit;
520 cursor: inherit;
515
521
516 td {
522 td {
517 background-color: inherit;
523 background-color: inherit;
518 }
524 }
519 }
525 }
520
526
521 tr:hover {
527 tr:hover {
522 cursor: pointer;
528 cursor: pointer;
523
529
524 td {
530 td {
525 background-color: lighten(@alert4,25%);
531 background-color: lighten(@alert4,25%);
526 }
532 }
527 }
533 }
528
534
529
535
530 }
536 }
531
537
532 .file_history {
538 .file_history {
533 td.td-actions {
539 td.td-actions {
534 text-align: right;
540 text-align: right;
535 }
541 }
536 }
542 }
537
543
538
544
539 // Gist List
545 // Gist List
540 #gist_list_table {
546 #gist_list_table {
541 td {
547 td {
542 vertical-align: middle;
548 vertical-align: middle;
543
549
544 div{
550 div{
545 display: inline-block;
551 display: inline-block;
546 vertical-align: middle;
552 vertical-align: middle;
547 }
553 }
548
554
549 img{
555 img{
550 vertical-align: middle;
556 vertical-align: middle;
551 }
557 }
552
558
553 &.td-expire {
559 &.td-expire {
554 width: 200px;
560 width: 200px;
555 }
561 }
556 &.td-gist-type {
562 &.td-gist-type {
557 width: 100px;
563 width: 100px;
558 }
564 }
559 }
565 }
560 }
566 }
@@ -1,230 +1,240 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Group Permissions: {}').format(c.repo_group.name)}</h3>
5 <h3 class="panel-title">${_('Repository Group Permissions: {}').format(c.repo_group.name)}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
8 ${h.secure_form(h.route_path('edit_repo_group_perms_update', repo_group_name=c.repo_group.group_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.repo_group.permissions():
20 %for _user in c.repo_group.permissions():
21 ## super admin/owner row
21 ## super admin/owner row
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
23 <tr class="perm_admin_row">
23 <tr class="perm_admin_row">
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
28 <td class="td-user">
28 <td class="td-user">
29 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
29 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
30 ${h.link_to_user(_user.username)}
30 ${h.link_to_user(_user.username)}
31 %if getattr(_user, 'admin_row', None):
31 %if getattr(_user, 'admin_row', None):
32 (${_('super-admin')})
32 (${_('super-admin')})
33 %endif
33 %endif
34 %if getattr(_user, 'owner_row', None):
34 %if getattr(_user, 'owner_row', None):
35 (${_('owner')})
35 (${_('owner')})
36 %endif
36 %endif
37 </td>
37 </td>
38 <td></td>
38 <td></td>
39 <td class="quick_repo_menu">
39 <td class="quick_repo_menu">
40 % if c.rhodecode_user.is_admin:
40 % if c.rhodecode_user.is_admin:
41 <i class="icon-more"></i>
41 <i class="icon-more"></i>
42 <div class="menu_items_container" style="display: none;">
42 <div class="menu_items_container" style="display: none;">
43 <ul class="menu_items">
43 <ul class="menu_items">
44 <li>
44 <li>
45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
45 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
46 </li>
46 </li>
47 </ul>
47 </ul>
48 </div>
48 </div>
49 % endif
49 % endif
50 </td>
50 </td>
51 </tr>
51 </tr>
52 %else:
52 %else:
53 <tr>
53 <tr>
54 ##forbid revoking permission from yourself, except if you're an super admin
54 ##forbid revoking permission from yourself, except if you're an super admin
55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
55 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
56 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', checked=_user.permission=='group.none')}</td>
57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
57 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', checked=_user.permission=='group.read')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
58 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', checked=_user.permission=='group.write')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
59 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', checked=_user.permission=='group.admin')}</td>
60 <td class="td-user">
60 <td class="td-user">
61 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
61 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
62 <span class="user">
62 <span class="user">
63 % if _user.username == h.DEFAULT_USER:
63 % if _user.username == h.DEFAULT_USER:
64 ${h.DEFAULT_USER}
64 ${h.DEFAULT_USER}
65 % if _user.active:
65 % if _user.active:
66 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
66 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
67 % else:
67 % else:
68 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
68 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
69 % endif
69 % endif
70 % else:
70 % else:
71 % if getattr(_user, 'duplicate_perm', None):
72 <span class="user-perm-duplicate">
71 ${h.link_to_user(_user.username)}
73 ${h.link_to_user(_user.username)}
72 %if getattr(_user, 'duplicate_perm', None):
74 <span class="tooltip" title="${_('This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually.')}">(${_('inactive duplicate')})
73 (${_('inactive duplicate')})
75 </span>
76 </span>
77 % else:
78 ${h.link_to_user(_user.username)}
74 %endif
79 % endif
75 % endif
80 % endif
76 </span>
81 </span>
77 </td>
82 </td>
78 <td class="td-action">
83 <td class="td-action">
79 %if _user.username != h.DEFAULT_USER:
84 %if _user.username != h.DEFAULT_USER:
80 <span class="btn btn-link btn-danger revoke_perm"
85 <span class="btn btn-link btn-danger revoke_perm"
81 member="${_user.user_id}" member_type="user">
86 member="${_user.user_id}" member_type="user">
82 ${_('Remove')}
87 ${_('Remove')}
83 </span>
88 </span>
84 %endif
89 %endif
85 </td>
90 </td>
86 <td class="quick_repo_menu">
91 <td class="quick_repo_menu">
87 % if c.rhodecode_user.is_admin:
92 % if c.rhodecode_user.is_admin:
88 <i class="icon-more"></i>
93 <i class="icon-more"></i>
89 <div class="menu_items_container" style="display: none;">
94 <div class="menu_items_container" style="display: none;">
90 <ul class="menu_items">
95 <ul class="menu_items">
91 <li>
96 <li>
92 % if _user.username == h.DEFAULT_USER:
97 % if _user.username == h.DEFAULT_USER:
93 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
98 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-groups-permissions'))}
94 % else:
99 % else:
95 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
100 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
96 % endif
101 % endif
97 </li>
102 </li>
98 </ul>
103 </ul>
99 </div>
104 </div>
100 % endif
105 % endif
101 </td>
106 </td>
102 %else:
107 %else:
103 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
108 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
104 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
109 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.none', disabled="disabled")}</td>
105 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
110 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.read', disabled="disabled")}</td>
106 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
111 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.write', disabled="disabled")}</td>
107 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
112 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'group.admin', disabled="disabled")}</td>
108 <td class="td-user">
113 <td class="td-user">
109 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
114 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
110 <span class="user">
115 <span class="user">
111 % if _user.username == h.DEFAULT_USER:
116 % if _user.username == h.DEFAULT_USER:
112 ${h.DEFAULT_USER}
117 ${h.DEFAULT_USER}
113 % if _user.active:
118 % if _user.active:
114 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
119 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
115 % else:
120 % else:
116 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
121 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
117 % endif
122 % endif
118 % else:
123 % else:
124 % if getattr(_user, 'duplicate_perm', None):
125 <span class="user-perm-duplicate">
119 ${h.link_to_user(_user.username)}
126 ${h.link_to_user(_user.username)}
120 %if getattr(_user, 'duplicate_perm', None):
127 <span class="tooltip" title="${_('This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually.')}">(${_('inactive duplicate')})
121 (${_('inactive duplicate')})
128 </span>
129 </span>
130 % else:
131 ${h.link_to_user(_user.username)}
122 %endif
132 % endif
123 % endif
133 % endif
124 <span class="user-perm-help-text">(${_('delegated admin')})</span>
134 <span class="user-perm-help-text">(${_('delegated admin')})</span>
125 </span>
135 </span>
126 </td>
136 </td>
127 <td></td>
137 <td></td>
128 <td class="quick_repo_menu">
138 <td class="quick_repo_menu">
129 % if c.rhodecode_user.is_admin:
139 % if c.rhodecode_user.is_admin:
130 <i class="icon-more"></i>
140 <i class="icon-more"></i>
131 <div class="menu_items_container" style="display: none;">
141 <div class="menu_items_container" style="display: none;">
132 <ul class="menu_items">
142 <ul class="menu_items">
133 <li>
143 <li>
134 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
144 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-groups-permissions'))}
135 </li>
145 </li>
136 </ul>
146 </ul>
137 </div>
147 </div>
138 % endif
148 % endif
139 </td>
149 </td>
140 %endif
150 %endif
141 </tr>
151 </tr>
142 %endif
152 %endif
143 %endfor
153 %endfor
144
154
145 ## USER GROUPS
155 ## USER GROUPS
146 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
156 %for _user_group in c.repo_group.permission_user_groups(with_members=True):
147 <tr id="id${id(_user_group.users_group_name)}">
157 <tr id="id${id(_user_group.users_group_name)}">
148 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
158 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.none', checked=_user_group.permission=='group.none')}</td>
149 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
159 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.read', checked=_user_group.permission=='group.read')}</td>
150 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
160 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.write', checked=_user_group.permission=='group.write')}</td>
151 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
161 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'group.admin', checked=_user_group.permission=='group.admin')}</td>
152 <td class="td-componentname">
162 <td class="td-componentname">
153 ${base.user_group_icon(_user_group, tooltip=True)}
163 ${base.user_group_icon(_user_group, tooltip=True)}
154
164
155 %if c.is_super_admin:
165 %if c.is_super_admin:
156 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
166 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
157 ${_user_group.users_group_name}
167 ${_user_group.users_group_name}
158 </a>
168 </a>
159 %else:
169 %else:
160 ${h.link_to_group(_user_group.users_group_name)}
170 ${h.link_to_group(_user_group.users_group_name)}
161 %endif
171 %endif
162 (${_('members')}: ${len(_user_group.members)})
172 (${_('members')}: ${len(_user_group.members)})
163 </td>
173 </td>
164 <td class="td-action">
174 <td class="td-action">
165 <span class="btn btn-link btn-danger revoke_perm"
175 <span class="btn btn-link btn-danger revoke_perm"
166 member="${_user_group.users_group_id}" member_type="user_group">
176 member="${_user_group.users_group_id}" member_type="user_group">
167 ${_('Remove')}
177 ${_('Remove')}
168 </span>
178 </span>
169 </td>
179 </td>
170 <td class="quick_repo_menu">
180 <td class="quick_repo_menu">
171 % if c.rhodecode_user.is_admin:
181 % if c.rhodecode_user.is_admin:
172 <i class="icon-more"></i>
182 <i class="icon-more"></i>
173 <div class="menu_items_container" style="display: none;">
183 <div class="menu_items_container" style="display: none;">
174 <ul class="menu_items">
184 <ul class="menu_items">
175 <li>
185 <li>
176 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-groups-permissions'))}
186 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-groups-permissions'))}
177 </li>
187 </li>
178 </ul>
188 </ul>
179 </div>
189 </div>
180 % endif
190 % endif
181 </td>
191 </td>
182 </tr>
192 </tr>
183 %endfor
193 %endfor
184
194
185 <tr class="new_members" id="add_perm_input"></tr>
195 <tr class="new_members" id="add_perm_input"></tr>
186 <tr>
196 <tr>
187 <td></td>
197 <td></td>
188 <td></td>
198 <td></td>
189 <td></td>
199 <td></td>
190 <td></td>
200 <td></td>
191 <td></td>
201 <td></td>
192 <td>
202 <td>
193 <span id="add_perm" class="link">
203 <span id="add_perm" class="link">
194 ${_('Add user/user group')}
204 ${_('Add user/user group')}
195 </span>
205 </span>
196 </td>
206 </td>
197 <td></td>
207 <td></td>
198 </tr>
208 </tr>
199 </table>
209 </table>
200
210
201 <div class="fields">
211 <div class="fields">
202 <div class="field">
212 <div class="field">
203 <div class="label label-radio">
213 <div class="label label-radio">
204 ${_('Apply to children')}:
214 ${_('Apply to children')}:
205 </div>
215 </div>
206 <div class="radios">
216 <div class="radios">
207 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
217 ${h.radio('recursive', 'none', label=_('None'), checked="checked")}
208 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
218 ${h.radio('recursive', 'groups', label=_('Repository Groups'))}
209 ${h.radio('recursive', 'repos', label=_('Repositories'))}
219 ${h.radio('recursive', 'repos', label=_('Repositories'))}
210 ${h.radio('recursive', 'all', label=_('Both'))}
220 ${h.radio('recursive', 'all', label=_('Both'))}
211 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
221 <span class="help-block">${_('Set or revoke permissions to selected types of children of this group, including non-private repositories and other groups if chosen.')}</span>
212 </div>
222 </div>
213 </div>
223 </div>
214 </div>
224 </div>
215 <div class="buttons">
225 <div class="buttons">
216 ${h.submit('save',_('Save'),class_="btn btn-primary")}
226 ${h.submit('save',_('Save'),class_="btn btn-primary")}
217 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
227 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
218 </div>
228 </div>
219 ${h.end_form()}
229 ${h.end_form()}
220 </div>
230 </div>
221 </div>
231 </div>
222 <script type="text/javascript">
232 <script type="text/javascript">
223 $('#add_perm').on('click', function(e){
233 $('#add_perm').on('click', function(e){
224 addNewPermInput($(this), 'group');
234 addNewPermInput($(this), 'group');
225 });
235 });
226 $('.revoke_perm').on('click', function(e){
236 $('.revoke_perm').on('click', function(e){
227 markRevokePermInput($(this), 'group');
237 markRevokePermInput($(this), 'group');
228 });
238 });
229 quick_repo_menu();
239 quick_repo_menu();
230 </script>
240 </script>
@@ -1,239 +1,245 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('Repository Access Permissions')}</h3>
5 <h3 class="panel-title">${_('Repository Access Permissions')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), request=request)}
9 <table id="permissions_manage" class="rctable permissions">
9 <table id="permissions_manage" class="rctable permissions">
10 <tr>
10 <tr>
11 <th class="td-radio">${_('None')}</th>
11 <th class="td-radio">${_('None')}</th>
12 <th class="td-radio">${_('Read')}</th>
12 <th class="td-radio">${_('Read')}</th>
13 <th class="td-radio">${_('Write')}</th>
13 <th class="td-radio">${_('Write')}</th>
14 <th class="td-radio">${_('Admin')}</th>
14 <th class="td-radio">${_('Admin')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
15 <th class="td-owner">${_('User/User Group')}</th>
16 <th class="td-action"></th>
16 <th class="td-action"></th>
17 <th class="td-action"></th>
17 <th class="td-action"></th>
18 </tr>
18 </tr>
19 ## USERS
19 ## USERS
20 %for _user in c.rhodecode_db_repo.permissions():
20 %for _user in c.rhodecode_db_repo.permissions():
21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
22 <tr class="perm_admin_row">
22 <tr class="perm_admin_row">
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
27 <td class="td-user">
27 <td class="td-user">
28 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
28 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
29 ${h.link_to_user(_user.username)}
29 ${h.link_to_user(_user.username)}
30 %if getattr(_user, 'admin_row', None):
30 %if getattr(_user, 'admin_row', None):
31 (${_('super-admin')})
31 (${_('super-admin')})
32 %endif
32 %endif
33 %if getattr(_user, 'owner_row', None):
33 %if getattr(_user, 'owner_row', None):
34 (${_('owner')})
34 (${_('owner')})
35 %endif
35 %endif
36 </td>
36 </td>
37 <td></td>
37 <td></td>
38 <td class="quick_repo_menu">
38 <td class="quick_repo_menu">
39 % if c.rhodecode_user.is_admin:
39 % if c.rhodecode_user.is_admin:
40 <i class="icon-more"></i>
40 <i class="icon-more"></i>
41 <div class="menu_items_container" style="display: none;">
41 <div class="menu_items_container" style="display: none;">
42 <ul class="menu_items">
42 <ul class="menu_items">
43 <li>
43 <li>
44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
44 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
45 </li>
45 </li>
46 </ul>
46 </ul>
47 </div>
47 </div>
48 % endif
48 % endif
49 </td>
49 </td>
50 </tr>
50 </tr>
51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
51 %elif _user.username == h.DEFAULT_USER and c.rhodecode_db_repo.private:
52 <tr>
52 <tr>
53 <td colspan="4">
53 <td colspan="4">
54 <span class="private_repo_msg">
54 <span class="private_repo_msg">
55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
55 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
56 </span>
56 </span>
57 </td>
57 </td>
58 <td class="private_repo_msg">
58 <td class="private_repo_msg">
59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
59 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
60 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
61 <td class="td-action">
61 <td class="td-action">
62 <span class="noselect tooltip btn btn-link btn-default" onclick="setPrivateRepo(this, false); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply')}">
62 <span class="noselect tooltip btn btn-link btn-default" onclick="setPrivateRepo(this, false); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply')}">
63 ${_('un-set private mode')}
63 ${_('un-set private mode')}
64 </span>
64 </span>
65 </td>
65 </td>
66 <td class="quick_repo_menu">
66 <td class="quick_repo_menu">
67 % if c.rhodecode_user.is_admin:
67 % if c.rhodecode_user.is_admin:
68 <i class="icon-more"></i>
68 <i class="icon-more"></i>
69 <div class="menu_items_container" style="display: none;">
69 <div class="menu_items_container" style="display: none;">
70 <ul class="menu_items">
70 <ul class="menu_items">
71 <li>
71 <li>
72 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
72 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
73 </li>
73 </li>
74 </ul>
74 </ul>
75 </div>
75 </div>
76 % endif
76 % endif
77 </td>
77 </td>
78 </tr>
78 </tr>
79 %else:
79 %else:
80 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
80 <% used_by_n_rules = len(getattr(_user, 'branch_rules', None) or []) %>
81 <tr>
81 <tr>
82 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
82 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
83 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
83 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read', disabled="disabled" if (used_by_n_rules and _user.username != h.DEFAULT_USER) else None)}</td>
84 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
84 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
85 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
85 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
86 <td class="td-user">
86 <td class="td-user">
87 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
87 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
88 <span class="user">
88 <span class="user">
89 % if _user.username == h.DEFAULT_USER:
89 % if _user.username == h.DEFAULT_USER:
90 ${h.DEFAULT_USER}
90 ${h.DEFAULT_USER}
91 % if _user.active:
91 % if _user.active:
92 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
92 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
93 % else:
93 % else:
94 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
94 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
95 % endif
95 % endif
96 % else:
96 % else:
97 % if getattr(_user, 'duplicate_perm', None):
98 <span class="user-perm-duplicate">
97 ${h.link_to_user(_user.username)}
99 ${h.link_to_user(_user.username)}
98 %if getattr(_user, 'duplicate_perm', None):
100 <span class="tooltip" title="${_('This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually.')}">(${_('inactive duplicate')})
99 (${_('inactive duplicate')})
101 </span>
102 </span>
103 % else:
104 ${h.link_to_user(_user.username)}
100 %endif
105 % endif
106
101 %if getattr(_user, 'branch_rules', None):
107 %if getattr(_user, 'branch_rules', None):
102 % if used_by_n_rules == 1:
108 % if used_by_n_rules == 1:
103 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
109 (${_('used by {} branch rule, requires write+ permissions').format(used_by_n_rules)})
104 % else:
110 % else:
105 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
111 (${_('used by {} branch rules, requires write+ permissions').format(used_by_n_rules)})
106 % endif
112 % endif
107 %endif
113 %endif
108 % endif
114 % endif
109 </span>
115 </span>
110 </td>
116 </td>
111 <td class="td-action">
117 <td class="td-action">
112 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
118 %if _user.username != h.DEFAULT_USER and getattr(_user, 'branch_rules', None) is None:
113 <span class="btn btn-link btn-danger revoke_perm"
119 <span class="btn btn-link btn-danger revoke_perm"
114 member="${_user.user_id}" member_type="user">
120 member="${_user.user_id}" member_type="user">
115 ${_('Remove')}
121 ${_('Remove')}
116 </span>
122 </span>
117 %elif _user.username == h.DEFAULT_USER:
123 %elif _user.username == h.DEFAULT_USER:
118 <span class="noselect tooltip btn btn-link btn-default" onclick="setPrivateRepo(this, true); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply')}">
124 <span class="noselect tooltip btn btn-link btn-default" onclick="setPrivateRepo(this, true); return false" title="${_('Private repositories are only visible to people explicitly added as collaborators. Default permissions wont apply')}">
119 ${_('set private mode')}
125 ${_('set private mode')}
120 </span>
126 </span>
121 %endif
127 %endif
122 </td>
128 </td>
123 <td class="quick_repo_menu">
129 <td class="quick_repo_menu">
124 % if c.rhodecode_user.is_admin:
130 % if c.rhodecode_user.is_admin:
125 <i class="icon-more"></i>
131 <i class="icon-more"></i>
126 <div class="menu_items_container" style="display: none;">
132 <div class="menu_items_container" style="display: none;">
127 <ul class="menu_items">
133 <ul class="menu_items">
128 <li>
134 <li>
129 % if _user.username == h.DEFAULT_USER:
135 % if _user.username == h.DEFAULT_USER:
130 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
136 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='repositories-permissions'))}
131 % else:
137 % else:
132 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
138 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='repositories-permissions'))}
133 % endif
139 % endif
134 </li>
140 </li>
135 </ul>
141 </ul>
136 </div>
142 </div>
137 % endif
143 % endif
138 </td>
144 </td>
139 </tr>
145 </tr>
140 %endif
146 %endif
141 %endfor
147 %endfor
142
148
143 ## USER GROUPS
149 ## USER GROUPS
144 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
150 %for _user_group in c.rhodecode_db_repo.permission_user_groups(with_members=True):
145 <tr>
151 <tr>
146 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
152 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
147 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
153 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
148 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
154 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
149 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
155 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
150 <td class="td-componentname">
156 <td class="td-componentname">
151 ${base.user_group_icon(_user_group, tooltip=True)}
157 ${base.user_group_icon(_user_group, tooltip=True)}
152 %if c.is_super_admin:
158 %if c.is_super_admin:
153 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
159 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
154 ${_user_group.users_group_name}
160 ${_user_group.users_group_name}
155 </a>
161 </a>
156 %else:
162 %else:
157 ${h.link_to_group(_user_group.users_group_name)}
163 ${h.link_to_group(_user_group.users_group_name)}
158 %endif
164 %endif
159 (${_('members')}: ${len(_user_group.members)})
165 (${_('members')}: ${len(_user_group.members)})
160 </td>
166 </td>
161 <td class="td-action">
167 <td class="td-action">
162 <span class="btn btn-link btn-danger revoke_perm"
168 <span class="btn btn-link btn-danger revoke_perm"
163 member="${_user_group.users_group_id}" member_type="user_group">
169 member="${_user_group.users_group_id}" member_type="user_group">
164 ${_('Remove')}
170 ${_('Remove')}
165 </span>
171 </span>
166 </td>
172 </td>
167 <td class="quick_repo_menu">
173 <td class="quick_repo_menu">
168 % if c.rhodecode_user.is_admin:
174 % if c.rhodecode_user.is_admin:
169 <i class="icon-more"></i>
175 <i class="icon-more"></i>
170 <div class="menu_items_container" style="display: none;">
176 <div class="menu_items_container" style="display: none;">
171 <ul class="menu_items">
177 <ul class="menu_items">
172 <li>
178 <li>
173 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
179 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='repositories-permissions'))}
174 </li>
180 </li>
175 </ul>
181 </ul>
176 </div>
182 </div>
177 % endif
183 % endif
178 </td>
184 </td>
179 </tr>
185 </tr>
180 %endfor
186 %endfor
181 <tr class="new_members" id="add_perm_input"></tr>
187 <tr class="new_members" id="add_perm_input"></tr>
182
188
183 <tr>
189 <tr>
184 <td></td>
190 <td></td>
185 <td></td>
191 <td></td>
186 <td></td>
192 <td></td>
187 <td></td>
193 <td></td>
188 <td></td>
194 <td></td>
189 <td>
195 <td>
190 <span id="add_perm" class="link">
196 <span id="add_perm" class="link">
191 ${_('Add user/user group')}
197 ${_('Add user/user group')}
192 </span>
198 </span>
193 </td>
199 </td>
194 <td></td>
200 <td></td>
195 </tr>
201 </tr>
196
202
197 </table>
203 </table>
198
204
199 <div class="buttons">
205 <div class="buttons">
200 ${h.submit('save',_('Save'),class_="btn btn-primary")}
206 ${h.submit('save',_('Save'),class_="btn btn-primary")}
201 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
207 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
202 </div>
208 </div>
203 ${h.end_form()}
209 ${h.end_form()}
204 </div>
210 </div>
205 </div>
211 </div>
206
212
207 <script type="text/javascript">
213 <script type="text/javascript">
208 $('#add_perm').on('click', function(e){
214 $('#add_perm').on('click', function(e){
209 addNewPermInput($(this), 'repository');
215 addNewPermInput($(this), 'repository');
210 });
216 });
211 $('.revoke_perm').on('click', function(e){
217 $('.revoke_perm').on('click', function(e){
212 markRevokePermInput($(this), 'repository');
218 markRevokePermInput($(this), 'repository');
213 });
219 });
214 quick_repo_menu();
220 quick_repo_menu();
215
221
216 var setPrivateRepo = function (elem, private) {
222 var setPrivateRepo = function (elem, private) {
217 var $elem = $(elem)
223 var $elem = $(elem)
218 if ($elem.hasClass('disabled')) {
224 if ($elem.hasClass('disabled')) {
219 return
225 return
220 }
226 }
221 $elem.addClass('disabled');
227 $elem.addClass('disabled');
222 $elem.css({"opacity": 0.3})
228 $elem.css({"opacity": 0.3})
223
229
224 var postData = {
230 var postData = {
225 'csrf_token': CSRF_TOKEN,
231 'csrf_token': CSRF_TOKEN,
226 'private': private
232 'private': private
227 };
233 };
228
234
229 var success = function(o) {
235 var success = function(o) {
230 var defaultUrl = pyroutes.url('edit_repo_perms', {"repo_name": templateContext.repo_name});
236 var defaultUrl = pyroutes.url('edit_repo_perms', {"repo_name": templateContext.repo_name});
231 window.location = o.redirect_url || defaultUrl;
237 window.location = o.redirect_url || defaultUrl;
232 };
238 };
233
239
234 ajaxPOST(
240 ajaxPOST(
235 pyroutes.url('edit_repo_perms_set_private', {"repo_name": templateContext.repo_name}),
241 pyroutes.url('edit_repo_perms_set_private', {"repo_name": templateContext.repo_name}),
236 postData,
242 postData,
237 success);
243 success);
238 }
244 }
239 </script>
245 </script>
@@ -1,221 +1,231 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">
5 <h3 class="panel-title">
6 <i class="icon-user-group" title="${_('User group')}"></i>
6 <i class="icon-user-group" title="${_('User group')}"></i>
7 ${h.link_to_group(c.user_group.users_group_name)}
7 ${h.link_to_group(c.user_group.users_group_name)}
8 - ${_('Access Permissions')}
8 - ${_('Access Permissions')}
9 </h3>
9 </h3>
10 </div>
10 </div>
11 <div class="panel-body">
11 <div class="panel-body">
12 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
12 ${h.secure_form(h.route_path('edit_user_group_perms_update', user_group_id=c.user_group.users_group_id), request=request)}
13 <table id="permissions_manage" class="rctable permissions">
13 <table id="permissions_manage" class="rctable permissions">
14 <tr>
14 <tr>
15 <th class="td-radio">${_('None')}</th>
15 <th class="td-radio">${_('None')}</th>
16 <th class="td-radio">${_('Read')}</th>
16 <th class="td-radio">${_('Read')}</th>
17 <th class="td-radio">${_('Write')}</th>
17 <th class="td-radio">${_('Write')}</th>
18 <th class="td-radio">${_('Admin')}</th>
18 <th class="td-radio">${_('Admin')}</th>
19 <th>${_('User/User Group')}</th>
19 <th>${_('User/User Group')}</th>
20 <th class="td-action"></th>
20 <th class="td-action"></th>
21 <th class="td-action"></th>
21 <th class="td-action"></th>
22 </tr>
22 </tr>
23 ## USERS
23 ## USERS
24 %for _user in c.user_group.permissions():
24 %for _user in c.user_group.permissions():
25 ## super admin/owner row
25 ## super admin/owner row
26 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
26 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
27 <tr class="perm_admin_row">
27 <tr class="perm_admin_row">
28 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
28 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
29 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
29 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
30 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
30 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
31 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
31 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
32 <td class="td-user">
32 <td class="td-user">
33 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
33 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
34 <span class="user">
34 <span class="user">
35 ${h.link_to_user(_user.username)}
35 ${h.link_to_user(_user.username)}
36 %if getattr(_user, 'admin_row', None):
36 %if getattr(_user, 'admin_row', None):
37 (${_('super-admin')})
37 (${_('super-admin')})
38 %endif
38 %endif
39 %if getattr(_user, 'owner_row', None):
39 %if getattr(_user, 'owner_row', None):
40 (${_('owner')})
40 (${_('owner')})
41 %endif
41 %endif
42 </span>
42 </span>
43 </td>
43 </td>
44 <td></td>
44 <td></td>
45 <td class="quick_repo_menu">
45 <td class="quick_repo_menu">
46 % if c.rhodecode_user.is_admin:
46 % if c.rhodecode_user.is_admin:
47 <i class="icon-more"></i>
47 <i class="icon-more"></i>
48 <div class="menu_items_container" style="display: none;">
48 <div class="menu_items_container" style="display: none;">
49 <ul class="menu_items">
49 <ul class="menu_items">
50 <li>
50 <li>
51 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
51 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
52 </li>
52 </li>
53 </ul>
53 </ul>
54 </div>
54 </div>
55 % endif
55 % endif
56 </td>
56 </td>
57 </tr>
57 </tr>
58 %else:
58 %else:
59 ##forbid revoking permission from yourself, except if you're an super admin
59 ##forbid revoking permission from yourself, except if you're an super admin
60 <tr>
60 <tr>
61 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
61 %if c.rhodecode_user.user_id != _user.user_id or c.rhodecode_user.is_admin:
62 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
62 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none')}</td>
63 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
63 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read')}</td>
64 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
64 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write')}</td>
65 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
65 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin')}</td>
66 <td class="td-user">
66 <td class="td-user">
67 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
67 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
68 <span class="user">
68 <span class="user">
69 % if _user.username == h.DEFAULT_USER:
69 % if _user.username == h.DEFAULT_USER:
70 ${h.DEFAULT_USER}
70 ${h.DEFAULT_USER}
71 % if _user.active:
71 % if _user.active:
72 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
72 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
73 % else:
73 % else:
74 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
74 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
75 % endif
75 % endif
76 % else:
76 % else:
77 % if getattr(_user, 'duplicate_perm', None):
78 <span class="user-perm-duplicate">
77 ${h.link_to_user(_user.username)}
79 ${h.link_to_user(_user.username)}
78 %if getattr(_user, 'duplicate_perm', None):
80 <span class="tooltip" title="${_('This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually.')}">(${_('inactive duplicate')})
79 (${_('inactive duplicate')})
81 </span>
82 </span>
83 % else:
84 ${h.link_to_user(_user.username)}
80 %endif
85 % endif
81 % endif
86 % endif
82 </span>
87 </span>
83 </td>
88 </td>
84 <td class="td-action">
89 <td class="td-action">
85 %if _user.username != h.DEFAULT_USER:
90 %if _user.username != h.DEFAULT_USER:
86 <span class="btn btn-link btn-danger revoke_perm"
91 <span class="btn btn-link btn-danger revoke_perm"
87 member="${_user.user_id}" member_type="user">
92 member="${_user.user_id}" member_type="user">
88 ${_('Remove')}
93 ${_('Remove')}
89 </span>
94 </span>
90 %endif
95 %endif
91 </td>
96 </td>
92 <td class="quick_repo_menu">
97 <td class="quick_repo_menu">
93 % if c.rhodecode_user.is_admin:
98 % if c.rhodecode_user.is_admin:
94 <i class="icon-more"></i>
99 <i class="icon-more"></i>
95 <div class="menu_items_container" style="display: none;">
100 <div class="menu_items_container" style="display: none;">
96 <ul class="menu_items">
101 <ul class="menu_items">
97 <li>
102 <li>
98 % if _user.username == h.DEFAULT_USER:
103 % if _user.username == h.DEFAULT_USER:
99 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
104 ${h.link_to('show permissions', h.route_path('admin_permissions_overview', _anchor='user-groups-permissions'))}
100 % else:
105 % else:
101 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
106 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
102 % endif
107 % endif
103 </li>
108 </li>
104 </ul>
109 </ul>
105 </div>
110 </div>
106 % endif
111 % endif
107 </td>
112 </td>
108 %else:
113 %else:
109 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
114 ## special case for currently logged-in user permissions, we make sure he cannot take his own permissions
110 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
115 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.none', disabled="disabled")}</td>
111 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
116 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.read', disabled="disabled")}</td>
112 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
117 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.write', disabled="disabled")}</td>
113 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
118 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'usergroup.admin', disabled="disabled")}</td>
114 <td class="td-user">
119 <td class="td-user">
115 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
120 ${base.gravatar(_user.email, 16, user=_user, tooltip=True)}
116 <span class="user">
121 <span class="user">
117 % if _user.username == h.DEFAULT_USER:
122 % if _user.username == h.DEFAULT_USER:
118 ${h.DEFAULT_USER}
123 ${h.DEFAULT_USER}
119 % if _user.active:
124 % if _user.active:
120 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
125 <span class="user-perm-help-text"> - ${_('permission for other logged in and anonymous users')}</span>
121 % else:
126 % else:
122 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
127 <span class="user-perm-help-text"> - ${_('permission for other logged in users')}</span>
123 % endif
128 % endif
124 % else:
129 % else:
130 % if getattr(_user, 'duplicate_perm', None):
131 <span class="user-perm-duplicate">
125 ${h.link_to_user(_user.username)}
132 ${h.link_to_user(_user.username)}
126 %if getattr(_user, 'duplicate_perm', None):
133 <span class="tooltip" title="${_('This entry is a duplicate, most probably left-over from previously set permission. This user has a higher permission set, so this entry is inactive. Please revoke this permission manually.')}">(${_('inactive duplicate')})
127 (${_('inactive duplicate')})
134 </span>
135 </span>
136 % else:
137 ${h.link_to_user(_user.username)}
128 %endif
138 % endif
129 % endif
139 % endif
130 <span class="user-perm-help-text">(${_('delegated admin')})</span>
140 <span class="user-perm-help-text">(${_('delegated admin')})</span>
131 </span>
141 </span>
132 </td>
142 </td>
133 <td></td>
143 <td></td>
134 <td class="quick_repo_menu">
144 <td class="quick_repo_menu">
135 % if c.rhodecode_user.is_admin:
145 % if c.rhodecode_user.is_admin:
136 <i class="icon-more"></i>
146 <i class="icon-more"></i>
137 <div class="menu_items_container" style="display: none;">
147 <div class="menu_items_container" style="display: none;">
138 <ul class="menu_items">
148 <ul class="menu_items">
139 <li>
149 <li>
140 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
150 ${h.link_to('show permissions', h.route_path('edit_user_perms_summary', user_id=_user.user_id, _anchor='user-groups-permissions'))}
141 </li>
151 </li>
142 </ul>
152 </ul>
143 </div>
153 </div>
144 % endif
154 % endif
145 </td>
155 </td>
146 %endif
156 %endif
147 </tr>
157 </tr>
148 %endif
158 %endif
149 %endfor
159 %endfor
150
160
151 ## USER GROUPS
161 ## USER GROUPS
152 %for _user_group in c.user_group.permission_user_groups(with_members=True):
162 %for _user_group in c.user_group.permission_user_groups(with_members=True):
153 <tr>
163 <tr>
154 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
164 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.none')}</td>
155 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
165 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.read')}</td>
156 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
166 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.write')}</td>
157 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
167 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'usergroup.admin')}</td>
158 <td class="td-user">
168 <td class="td-user">
159 <i class="icon-user-group"></i>
169 <i class="icon-user-group"></i>
160 %if c.is_super_admin:
170 %if c.is_super_admin:
161 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
171 <a href="${h.route_path('edit_user_group',user_group_id=_user_group.users_group_id)}">
162 ${_user_group.users_group_name}
172 ${_user_group.users_group_name}
163 </a>
173 </a>
164 %else:
174 %else:
165 ${h.link_to_group(_user_group.users_group_name)}
175 ${h.link_to_group(_user_group.users_group_name)}
166 %endif
176 %endif
167 (${_('members')}: ${len(_user_group.members)})
177 (${_('members')}: ${len(_user_group.members)})
168 </td>
178 </td>
169 <td class="td-action">
179 <td class="td-action">
170 <span class="btn btn-link btn-danger revoke_perm"
180 <span class="btn btn-link btn-danger revoke_perm"
171 member="${_user_group.users_group_id}" member_type="user_group">
181 member="${_user_group.users_group_id}" member_type="user_group">
172 ${_('Remove')}
182 ${_('Remove')}
173 </span>
183 </span>
174 </td>
184 </td>
175 <td class="quick_repo_menu">
185 <td class="quick_repo_menu">
176 % if c.rhodecode_user.is_admin:
186 % if c.rhodecode_user.is_admin:
177 <i class="icon-more"></i>
187 <i class="icon-more"></i>
178 <div class="menu_items_container" style="display: none;">
188 <div class="menu_items_container" style="display: none;">
179 <ul class="menu_items">
189 <ul class="menu_items">
180 <li>
190 <li>
181 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='user-groups-permissions'))}
191 ${h.link_to('show permissions', h.route_path('edit_user_group_perms_summary', user_group_id=_user_group.users_group_id, _anchor='user-groups-permissions'))}
182 </li>
192 </li>
183 </ul>
193 </ul>
184 </div>
194 </div>
185 % endif
195 % endif
186 </td>
196 </td>
187 </tr>
197 </tr>
188 %endfor
198 %endfor
189 <tr class="new_members" id="add_perm_input"></tr>
199 <tr class="new_members" id="add_perm_input"></tr>
190 <tr>
200 <tr>
191 <td></td>
201 <td></td>
192 <td></td>
202 <td></td>
193 <td></td>
203 <td></td>
194 <td></td>
204 <td></td>
195 <td></td>
205 <td></td>
196 <td>
206 <td>
197 <span id="add_perm" class="link">
207 <span id="add_perm" class="link">
198 ${_('Add user/user group')}
208 ${_('Add user/user group')}
199 </span>
209 </span>
200 </td>
210 </td>
201 <td></td>
211 <td></td>
202 </tr>
212 </tr>
203 </table>
213 </table>
204
214
205 <div class="buttons">
215 <div class="buttons">
206 ${h.submit('save',_('Save'),class_="btn btn-primary")}
216 ${h.submit('save',_('Save'),class_="btn btn-primary")}
207 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
217 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
208 </div>
218 </div>
209 ${h.end_form()}
219 ${h.end_form()}
210 </div>
220 </div>
211 </div>
221 </div>
212
222
213 <script type="text/javascript">
223 <script type="text/javascript">
214 $('#add_perm').on('click', function(e){
224 $('#add_perm').on('click', function(e){
215 addNewPermInput($(this), 'usergroup');
225 addNewPermInput($(this), 'usergroup');
216 });
226 });
217 $('.revoke_perm').on('click', function(e){
227 $('.revoke_perm').on('click', function(e){
218 markRevokePermInput($(this), 'usergroup');
228 markRevokePermInput($(this), 'usergroup');
219 });
229 });
220 quick_repo_menu()
230 quick_repo_menu()
221 </script>
231 </script>
General Comments 0
You need to be logged in to leave comments. Login now