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