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