##// END OF EJS Templates
last-update: use smarter calculation that picks the newer dates first...
marcink -
r2812:24d6ff87 default
parent child Browse files
Show More
@@ -1,4530 +1,4535 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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.user_group_description)
1330 return h.escape(self.user_group_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 @classmethod
1347 @classmethod
1348 def _load_sync(cls, group_data):
1348 def _load_sync(cls, group_data):
1349 if group_data:
1349 if group_data:
1350 return group_data.get('extern_type')
1350 return group_data.get('extern_type')
1351
1351
1352 @property
1352 @property
1353 def sync(self):
1353 def sync(self):
1354 return self._load_sync(self.group_data)
1354 return self._load_sync(self.group_data)
1355
1355
1356 def __unicode__(self):
1356 def __unicode__(self):
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1357 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1358 self.users_group_id,
1358 self.users_group_id,
1359 self.users_group_name)
1359 self.users_group_name)
1360
1360
1361 @classmethod
1361 @classmethod
1362 def get_by_group_name(cls, group_name, cache=False,
1362 def get_by_group_name(cls, group_name, cache=False,
1363 case_insensitive=False):
1363 case_insensitive=False):
1364 if case_insensitive:
1364 if case_insensitive:
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1365 q = cls.query().filter(func.lower(cls.users_group_name) ==
1366 func.lower(group_name))
1366 func.lower(group_name))
1367
1367
1368 else:
1368 else:
1369 q = cls.query().filter(cls.users_group_name == group_name)
1369 q = cls.query().filter(cls.users_group_name == group_name)
1370 if cache:
1370 if cache:
1371 q = q.options(
1371 q = q.options(
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1372 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1373 return q.scalar()
1373 return q.scalar()
1374
1374
1375 @classmethod
1375 @classmethod
1376 def get(cls, user_group_id, cache=False):
1376 def get(cls, user_group_id, cache=False):
1377 if not user_group_id:
1377 if not user_group_id:
1378 return
1378 return
1379
1379
1380 user_group = cls.query()
1380 user_group = cls.query()
1381 if cache:
1381 if cache:
1382 user_group = user_group.options(
1382 user_group = user_group.options(
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1383 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1384 return user_group.get(user_group_id)
1384 return user_group.get(user_group_id)
1385
1385
1386 def permissions(self, with_admins=True, with_owner=True):
1386 def permissions(self, with_admins=True, with_owner=True):
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1387 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1388 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1389 joinedload(UserUserGroupToPerm.user),
1389 joinedload(UserUserGroupToPerm.user),
1390 joinedload(UserUserGroupToPerm.permission),)
1390 joinedload(UserUserGroupToPerm.permission),)
1391
1391
1392 # get owners and admins and permissions. We do a trick of re-writing
1392 # get owners and admins and permissions. We do a trick of re-writing
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1393 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1394 # has a global reference and changing one object propagates to all
1394 # has a global reference and changing one object propagates to all
1395 # others. This means if admin is also an owner admin_row that change
1395 # others. This means if admin is also an owner admin_row that change
1396 # would propagate to both objects
1396 # would propagate to both objects
1397 perm_rows = []
1397 perm_rows = []
1398 for _usr in q.all():
1398 for _usr in q.all():
1399 usr = AttributeDict(_usr.user.get_dict())
1399 usr = AttributeDict(_usr.user.get_dict())
1400 usr.permission = _usr.permission.permission_name
1400 usr.permission = _usr.permission.permission_name
1401 perm_rows.append(usr)
1401 perm_rows.append(usr)
1402
1402
1403 # filter the perm rows by 'default' first and then sort them by
1403 # filter the perm rows by 'default' first and then sort them by
1404 # admin,write,read,none permissions sorted again alphabetically in
1404 # admin,write,read,none permissions sorted again alphabetically in
1405 # each group
1405 # each group
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1406 perm_rows = sorted(perm_rows, key=display_user_sort)
1407
1407
1408 _admin_perm = 'usergroup.admin'
1408 _admin_perm = 'usergroup.admin'
1409 owner_row = []
1409 owner_row = []
1410 if with_owner:
1410 if with_owner:
1411 usr = AttributeDict(self.user.get_dict())
1411 usr = AttributeDict(self.user.get_dict())
1412 usr.owner_row = True
1412 usr.owner_row = True
1413 usr.permission = _admin_perm
1413 usr.permission = _admin_perm
1414 owner_row.append(usr)
1414 owner_row.append(usr)
1415
1415
1416 super_admin_rows = []
1416 super_admin_rows = []
1417 if with_admins:
1417 if with_admins:
1418 for usr in User.get_all_super_admins():
1418 for usr in User.get_all_super_admins():
1419 # if this admin is also owner, don't double the record
1419 # if this admin is also owner, don't double the record
1420 if usr.user_id == owner_row[0].user_id:
1420 if usr.user_id == owner_row[0].user_id:
1421 owner_row[0].admin_row = True
1421 owner_row[0].admin_row = True
1422 else:
1422 else:
1423 usr = AttributeDict(usr.get_dict())
1423 usr = AttributeDict(usr.get_dict())
1424 usr.admin_row = True
1424 usr.admin_row = True
1425 usr.permission = _admin_perm
1425 usr.permission = _admin_perm
1426 super_admin_rows.append(usr)
1426 super_admin_rows.append(usr)
1427
1427
1428 return super_admin_rows + owner_row + perm_rows
1428 return super_admin_rows + owner_row + perm_rows
1429
1429
1430 def permission_user_groups(self):
1430 def permission_user_groups(self):
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1431 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1432 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1433 joinedload(UserGroupUserGroupToPerm.target_user_group),
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1434 joinedload(UserGroupUserGroupToPerm.permission),)
1435
1435
1436 perm_rows = []
1436 perm_rows = []
1437 for _user_group in q.all():
1437 for _user_group in q.all():
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1438 usr = AttributeDict(_user_group.user_group.get_dict())
1439 usr.permission = _user_group.permission.permission_name
1439 usr.permission = _user_group.permission.permission_name
1440 perm_rows.append(usr)
1440 perm_rows.append(usr)
1441
1441
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1442 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1443 return perm_rows
1443 return perm_rows
1444
1444
1445 def _get_default_perms(self, user_group, suffix=''):
1445 def _get_default_perms(self, user_group, suffix=''):
1446 from rhodecode.model.permission import PermissionModel
1446 from rhodecode.model.permission import PermissionModel
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1447 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1448
1448
1449 def get_default_perms(self, suffix=''):
1449 def get_default_perms(self, suffix=''):
1450 return self._get_default_perms(self, suffix)
1450 return self._get_default_perms(self, suffix)
1451
1451
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1452 def get_api_data(self, with_group_members=True, include_secrets=False):
1453 """
1453 """
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1454 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1455 basically forwarded.
1455 basically forwarded.
1456
1456
1457 """
1457 """
1458 user_group = self
1458 user_group = self
1459 data = {
1459 data = {
1460 'users_group_id': user_group.users_group_id,
1460 'users_group_id': user_group.users_group_id,
1461 'group_name': user_group.users_group_name,
1461 'group_name': user_group.users_group_name,
1462 'group_description': user_group.user_group_description,
1462 'group_description': user_group.user_group_description,
1463 'active': user_group.users_group_active,
1463 'active': user_group.users_group_active,
1464 'owner': user_group.user.username,
1464 'owner': user_group.user.username,
1465 'sync': user_group.sync,
1465 'sync': user_group.sync,
1466 'owner_email': user_group.user.email,
1466 'owner_email': user_group.user.email,
1467 }
1467 }
1468
1468
1469 if with_group_members:
1469 if with_group_members:
1470 users = []
1470 users = []
1471 for user in user_group.members:
1471 for user in user_group.members:
1472 user = user.user
1472 user = user.user
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1473 users.append(user.get_api_data(include_secrets=include_secrets))
1474 data['users'] = users
1474 data['users'] = users
1475
1475
1476 return data
1476 return data
1477
1477
1478
1478
1479 class UserGroupMember(Base, BaseModel):
1479 class UserGroupMember(Base, BaseModel):
1480 __tablename__ = 'users_groups_members'
1480 __tablename__ = 'users_groups_members'
1481 __table_args__ = (
1481 __table_args__ = (
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1484 )
1484 )
1485
1485
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1486 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1487 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1489
1489
1490 user = relationship('User', lazy='joined')
1490 user = relationship('User', lazy='joined')
1491 users_group = relationship('UserGroup')
1491 users_group = relationship('UserGroup')
1492
1492
1493 def __init__(self, gr_id='', u_id=''):
1493 def __init__(self, gr_id='', u_id=''):
1494 self.users_group_id = gr_id
1494 self.users_group_id = gr_id
1495 self.user_id = u_id
1495 self.user_id = u_id
1496
1496
1497
1497
1498 class RepositoryField(Base, BaseModel):
1498 class RepositoryField(Base, BaseModel):
1499 __tablename__ = 'repositories_fields'
1499 __tablename__ = 'repositories_fields'
1500 __table_args__ = (
1500 __table_args__ = (
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1501 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1504 )
1504 )
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1505 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1506
1506
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1507 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1508 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1509 field_key = Column("field_key", String(250))
1509 field_key = Column("field_key", String(250))
1510 field_label = Column("field_label", String(1024), nullable=False)
1510 field_label = Column("field_label", String(1024), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1511 field_value = Column("field_value", String(10000), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1512 field_desc = Column("field_desc", String(1024), nullable=False)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1513 field_type = Column("field_type", String(255), nullable=False, unique=None)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1515
1515
1516 repository = relationship('Repository')
1516 repository = relationship('Repository')
1517
1517
1518 @property
1518 @property
1519 def field_key_prefixed(self):
1519 def field_key_prefixed(self):
1520 return 'ex_%s' % self.field_key
1520 return 'ex_%s' % self.field_key
1521
1521
1522 @classmethod
1522 @classmethod
1523 def un_prefix_key(cls, key):
1523 def un_prefix_key(cls, key):
1524 if key.startswith(cls.PREFIX):
1524 if key.startswith(cls.PREFIX):
1525 return key[len(cls.PREFIX):]
1525 return key[len(cls.PREFIX):]
1526 return key
1526 return key
1527
1527
1528 @classmethod
1528 @classmethod
1529 def get_by_key_name(cls, key, repo):
1529 def get_by_key_name(cls, key, repo):
1530 row = cls.query()\
1530 row = cls.query()\
1531 .filter(cls.repository == repo)\
1531 .filter(cls.repository == repo)\
1532 .filter(cls.field_key == key).scalar()
1532 .filter(cls.field_key == key).scalar()
1533 return row
1533 return row
1534
1534
1535
1535
1536 class Repository(Base, BaseModel):
1536 class Repository(Base, BaseModel):
1537 __tablename__ = 'repositories'
1537 __tablename__ = 'repositories'
1538 __table_args__ = (
1538 __table_args__ = (
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1539 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1541 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1542 )
1542 )
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1543 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1544 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1545 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1546
1546
1547 STATE_CREATED = 'repo_state_created'
1547 STATE_CREATED = 'repo_state_created'
1548 STATE_PENDING = 'repo_state_pending'
1548 STATE_PENDING = 'repo_state_pending'
1549 STATE_ERROR = 'repo_state_error'
1549 STATE_ERROR = 'repo_state_error'
1550
1550
1551 LOCK_AUTOMATIC = 'lock_auto'
1551 LOCK_AUTOMATIC = 'lock_auto'
1552 LOCK_API = 'lock_api'
1552 LOCK_API = 'lock_api'
1553 LOCK_WEB = 'lock_web'
1553 LOCK_WEB = 'lock_web'
1554 LOCK_PULL = 'lock_pull'
1554 LOCK_PULL = 'lock_pull'
1555
1555
1556 NAME_SEP = URL_SEP
1556 NAME_SEP = URL_SEP
1557
1557
1558 repo_id = Column(
1558 repo_id = Column(
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1559 "repo_id", Integer(), nullable=False, unique=True, default=None,
1560 primary_key=True)
1560 primary_key=True)
1561 _repo_name = Column(
1561 _repo_name = Column(
1562 "repo_name", Text(), nullable=False, default=None)
1562 "repo_name", Text(), nullable=False, default=None)
1563 _repo_name_hash = Column(
1563 _repo_name_hash = Column(
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1564 "repo_name_hash", String(255), nullable=False, unique=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1565 repo_state = Column("repo_state", String(255), nullable=True)
1566
1566
1567 clone_uri = Column(
1567 clone_uri = Column(
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1568 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1569 default=None)
1569 default=None)
1570 push_uri = Column(
1570 push_uri = Column(
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1571 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1572 default=None)
1572 default=None)
1573 repo_type = Column(
1573 repo_type = Column(
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1574 "repo_type", String(255), nullable=False, unique=False, default=None)
1575 user_id = Column(
1575 user_id = Column(
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1576 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1577 unique=False, default=None)
1577 unique=False, default=None)
1578 private = Column(
1578 private = Column(
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1579 "private", Boolean(), nullable=True, unique=None, default=None)
1580 enable_statistics = Column(
1580 enable_statistics = Column(
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1581 "statistics", Boolean(), nullable=True, unique=None, default=True)
1582 enable_downloads = Column(
1582 enable_downloads = Column(
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1583 "downloads", Boolean(), nullable=True, unique=None, default=True)
1584 description = Column(
1584 description = Column(
1585 "description", String(10000), nullable=True, unique=None, default=None)
1585 "description", String(10000), nullable=True, unique=None, default=None)
1586 created_on = Column(
1586 created_on = Column(
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1587 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1588 default=datetime.datetime.now)
1588 default=datetime.datetime.now)
1589 updated_on = Column(
1589 updated_on = Column(
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1590 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1591 default=datetime.datetime.now)
1591 default=datetime.datetime.now)
1592 _landing_revision = Column(
1592 _landing_revision = Column(
1593 "landing_revision", String(255), nullable=False, unique=False,
1593 "landing_revision", String(255), nullable=False, unique=False,
1594 default=None)
1594 default=None)
1595 enable_locking = Column(
1595 enable_locking = Column(
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1596 "enable_locking", Boolean(), nullable=False, unique=None,
1597 default=False)
1597 default=False)
1598 _locked = Column(
1598 _locked = Column(
1599 "locked", String(255), nullable=True, unique=False, default=None)
1599 "locked", String(255), nullable=True, unique=False, default=None)
1600 _changeset_cache = Column(
1600 _changeset_cache = Column(
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1601 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1602
1602
1603 fork_id = Column(
1603 fork_id = Column(
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1604 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1605 nullable=True, unique=False, default=None)
1605 nullable=True, unique=False, default=None)
1606 group_id = Column(
1606 group_id = Column(
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1607 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1608 unique=False, default=None)
1608 unique=False, default=None)
1609
1609
1610 user = relationship('User', lazy='joined')
1610 user = relationship('User', lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1611 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1612 group = relationship('RepoGroup', lazy='joined')
1613 repo_to_perm = relationship(
1613 repo_to_perm = relationship(
1614 'UserRepoToPerm', cascade='all',
1614 'UserRepoToPerm', cascade='all',
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1615 order_by='UserRepoToPerm.repo_to_perm_id')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1616 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1617 stats = relationship('Statistics', cascade='all', uselist=False)
1618
1618
1619 followers = relationship(
1619 followers = relationship(
1620 'UserFollowing',
1620 'UserFollowing',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1622 cascade='all')
1622 cascade='all')
1623 extra_fields = relationship(
1623 extra_fields = relationship(
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1624 'RepositoryField', cascade="all, delete, delete-orphan")
1625 logs = relationship('UserLog')
1625 logs = relationship('UserLog')
1626 comments = relationship(
1626 comments = relationship(
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1627 'ChangesetComment', cascade="all, delete, delete-orphan")
1628 pull_requests_source = relationship(
1628 pull_requests_source = relationship(
1629 'PullRequest',
1629 'PullRequest',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1630 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1631 cascade="all, delete, delete-orphan")
1631 cascade="all, delete, delete-orphan")
1632 pull_requests_target = relationship(
1632 pull_requests_target = relationship(
1633 'PullRequest',
1633 'PullRequest',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1634 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1635 cascade="all, delete, delete-orphan")
1635 cascade="all, delete, delete-orphan")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1636 ui = relationship('RepoRhodeCodeUi', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1637 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1638 integrations = relationship('Integration',
1638 integrations = relationship('Integration',
1639 cascade="all, delete, delete-orphan")
1639 cascade="all, delete, delete-orphan")
1640
1640
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1641 scoped_tokens = relationship('UserApiKeys', cascade="all")
1642
1642
1643 def __unicode__(self):
1643 def __unicode__(self):
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1645 safe_unicode(self.repo_name))
1645 safe_unicode(self.repo_name))
1646
1646
1647 @hybrid_property
1647 @hybrid_property
1648 def description_safe(self):
1648 def description_safe(self):
1649 from rhodecode.lib import helpers as h
1649 from rhodecode.lib import helpers as h
1650 return h.escape(self.description)
1650 return h.escape(self.description)
1651
1651
1652 @hybrid_property
1652 @hybrid_property
1653 def landing_rev(self):
1653 def landing_rev(self):
1654 # always should return [rev_type, rev]
1654 # always should return [rev_type, rev]
1655 if self._landing_revision:
1655 if self._landing_revision:
1656 _rev_info = self._landing_revision.split(':')
1656 _rev_info = self._landing_revision.split(':')
1657 if len(_rev_info) < 2:
1657 if len(_rev_info) < 2:
1658 _rev_info.insert(0, 'rev')
1658 _rev_info.insert(0, 'rev')
1659 return [_rev_info[0], _rev_info[1]]
1659 return [_rev_info[0], _rev_info[1]]
1660 return [None, None]
1660 return [None, None]
1661
1661
1662 @landing_rev.setter
1662 @landing_rev.setter
1663 def landing_rev(self, val):
1663 def landing_rev(self, val):
1664 if ':' not in val:
1664 if ':' not in val:
1665 raise ValueError('value must be delimited with `:` and consist '
1665 raise ValueError('value must be delimited with `:` and consist '
1666 'of <rev_type>:<rev>, got %s instead' % val)
1666 'of <rev_type>:<rev>, got %s instead' % val)
1667 self._landing_revision = val
1667 self._landing_revision = val
1668
1668
1669 @hybrid_property
1669 @hybrid_property
1670 def locked(self):
1670 def locked(self):
1671 if self._locked:
1671 if self._locked:
1672 user_id, timelocked, reason = self._locked.split(':')
1672 user_id, timelocked, reason = self._locked.split(':')
1673 lock_values = int(user_id), timelocked, reason
1673 lock_values = int(user_id), timelocked, reason
1674 else:
1674 else:
1675 lock_values = [None, None, None]
1675 lock_values = [None, None, None]
1676 return lock_values
1676 return lock_values
1677
1677
1678 @locked.setter
1678 @locked.setter
1679 def locked(self, val):
1679 def locked(self, val):
1680 if val and isinstance(val, (list, tuple)):
1680 if val and isinstance(val, (list, tuple)):
1681 self._locked = ':'.join(map(str, val))
1681 self._locked = ':'.join(map(str, val))
1682 else:
1682 else:
1683 self._locked = None
1683 self._locked = None
1684
1684
1685 @hybrid_property
1685 @hybrid_property
1686 def changeset_cache(self):
1686 def changeset_cache(self):
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1687 from rhodecode.lib.vcs.backends.base import EmptyCommit
1688 dummy = EmptyCommit().__json__()
1688 dummy = EmptyCommit().__json__()
1689 if not self._changeset_cache:
1689 if not self._changeset_cache:
1690 return dummy
1690 return dummy
1691 try:
1691 try:
1692 return json.loads(self._changeset_cache)
1692 return json.loads(self._changeset_cache)
1693 except TypeError:
1693 except TypeError:
1694 return dummy
1694 return dummy
1695 except Exception:
1695 except Exception:
1696 log.error(traceback.format_exc())
1696 log.error(traceback.format_exc())
1697 return dummy
1697 return dummy
1698
1698
1699 @changeset_cache.setter
1699 @changeset_cache.setter
1700 def changeset_cache(self, val):
1700 def changeset_cache(self, val):
1701 try:
1701 try:
1702 self._changeset_cache = json.dumps(val)
1702 self._changeset_cache = json.dumps(val)
1703 except Exception:
1703 except Exception:
1704 log.error(traceback.format_exc())
1704 log.error(traceback.format_exc())
1705
1705
1706 @hybrid_property
1706 @hybrid_property
1707 def repo_name(self):
1707 def repo_name(self):
1708 return self._repo_name
1708 return self._repo_name
1709
1709
1710 @repo_name.setter
1710 @repo_name.setter
1711 def repo_name(self, value):
1711 def repo_name(self, value):
1712 self._repo_name = value
1712 self._repo_name = value
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1713 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1714
1714
1715 @classmethod
1715 @classmethod
1716 def normalize_repo_name(cls, repo_name):
1716 def normalize_repo_name(cls, repo_name):
1717 """
1717 """
1718 Normalizes os specific repo_name to the format internally stored inside
1718 Normalizes os specific repo_name to the format internally stored inside
1719 database using URL_SEP
1719 database using URL_SEP
1720
1720
1721 :param cls:
1721 :param cls:
1722 :param repo_name:
1722 :param repo_name:
1723 """
1723 """
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1724 return cls.NAME_SEP.join(repo_name.split(os.sep))
1725
1725
1726 @classmethod
1726 @classmethod
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1727 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1728 session = Session()
1728 session = Session()
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1729 q = session.query(cls).filter(cls.repo_name == repo_name)
1730
1730
1731 if cache:
1731 if cache:
1732 if identity_cache:
1732 if identity_cache:
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1733 val = cls.identity_cache(session, 'repo_name', repo_name)
1734 if val:
1734 if val:
1735 return val
1735 return val
1736 else:
1736 else:
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1737 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1738 q = q.options(
1738 q = q.options(
1739 FromCache("sql_cache_short", cache_key))
1739 FromCache("sql_cache_short", cache_key))
1740
1740
1741 return q.scalar()
1741 return q.scalar()
1742
1742
1743 @classmethod
1743 @classmethod
1744 def get_by_id_or_repo_name(cls, repoid):
1744 def get_by_id_or_repo_name(cls, repoid):
1745 if isinstance(repoid, (int, long)):
1745 if isinstance(repoid, (int, long)):
1746 try:
1746 try:
1747 repo = cls.get(repoid)
1747 repo = cls.get(repoid)
1748 except ValueError:
1748 except ValueError:
1749 repo = None
1749 repo = None
1750 else:
1750 else:
1751 repo = cls.get_by_repo_name(repoid)
1751 repo = cls.get_by_repo_name(repoid)
1752 return repo
1752 return repo
1753
1753
1754 @classmethod
1754 @classmethod
1755 def get_by_full_path(cls, repo_full_path):
1755 def get_by_full_path(cls, repo_full_path):
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1756 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1757 repo_name = cls.normalize_repo_name(repo_name)
1757 repo_name = cls.normalize_repo_name(repo_name)
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1758 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1759
1759
1760 @classmethod
1760 @classmethod
1761 def get_repo_forks(cls, repo_id):
1761 def get_repo_forks(cls, repo_id):
1762 return cls.query().filter(Repository.fork_id == repo_id)
1762 return cls.query().filter(Repository.fork_id == repo_id)
1763
1763
1764 @classmethod
1764 @classmethod
1765 def base_path(cls):
1765 def base_path(cls):
1766 """
1766 """
1767 Returns base path when all repos are stored
1767 Returns base path when all repos are stored
1768
1768
1769 :param cls:
1769 :param cls:
1770 """
1770 """
1771 q = Session().query(RhodeCodeUi)\
1771 q = Session().query(RhodeCodeUi)\
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1772 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1773 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1774 return q.one().ui_value
1774 return q.one().ui_value
1775
1775
1776 @classmethod
1776 @classmethod
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1777 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1778 case_insensitive=True):
1778 case_insensitive=True):
1779 q = Repository.query()
1779 q = Repository.query()
1780
1780
1781 if not isinstance(user_id, Optional):
1781 if not isinstance(user_id, Optional):
1782 q = q.filter(Repository.user_id == user_id)
1782 q = q.filter(Repository.user_id == user_id)
1783
1783
1784 if not isinstance(group_id, Optional):
1784 if not isinstance(group_id, Optional):
1785 q = q.filter(Repository.group_id == group_id)
1785 q = q.filter(Repository.group_id == group_id)
1786
1786
1787 if case_insensitive:
1787 if case_insensitive:
1788 q = q.order_by(func.lower(Repository.repo_name))
1788 q = q.order_by(func.lower(Repository.repo_name))
1789 else:
1789 else:
1790 q = q.order_by(Repository.repo_name)
1790 q = q.order_by(Repository.repo_name)
1791 return q.all()
1791 return q.all()
1792
1792
1793 @property
1793 @property
1794 def forks(self):
1794 def forks(self):
1795 """
1795 """
1796 Return forks of this repo
1796 Return forks of this repo
1797 """
1797 """
1798 return Repository.get_repo_forks(self.repo_id)
1798 return Repository.get_repo_forks(self.repo_id)
1799
1799
1800 @property
1800 @property
1801 def parent(self):
1801 def parent(self):
1802 """
1802 """
1803 Returns fork parent
1803 Returns fork parent
1804 """
1804 """
1805 return self.fork
1805 return self.fork
1806
1806
1807 @property
1807 @property
1808 def just_name(self):
1808 def just_name(self):
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1809 return self.repo_name.split(self.NAME_SEP)[-1]
1810
1810
1811 @property
1811 @property
1812 def groups_with_parents(self):
1812 def groups_with_parents(self):
1813 groups = []
1813 groups = []
1814 if self.group is None:
1814 if self.group is None:
1815 return groups
1815 return groups
1816
1816
1817 cur_gr = self.group
1817 cur_gr = self.group
1818 groups.insert(0, cur_gr)
1818 groups.insert(0, cur_gr)
1819 while 1:
1819 while 1:
1820 gr = getattr(cur_gr, 'parent_group', None)
1820 gr = getattr(cur_gr, 'parent_group', None)
1821 cur_gr = cur_gr.parent_group
1821 cur_gr = cur_gr.parent_group
1822 if gr is None:
1822 if gr is None:
1823 break
1823 break
1824 groups.insert(0, gr)
1824 groups.insert(0, gr)
1825
1825
1826 return groups
1826 return groups
1827
1827
1828 @property
1828 @property
1829 def groups_and_repo(self):
1829 def groups_and_repo(self):
1830 return self.groups_with_parents, self
1830 return self.groups_with_parents, self
1831
1831
1832 @LazyProperty
1832 @LazyProperty
1833 def repo_path(self):
1833 def repo_path(self):
1834 """
1834 """
1835 Returns base full path for that repository means where it actually
1835 Returns base full path for that repository means where it actually
1836 exists on a filesystem
1836 exists on a filesystem
1837 """
1837 """
1838 q = Session().query(RhodeCodeUi).filter(
1838 q = Session().query(RhodeCodeUi).filter(
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1839 RhodeCodeUi.ui_key == self.NAME_SEP)
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1841 return q.one().ui_value
1841 return q.one().ui_value
1842
1842
1843 @property
1843 @property
1844 def repo_full_path(self):
1844 def repo_full_path(self):
1845 p = [self.repo_path]
1845 p = [self.repo_path]
1846 # we need to split the name by / since this is how we store the
1846 # we need to split the name by / since this is how we store the
1847 # names in the database, but that eventually needs to be converted
1847 # names in the database, but that eventually needs to be converted
1848 # into a valid system path
1848 # into a valid system path
1849 p += self.repo_name.split(self.NAME_SEP)
1849 p += self.repo_name.split(self.NAME_SEP)
1850 return os.path.join(*map(safe_unicode, p))
1850 return os.path.join(*map(safe_unicode, p))
1851
1851
1852 @property
1852 @property
1853 def cache_keys(self):
1853 def cache_keys(self):
1854 """
1854 """
1855 Returns associated cache keys for that repo
1855 Returns associated cache keys for that repo
1856 """
1856 """
1857 return CacheKey.query()\
1857 return CacheKey.query()\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1858 .filter(CacheKey.cache_args == self.repo_name)\
1859 .order_by(CacheKey.cache_key)\
1859 .order_by(CacheKey.cache_key)\
1860 .all()
1860 .all()
1861
1861
1862 @property
1862 @property
1863 def cached_diffs_relative_dir(self):
1863 def cached_diffs_relative_dir(self):
1864 """
1864 """
1865 Return a relative to the repository store path of cached diffs
1865 Return a relative to the repository store path of cached diffs
1866 used for safe display for users, who shouldn't know the absolute store
1866 used for safe display for users, who shouldn't know the absolute store
1867 path
1867 path
1868 """
1868 """
1869 return os.path.join(
1869 return os.path.join(
1870 os.path.dirname(self.repo_name),
1870 os.path.dirname(self.repo_name),
1871 self.cached_diffs_dir.split(os.path.sep)[-1])
1871 self.cached_diffs_dir.split(os.path.sep)[-1])
1872
1872
1873 @property
1873 @property
1874 def cached_diffs_dir(self):
1874 def cached_diffs_dir(self):
1875 path = self.repo_full_path
1875 path = self.repo_full_path
1876 return os.path.join(
1876 return os.path.join(
1877 os.path.dirname(path),
1877 os.path.dirname(path),
1878 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1878 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1879
1879
1880 def cached_diffs(self):
1880 def cached_diffs(self):
1881 diff_cache_dir = self.cached_diffs_dir
1881 diff_cache_dir = self.cached_diffs_dir
1882 if os.path.isdir(diff_cache_dir):
1882 if os.path.isdir(diff_cache_dir):
1883 return os.listdir(diff_cache_dir)
1883 return os.listdir(diff_cache_dir)
1884 return []
1884 return []
1885
1885
1886 def shadow_repos(self):
1886 def shadow_repos(self):
1887 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1887 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1888 return [
1888 return [
1889 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1889 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1890 if x.startswith(shadow_repos_pattern)]
1890 if x.startswith(shadow_repos_pattern)]
1891
1891
1892 def get_new_name(self, repo_name):
1892 def get_new_name(self, repo_name):
1893 """
1893 """
1894 returns new full repository name based on assigned group and new new
1894 returns new full repository name based on assigned group and new new
1895
1895
1896 :param group_name:
1896 :param group_name:
1897 """
1897 """
1898 path_prefix = self.group.full_path_splitted if self.group else []
1898 path_prefix = self.group.full_path_splitted if self.group else []
1899 return self.NAME_SEP.join(path_prefix + [repo_name])
1899 return self.NAME_SEP.join(path_prefix + [repo_name])
1900
1900
1901 @property
1901 @property
1902 def _config(self):
1902 def _config(self):
1903 """
1903 """
1904 Returns db based config object.
1904 Returns db based config object.
1905 """
1905 """
1906 from rhodecode.lib.utils import make_db_config
1906 from rhodecode.lib.utils import make_db_config
1907 return make_db_config(clear_session=False, repo=self)
1907 return make_db_config(clear_session=False, repo=self)
1908
1908
1909 def permissions(self, with_admins=True, with_owner=True):
1909 def permissions(self, with_admins=True, with_owner=True):
1910 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1910 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1911 q = q.options(joinedload(UserRepoToPerm.repository),
1911 q = q.options(joinedload(UserRepoToPerm.repository),
1912 joinedload(UserRepoToPerm.user),
1912 joinedload(UserRepoToPerm.user),
1913 joinedload(UserRepoToPerm.permission),)
1913 joinedload(UserRepoToPerm.permission),)
1914
1914
1915 # get owners and admins and permissions. We do a trick of re-writing
1915 # get owners and admins and permissions. We do a trick of re-writing
1916 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1916 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1917 # has a global reference and changing one object propagates to all
1917 # has a global reference and changing one object propagates to all
1918 # others. This means if admin is also an owner admin_row that change
1918 # others. This means if admin is also an owner admin_row that change
1919 # would propagate to both objects
1919 # would propagate to both objects
1920 perm_rows = []
1920 perm_rows = []
1921 for _usr in q.all():
1921 for _usr in q.all():
1922 usr = AttributeDict(_usr.user.get_dict())
1922 usr = AttributeDict(_usr.user.get_dict())
1923 usr.permission = _usr.permission.permission_name
1923 usr.permission = _usr.permission.permission_name
1924 perm_rows.append(usr)
1924 perm_rows.append(usr)
1925
1925
1926 # filter the perm rows by 'default' first and then sort them by
1926 # filter the perm rows by 'default' first and then sort them by
1927 # admin,write,read,none permissions sorted again alphabetically in
1927 # admin,write,read,none permissions sorted again alphabetically in
1928 # each group
1928 # each group
1929 perm_rows = sorted(perm_rows, key=display_user_sort)
1929 perm_rows = sorted(perm_rows, key=display_user_sort)
1930
1930
1931 _admin_perm = 'repository.admin'
1931 _admin_perm = 'repository.admin'
1932 owner_row = []
1932 owner_row = []
1933 if with_owner:
1933 if with_owner:
1934 usr = AttributeDict(self.user.get_dict())
1934 usr = AttributeDict(self.user.get_dict())
1935 usr.owner_row = True
1935 usr.owner_row = True
1936 usr.permission = _admin_perm
1936 usr.permission = _admin_perm
1937 owner_row.append(usr)
1937 owner_row.append(usr)
1938
1938
1939 super_admin_rows = []
1939 super_admin_rows = []
1940 if with_admins:
1940 if with_admins:
1941 for usr in User.get_all_super_admins():
1941 for usr in User.get_all_super_admins():
1942 # if this admin is also owner, don't double the record
1942 # if this admin is also owner, don't double the record
1943 if usr.user_id == owner_row[0].user_id:
1943 if usr.user_id == owner_row[0].user_id:
1944 owner_row[0].admin_row = True
1944 owner_row[0].admin_row = True
1945 else:
1945 else:
1946 usr = AttributeDict(usr.get_dict())
1946 usr = AttributeDict(usr.get_dict())
1947 usr.admin_row = True
1947 usr.admin_row = True
1948 usr.permission = _admin_perm
1948 usr.permission = _admin_perm
1949 super_admin_rows.append(usr)
1949 super_admin_rows.append(usr)
1950
1950
1951 return super_admin_rows + owner_row + perm_rows
1951 return super_admin_rows + owner_row + perm_rows
1952
1952
1953 def permission_user_groups(self):
1953 def permission_user_groups(self):
1954 q = UserGroupRepoToPerm.query().filter(
1954 q = UserGroupRepoToPerm.query().filter(
1955 UserGroupRepoToPerm.repository == self)
1955 UserGroupRepoToPerm.repository == self)
1956 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1956 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1957 joinedload(UserGroupRepoToPerm.users_group),
1957 joinedload(UserGroupRepoToPerm.users_group),
1958 joinedload(UserGroupRepoToPerm.permission),)
1958 joinedload(UserGroupRepoToPerm.permission),)
1959
1959
1960 perm_rows = []
1960 perm_rows = []
1961 for _user_group in q.all():
1961 for _user_group in q.all():
1962 usr = AttributeDict(_user_group.users_group.get_dict())
1962 usr = AttributeDict(_user_group.users_group.get_dict())
1963 usr.permission = _user_group.permission.permission_name
1963 usr.permission = _user_group.permission.permission_name
1964 perm_rows.append(usr)
1964 perm_rows.append(usr)
1965
1965
1966 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1966 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1967 return perm_rows
1967 return perm_rows
1968
1968
1969 def get_api_data(self, include_secrets=False):
1969 def get_api_data(self, include_secrets=False):
1970 """
1970 """
1971 Common function for generating repo api data
1971 Common function for generating repo api data
1972
1972
1973 :param include_secrets: See :meth:`User.get_api_data`.
1973 :param include_secrets: See :meth:`User.get_api_data`.
1974
1974
1975 """
1975 """
1976 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1976 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1977 # move this methods on models level.
1977 # move this methods on models level.
1978 from rhodecode.model.settings import SettingsModel
1978 from rhodecode.model.settings import SettingsModel
1979 from rhodecode.model.repo import RepoModel
1979 from rhodecode.model.repo import RepoModel
1980
1980
1981 repo = self
1981 repo = self
1982 _user_id, _time, _reason = self.locked
1982 _user_id, _time, _reason = self.locked
1983
1983
1984 data = {
1984 data = {
1985 'repo_id': repo.repo_id,
1985 'repo_id': repo.repo_id,
1986 'repo_name': repo.repo_name,
1986 'repo_name': repo.repo_name,
1987 'repo_type': repo.repo_type,
1987 'repo_type': repo.repo_type,
1988 'clone_uri': repo.clone_uri or '',
1988 'clone_uri': repo.clone_uri or '',
1989 'push_uri': repo.push_uri or '',
1989 'push_uri': repo.push_uri or '',
1990 'url': RepoModel().get_url(self),
1990 'url': RepoModel().get_url(self),
1991 'private': repo.private,
1991 'private': repo.private,
1992 'created_on': repo.created_on,
1992 'created_on': repo.created_on,
1993 'description': repo.description_safe,
1993 'description': repo.description_safe,
1994 'landing_rev': repo.landing_rev,
1994 'landing_rev': repo.landing_rev,
1995 'owner': repo.user.username,
1995 'owner': repo.user.username,
1996 'fork_of': repo.fork.repo_name if repo.fork else None,
1996 'fork_of': repo.fork.repo_name if repo.fork else None,
1997 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1997 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1998 'enable_statistics': repo.enable_statistics,
1998 'enable_statistics': repo.enable_statistics,
1999 'enable_locking': repo.enable_locking,
1999 'enable_locking': repo.enable_locking,
2000 'enable_downloads': repo.enable_downloads,
2000 'enable_downloads': repo.enable_downloads,
2001 'last_changeset': repo.changeset_cache,
2001 'last_changeset': repo.changeset_cache,
2002 'locked_by': User.get(_user_id).get_api_data(
2002 'locked_by': User.get(_user_id).get_api_data(
2003 include_secrets=include_secrets) if _user_id else None,
2003 include_secrets=include_secrets) if _user_id else None,
2004 'locked_date': time_to_datetime(_time) if _time else None,
2004 'locked_date': time_to_datetime(_time) if _time else None,
2005 'lock_reason': _reason if _reason else None,
2005 'lock_reason': _reason if _reason else None,
2006 }
2006 }
2007
2007
2008 # TODO: mikhail: should be per-repo settings here
2008 # TODO: mikhail: should be per-repo settings here
2009 rc_config = SettingsModel().get_all_settings()
2009 rc_config = SettingsModel().get_all_settings()
2010 repository_fields = str2bool(
2010 repository_fields = str2bool(
2011 rc_config.get('rhodecode_repository_fields'))
2011 rc_config.get('rhodecode_repository_fields'))
2012 if repository_fields:
2012 if repository_fields:
2013 for f in self.extra_fields:
2013 for f in self.extra_fields:
2014 data[f.field_key_prefixed] = f.field_value
2014 data[f.field_key_prefixed] = f.field_value
2015
2015
2016 return data
2016 return data
2017
2017
2018 @classmethod
2018 @classmethod
2019 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2019 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2020 if not lock_time:
2020 if not lock_time:
2021 lock_time = time.time()
2021 lock_time = time.time()
2022 if not lock_reason:
2022 if not lock_reason:
2023 lock_reason = cls.LOCK_AUTOMATIC
2023 lock_reason = cls.LOCK_AUTOMATIC
2024 repo.locked = [user_id, lock_time, lock_reason]
2024 repo.locked = [user_id, lock_time, lock_reason]
2025 Session().add(repo)
2025 Session().add(repo)
2026 Session().commit()
2026 Session().commit()
2027
2027
2028 @classmethod
2028 @classmethod
2029 def unlock(cls, repo):
2029 def unlock(cls, repo):
2030 repo.locked = None
2030 repo.locked = None
2031 Session().add(repo)
2031 Session().add(repo)
2032 Session().commit()
2032 Session().commit()
2033
2033
2034 @classmethod
2034 @classmethod
2035 def getlock(cls, repo):
2035 def getlock(cls, repo):
2036 return repo.locked
2036 return repo.locked
2037
2037
2038 def is_user_lock(self, user_id):
2038 def is_user_lock(self, user_id):
2039 if self.lock[0]:
2039 if self.lock[0]:
2040 lock_user_id = safe_int(self.lock[0])
2040 lock_user_id = safe_int(self.lock[0])
2041 user_id = safe_int(user_id)
2041 user_id = safe_int(user_id)
2042 # both are ints, and they are equal
2042 # both are ints, and they are equal
2043 return all([lock_user_id, user_id]) and lock_user_id == user_id
2043 return all([lock_user_id, user_id]) and lock_user_id == user_id
2044
2044
2045 return False
2045 return False
2046
2046
2047 def get_locking_state(self, action, user_id, only_when_enabled=True):
2047 def get_locking_state(self, action, user_id, only_when_enabled=True):
2048 """
2048 """
2049 Checks locking on this repository, if locking is enabled and lock is
2049 Checks locking on this repository, if locking is enabled and lock is
2050 present returns a tuple of make_lock, locked, locked_by.
2050 present returns a tuple of make_lock, locked, locked_by.
2051 make_lock can have 3 states None (do nothing) True, make lock
2051 make_lock can have 3 states None (do nothing) True, make lock
2052 False release lock, This value is later propagated to hooks, which
2052 False release lock, This value is later propagated to hooks, which
2053 do the locking. Think about this as signals passed to hooks what to do.
2053 do the locking. Think about this as signals passed to hooks what to do.
2054
2054
2055 """
2055 """
2056 # TODO: johbo: This is part of the business logic and should be moved
2056 # TODO: johbo: This is part of the business logic and should be moved
2057 # into the RepositoryModel.
2057 # into the RepositoryModel.
2058
2058
2059 if action not in ('push', 'pull'):
2059 if action not in ('push', 'pull'):
2060 raise ValueError("Invalid action value: %s" % repr(action))
2060 raise ValueError("Invalid action value: %s" % repr(action))
2061
2061
2062 # defines if locked error should be thrown to user
2062 # defines if locked error should be thrown to user
2063 currently_locked = False
2063 currently_locked = False
2064 # defines if new lock should be made, tri-state
2064 # defines if new lock should be made, tri-state
2065 make_lock = None
2065 make_lock = None
2066 repo = self
2066 repo = self
2067 user = User.get(user_id)
2067 user = User.get(user_id)
2068
2068
2069 lock_info = repo.locked
2069 lock_info = repo.locked
2070
2070
2071 if repo and (repo.enable_locking or not only_when_enabled):
2071 if repo and (repo.enable_locking or not only_when_enabled):
2072 if action == 'push':
2072 if action == 'push':
2073 # check if it's already locked !, if it is compare users
2073 # check if it's already locked !, if it is compare users
2074 locked_by_user_id = lock_info[0]
2074 locked_by_user_id = lock_info[0]
2075 if user.user_id == locked_by_user_id:
2075 if user.user_id == locked_by_user_id:
2076 log.debug(
2076 log.debug(
2077 'Got `push` action from user %s, now unlocking', user)
2077 'Got `push` action from user %s, now unlocking', user)
2078 # unlock if we have push from user who locked
2078 # unlock if we have push from user who locked
2079 make_lock = False
2079 make_lock = False
2080 else:
2080 else:
2081 # we're not the same user who locked, ban with
2081 # we're not the same user who locked, ban with
2082 # code defined in settings (default is 423 HTTP Locked) !
2082 # code defined in settings (default is 423 HTTP Locked) !
2083 log.debug('Repo %s is currently locked by %s', repo, user)
2083 log.debug('Repo %s is currently locked by %s', repo, user)
2084 currently_locked = True
2084 currently_locked = True
2085 elif action == 'pull':
2085 elif action == 'pull':
2086 # [0] user [1] date
2086 # [0] user [1] date
2087 if lock_info[0] and lock_info[1]:
2087 if lock_info[0] and lock_info[1]:
2088 log.debug('Repo %s is currently locked by %s', repo, user)
2088 log.debug('Repo %s is currently locked by %s', repo, user)
2089 currently_locked = True
2089 currently_locked = True
2090 else:
2090 else:
2091 log.debug('Setting lock on repo %s by %s', repo, user)
2091 log.debug('Setting lock on repo %s by %s', repo, user)
2092 make_lock = True
2092 make_lock = True
2093
2093
2094 else:
2094 else:
2095 log.debug('Repository %s do not have locking enabled', repo)
2095 log.debug('Repository %s do not have locking enabled', repo)
2096
2096
2097 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2097 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2098 make_lock, currently_locked, lock_info)
2098 make_lock, currently_locked, lock_info)
2099
2099
2100 from rhodecode.lib.auth import HasRepoPermissionAny
2100 from rhodecode.lib.auth import HasRepoPermissionAny
2101 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2101 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2102 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2102 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2103 # if we don't have at least write permission we cannot make a lock
2103 # if we don't have at least write permission we cannot make a lock
2104 log.debug('lock state reset back to FALSE due to lack '
2104 log.debug('lock state reset back to FALSE due to lack '
2105 'of at least read permission')
2105 'of at least read permission')
2106 make_lock = False
2106 make_lock = False
2107
2107
2108 return make_lock, currently_locked, lock_info
2108 return make_lock, currently_locked, lock_info
2109
2109
2110 @property
2110 @property
2111 def last_db_change(self):
2111 def last_db_change(self):
2112 return self.updated_on
2112 return self.updated_on
2113
2113
2114 @property
2114 @property
2115 def clone_uri_hidden(self):
2115 def clone_uri_hidden(self):
2116 clone_uri = self.clone_uri
2116 clone_uri = self.clone_uri
2117 if clone_uri:
2117 if clone_uri:
2118 import urlobject
2118 import urlobject
2119 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2119 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2120 if url_obj.password:
2120 if url_obj.password:
2121 clone_uri = url_obj.with_password('*****')
2121 clone_uri = url_obj.with_password('*****')
2122 return clone_uri
2122 return clone_uri
2123
2123
2124 @property
2124 @property
2125 def push_uri_hidden(self):
2125 def push_uri_hidden(self):
2126 push_uri = self.push_uri
2126 push_uri = self.push_uri
2127 if push_uri:
2127 if push_uri:
2128 import urlobject
2128 import urlobject
2129 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2129 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2130 if url_obj.password:
2130 if url_obj.password:
2131 push_uri = url_obj.with_password('*****')
2131 push_uri = url_obj.with_password('*****')
2132 return push_uri
2132 return push_uri
2133
2133
2134 def clone_url(self, **override):
2134 def clone_url(self, **override):
2135 from rhodecode.model.settings import SettingsModel
2135 from rhodecode.model.settings import SettingsModel
2136
2136
2137 uri_tmpl = None
2137 uri_tmpl = None
2138 if 'with_id' in override:
2138 if 'with_id' in override:
2139 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2139 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2140 del override['with_id']
2140 del override['with_id']
2141
2141
2142 if 'uri_tmpl' in override:
2142 if 'uri_tmpl' in override:
2143 uri_tmpl = override['uri_tmpl']
2143 uri_tmpl = override['uri_tmpl']
2144 del override['uri_tmpl']
2144 del override['uri_tmpl']
2145
2145
2146 ssh = False
2146 ssh = False
2147 if 'ssh' in override:
2147 if 'ssh' in override:
2148 ssh = True
2148 ssh = True
2149 del override['ssh']
2149 del override['ssh']
2150
2150
2151 # we didn't override our tmpl from **overrides
2151 # we didn't override our tmpl from **overrides
2152 if not uri_tmpl:
2152 if not uri_tmpl:
2153 rc_config = SettingsModel().get_all_settings(cache=True)
2153 rc_config = SettingsModel().get_all_settings(cache=True)
2154 if ssh:
2154 if ssh:
2155 uri_tmpl = rc_config.get(
2155 uri_tmpl = rc_config.get(
2156 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2156 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2157 else:
2157 else:
2158 uri_tmpl = rc_config.get(
2158 uri_tmpl = rc_config.get(
2159 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2159 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2160
2160
2161 request = get_current_request()
2161 request = get_current_request()
2162 return get_clone_url(request=request,
2162 return get_clone_url(request=request,
2163 uri_tmpl=uri_tmpl,
2163 uri_tmpl=uri_tmpl,
2164 repo_name=self.repo_name,
2164 repo_name=self.repo_name,
2165 repo_id=self.repo_id, **override)
2165 repo_id=self.repo_id, **override)
2166
2166
2167 def set_state(self, state):
2167 def set_state(self, state):
2168 self.repo_state = state
2168 self.repo_state = state
2169 Session().add(self)
2169 Session().add(self)
2170 #==========================================================================
2170 #==========================================================================
2171 # SCM PROPERTIES
2171 # SCM PROPERTIES
2172 #==========================================================================
2172 #==========================================================================
2173
2173
2174 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2174 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2175 return get_commit_safe(
2175 return get_commit_safe(
2176 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2176 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2177
2177
2178 def get_changeset(self, rev=None, pre_load=None):
2178 def get_changeset(self, rev=None, pre_load=None):
2179 warnings.warn("Use get_commit", DeprecationWarning)
2179 warnings.warn("Use get_commit", DeprecationWarning)
2180 commit_id = None
2180 commit_id = None
2181 commit_idx = None
2181 commit_idx = None
2182 if isinstance(rev, basestring):
2182 if isinstance(rev, basestring):
2183 commit_id = rev
2183 commit_id = rev
2184 else:
2184 else:
2185 commit_idx = rev
2185 commit_idx = rev
2186 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2186 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2187 pre_load=pre_load)
2187 pre_load=pre_load)
2188
2188
2189 def get_landing_commit(self):
2189 def get_landing_commit(self):
2190 """
2190 """
2191 Returns landing commit, or if that doesn't exist returns the tip
2191 Returns landing commit, or if that doesn't exist returns the tip
2192 """
2192 """
2193 _rev_type, _rev = self.landing_rev
2193 _rev_type, _rev = self.landing_rev
2194 commit = self.get_commit(_rev)
2194 commit = self.get_commit(_rev)
2195 if isinstance(commit, EmptyCommit):
2195 if isinstance(commit, EmptyCommit):
2196 return self.get_commit()
2196 return self.get_commit()
2197 return commit
2197 return commit
2198
2198
2199 def update_commit_cache(self, cs_cache=None, config=None):
2199 def update_commit_cache(self, cs_cache=None, config=None):
2200 """
2200 """
2201 Update cache of last changeset for repository, keys should be::
2201 Update cache of last changeset for repository, keys should be::
2202
2202
2203 short_id
2203 short_id
2204 raw_id
2204 raw_id
2205 revision
2205 revision
2206 parents
2206 parents
2207 message
2207 message
2208 date
2208 date
2209 author
2209 author
2210
2210
2211 :param cs_cache:
2211 :param cs_cache:
2212 """
2212 """
2213 from rhodecode.lib.vcs.backends.base import BaseChangeset
2213 from rhodecode.lib.vcs.backends.base import BaseChangeset
2214 if cs_cache is None:
2214 if cs_cache is None:
2215 # use no-cache version here
2215 # use no-cache version here
2216 scm_repo = self.scm_instance(cache=False, config=config)
2216 scm_repo = self.scm_instance(cache=False, config=config)
2217 if scm_repo:
2217 if scm_repo:
2218 cs_cache = scm_repo.get_commit(
2218 cs_cache = scm_repo.get_commit(
2219 pre_load=["author", "date", "message", "parents"])
2219 pre_load=["author", "date", "message", "parents"])
2220 else:
2220 else:
2221 cs_cache = EmptyCommit()
2221 cs_cache = EmptyCommit()
2222
2222
2223 if isinstance(cs_cache, BaseChangeset):
2223 if isinstance(cs_cache, BaseChangeset):
2224 cs_cache = cs_cache.__json__()
2224 cs_cache = cs_cache.__json__()
2225
2225
2226 def is_outdated(new_cs_cache):
2226 def is_outdated(new_cs_cache):
2227 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2227 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2228 new_cs_cache['revision'] != self.changeset_cache['revision']):
2228 new_cs_cache['revision'] != self.changeset_cache['revision']):
2229 return True
2229 return True
2230 return False
2230 return False
2231
2231
2232 # check if we have maybe already latest cached revision
2232 # check if we have maybe already latest cached revision
2233 if is_outdated(cs_cache) or not self.changeset_cache:
2233 if is_outdated(cs_cache) or not self.changeset_cache:
2234 _default = datetime.datetime.fromtimestamp(0)
2234 _default = datetime.datetime.utcnow()
2235 last_change = cs_cache.get('date') or _default
2235 last_change = cs_cache.get('date') or _default
2236 if self.updated_on and self.updated_on > last_change:
2237 # we check if last update is newer than the new value
2238 # if yes, we use the current timestamp instead. Imagine you get
2239 # old commit pushed 1y ago, we'd set last update 1y to ago.
2240 last_change = _default
2236 log.debug('updated repo %s with new cs cache %s',
2241 log.debug('updated repo %s with new cs cache %s',
2237 self.repo_name, cs_cache)
2242 self.repo_name, cs_cache)
2238 self.updated_on = last_change
2243 self.updated_on = last_change
2239 self.changeset_cache = cs_cache
2244 self.changeset_cache = cs_cache
2240 Session().add(self)
2245 Session().add(self)
2241 Session().commit()
2246 Session().commit()
2242 else:
2247 else:
2243 log.debug('Skipping update_commit_cache for repo:`%s` '
2248 log.debug('Skipping update_commit_cache for repo:`%s` '
2244 'commit already with latest changes', self.repo_name)
2249 'commit already with latest changes', self.repo_name)
2245
2250
2246 @property
2251 @property
2247 def tip(self):
2252 def tip(self):
2248 return self.get_commit('tip')
2253 return self.get_commit('tip')
2249
2254
2250 @property
2255 @property
2251 def author(self):
2256 def author(self):
2252 return self.tip.author
2257 return self.tip.author
2253
2258
2254 @property
2259 @property
2255 def last_change(self):
2260 def last_change(self):
2256 return self.scm_instance().last_change
2261 return self.scm_instance().last_change
2257
2262
2258 def get_comments(self, revisions=None):
2263 def get_comments(self, revisions=None):
2259 """
2264 """
2260 Returns comments for this repository grouped by revisions
2265 Returns comments for this repository grouped by revisions
2261
2266
2262 :param revisions: filter query by revisions only
2267 :param revisions: filter query by revisions only
2263 """
2268 """
2264 cmts = ChangesetComment.query()\
2269 cmts = ChangesetComment.query()\
2265 .filter(ChangesetComment.repo == self)
2270 .filter(ChangesetComment.repo == self)
2266 if revisions:
2271 if revisions:
2267 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2272 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2268 grouped = collections.defaultdict(list)
2273 grouped = collections.defaultdict(list)
2269 for cmt in cmts.all():
2274 for cmt in cmts.all():
2270 grouped[cmt.revision].append(cmt)
2275 grouped[cmt.revision].append(cmt)
2271 return grouped
2276 return grouped
2272
2277
2273 def statuses(self, revisions=None):
2278 def statuses(self, revisions=None):
2274 """
2279 """
2275 Returns statuses for this repository
2280 Returns statuses for this repository
2276
2281
2277 :param revisions: list of revisions to get statuses for
2282 :param revisions: list of revisions to get statuses for
2278 """
2283 """
2279 statuses = ChangesetStatus.query()\
2284 statuses = ChangesetStatus.query()\
2280 .filter(ChangesetStatus.repo == self)\
2285 .filter(ChangesetStatus.repo == self)\
2281 .filter(ChangesetStatus.version == 0)
2286 .filter(ChangesetStatus.version == 0)
2282
2287
2283 if revisions:
2288 if revisions:
2284 # Try doing the filtering in chunks to avoid hitting limits
2289 # Try doing the filtering in chunks to avoid hitting limits
2285 size = 500
2290 size = 500
2286 status_results = []
2291 status_results = []
2287 for chunk in xrange(0, len(revisions), size):
2292 for chunk in xrange(0, len(revisions), size):
2288 status_results += statuses.filter(
2293 status_results += statuses.filter(
2289 ChangesetStatus.revision.in_(
2294 ChangesetStatus.revision.in_(
2290 revisions[chunk: chunk+size])
2295 revisions[chunk: chunk+size])
2291 ).all()
2296 ).all()
2292 else:
2297 else:
2293 status_results = statuses.all()
2298 status_results = statuses.all()
2294
2299
2295 grouped = {}
2300 grouped = {}
2296
2301
2297 # maybe we have open new pullrequest without a status?
2302 # maybe we have open new pullrequest without a status?
2298 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2303 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2299 status_lbl = ChangesetStatus.get_status_lbl(stat)
2304 status_lbl = ChangesetStatus.get_status_lbl(stat)
2300 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2305 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2301 for rev in pr.revisions:
2306 for rev in pr.revisions:
2302 pr_id = pr.pull_request_id
2307 pr_id = pr.pull_request_id
2303 pr_repo = pr.target_repo.repo_name
2308 pr_repo = pr.target_repo.repo_name
2304 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2309 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2305
2310
2306 for stat in status_results:
2311 for stat in status_results:
2307 pr_id = pr_repo = None
2312 pr_id = pr_repo = None
2308 if stat.pull_request:
2313 if stat.pull_request:
2309 pr_id = stat.pull_request.pull_request_id
2314 pr_id = stat.pull_request.pull_request_id
2310 pr_repo = stat.pull_request.target_repo.repo_name
2315 pr_repo = stat.pull_request.target_repo.repo_name
2311 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2316 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2312 pr_id, pr_repo]
2317 pr_id, pr_repo]
2313 return grouped
2318 return grouped
2314
2319
2315 # ==========================================================================
2320 # ==========================================================================
2316 # SCM CACHE INSTANCE
2321 # SCM CACHE INSTANCE
2317 # ==========================================================================
2322 # ==========================================================================
2318
2323
2319 def scm_instance(self, **kwargs):
2324 def scm_instance(self, **kwargs):
2320 import rhodecode
2325 import rhodecode
2321
2326
2322 # Passing a config will not hit the cache currently only used
2327 # Passing a config will not hit the cache currently only used
2323 # for repo2dbmapper
2328 # for repo2dbmapper
2324 config = kwargs.pop('config', None)
2329 config = kwargs.pop('config', None)
2325 cache = kwargs.pop('cache', None)
2330 cache = kwargs.pop('cache', None)
2326 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2331 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2327 # if cache is NOT defined use default global, else we have a full
2332 # if cache is NOT defined use default global, else we have a full
2328 # control over cache behaviour
2333 # control over cache behaviour
2329 if cache is None and full_cache and not config:
2334 if cache is None and full_cache and not config:
2330 return self._get_instance_cached()
2335 return self._get_instance_cached()
2331 return self._get_instance(cache=bool(cache), config=config)
2336 return self._get_instance(cache=bool(cache), config=config)
2332
2337
2333 def _get_instance_cached(self):
2338 def _get_instance_cached(self):
2334 @cache_region('long_term')
2339 @cache_region('long_term')
2335 def _get_repo(cache_key):
2340 def _get_repo(cache_key):
2336 return self._get_instance()
2341 return self._get_instance()
2337
2342
2338 invalidator_context = CacheKey.repo_context_cache(
2343 invalidator_context = CacheKey.repo_context_cache(
2339 _get_repo, self.repo_name, None, thread_scoped=True)
2344 _get_repo, self.repo_name, None, thread_scoped=True)
2340
2345
2341 with invalidator_context as context:
2346 with invalidator_context as context:
2342 context.invalidate()
2347 context.invalidate()
2343 repo = context.compute()
2348 repo = context.compute()
2344
2349
2345 return repo
2350 return repo
2346
2351
2347 def _get_instance(self, cache=True, config=None):
2352 def _get_instance(self, cache=True, config=None):
2348 config = config or self._config
2353 config = config or self._config
2349 custom_wire = {
2354 custom_wire = {
2350 'cache': cache # controls the vcs.remote cache
2355 'cache': cache # controls the vcs.remote cache
2351 }
2356 }
2352 repo = get_vcs_instance(
2357 repo = get_vcs_instance(
2353 repo_path=safe_str(self.repo_full_path),
2358 repo_path=safe_str(self.repo_full_path),
2354 config=config,
2359 config=config,
2355 with_wire=custom_wire,
2360 with_wire=custom_wire,
2356 create=False,
2361 create=False,
2357 _vcs_alias=self.repo_type)
2362 _vcs_alias=self.repo_type)
2358
2363
2359 return repo
2364 return repo
2360
2365
2361 def __json__(self):
2366 def __json__(self):
2362 return {'landing_rev': self.landing_rev}
2367 return {'landing_rev': self.landing_rev}
2363
2368
2364 def get_dict(self):
2369 def get_dict(self):
2365
2370
2366 # Since we transformed `repo_name` to a hybrid property, we need to
2371 # Since we transformed `repo_name` to a hybrid property, we need to
2367 # keep compatibility with the code which uses `repo_name` field.
2372 # keep compatibility with the code which uses `repo_name` field.
2368
2373
2369 result = super(Repository, self).get_dict()
2374 result = super(Repository, self).get_dict()
2370 result['repo_name'] = result.pop('_repo_name', None)
2375 result['repo_name'] = result.pop('_repo_name', None)
2371 return result
2376 return result
2372
2377
2373
2378
2374 class RepoGroup(Base, BaseModel):
2379 class RepoGroup(Base, BaseModel):
2375 __tablename__ = 'groups'
2380 __tablename__ = 'groups'
2376 __table_args__ = (
2381 __table_args__ = (
2377 UniqueConstraint('group_name', 'group_parent_id'),
2382 UniqueConstraint('group_name', 'group_parent_id'),
2378 CheckConstraint('group_id != group_parent_id'),
2383 CheckConstraint('group_id != group_parent_id'),
2379 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2384 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2380 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2385 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2381 )
2386 )
2382 __mapper_args__ = {'order_by': 'group_name'}
2387 __mapper_args__ = {'order_by': 'group_name'}
2383
2388
2384 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2389 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2385
2390
2386 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2391 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2387 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2392 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2388 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2393 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2389 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2394 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2390 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2395 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2391 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2396 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2392 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2397 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2393 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2398 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2394 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2399 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2395
2400
2396 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2401 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2397 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2402 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2398 parent_group = relationship('RepoGroup', remote_side=group_id)
2403 parent_group = relationship('RepoGroup', remote_side=group_id)
2399 user = relationship('User')
2404 user = relationship('User')
2400 integrations = relationship('Integration',
2405 integrations = relationship('Integration',
2401 cascade="all, delete, delete-orphan")
2406 cascade="all, delete, delete-orphan")
2402
2407
2403 def __init__(self, group_name='', parent_group=None):
2408 def __init__(self, group_name='', parent_group=None):
2404 self.group_name = group_name
2409 self.group_name = group_name
2405 self.parent_group = parent_group
2410 self.parent_group = parent_group
2406
2411
2407 def __unicode__(self):
2412 def __unicode__(self):
2408 return u"<%s('id:%s:%s')>" % (
2413 return u"<%s('id:%s:%s')>" % (
2409 self.__class__.__name__, self.group_id, self.group_name)
2414 self.__class__.__name__, self.group_id, self.group_name)
2410
2415
2411 @hybrid_property
2416 @hybrid_property
2412 def description_safe(self):
2417 def description_safe(self):
2413 from rhodecode.lib import helpers as h
2418 from rhodecode.lib import helpers as h
2414 return h.escape(self.group_description)
2419 return h.escape(self.group_description)
2415
2420
2416 @classmethod
2421 @classmethod
2417 def _generate_choice(cls, repo_group):
2422 def _generate_choice(cls, repo_group):
2418 from webhelpers.html import literal as _literal
2423 from webhelpers.html import literal as _literal
2419 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2424 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2420 return repo_group.group_id, _name(repo_group.full_path_splitted)
2425 return repo_group.group_id, _name(repo_group.full_path_splitted)
2421
2426
2422 @classmethod
2427 @classmethod
2423 def groups_choices(cls, groups=None, show_empty_group=True):
2428 def groups_choices(cls, groups=None, show_empty_group=True):
2424 if not groups:
2429 if not groups:
2425 groups = cls.query().all()
2430 groups = cls.query().all()
2426
2431
2427 repo_groups = []
2432 repo_groups = []
2428 if show_empty_group:
2433 if show_empty_group:
2429 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2434 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2430
2435
2431 repo_groups.extend([cls._generate_choice(x) for x in groups])
2436 repo_groups.extend([cls._generate_choice(x) for x in groups])
2432
2437
2433 repo_groups = sorted(
2438 repo_groups = sorted(
2434 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2439 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2435 return repo_groups
2440 return repo_groups
2436
2441
2437 @classmethod
2442 @classmethod
2438 def url_sep(cls):
2443 def url_sep(cls):
2439 return URL_SEP
2444 return URL_SEP
2440
2445
2441 @classmethod
2446 @classmethod
2442 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2447 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2443 if case_insensitive:
2448 if case_insensitive:
2444 gr = cls.query().filter(func.lower(cls.group_name)
2449 gr = cls.query().filter(func.lower(cls.group_name)
2445 == func.lower(group_name))
2450 == func.lower(group_name))
2446 else:
2451 else:
2447 gr = cls.query().filter(cls.group_name == group_name)
2452 gr = cls.query().filter(cls.group_name == group_name)
2448 if cache:
2453 if cache:
2449 name_key = _hash_key(group_name)
2454 name_key = _hash_key(group_name)
2450 gr = gr.options(
2455 gr = gr.options(
2451 FromCache("sql_cache_short", "get_group_%s" % name_key))
2456 FromCache("sql_cache_short", "get_group_%s" % name_key))
2452 return gr.scalar()
2457 return gr.scalar()
2453
2458
2454 @classmethod
2459 @classmethod
2455 def get_user_personal_repo_group(cls, user_id):
2460 def get_user_personal_repo_group(cls, user_id):
2456 user = User.get(user_id)
2461 user = User.get(user_id)
2457 if user.username == User.DEFAULT_USER:
2462 if user.username == User.DEFAULT_USER:
2458 return None
2463 return None
2459
2464
2460 return cls.query()\
2465 return cls.query()\
2461 .filter(cls.personal == true()) \
2466 .filter(cls.personal == true()) \
2462 .filter(cls.user == user).scalar()
2467 .filter(cls.user == user).scalar()
2463
2468
2464 @classmethod
2469 @classmethod
2465 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2470 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2466 case_insensitive=True):
2471 case_insensitive=True):
2467 q = RepoGroup.query()
2472 q = RepoGroup.query()
2468
2473
2469 if not isinstance(user_id, Optional):
2474 if not isinstance(user_id, Optional):
2470 q = q.filter(RepoGroup.user_id == user_id)
2475 q = q.filter(RepoGroup.user_id == user_id)
2471
2476
2472 if not isinstance(group_id, Optional):
2477 if not isinstance(group_id, Optional):
2473 q = q.filter(RepoGroup.group_parent_id == group_id)
2478 q = q.filter(RepoGroup.group_parent_id == group_id)
2474
2479
2475 if case_insensitive:
2480 if case_insensitive:
2476 q = q.order_by(func.lower(RepoGroup.group_name))
2481 q = q.order_by(func.lower(RepoGroup.group_name))
2477 else:
2482 else:
2478 q = q.order_by(RepoGroup.group_name)
2483 q = q.order_by(RepoGroup.group_name)
2479 return q.all()
2484 return q.all()
2480
2485
2481 @property
2486 @property
2482 def parents(self):
2487 def parents(self):
2483 parents_recursion_limit = 10
2488 parents_recursion_limit = 10
2484 groups = []
2489 groups = []
2485 if self.parent_group is None:
2490 if self.parent_group is None:
2486 return groups
2491 return groups
2487 cur_gr = self.parent_group
2492 cur_gr = self.parent_group
2488 groups.insert(0, cur_gr)
2493 groups.insert(0, cur_gr)
2489 cnt = 0
2494 cnt = 0
2490 while 1:
2495 while 1:
2491 cnt += 1
2496 cnt += 1
2492 gr = getattr(cur_gr, 'parent_group', None)
2497 gr = getattr(cur_gr, 'parent_group', None)
2493 cur_gr = cur_gr.parent_group
2498 cur_gr = cur_gr.parent_group
2494 if gr is None:
2499 if gr is None:
2495 break
2500 break
2496 if cnt == parents_recursion_limit:
2501 if cnt == parents_recursion_limit:
2497 # this will prevent accidental infinit loops
2502 # this will prevent accidental infinit loops
2498 log.error(('more than %s parents found for group %s, stopping '
2503 log.error(('more than %s parents found for group %s, stopping '
2499 'recursive parent fetching' % (parents_recursion_limit, self)))
2504 'recursive parent fetching' % (parents_recursion_limit, self)))
2500 break
2505 break
2501
2506
2502 groups.insert(0, gr)
2507 groups.insert(0, gr)
2503 return groups
2508 return groups
2504
2509
2505 @property
2510 @property
2506 def last_db_change(self):
2511 def last_db_change(self):
2507 return self.updated_on
2512 return self.updated_on
2508
2513
2509 @property
2514 @property
2510 def children(self):
2515 def children(self):
2511 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2516 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2512
2517
2513 @property
2518 @property
2514 def name(self):
2519 def name(self):
2515 return self.group_name.split(RepoGroup.url_sep())[-1]
2520 return self.group_name.split(RepoGroup.url_sep())[-1]
2516
2521
2517 @property
2522 @property
2518 def full_path(self):
2523 def full_path(self):
2519 return self.group_name
2524 return self.group_name
2520
2525
2521 @property
2526 @property
2522 def full_path_splitted(self):
2527 def full_path_splitted(self):
2523 return self.group_name.split(RepoGroup.url_sep())
2528 return self.group_name.split(RepoGroup.url_sep())
2524
2529
2525 @property
2530 @property
2526 def repositories(self):
2531 def repositories(self):
2527 return Repository.query()\
2532 return Repository.query()\
2528 .filter(Repository.group == self)\
2533 .filter(Repository.group == self)\
2529 .order_by(Repository.repo_name)
2534 .order_by(Repository.repo_name)
2530
2535
2531 @property
2536 @property
2532 def repositories_recursive_count(self):
2537 def repositories_recursive_count(self):
2533 cnt = self.repositories.count()
2538 cnt = self.repositories.count()
2534
2539
2535 def children_count(group):
2540 def children_count(group):
2536 cnt = 0
2541 cnt = 0
2537 for child in group.children:
2542 for child in group.children:
2538 cnt += child.repositories.count()
2543 cnt += child.repositories.count()
2539 cnt += children_count(child)
2544 cnt += children_count(child)
2540 return cnt
2545 return cnt
2541
2546
2542 return cnt + children_count(self)
2547 return cnt + children_count(self)
2543
2548
2544 def _recursive_objects(self, include_repos=True):
2549 def _recursive_objects(self, include_repos=True):
2545 all_ = []
2550 all_ = []
2546
2551
2547 def _get_members(root_gr):
2552 def _get_members(root_gr):
2548 if include_repos:
2553 if include_repos:
2549 for r in root_gr.repositories:
2554 for r in root_gr.repositories:
2550 all_.append(r)
2555 all_.append(r)
2551 childs = root_gr.children.all()
2556 childs = root_gr.children.all()
2552 if childs:
2557 if childs:
2553 for gr in childs:
2558 for gr in childs:
2554 all_.append(gr)
2559 all_.append(gr)
2555 _get_members(gr)
2560 _get_members(gr)
2556
2561
2557 _get_members(self)
2562 _get_members(self)
2558 return [self] + all_
2563 return [self] + all_
2559
2564
2560 def recursive_groups_and_repos(self):
2565 def recursive_groups_and_repos(self):
2561 """
2566 """
2562 Recursive return all groups, with repositories in those groups
2567 Recursive return all groups, with repositories in those groups
2563 """
2568 """
2564 return self._recursive_objects()
2569 return self._recursive_objects()
2565
2570
2566 def recursive_groups(self):
2571 def recursive_groups(self):
2567 """
2572 """
2568 Returns all children groups for this group including children of children
2573 Returns all children groups for this group including children of children
2569 """
2574 """
2570 return self._recursive_objects(include_repos=False)
2575 return self._recursive_objects(include_repos=False)
2571
2576
2572 def get_new_name(self, group_name):
2577 def get_new_name(self, group_name):
2573 """
2578 """
2574 returns new full group name based on parent and new name
2579 returns new full group name based on parent and new name
2575
2580
2576 :param group_name:
2581 :param group_name:
2577 """
2582 """
2578 path_prefix = (self.parent_group.full_path_splitted if
2583 path_prefix = (self.parent_group.full_path_splitted if
2579 self.parent_group else [])
2584 self.parent_group else [])
2580 return RepoGroup.url_sep().join(path_prefix + [group_name])
2585 return RepoGroup.url_sep().join(path_prefix + [group_name])
2581
2586
2582 def permissions(self, with_admins=True, with_owner=True):
2587 def permissions(self, with_admins=True, with_owner=True):
2583 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2588 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2584 q = q.options(joinedload(UserRepoGroupToPerm.group),
2589 q = q.options(joinedload(UserRepoGroupToPerm.group),
2585 joinedload(UserRepoGroupToPerm.user),
2590 joinedload(UserRepoGroupToPerm.user),
2586 joinedload(UserRepoGroupToPerm.permission),)
2591 joinedload(UserRepoGroupToPerm.permission),)
2587
2592
2588 # get owners and admins and permissions. We do a trick of re-writing
2593 # get owners and admins and permissions. We do a trick of re-writing
2589 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2594 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2590 # has a global reference and changing one object propagates to all
2595 # has a global reference and changing one object propagates to all
2591 # others. This means if admin is also an owner admin_row that change
2596 # others. This means if admin is also an owner admin_row that change
2592 # would propagate to both objects
2597 # would propagate to both objects
2593 perm_rows = []
2598 perm_rows = []
2594 for _usr in q.all():
2599 for _usr in q.all():
2595 usr = AttributeDict(_usr.user.get_dict())
2600 usr = AttributeDict(_usr.user.get_dict())
2596 usr.permission = _usr.permission.permission_name
2601 usr.permission = _usr.permission.permission_name
2597 perm_rows.append(usr)
2602 perm_rows.append(usr)
2598
2603
2599 # filter the perm rows by 'default' first and then sort them by
2604 # filter the perm rows by 'default' first and then sort them by
2600 # admin,write,read,none permissions sorted again alphabetically in
2605 # admin,write,read,none permissions sorted again alphabetically in
2601 # each group
2606 # each group
2602 perm_rows = sorted(perm_rows, key=display_user_sort)
2607 perm_rows = sorted(perm_rows, key=display_user_sort)
2603
2608
2604 _admin_perm = 'group.admin'
2609 _admin_perm = 'group.admin'
2605 owner_row = []
2610 owner_row = []
2606 if with_owner:
2611 if with_owner:
2607 usr = AttributeDict(self.user.get_dict())
2612 usr = AttributeDict(self.user.get_dict())
2608 usr.owner_row = True
2613 usr.owner_row = True
2609 usr.permission = _admin_perm
2614 usr.permission = _admin_perm
2610 owner_row.append(usr)
2615 owner_row.append(usr)
2611
2616
2612 super_admin_rows = []
2617 super_admin_rows = []
2613 if with_admins:
2618 if with_admins:
2614 for usr in User.get_all_super_admins():
2619 for usr in User.get_all_super_admins():
2615 # if this admin is also owner, don't double the record
2620 # if this admin is also owner, don't double the record
2616 if usr.user_id == owner_row[0].user_id:
2621 if usr.user_id == owner_row[0].user_id:
2617 owner_row[0].admin_row = True
2622 owner_row[0].admin_row = True
2618 else:
2623 else:
2619 usr = AttributeDict(usr.get_dict())
2624 usr = AttributeDict(usr.get_dict())
2620 usr.admin_row = True
2625 usr.admin_row = True
2621 usr.permission = _admin_perm
2626 usr.permission = _admin_perm
2622 super_admin_rows.append(usr)
2627 super_admin_rows.append(usr)
2623
2628
2624 return super_admin_rows + owner_row + perm_rows
2629 return super_admin_rows + owner_row + perm_rows
2625
2630
2626 def permission_user_groups(self):
2631 def permission_user_groups(self):
2627 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2632 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2628 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2633 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2629 joinedload(UserGroupRepoGroupToPerm.users_group),
2634 joinedload(UserGroupRepoGroupToPerm.users_group),
2630 joinedload(UserGroupRepoGroupToPerm.permission),)
2635 joinedload(UserGroupRepoGroupToPerm.permission),)
2631
2636
2632 perm_rows = []
2637 perm_rows = []
2633 for _user_group in q.all():
2638 for _user_group in q.all():
2634 usr = AttributeDict(_user_group.users_group.get_dict())
2639 usr = AttributeDict(_user_group.users_group.get_dict())
2635 usr.permission = _user_group.permission.permission_name
2640 usr.permission = _user_group.permission.permission_name
2636 perm_rows.append(usr)
2641 perm_rows.append(usr)
2637
2642
2638 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2643 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2639 return perm_rows
2644 return perm_rows
2640
2645
2641 def get_api_data(self):
2646 def get_api_data(self):
2642 """
2647 """
2643 Common function for generating api data
2648 Common function for generating api data
2644
2649
2645 """
2650 """
2646 group = self
2651 group = self
2647 data = {
2652 data = {
2648 'group_id': group.group_id,
2653 'group_id': group.group_id,
2649 'group_name': group.group_name,
2654 'group_name': group.group_name,
2650 'group_description': group.description_safe,
2655 'group_description': group.description_safe,
2651 'parent_group': group.parent_group.group_name if group.parent_group else None,
2656 'parent_group': group.parent_group.group_name if group.parent_group else None,
2652 'repositories': [x.repo_name for x in group.repositories],
2657 'repositories': [x.repo_name for x in group.repositories],
2653 'owner': group.user.username,
2658 'owner': group.user.username,
2654 }
2659 }
2655 return data
2660 return data
2656
2661
2657
2662
2658 class Permission(Base, BaseModel):
2663 class Permission(Base, BaseModel):
2659 __tablename__ = 'permissions'
2664 __tablename__ = 'permissions'
2660 __table_args__ = (
2665 __table_args__ = (
2661 Index('p_perm_name_idx', 'permission_name'),
2666 Index('p_perm_name_idx', 'permission_name'),
2662 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2667 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2663 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2668 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2664 )
2669 )
2665 PERMS = [
2670 PERMS = [
2666 ('hg.admin', _('RhodeCode Super Administrator')),
2671 ('hg.admin', _('RhodeCode Super Administrator')),
2667
2672
2668 ('repository.none', _('Repository no access')),
2673 ('repository.none', _('Repository no access')),
2669 ('repository.read', _('Repository read access')),
2674 ('repository.read', _('Repository read access')),
2670 ('repository.write', _('Repository write access')),
2675 ('repository.write', _('Repository write access')),
2671 ('repository.admin', _('Repository admin access')),
2676 ('repository.admin', _('Repository admin access')),
2672
2677
2673 ('group.none', _('Repository group no access')),
2678 ('group.none', _('Repository group no access')),
2674 ('group.read', _('Repository group read access')),
2679 ('group.read', _('Repository group read access')),
2675 ('group.write', _('Repository group write access')),
2680 ('group.write', _('Repository group write access')),
2676 ('group.admin', _('Repository group admin access')),
2681 ('group.admin', _('Repository group admin access')),
2677
2682
2678 ('usergroup.none', _('User group no access')),
2683 ('usergroup.none', _('User group no access')),
2679 ('usergroup.read', _('User group read access')),
2684 ('usergroup.read', _('User group read access')),
2680 ('usergroup.write', _('User group write access')),
2685 ('usergroup.write', _('User group write access')),
2681 ('usergroup.admin', _('User group admin access')),
2686 ('usergroup.admin', _('User group admin access')),
2682
2687
2683 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2688 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2684 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2689 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2685
2690
2686 ('hg.usergroup.create.false', _('User Group creation disabled')),
2691 ('hg.usergroup.create.false', _('User Group creation disabled')),
2687 ('hg.usergroup.create.true', _('User Group creation enabled')),
2692 ('hg.usergroup.create.true', _('User Group creation enabled')),
2688
2693
2689 ('hg.create.none', _('Repository creation disabled')),
2694 ('hg.create.none', _('Repository creation disabled')),
2690 ('hg.create.repository', _('Repository creation enabled')),
2695 ('hg.create.repository', _('Repository creation enabled')),
2691 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2696 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2692 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2697 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2693
2698
2694 ('hg.fork.none', _('Repository forking disabled')),
2699 ('hg.fork.none', _('Repository forking disabled')),
2695 ('hg.fork.repository', _('Repository forking enabled')),
2700 ('hg.fork.repository', _('Repository forking enabled')),
2696
2701
2697 ('hg.register.none', _('Registration disabled')),
2702 ('hg.register.none', _('Registration disabled')),
2698 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2703 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2699 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2704 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2700
2705
2701 ('hg.password_reset.enabled', _('Password reset enabled')),
2706 ('hg.password_reset.enabled', _('Password reset enabled')),
2702 ('hg.password_reset.hidden', _('Password reset hidden')),
2707 ('hg.password_reset.hidden', _('Password reset hidden')),
2703 ('hg.password_reset.disabled', _('Password reset disabled')),
2708 ('hg.password_reset.disabled', _('Password reset disabled')),
2704
2709
2705 ('hg.extern_activate.manual', _('Manual activation of external account')),
2710 ('hg.extern_activate.manual', _('Manual activation of external account')),
2706 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2711 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2707
2712
2708 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2713 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2709 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2714 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2710 ]
2715 ]
2711
2716
2712 # definition of system default permissions for DEFAULT user
2717 # definition of system default permissions for DEFAULT user
2713 DEFAULT_USER_PERMISSIONS = [
2718 DEFAULT_USER_PERMISSIONS = [
2714 'repository.read',
2719 'repository.read',
2715 'group.read',
2720 'group.read',
2716 'usergroup.read',
2721 'usergroup.read',
2717 'hg.create.repository',
2722 'hg.create.repository',
2718 'hg.repogroup.create.false',
2723 'hg.repogroup.create.false',
2719 'hg.usergroup.create.false',
2724 'hg.usergroup.create.false',
2720 'hg.create.write_on_repogroup.true',
2725 'hg.create.write_on_repogroup.true',
2721 'hg.fork.repository',
2726 'hg.fork.repository',
2722 'hg.register.manual_activate',
2727 'hg.register.manual_activate',
2723 'hg.password_reset.enabled',
2728 'hg.password_reset.enabled',
2724 'hg.extern_activate.auto',
2729 'hg.extern_activate.auto',
2725 'hg.inherit_default_perms.true',
2730 'hg.inherit_default_perms.true',
2726 ]
2731 ]
2727
2732
2728 # defines which permissions are more important higher the more important
2733 # defines which permissions are more important higher the more important
2729 # Weight defines which permissions are more important.
2734 # Weight defines which permissions are more important.
2730 # The higher number the more important.
2735 # The higher number the more important.
2731 PERM_WEIGHTS = {
2736 PERM_WEIGHTS = {
2732 'repository.none': 0,
2737 'repository.none': 0,
2733 'repository.read': 1,
2738 'repository.read': 1,
2734 'repository.write': 3,
2739 'repository.write': 3,
2735 'repository.admin': 4,
2740 'repository.admin': 4,
2736
2741
2737 'group.none': 0,
2742 'group.none': 0,
2738 'group.read': 1,
2743 'group.read': 1,
2739 'group.write': 3,
2744 'group.write': 3,
2740 'group.admin': 4,
2745 'group.admin': 4,
2741
2746
2742 'usergroup.none': 0,
2747 'usergroup.none': 0,
2743 'usergroup.read': 1,
2748 'usergroup.read': 1,
2744 'usergroup.write': 3,
2749 'usergroup.write': 3,
2745 'usergroup.admin': 4,
2750 'usergroup.admin': 4,
2746
2751
2747 'hg.repogroup.create.false': 0,
2752 'hg.repogroup.create.false': 0,
2748 'hg.repogroup.create.true': 1,
2753 'hg.repogroup.create.true': 1,
2749
2754
2750 'hg.usergroup.create.false': 0,
2755 'hg.usergroup.create.false': 0,
2751 'hg.usergroup.create.true': 1,
2756 'hg.usergroup.create.true': 1,
2752
2757
2753 'hg.fork.none': 0,
2758 'hg.fork.none': 0,
2754 'hg.fork.repository': 1,
2759 'hg.fork.repository': 1,
2755 'hg.create.none': 0,
2760 'hg.create.none': 0,
2756 'hg.create.repository': 1
2761 'hg.create.repository': 1
2757 }
2762 }
2758
2763
2759 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2764 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2760 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2765 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2761 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2766 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2762
2767
2763 def __unicode__(self):
2768 def __unicode__(self):
2764 return u"<%s('%s:%s')>" % (
2769 return u"<%s('%s:%s')>" % (
2765 self.__class__.__name__, self.permission_id, self.permission_name
2770 self.__class__.__name__, self.permission_id, self.permission_name
2766 )
2771 )
2767
2772
2768 @classmethod
2773 @classmethod
2769 def get_by_key(cls, key):
2774 def get_by_key(cls, key):
2770 return cls.query().filter(cls.permission_name == key).scalar()
2775 return cls.query().filter(cls.permission_name == key).scalar()
2771
2776
2772 @classmethod
2777 @classmethod
2773 def get_default_repo_perms(cls, user_id, repo_id=None):
2778 def get_default_repo_perms(cls, user_id, repo_id=None):
2774 q = Session().query(UserRepoToPerm, Repository, Permission)\
2779 q = Session().query(UserRepoToPerm, Repository, Permission)\
2775 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2780 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2776 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2781 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2777 .filter(UserRepoToPerm.user_id == user_id)
2782 .filter(UserRepoToPerm.user_id == user_id)
2778 if repo_id:
2783 if repo_id:
2779 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2784 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2780 return q.all()
2785 return q.all()
2781
2786
2782 @classmethod
2787 @classmethod
2783 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2788 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2784 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2789 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2785 .join(
2790 .join(
2786 Permission,
2791 Permission,
2787 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2792 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2788 .join(
2793 .join(
2789 Repository,
2794 Repository,
2790 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2795 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2791 .join(
2796 .join(
2792 UserGroup,
2797 UserGroup,
2793 UserGroupRepoToPerm.users_group_id ==
2798 UserGroupRepoToPerm.users_group_id ==
2794 UserGroup.users_group_id)\
2799 UserGroup.users_group_id)\
2795 .join(
2800 .join(
2796 UserGroupMember,
2801 UserGroupMember,
2797 UserGroupRepoToPerm.users_group_id ==
2802 UserGroupRepoToPerm.users_group_id ==
2798 UserGroupMember.users_group_id)\
2803 UserGroupMember.users_group_id)\
2799 .filter(
2804 .filter(
2800 UserGroupMember.user_id == user_id,
2805 UserGroupMember.user_id == user_id,
2801 UserGroup.users_group_active == true())
2806 UserGroup.users_group_active == true())
2802 if repo_id:
2807 if repo_id:
2803 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2808 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2804 return q.all()
2809 return q.all()
2805
2810
2806 @classmethod
2811 @classmethod
2807 def get_default_group_perms(cls, user_id, repo_group_id=None):
2812 def get_default_group_perms(cls, user_id, repo_group_id=None):
2808 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2813 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2809 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2814 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2810 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2815 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2811 .filter(UserRepoGroupToPerm.user_id == user_id)
2816 .filter(UserRepoGroupToPerm.user_id == user_id)
2812 if repo_group_id:
2817 if repo_group_id:
2813 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2818 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2814 return q.all()
2819 return q.all()
2815
2820
2816 @classmethod
2821 @classmethod
2817 def get_default_group_perms_from_user_group(
2822 def get_default_group_perms_from_user_group(
2818 cls, user_id, repo_group_id=None):
2823 cls, user_id, repo_group_id=None):
2819 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2824 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2820 .join(
2825 .join(
2821 Permission,
2826 Permission,
2822 UserGroupRepoGroupToPerm.permission_id ==
2827 UserGroupRepoGroupToPerm.permission_id ==
2823 Permission.permission_id)\
2828 Permission.permission_id)\
2824 .join(
2829 .join(
2825 RepoGroup,
2830 RepoGroup,
2826 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2831 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2827 .join(
2832 .join(
2828 UserGroup,
2833 UserGroup,
2829 UserGroupRepoGroupToPerm.users_group_id ==
2834 UserGroupRepoGroupToPerm.users_group_id ==
2830 UserGroup.users_group_id)\
2835 UserGroup.users_group_id)\
2831 .join(
2836 .join(
2832 UserGroupMember,
2837 UserGroupMember,
2833 UserGroupRepoGroupToPerm.users_group_id ==
2838 UserGroupRepoGroupToPerm.users_group_id ==
2834 UserGroupMember.users_group_id)\
2839 UserGroupMember.users_group_id)\
2835 .filter(
2840 .filter(
2836 UserGroupMember.user_id == user_id,
2841 UserGroupMember.user_id == user_id,
2837 UserGroup.users_group_active == true())
2842 UserGroup.users_group_active == true())
2838 if repo_group_id:
2843 if repo_group_id:
2839 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2844 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2840 return q.all()
2845 return q.all()
2841
2846
2842 @classmethod
2847 @classmethod
2843 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2848 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2844 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2849 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2845 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2850 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2846 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2851 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2847 .filter(UserUserGroupToPerm.user_id == user_id)
2852 .filter(UserUserGroupToPerm.user_id == user_id)
2848 if user_group_id:
2853 if user_group_id:
2849 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2854 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2850 return q.all()
2855 return q.all()
2851
2856
2852 @classmethod
2857 @classmethod
2853 def get_default_user_group_perms_from_user_group(
2858 def get_default_user_group_perms_from_user_group(
2854 cls, user_id, user_group_id=None):
2859 cls, user_id, user_group_id=None):
2855 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2860 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2856 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2861 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2857 .join(
2862 .join(
2858 Permission,
2863 Permission,
2859 UserGroupUserGroupToPerm.permission_id ==
2864 UserGroupUserGroupToPerm.permission_id ==
2860 Permission.permission_id)\
2865 Permission.permission_id)\
2861 .join(
2866 .join(
2862 TargetUserGroup,
2867 TargetUserGroup,
2863 UserGroupUserGroupToPerm.target_user_group_id ==
2868 UserGroupUserGroupToPerm.target_user_group_id ==
2864 TargetUserGroup.users_group_id)\
2869 TargetUserGroup.users_group_id)\
2865 .join(
2870 .join(
2866 UserGroup,
2871 UserGroup,
2867 UserGroupUserGroupToPerm.user_group_id ==
2872 UserGroupUserGroupToPerm.user_group_id ==
2868 UserGroup.users_group_id)\
2873 UserGroup.users_group_id)\
2869 .join(
2874 .join(
2870 UserGroupMember,
2875 UserGroupMember,
2871 UserGroupUserGroupToPerm.user_group_id ==
2876 UserGroupUserGroupToPerm.user_group_id ==
2872 UserGroupMember.users_group_id)\
2877 UserGroupMember.users_group_id)\
2873 .filter(
2878 .filter(
2874 UserGroupMember.user_id == user_id,
2879 UserGroupMember.user_id == user_id,
2875 UserGroup.users_group_active == true())
2880 UserGroup.users_group_active == true())
2876 if user_group_id:
2881 if user_group_id:
2877 q = q.filter(
2882 q = q.filter(
2878 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2883 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2879
2884
2880 return q.all()
2885 return q.all()
2881
2886
2882
2887
2883 class UserRepoToPerm(Base, BaseModel):
2888 class UserRepoToPerm(Base, BaseModel):
2884 __tablename__ = 'repo_to_perm'
2889 __tablename__ = 'repo_to_perm'
2885 __table_args__ = (
2890 __table_args__ = (
2886 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2891 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2892 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2893 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2889 )
2894 )
2890 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2895 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2896 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2892 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2897 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2893 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2898 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2894
2899
2895 user = relationship('User')
2900 user = relationship('User')
2896 repository = relationship('Repository')
2901 repository = relationship('Repository')
2897 permission = relationship('Permission')
2902 permission = relationship('Permission')
2898
2903
2899 @classmethod
2904 @classmethod
2900 def create(cls, user, repository, permission):
2905 def create(cls, user, repository, permission):
2901 n = cls()
2906 n = cls()
2902 n.user = user
2907 n.user = user
2903 n.repository = repository
2908 n.repository = repository
2904 n.permission = permission
2909 n.permission = permission
2905 Session().add(n)
2910 Session().add(n)
2906 return n
2911 return n
2907
2912
2908 def __unicode__(self):
2913 def __unicode__(self):
2909 return u'<%s => %s >' % (self.user, self.repository)
2914 return u'<%s => %s >' % (self.user, self.repository)
2910
2915
2911
2916
2912 class UserUserGroupToPerm(Base, BaseModel):
2917 class UserUserGroupToPerm(Base, BaseModel):
2913 __tablename__ = 'user_user_group_to_perm'
2918 __tablename__ = 'user_user_group_to_perm'
2914 __table_args__ = (
2919 __table_args__ = (
2915 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2920 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2916 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2921 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2917 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2922 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2918 )
2923 )
2919 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2924 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2920 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2925 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2921 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2926 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2922 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2927 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2923
2928
2924 user = relationship('User')
2929 user = relationship('User')
2925 user_group = relationship('UserGroup')
2930 user_group = relationship('UserGroup')
2926 permission = relationship('Permission')
2931 permission = relationship('Permission')
2927
2932
2928 @classmethod
2933 @classmethod
2929 def create(cls, user, user_group, permission):
2934 def create(cls, user, user_group, permission):
2930 n = cls()
2935 n = cls()
2931 n.user = user
2936 n.user = user
2932 n.user_group = user_group
2937 n.user_group = user_group
2933 n.permission = permission
2938 n.permission = permission
2934 Session().add(n)
2939 Session().add(n)
2935 return n
2940 return n
2936
2941
2937 def __unicode__(self):
2942 def __unicode__(self):
2938 return u'<%s => %s >' % (self.user, self.user_group)
2943 return u'<%s => %s >' % (self.user, self.user_group)
2939
2944
2940
2945
2941 class UserToPerm(Base, BaseModel):
2946 class UserToPerm(Base, BaseModel):
2942 __tablename__ = 'user_to_perm'
2947 __tablename__ = 'user_to_perm'
2943 __table_args__ = (
2948 __table_args__ = (
2944 UniqueConstraint('user_id', 'permission_id'),
2949 UniqueConstraint('user_id', 'permission_id'),
2945 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2946 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2951 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2947 )
2952 )
2948 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2953 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2954 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2950 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2955 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2951
2956
2952 user = relationship('User')
2957 user = relationship('User')
2953 permission = relationship('Permission', lazy='joined')
2958 permission = relationship('Permission', lazy='joined')
2954
2959
2955 def __unicode__(self):
2960 def __unicode__(self):
2956 return u'<%s => %s >' % (self.user, self.permission)
2961 return u'<%s => %s >' % (self.user, self.permission)
2957
2962
2958
2963
2959 class UserGroupRepoToPerm(Base, BaseModel):
2964 class UserGroupRepoToPerm(Base, BaseModel):
2960 __tablename__ = 'users_group_repo_to_perm'
2965 __tablename__ = 'users_group_repo_to_perm'
2961 __table_args__ = (
2966 __table_args__ = (
2962 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2967 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2969 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2965 )
2970 )
2966 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2971 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2972 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2968 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2973 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2969 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2974 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2970
2975
2971 users_group = relationship('UserGroup')
2976 users_group = relationship('UserGroup')
2972 permission = relationship('Permission')
2977 permission = relationship('Permission')
2973 repository = relationship('Repository')
2978 repository = relationship('Repository')
2974
2979
2975 @classmethod
2980 @classmethod
2976 def create(cls, users_group, repository, permission):
2981 def create(cls, users_group, repository, permission):
2977 n = cls()
2982 n = cls()
2978 n.users_group = users_group
2983 n.users_group = users_group
2979 n.repository = repository
2984 n.repository = repository
2980 n.permission = permission
2985 n.permission = permission
2981 Session().add(n)
2986 Session().add(n)
2982 return n
2987 return n
2983
2988
2984 def __unicode__(self):
2989 def __unicode__(self):
2985 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2990 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2986
2991
2987
2992
2988 class UserGroupUserGroupToPerm(Base, BaseModel):
2993 class UserGroupUserGroupToPerm(Base, BaseModel):
2989 __tablename__ = 'user_group_user_group_to_perm'
2994 __tablename__ = 'user_group_user_group_to_perm'
2990 __table_args__ = (
2995 __table_args__ = (
2991 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2996 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2992 CheckConstraint('target_user_group_id != user_group_id'),
2997 CheckConstraint('target_user_group_id != user_group_id'),
2993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2998 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2999 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2995 )
3000 )
2996 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)
3001 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)
2997 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3002 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2998 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3003 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2999 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3004 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3000
3005
3001 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3006 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3002 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3007 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3003 permission = relationship('Permission')
3008 permission = relationship('Permission')
3004
3009
3005 @classmethod
3010 @classmethod
3006 def create(cls, target_user_group, user_group, permission):
3011 def create(cls, target_user_group, user_group, permission):
3007 n = cls()
3012 n = cls()
3008 n.target_user_group = target_user_group
3013 n.target_user_group = target_user_group
3009 n.user_group = user_group
3014 n.user_group = user_group
3010 n.permission = permission
3015 n.permission = permission
3011 Session().add(n)
3016 Session().add(n)
3012 return n
3017 return n
3013
3018
3014 def __unicode__(self):
3019 def __unicode__(self):
3015 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3020 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3016
3021
3017
3022
3018 class UserGroupToPerm(Base, BaseModel):
3023 class UserGroupToPerm(Base, BaseModel):
3019 __tablename__ = 'users_group_to_perm'
3024 __tablename__ = 'users_group_to_perm'
3020 __table_args__ = (
3025 __table_args__ = (
3021 UniqueConstraint('users_group_id', 'permission_id',),
3026 UniqueConstraint('users_group_id', 'permission_id',),
3022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3027 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3028 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3024 )
3029 )
3025 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3030 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3026 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3031 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3027 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3032 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3028
3033
3029 users_group = relationship('UserGroup')
3034 users_group = relationship('UserGroup')
3030 permission = relationship('Permission')
3035 permission = relationship('Permission')
3031
3036
3032
3037
3033 class UserRepoGroupToPerm(Base, BaseModel):
3038 class UserRepoGroupToPerm(Base, BaseModel):
3034 __tablename__ = 'user_repo_group_to_perm'
3039 __tablename__ = 'user_repo_group_to_perm'
3035 __table_args__ = (
3040 __table_args__ = (
3036 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3041 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3042 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3038 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3043 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3039 )
3044 )
3040
3045
3041 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3046 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3047 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3043 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3048 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3044 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3049 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3045
3050
3046 user = relationship('User')
3051 user = relationship('User')
3047 group = relationship('RepoGroup')
3052 group = relationship('RepoGroup')
3048 permission = relationship('Permission')
3053 permission = relationship('Permission')
3049
3054
3050 @classmethod
3055 @classmethod
3051 def create(cls, user, repository_group, permission):
3056 def create(cls, user, repository_group, permission):
3052 n = cls()
3057 n = cls()
3053 n.user = user
3058 n.user = user
3054 n.group = repository_group
3059 n.group = repository_group
3055 n.permission = permission
3060 n.permission = permission
3056 Session().add(n)
3061 Session().add(n)
3057 return n
3062 return n
3058
3063
3059
3064
3060 class UserGroupRepoGroupToPerm(Base, BaseModel):
3065 class UserGroupRepoGroupToPerm(Base, BaseModel):
3061 __tablename__ = 'users_group_repo_group_to_perm'
3066 __tablename__ = 'users_group_repo_group_to_perm'
3062 __table_args__ = (
3067 __table_args__ = (
3063 UniqueConstraint('users_group_id', 'group_id'),
3068 UniqueConstraint('users_group_id', 'group_id'),
3064 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3069 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3065 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3070 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3066 )
3071 )
3067
3072
3068 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)
3073 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)
3069 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3074 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3070 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3075 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3071 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3076 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3072
3077
3073 users_group = relationship('UserGroup')
3078 users_group = relationship('UserGroup')
3074 permission = relationship('Permission')
3079 permission = relationship('Permission')
3075 group = relationship('RepoGroup')
3080 group = relationship('RepoGroup')
3076
3081
3077 @classmethod
3082 @classmethod
3078 def create(cls, user_group, repository_group, permission):
3083 def create(cls, user_group, repository_group, permission):
3079 n = cls()
3084 n = cls()
3080 n.users_group = user_group
3085 n.users_group = user_group
3081 n.group = repository_group
3086 n.group = repository_group
3082 n.permission = permission
3087 n.permission = permission
3083 Session().add(n)
3088 Session().add(n)
3084 return n
3089 return n
3085
3090
3086 def __unicode__(self):
3091 def __unicode__(self):
3087 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3092 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3088
3093
3089
3094
3090 class Statistics(Base, BaseModel):
3095 class Statistics(Base, BaseModel):
3091 __tablename__ = 'statistics'
3096 __tablename__ = 'statistics'
3092 __table_args__ = (
3097 __table_args__ = (
3093 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3094 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3095 )
3100 )
3096 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3101 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3097 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3102 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3098 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3103 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3099 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3104 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3100 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3105 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3101 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3106 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3102
3107
3103 repository = relationship('Repository', single_parent=True)
3108 repository = relationship('Repository', single_parent=True)
3104
3109
3105
3110
3106 class UserFollowing(Base, BaseModel):
3111 class UserFollowing(Base, BaseModel):
3107 __tablename__ = 'user_followings'
3112 __tablename__ = 'user_followings'
3108 __table_args__ = (
3113 __table_args__ = (
3109 UniqueConstraint('user_id', 'follows_repository_id'),
3114 UniqueConstraint('user_id', 'follows_repository_id'),
3110 UniqueConstraint('user_id', 'follows_user_id'),
3115 UniqueConstraint('user_id', 'follows_user_id'),
3111 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3116 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3112 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3117 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3113 )
3118 )
3114
3119
3115 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3120 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3116 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3121 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3117 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3122 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3118 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3123 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3119 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3124 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3120
3125
3121 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3126 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3122
3127
3123 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3128 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3124 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3129 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3125
3130
3126 @classmethod
3131 @classmethod
3127 def get_repo_followers(cls, repo_id):
3132 def get_repo_followers(cls, repo_id):
3128 return cls.query().filter(cls.follows_repo_id == repo_id)
3133 return cls.query().filter(cls.follows_repo_id == repo_id)
3129
3134
3130
3135
3131 class CacheKey(Base, BaseModel):
3136 class CacheKey(Base, BaseModel):
3132 __tablename__ = 'cache_invalidation'
3137 __tablename__ = 'cache_invalidation'
3133 __table_args__ = (
3138 __table_args__ = (
3134 UniqueConstraint('cache_key'),
3139 UniqueConstraint('cache_key'),
3135 Index('key_idx', 'cache_key'),
3140 Index('key_idx', 'cache_key'),
3136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3141 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3137 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3142 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3138 )
3143 )
3139 CACHE_TYPE_ATOM = 'ATOM'
3144 CACHE_TYPE_ATOM = 'ATOM'
3140 CACHE_TYPE_RSS = 'RSS'
3145 CACHE_TYPE_RSS = 'RSS'
3141 CACHE_TYPE_README = 'README'
3146 CACHE_TYPE_README = 'README'
3142
3147
3143 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3148 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3144 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3149 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3145 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3150 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3146 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3151 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3147
3152
3148 def __init__(self, cache_key, cache_args=''):
3153 def __init__(self, cache_key, cache_args=''):
3149 self.cache_key = cache_key
3154 self.cache_key = cache_key
3150 self.cache_args = cache_args
3155 self.cache_args = cache_args
3151 self.cache_active = False
3156 self.cache_active = False
3152
3157
3153 def __unicode__(self):
3158 def __unicode__(self):
3154 return u"<%s('%s:%s[%s]')>" % (
3159 return u"<%s('%s:%s[%s]')>" % (
3155 self.__class__.__name__,
3160 self.__class__.__name__,
3156 self.cache_id, self.cache_key, self.cache_active)
3161 self.cache_id, self.cache_key, self.cache_active)
3157
3162
3158 def _cache_key_partition(self):
3163 def _cache_key_partition(self):
3159 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3164 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3160 return prefix, repo_name, suffix
3165 return prefix, repo_name, suffix
3161
3166
3162 def get_prefix(self):
3167 def get_prefix(self):
3163 """
3168 """
3164 Try to extract prefix from existing cache key. The key could consist
3169 Try to extract prefix from existing cache key. The key could consist
3165 of prefix, repo_name, suffix
3170 of prefix, repo_name, suffix
3166 """
3171 """
3167 # this returns prefix, repo_name, suffix
3172 # this returns prefix, repo_name, suffix
3168 return self._cache_key_partition()[0]
3173 return self._cache_key_partition()[0]
3169
3174
3170 def get_suffix(self):
3175 def get_suffix(self):
3171 """
3176 """
3172 get suffix that might have been used in _get_cache_key to
3177 get suffix that might have been used in _get_cache_key to
3173 generate self.cache_key. Only used for informational purposes
3178 generate self.cache_key. Only used for informational purposes
3174 in repo_edit.mako.
3179 in repo_edit.mako.
3175 """
3180 """
3176 # prefix, repo_name, suffix
3181 # prefix, repo_name, suffix
3177 return self._cache_key_partition()[2]
3182 return self._cache_key_partition()[2]
3178
3183
3179 @classmethod
3184 @classmethod
3180 def delete_all_cache(cls):
3185 def delete_all_cache(cls):
3181 """
3186 """
3182 Delete all cache keys from database.
3187 Delete all cache keys from database.
3183 Should only be run when all instances are down and all entries
3188 Should only be run when all instances are down and all entries
3184 thus stale.
3189 thus stale.
3185 """
3190 """
3186 cls.query().delete()
3191 cls.query().delete()
3187 Session().commit()
3192 Session().commit()
3188
3193
3189 @classmethod
3194 @classmethod
3190 def get_cache_key(cls, repo_name, cache_type):
3195 def get_cache_key(cls, repo_name, cache_type):
3191 """
3196 """
3192
3197
3193 Generate a cache key for this process of RhodeCode instance.
3198 Generate a cache key for this process of RhodeCode instance.
3194 Prefix most likely will be process id or maybe explicitly set
3199 Prefix most likely will be process id or maybe explicitly set
3195 instance_id from .ini file.
3200 instance_id from .ini file.
3196 """
3201 """
3197 import rhodecode
3202 import rhodecode
3198 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3203 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3199
3204
3200 repo_as_unicode = safe_unicode(repo_name)
3205 repo_as_unicode = safe_unicode(repo_name)
3201 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3206 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3202 if cache_type else repo_as_unicode
3207 if cache_type else repo_as_unicode
3203
3208
3204 return u'{}{}'.format(prefix, key)
3209 return u'{}{}'.format(prefix, key)
3205
3210
3206 @classmethod
3211 @classmethod
3207 def set_invalidate(cls, repo_name, delete=False):
3212 def set_invalidate(cls, repo_name, delete=False):
3208 """
3213 """
3209 Mark all caches of a repo as invalid in the database.
3214 Mark all caches of a repo as invalid in the database.
3210 """
3215 """
3211
3216
3212 try:
3217 try:
3213 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3218 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3214 if delete:
3219 if delete:
3215 log.debug('cache objects deleted for repo %s',
3220 log.debug('cache objects deleted for repo %s',
3216 safe_str(repo_name))
3221 safe_str(repo_name))
3217 qry.delete()
3222 qry.delete()
3218 else:
3223 else:
3219 log.debug('cache objects marked as invalid for repo %s',
3224 log.debug('cache objects marked as invalid for repo %s',
3220 safe_str(repo_name))
3225 safe_str(repo_name))
3221 qry.update({"cache_active": False})
3226 qry.update({"cache_active": False})
3222
3227
3223 Session().commit()
3228 Session().commit()
3224 except Exception:
3229 except Exception:
3225 log.exception(
3230 log.exception(
3226 'Cache key invalidation failed for repository %s',
3231 'Cache key invalidation failed for repository %s',
3227 safe_str(repo_name))
3232 safe_str(repo_name))
3228 Session().rollback()
3233 Session().rollback()
3229
3234
3230 @classmethod
3235 @classmethod
3231 def get_active_cache(cls, cache_key):
3236 def get_active_cache(cls, cache_key):
3232 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3237 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3233 if inv_obj:
3238 if inv_obj:
3234 return inv_obj
3239 return inv_obj
3235 return None
3240 return None
3236
3241
3237 @classmethod
3242 @classmethod
3238 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3243 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3239 thread_scoped=False):
3244 thread_scoped=False):
3240 """
3245 """
3241 @cache_region('long_term')
3246 @cache_region('long_term')
3242 def _heavy_calculation(cache_key):
3247 def _heavy_calculation(cache_key):
3243 return 'result'
3248 return 'result'
3244
3249
3245 cache_context = CacheKey.repo_context_cache(
3250 cache_context = CacheKey.repo_context_cache(
3246 _heavy_calculation, repo_name, cache_type)
3251 _heavy_calculation, repo_name, cache_type)
3247
3252
3248 with cache_context as context:
3253 with cache_context as context:
3249 context.invalidate()
3254 context.invalidate()
3250 computed = context.compute()
3255 computed = context.compute()
3251
3256
3252 assert computed == 'result'
3257 assert computed == 'result'
3253 """
3258 """
3254 from rhodecode.lib import caches
3259 from rhodecode.lib import caches
3255 return caches.InvalidationContext(
3260 return caches.InvalidationContext(
3256 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3261 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3257
3262
3258
3263
3259 class ChangesetComment(Base, BaseModel):
3264 class ChangesetComment(Base, BaseModel):
3260 __tablename__ = 'changeset_comments'
3265 __tablename__ = 'changeset_comments'
3261 __table_args__ = (
3266 __table_args__ = (
3262 Index('cc_revision_idx', 'revision'),
3267 Index('cc_revision_idx', 'revision'),
3263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3268 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3269 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3265 )
3270 )
3266
3271
3267 COMMENT_OUTDATED = u'comment_outdated'
3272 COMMENT_OUTDATED = u'comment_outdated'
3268 COMMENT_TYPE_NOTE = u'note'
3273 COMMENT_TYPE_NOTE = u'note'
3269 COMMENT_TYPE_TODO = u'todo'
3274 COMMENT_TYPE_TODO = u'todo'
3270 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3275 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3271
3276
3272 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3277 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3273 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3278 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3274 revision = Column('revision', String(40), nullable=True)
3279 revision = Column('revision', String(40), nullable=True)
3275 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3280 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3276 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3281 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3277 line_no = Column('line_no', Unicode(10), nullable=True)
3282 line_no = Column('line_no', Unicode(10), nullable=True)
3278 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3283 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3279 f_path = Column('f_path', Unicode(1000), nullable=True)
3284 f_path = Column('f_path', Unicode(1000), nullable=True)
3280 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3285 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3281 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3286 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3282 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3287 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3283 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3288 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3284 renderer = Column('renderer', Unicode(64), nullable=True)
3289 renderer = Column('renderer', Unicode(64), nullable=True)
3285 display_state = Column('display_state', Unicode(128), nullable=True)
3290 display_state = Column('display_state', Unicode(128), nullable=True)
3286
3291
3287 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3292 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3288 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3293 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3289 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3294 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3290 author = relationship('User', lazy='joined')
3295 author = relationship('User', lazy='joined')
3291 repo = relationship('Repository')
3296 repo = relationship('Repository')
3292 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3297 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3293 pull_request = relationship('PullRequest', lazy='joined')
3298 pull_request = relationship('PullRequest', lazy='joined')
3294 pull_request_version = relationship('PullRequestVersion')
3299 pull_request_version = relationship('PullRequestVersion')
3295
3300
3296 @classmethod
3301 @classmethod
3297 def get_users(cls, revision=None, pull_request_id=None):
3302 def get_users(cls, revision=None, pull_request_id=None):
3298 """
3303 """
3299 Returns user associated with this ChangesetComment. ie those
3304 Returns user associated with this ChangesetComment. ie those
3300 who actually commented
3305 who actually commented
3301
3306
3302 :param cls:
3307 :param cls:
3303 :param revision:
3308 :param revision:
3304 """
3309 """
3305 q = Session().query(User)\
3310 q = Session().query(User)\
3306 .join(ChangesetComment.author)
3311 .join(ChangesetComment.author)
3307 if revision:
3312 if revision:
3308 q = q.filter(cls.revision == revision)
3313 q = q.filter(cls.revision == revision)
3309 elif pull_request_id:
3314 elif pull_request_id:
3310 q = q.filter(cls.pull_request_id == pull_request_id)
3315 q = q.filter(cls.pull_request_id == pull_request_id)
3311 return q.all()
3316 return q.all()
3312
3317
3313 @classmethod
3318 @classmethod
3314 def get_index_from_version(cls, pr_version, versions):
3319 def get_index_from_version(cls, pr_version, versions):
3315 num_versions = [x.pull_request_version_id for x in versions]
3320 num_versions = [x.pull_request_version_id for x in versions]
3316 try:
3321 try:
3317 return num_versions.index(pr_version) +1
3322 return num_versions.index(pr_version) +1
3318 except (IndexError, ValueError):
3323 except (IndexError, ValueError):
3319 return
3324 return
3320
3325
3321 @property
3326 @property
3322 def outdated(self):
3327 def outdated(self):
3323 return self.display_state == self.COMMENT_OUTDATED
3328 return self.display_state == self.COMMENT_OUTDATED
3324
3329
3325 def outdated_at_version(self, version):
3330 def outdated_at_version(self, version):
3326 """
3331 """
3327 Checks if comment is outdated for given pull request version
3332 Checks if comment is outdated for given pull request version
3328 """
3333 """
3329 return self.outdated and self.pull_request_version_id != version
3334 return self.outdated and self.pull_request_version_id != version
3330
3335
3331 def older_than_version(self, version):
3336 def older_than_version(self, version):
3332 """
3337 """
3333 Checks if comment is made from previous version than given
3338 Checks if comment is made from previous version than given
3334 """
3339 """
3335 if version is None:
3340 if version is None:
3336 return self.pull_request_version_id is not None
3341 return self.pull_request_version_id is not None
3337
3342
3338 return self.pull_request_version_id < version
3343 return self.pull_request_version_id < version
3339
3344
3340 @property
3345 @property
3341 def resolved(self):
3346 def resolved(self):
3342 return self.resolved_by[0] if self.resolved_by else None
3347 return self.resolved_by[0] if self.resolved_by else None
3343
3348
3344 @property
3349 @property
3345 def is_todo(self):
3350 def is_todo(self):
3346 return self.comment_type == self.COMMENT_TYPE_TODO
3351 return self.comment_type == self.COMMENT_TYPE_TODO
3347
3352
3348 @property
3353 @property
3349 def is_inline(self):
3354 def is_inline(self):
3350 return self.line_no and self.f_path
3355 return self.line_no and self.f_path
3351
3356
3352 def get_index_version(self, versions):
3357 def get_index_version(self, versions):
3353 return self.get_index_from_version(
3358 return self.get_index_from_version(
3354 self.pull_request_version_id, versions)
3359 self.pull_request_version_id, versions)
3355
3360
3356 def __repr__(self):
3361 def __repr__(self):
3357 if self.comment_id:
3362 if self.comment_id:
3358 return '<DB:Comment #%s>' % self.comment_id
3363 return '<DB:Comment #%s>' % self.comment_id
3359 else:
3364 else:
3360 return '<DB:Comment at %#x>' % id(self)
3365 return '<DB:Comment at %#x>' % id(self)
3361
3366
3362 def get_api_data(self):
3367 def get_api_data(self):
3363 comment = self
3368 comment = self
3364 data = {
3369 data = {
3365 'comment_id': comment.comment_id,
3370 'comment_id': comment.comment_id,
3366 'comment_type': comment.comment_type,
3371 'comment_type': comment.comment_type,
3367 'comment_text': comment.text,
3372 'comment_text': comment.text,
3368 'comment_status': comment.status_change,
3373 'comment_status': comment.status_change,
3369 'comment_f_path': comment.f_path,
3374 'comment_f_path': comment.f_path,
3370 'comment_lineno': comment.line_no,
3375 'comment_lineno': comment.line_no,
3371 'comment_author': comment.author,
3376 'comment_author': comment.author,
3372 'comment_created_on': comment.created_on
3377 'comment_created_on': comment.created_on
3373 }
3378 }
3374 return data
3379 return data
3375
3380
3376 def __json__(self):
3381 def __json__(self):
3377 data = dict()
3382 data = dict()
3378 data.update(self.get_api_data())
3383 data.update(self.get_api_data())
3379 return data
3384 return data
3380
3385
3381
3386
3382 class ChangesetStatus(Base, BaseModel):
3387 class ChangesetStatus(Base, BaseModel):
3383 __tablename__ = 'changeset_statuses'
3388 __tablename__ = 'changeset_statuses'
3384 __table_args__ = (
3389 __table_args__ = (
3385 Index('cs_revision_idx', 'revision'),
3390 Index('cs_revision_idx', 'revision'),
3386 Index('cs_version_idx', 'version'),
3391 Index('cs_version_idx', 'version'),
3387 UniqueConstraint('repo_id', 'revision', 'version'),
3392 UniqueConstraint('repo_id', 'revision', 'version'),
3388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3389 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3390 )
3395 )
3391 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3396 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3392 STATUS_APPROVED = 'approved'
3397 STATUS_APPROVED = 'approved'
3393 STATUS_REJECTED = 'rejected'
3398 STATUS_REJECTED = 'rejected'
3394 STATUS_UNDER_REVIEW = 'under_review'
3399 STATUS_UNDER_REVIEW = 'under_review'
3395
3400
3396 STATUSES = [
3401 STATUSES = [
3397 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3402 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3398 (STATUS_APPROVED, _("Approved")),
3403 (STATUS_APPROVED, _("Approved")),
3399 (STATUS_REJECTED, _("Rejected")),
3404 (STATUS_REJECTED, _("Rejected")),
3400 (STATUS_UNDER_REVIEW, _("Under Review")),
3405 (STATUS_UNDER_REVIEW, _("Under Review")),
3401 ]
3406 ]
3402
3407
3403 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3408 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3404 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3409 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3405 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3410 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3406 revision = Column('revision', String(40), nullable=False)
3411 revision = Column('revision', String(40), nullable=False)
3407 status = Column('status', String(128), nullable=False, default=DEFAULT)
3412 status = Column('status', String(128), nullable=False, default=DEFAULT)
3408 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3413 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3409 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3414 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3410 version = Column('version', Integer(), nullable=False, default=0)
3415 version = Column('version', Integer(), nullable=False, default=0)
3411 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3416 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3412
3417
3413 author = relationship('User', lazy='joined')
3418 author = relationship('User', lazy='joined')
3414 repo = relationship('Repository')
3419 repo = relationship('Repository')
3415 comment = relationship('ChangesetComment', lazy='joined')
3420 comment = relationship('ChangesetComment', lazy='joined')
3416 pull_request = relationship('PullRequest', lazy='joined')
3421 pull_request = relationship('PullRequest', lazy='joined')
3417
3422
3418 def __unicode__(self):
3423 def __unicode__(self):
3419 return u"<%s('%s[v%s]:%s')>" % (
3424 return u"<%s('%s[v%s]:%s')>" % (
3420 self.__class__.__name__,
3425 self.__class__.__name__,
3421 self.status, self.version, self.author
3426 self.status, self.version, self.author
3422 )
3427 )
3423
3428
3424 @classmethod
3429 @classmethod
3425 def get_status_lbl(cls, value):
3430 def get_status_lbl(cls, value):
3426 return dict(cls.STATUSES).get(value)
3431 return dict(cls.STATUSES).get(value)
3427
3432
3428 @property
3433 @property
3429 def status_lbl(self):
3434 def status_lbl(self):
3430 return ChangesetStatus.get_status_lbl(self.status)
3435 return ChangesetStatus.get_status_lbl(self.status)
3431
3436
3432 def get_api_data(self):
3437 def get_api_data(self):
3433 status = self
3438 status = self
3434 data = {
3439 data = {
3435 'status_id': status.changeset_status_id,
3440 'status_id': status.changeset_status_id,
3436 'status': status.status,
3441 'status': status.status,
3437 }
3442 }
3438 return data
3443 return data
3439
3444
3440 def __json__(self):
3445 def __json__(self):
3441 data = dict()
3446 data = dict()
3442 data.update(self.get_api_data())
3447 data.update(self.get_api_data())
3443 return data
3448 return data
3444
3449
3445
3450
3446 class _PullRequestBase(BaseModel):
3451 class _PullRequestBase(BaseModel):
3447 """
3452 """
3448 Common attributes of pull request and version entries.
3453 Common attributes of pull request and version entries.
3449 """
3454 """
3450
3455
3451 # .status values
3456 # .status values
3452 STATUS_NEW = u'new'
3457 STATUS_NEW = u'new'
3453 STATUS_OPEN = u'open'
3458 STATUS_OPEN = u'open'
3454 STATUS_CLOSED = u'closed'
3459 STATUS_CLOSED = u'closed'
3455
3460
3456 title = Column('title', Unicode(255), nullable=True)
3461 title = Column('title', Unicode(255), nullable=True)
3457 description = Column(
3462 description = Column(
3458 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3463 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3459 nullable=True)
3464 nullable=True)
3460 # new/open/closed status of pull request (not approve/reject/etc)
3465 # new/open/closed status of pull request (not approve/reject/etc)
3461 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3466 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3462 created_on = Column(
3467 created_on = Column(
3463 'created_on', DateTime(timezone=False), nullable=False,
3468 'created_on', DateTime(timezone=False), nullable=False,
3464 default=datetime.datetime.now)
3469 default=datetime.datetime.now)
3465 updated_on = Column(
3470 updated_on = Column(
3466 'updated_on', DateTime(timezone=False), nullable=False,
3471 'updated_on', DateTime(timezone=False), nullable=False,
3467 default=datetime.datetime.now)
3472 default=datetime.datetime.now)
3468
3473
3469 @declared_attr
3474 @declared_attr
3470 def user_id(cls):
3475 def user_id(cls):
3471 return Column(
3476 return Column(
3472 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3477 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3473 unique=None)
3478 unique=None)
3474
3479
3475 # 500 revisions max
3480 # 500 revisions max
3476 _revisions = Column(
3481 _revisions = Column(
3477 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3482 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3478
3483
3479 @declared_attr
3484 @declared_attr
3480 def source_repo_id(cls):
3485 def source_repo_id(cls):
3481 # TODO: dan: rename column to source_repo_id
3486 # TODO: dan: rename column to source_repo_id
3482 return Column(
3487 return Column(
3483 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3488 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3484 nullable=False)
3489 nullable=False)
3485
3490
3486 source_ref = Column('org_ref', Unicode(255), nullable=False)
3491 source_ref = Column('org_ref', Unicode(255), nullable=False)
3487
3492
3488 @declared_attr
3493 @declared_attr
3489 def target_repo_id(cls):
3494 def target_repo_id(cls):
3490 # TODO: dan: rename column to target_repo_id
3495 # TODO: dan: rename column to target_repo_id
3491 return Column(
3496 return Column(
3492 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3497 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3493 nullable=False)
3498 nullable=False)
3494
3499
3495 target_ref = Column('other_ref', Unicode(255), nullable=False)
3500 target_ref = Column('other_ref', Unicode(255), nullable=False)
3496 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3501 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3497
3502
3498 # TODO: dan: rename column to last_merge_source_rev
3503 # TODO: dan: rename column to last_merge_source_rev
3499 _last_merge_source_rev = Column(
3504 _last_merge_source_rev = Column(
3500 'last_merge_org_rev', String(40), nullable=True)
3505 'last_merge_org_rev', String(40), nullable=True)
3501 # TODO: dan: rename column to last_merge_target_rev
3506 # TODO: dan: rename column to last_merge_target_rev
3502 _last_merge_target_rev = Column(
3507 _last_merge_target_rev = Column(
3503 'last_merge_other_rev', String(40), nullable=True)
3508 'last_merge_other_rev', String(40), nullable=True)
3504 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3509 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3505 merge_rev = Column('merge_rev', String(40), nullable=True)
3510 merge_rev = Column('merge_rev', String(40), nullable=True)
3506
3511
3507 reviewer_data = Column(
3512 reviewer_data = Column(
3508 'reviewer_data_json', MutationObj.as_mutable(
3513 'reviewer_data_json', MutationObj.as_mutable(
3509 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3514 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3510
3515
3511 @property
3516 @property
3512 def reviewer_data_json(self):
3517 def reviewer_data_json(self):
3513 return json.dumps(self.reviewer_data)
3518 return json.dumps(self.reviewer_data)
3514
3519
3515 @hybrid_property
3520 @hybrid_property
3516 def description_safe(self):
3521 def description_safe(self):
3517 from rhodecode.lib import helpers as h
3522 from rhodecode.lib import helpers as h
3518 return h.escape(self.description)
3523 return h.escape(self.description)
3519
3524
3520 @hybrid_property
3525 @hybrid_property
3521 def revisions(self):
3526 def revisions(self):
3522 return self._revisions.split(':') if self._revisions else []
3527 return self._revisions.split(':') if self._revisions else []
3523
3528
3524 @revisions.setter
3529 @revisions.setter
3525 def revisions(self, val):
3530 def revisions(self, val):
3526 self._revisions = ':'.join(val)
3531 self._revisions = ':'.join(val)
3527
3532
3528 @hybrid_property
3533 @hybrid_property
3529 def last_merge_status(self):
3534 def last_merge_status(self):
3530 return safe_int(self._last_merge_status)
3535 return safe_int(self._last_merge_status)
3531
3536
3532 @last_merge_status.setter
3537 @last_merge_status.setter
3533 def last_merge_status(self, val):
3538 def last_merge_status(self, val):
3534 self._last_merge_status = val
3539 self._last_merge_status = val
3535
3540
3536 @declared_attr
3541 @declared_attr
3537 def author(cls):
3542 def author(cls):
3538 return relationship('User', lazy='joined')
3543 return relationship('User', lazy='joined')
3539
3544
3540 @declared_attr
3545 @declared_attr
3541 def source_repo(cls):
3546 def source_repo(cls):
3542 return relationship(
3547 return relationship(
3543 'Repository',
3548 'Repository',
3544 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3549 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3545
3550
3546 @property
3551 @property
3547 def source_ref_parts(self):
3552 def source_ref_parts(self):
3548 return self.unicode_to_reference(self.source_ref)
3553 return self.unicode_to_reference(self.source_ref)
3549
3554
3550 @declared_attr
3555 @declared_attr
3551 def target_repo(cls):
3556 def target_repo(cls):
3552 return relationship(
3557 return relationship(
3553 'Repository',
3558 'Repository',
3554 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3559 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3555
3560
3556 @property
3561 @property
3557 def target_ref_parts(self):
3562 def target_ref_parts(self):
3558 return self.unicode_to_reference(self.target_ref)
3563 return self.unicode_to_reference(self.target_ref)
3559
3564
3560 @property
3565 @property
3561 def shadow_merge_ref(self):
3566 def shadow_merge_ref(self):
3562 return self.unicode_to_reference(self._shadow_merge_ref)
3567 return self.unicode_to_reference(self._shadow_merge_ref)
3563
3568
3564 @shadow_merge_ref.setter
3569 @shadow_merge_ref.setter
3565 def shadow_merge_ref(self, ref):
3570 def shadow_merge_ref(self, ref):
3566 self._shadow_merge_ref = self.reference_to_unicode(ref)
3571 self._shadow_merge_ref = self.reference_to_unicode(ref)
3567
3572
3568 def unicode_to_reference(self, raw):
3573 def unicode_to_reference(self, raw):
3569 """
3574 """
3570 Convert a unicode (or string) to a reference object.
3575 Convert a unicode (or string) to a reference object.
3571 If unicode evaluates to False it returns None.
3576 If unicode evaluates to False it returns None.
3572 """
3577 """
3573 if raw:
3578 if raw:
3574 refs = raw.split(':')
3579 refs = raw.split(':')
3575 return Reference(*refs)
3580 return Reference(*refs)
3576 else:
3581 else:
3577 return None
3582 return None
3578
3583
3579 def reference_to_unicode(self, ref):
3584 def reference_to_unicode(self, ref):
3580 """
3585 """
3581 Convert a reference object to unicode.
3586 Convert a reference object to unicode.
3582 If reference is None it returns None.
3587 If reference is None it returns None.
3583 """
3588 """
3584 if ref:
3589 if ref:
3585 return u':'.join(ref)
3590 return u':'.join(ref)
3586 else:
3591 else:
3587 return None
3592 return None
3588
3593
3589 def get_api_data(self, with_merge_state=True):
3594 def get_api_data(self, with_merge_state=True):
3590 from rhodecode.model.pull_request import PullRequestModel
3595 from rhodecode.model.pull_request import PullRequestModel
3591
3596
3592 pull_request = self
3597 pull_request = self
3593 if with_merge_state:
3598 if with_merge_state:
3594 merge_status = PullRequestModel().merge_status(pull_request)
3599 merge_status = PullRequestModel().merge_status(pull_request)
3595 merge_state = {
3600 merge_state = {
3596 'status': merge_status[0],
3601 'status': merge_status[0],
3597 'message': safe_unicode(merge_status[1]),
3602 'message': safe_unicode(merge_status[1]),
3598 }
3603 }
3599 else:
3604 else:
3600 merge_state = {'status': 'not_available',
3605 merge_state = {'status': 'not_available',
3601 'message': 'not_available'}
3606 'message': 'not_available'}
3602
3607
3603 merge_data = {
3608 merge_data = {
3604 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3609 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3605 'reference': (
3610 'reference': (
3606 pull_request.shadow_merge_ref._asdict()
3611 pull_request.shadow_merge_ref._asdict()
3607 if pull_request.shadow_merge_ref else None),
3612 if pull_request.shadow_merge_ref else None),
3608 }
3613 }
3609
3614
3610 data = {
3615 data = {
3611 'pull_request_id': pull_request.pull_request_id,
3616 'pull_request_id': pull_request.pull_request_id,
3612 'url': PullRequestModel().get_url(pull_request),
3617 'url': PullRequestModel().get_url(pull_request),
3613 'title': pull_request.title,
3618 'title': pull_request.title,
3614 'description': pull_request.description,
3619 'description': pull_request.description,
3615 'status': pull_request.status,
3620 'status': pull_request.status,
3616 'created_on': pull_request.created_on,
3621 'created_on': pull_request.created_on,
3617 'updated_on': pull_request.updated_on,
3622 'updated_on': pull_request.updated_on,
3618 'commit_ids': pull_request.revisions,
3623 'commit_ids': pull_request.revisions,
3619 'review_status': pull_request.calculated_review_status(),
3624 'review_status': pull_request.calculated_review_status(),
3620 'mergeable': merge_state,
3625 'mergeable': merge_state,
3621 'source': {
3626 'source': {
3622 'clone_url': pull_request.source_repo.clone_url(),
3627 'clone_url': pull_request.source_repo.clone_url(),
3623 'repository': pull_request.source_repo.repo_name,
3628 'repository': pull_request.source_repo.repo_name,
3624 'reference': {
3629 'reference': {
3625 'name': pull_request.source_ref_parts.name,
3630 'name': pull_request.source_ref_parts.name,
3626 'type': pull_request.source_ref_parts.type,
3631 'type': pull_request.source_ref_parts.type,
3627 'commit_id': pull_request.source_ref_parts.commit_id,
3632 'commit_id': pull_request.source_ref_parts.commit_id,
3628 },
3633 },
3629 },
3634 },
3630 'target': {
3635 'target': {
3631 'clone_url': pull_request.target_repo.clone_url(),
3636 'clone_url': pull_request.target_repo.clone_url(),
3632 'repository': pull_request.target_repo.repo_name,
3637 'repository': pull_request.target_repo.repo_name,
3633 'reference': {
3638 'reference': {
3634 'name': pull_request.target_ref_parts.name,
3639 'name': pull_request.target_ref_parts.name,
3635 'type': pull_request.target_ref_parts.type,
3640 'type': pull_request.target_ref_parts.type,
3636 'commit_id': pull_request.target_ref_parts.commit_id,
3641 'commit_id': pull_request.target_ref_parts.commit_id,
3637 },
3642 },
3638 },
3643 },
3639 'merge': merge_data,
3644 'merge': merge_data,
3640 'author': pull_request.author.get_api_data(include_secrets=False,
3645 'author': pull_request.author.get_api_data(include_secrets=False,
3641 details='basic'),
3646 details='basic'),
3642 'reviewers': [
3647 'reviewers': [
3643 {
3648 {
3644 'user': reviewer.get_api_data(include_secrets=False,
3649 'user': reviewer.get_api_data(include_secrets=False,
3645 details='basic'),
3650 details='basic'),
3646 'reasons': reasons,
3651 'reasons': reasons,
3647 'review_status': st[0][1].status if st else 'not_reviewed',
3652 'review_status': st[0][1].status if st else 'not_reviewed',
3648 }
3653 }
3649 for obj, reviewer, reasons, mandatory, st in
3654 for obj, reviewer, reasons, mandatory, st in
3650 pull_request.reviewers_statuses()
3655 pull_request.reviewers_statuses()
3651 ]
3656 ]
3652 }
3657 }
3653
3658
3654 return data
3659 return data
3655
3660
3656
3661
3657 class PullRequest(Base, _PullRequestBase):
3662 class PullRequest(Base, _PullRequestBase):
3658 __tablename__ = 'pull_requests'
3663 __tablename__ = 'pull_requests'
3659 __table_args__ = (
3664 __table_args__ = (
3660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3661 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3666 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3662 )
3667 )
3663
3668
3664 pull_request_id = Column(
3669 pull_request_id = Column(
3665 'pull_request_id', Integer(), nullable=False, primary_key=True)
3670 'pull_request_id', Integer(), nullable=False, primary_key=True)
3666
3671
3667 def __repr__(self):
3672 def __repr__(self):
3668 if self.pull_request_id:
3673 if self.pull_request_id:
3669 return '<DB:PullRequest #%s>' % self.pull_request_id
3674 return '<DB:PullRequest #%s>' % self.pull_request_id
3670 else:
3675 else:
3671 return '<DB:PullRequest at %#x>' % id(self)
3676 return '<DB:PullRequest at %#x>' % id(self)
3672
3677
3673 reviewers = relationship('PullRequestReviewers',
3678 reviewers = relationship('PullRequestReviewers',
3674 cascade="all, delete, delete-orphan")
3679 cascade="all, delete, delete-orphan")
3675 statuses = relationship('ChangesetStatus',
3680 statuses = relationship('ChangesetStatus',
3676 cascade="all, delete, delete-orphan")
3681 cascade="all, delete, delete-orphan")
3677 comments = relationship('ChangesetComment',
3682 comments = relationship('ChangesetComment',
3678 cascade="all, delete, delete-orphan")
3683 cascade="all, delete, delete-orphan")
3679 versions = relationship('PullRequestVersion',
3684 versions = relationship('PullRequestVersion',
3680 cascade="all, delete, delete-orphan",
3685 cascade="all, delete, delete-orphan",
3681 lazy='dynamic')
3686 lazy='dynamic')
3682
3687
3683 @classmethod
3688 @classmethod
3684 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3689 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3685 internal_methods=None):
3690 internal_methods=None):
3686
3691
3687 class PullRequestDisplay(object):
3692 class PullRequestDisplay(object):
3688 """
3693 """
3689 Special object wrapper for showing PullRequest data via Versions
3694 Special object wrapper for showing PullRequest data via Versions
3690 It mimics PR object as close as possible. This is read only object
3695 It mimics PR object as close as possible. This is read only object
3691 just for display
3696 just for display
3692 """
3697 """
3693
3698
3694 def __init__(self, attrs, internal=None):
3699 def __init__(self, attrs, internal=None):
3695 self.attrs = attrs
3700 self.attrs = attrs
3696 # internal have priority over the given ones via attrs
3701 # internal have priority over the given ones via attrs
3697 self.internal = internal or ['versions']
3702 self.internal = internal or ['versions']
3698
3703
3699 def __getattr__(self, item):
3704 def __getattr__(self, item):
3700 if item in self.internal:
3705 if item in self.internal:
3701 return getattr(self, item)
3706 return getattr(self, item)
3702 try:
3707 try:
3703 return self.attrs[item]
3708 return self.attrs[item]
3704 except KeyError:
3709 except KeyError:
3705 raise AttributeError(
3710 raise AttributeError(
3706 '%s object has no attribute %s' % (self, item))
3711 '%s object has no attribute %s' % (self, item))
3707
3712
3708 def __repr__(self):
3713 def __repr__(self):
3709 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3714 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3710
3715
3711 def versions(self):
3716 def versions(self):
3712 return pull_request_obj.versions.order_by(
3717 return pull_request_obj.versions.order_by(
3713 PullRequestVersion.pull_request_version_id).all()
3718 PullRequestVersion.pull_request_version_id).all()
3714
3719
3715 def is_closed(self):
3720 def is_closed(self):
3716 return pull_request_obj.is_closed()
3721 return pull_request_obj.is_closed()
3717
3722
3718 @property
3723 @property
3719 def pull_request_version_id(self):
3724 def pull_request_version_id(self):
3720 return getattr(pull_request_obj, 'pull_request_version_id', None)
3725 return getattr(pull_request_obj, 'pull_request_version_id', None)
3721
3726
3722 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3727 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3723
3728
3724 attrs.author = StrictAttributeDict(
3729 attrs.author = StrictAttributeDict(
3725 pull_request_obj.author.get_api_data())
3730 pull_request_obj.author.get_api_data())
3726 if pull_request_obj.target_repo:
3731 if pull_request_obj.target_repo:
3727 attrs.target_repo = StrictAttributeDict(
3732 attrs.target_repo = StrictAttributeDict(
3728 pull_request_obj.target_repo.get_api_data())
3733 pull_request_obj.target_repo.get_api_data())
3729 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3734 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3730
3735
3731 if pull_request_obj.source_repo:
3736 if pull_request_obj.source_repo:
3732 attrs.source_repo = StrictAttributeDict(
3737 attrs.source_repo = StrictAttributeDict(
3733 pull_request_obj.source_repo.get_api_data())
3738 pull_request_obj.source_repo.get_api_data())
3734 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3739 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3735
3740
3736 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3741 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3737 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3742 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3738 attrs.revisions = pull_request_obj.revisions
3743 attrs.revisions = pull_request_obj.revisions
3739
3744
3740 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3745 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3741 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3746 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3742 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3747 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3743
3748
3744 return PullRequestDisplay(attrs, internal=internal_methods)
3749 return PullRequestDisplay(attrs, internal=internal_methods)
3745
3750
3746 def is_closed(self):
3751 def is_closed(self):
3747 return self.status == self.STATUS_CLOSED
3752 return self.status == self.STATUS_CLOSED
3748
3753
3749 def __json__(self):
3754 def __json__(self):
3750 return {
3755 return {
3751 'revisions': self.revisions,
3756 'revisions': self.revisions,
3752 }
3757 }
3753
3758
3754 def calculated_review_status(self):
3759 def calculated_review_status(self):
3755 from rhodecode.model.changeset_status import ChangesetStatusModel
3760 from rhodecode.model.changeset_status import ChangesetStatusModel
3756 return ChangesetStatusModel().calculated_review_status(self)
3761 return ChangesetStatusModel().calculated_review_status(self)
3757
3762
3758 def reviewers_statuses(self):
3763 def reviewers_statuses(self):
3759 from rhodecode.model.changeset_status import ChangesetStatusModel
3764 from rhodecode.model.changeset_status import ChangesetStatusModel
3760 return ChangesetStatusModel().reviewers_statuses(self)
3765 return ChangesetStatusModel().reviewers_statuses(self)
3761
3766
3762 @property
3767 @property
3763 def workspace_id(self):
3768 def workspace_id(self):
3764 from rhodecode.model.pull_request import PullRequestModel
3769 from rhodecode.model.pull_request import PullRequestModel
3765 return PullRequestModel()._workspace_id(self)
3770 return PullRequestModel()._workspace_id(self)
3766
3771
3767 def get_shadow_repo(self):
3772 def get_shadow_repo(self):
3768 workspace_id = self.workspace_id
3773 workspace_id = self.workspace_id
3769 vcs_obj = self.target_repo.scm_instance()
3774 vcs_obj = self.target_repo.scm_instance()
3770 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3775 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3771 self.target_repo.repo_id, workspace_id)
3776 self.target_repo.repo_id, workspace_id)
3772 if os.path.isdir(shadow_repository_path):
3777 if os.path.isdir(shadow_repository_path):
3773 return vcs_obj._get_shadow_instance(shadow_repository_path)
3778 return vcs_obj._get_shadow_instance(shadow_repository_path)
3774
3779
3775
3780
3776 class PullRequestVersion(Base, _PullRequestBase):
3781 class PullRequestVersion(Base, _PullRequestBase):
3777 __tablename__ = 'pull_request_versions'
3782 __tablename__ = 'pull_request_versions'
3778 __table_args__ = (
3783 __table_args__ = (
3779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3784 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3780 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3785 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3781 )
3786 )
3782
3787
3783 pull_request_version_id = Column(
3788 pull_request_version_id = Column(
3784 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3789 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3785 pull_request_id = Column(
3790 pull_request_id = Column(
3786 'pull_request_id', Integer(),
3791 'pull_request_id', Integer(),
3787 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3792 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3788 pull_request = relationship('PullRequest')
3793 pull_request = relationship('PullRequest')
3789
3794
3790 def __repr__(self):
3795 def __repr__(self):
3791 if self.pull_request_version_id:
3796 if self.pull_request_version_id:
3792 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3797 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3793 else:
3798 else:
3794 return '<DB:PullRequestVersion at %#x>' % id(self)
3799 return '<DB:PullRequestVersion at %#x>' % id(self)
3795
3800
3796 @property
3801 @property
3797 def reviewers(self):
3802 def reviewers(self):
3798 return self.pull_request.reviewers
3803 return self.pull_request.reviewers
3799
3804
3800 @property
3805 @property
3801 def versions(self):
3806 def versions(self):
3802 return self.pull_request.versions
3807 return self.pull_request.versions
3803
3808
3804 def is_closed(self):
3809 def is_closed(self):
3805 # calculate from original
3810 # calculate from original
3806 return self.pull_request.status == self.STATUS_CLOSED
3811 return self.pull_request.status == self.STATUS_CLOSED
3807
3812
3808 def calculated_review_status(self):
3813 def calculated_review_status(self):
3809 return self.pull_request.calculated_review_status()
3814 return self.pull_request.calculated_review_status()
3810
3815
3811 def reviewers_statuses(self):
3816 def reviewers_statuses(self):
3812 return self.pull_request.reviewers_statuses()
3817 return self.pull_request.reviewers_statuses()
3813
3818
3814
3819
3815 class PullRequestReviewers(Base, BaseModel):
3820 class PullRequestReviewers(Base, BaseModel):
3816 __tablename__ = 'pull_request_reviewers'
3821 __tablename__ = 'pull_request_reviewers'
3817 __table_args__ = (
3822 __table_args__ = (
3818 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3823 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3819 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3824 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3820 )
3825 )
3821
3826
3822 @hybrid_property
3827 @hybrid_property
3823 def reasons(self):
3828 def reasons(self):
3824 if not self._reasons:
3829 if not self._reasons:
3825 return []
3830 return []
3826 return self._reasons
3831 return self._reasons
3827
3832
3828 @reasons.setter
3833 @reasons.setter
3829 def reasons(self, val):
3834 def reasons(self, val):
3830 val = val or []
3835 val = val or []
3831 if any(not isinstance(x, basestring) for x in val):
3836 if any(not isinstance(x, basestring) for x in val):
3832 raise Exception('invalid reasons type, must be list of strings')
3837 raise Exception('invalid reasons type, must be list of strings')
3833 self._reasons = val
3838 self._reasons = val
3834
3839
3835 pull_requests_reviewers_id = Column(
3840 pull_requests_reviewers_id = Column(
3836 'pull_requests_reviewers_id', Integer(), nullable=False,
3841 'pull_requests_reviewers_id', Integer(), nullable=False,
3837 primary_key=True)
3842 primary_key=True)
3838 pull_request_id = Column(
3843 pull_request_id = Column(
3839 "pull_request_id", Integer(),
3844 "pull_request_id", Integer(),
3840 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3845 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3841 user_id = Column(
3846 user_id = Column(
3842 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3847 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3843 _reasons = Column(
3848 _reasons = Column(
3844 'reason', MutationList.as_mutable(
3849 'reason', MutationList.as_mutable(
3845 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3850 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3846
3851
3847 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3852 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3848 user = relationship('User')
3853 user = relationship('User')
3849 pull_request = relationship('PullRequest')
3854 pull_request = relationship('PullRequest')
3850
3855
3851 rule_data = Column(
3856 rule_data = Column(
3852 'rule_data_json',
3857 'rule_data_json',
3853 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3858 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
3854
3859
3855 def rule_user_group_data(self):
3860 def rule_user_group_data(self):
3856 """
3861 """
3857 Returns the voting user group rule data for this reviewer
3862 Returns the voting user group rule data for this reviewer
3858 """
3863 """
3859
3864
3860 if self.rule_data and 'vote_rule' in self.rule_data:
3865 if self.rule_data and 'vote_rule' in self.rule_data:
3861 user_group_data = {}
3866 user_group_data = {}
3862 if 'rule_user_group_entry_id' in self.rule_data:
3867 if 'rule_user_group_entry_id' in self.rule_data:
3863 # means a group with voting rules !
3868 # means a group with voting rules !
3864 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3869 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
3865 user_group_data['name'] = self.rule_data['rule_name']
3870 user_group_data['name'] = self.rule_data['rule_name']
3866 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3871 user_group_data['vote_rule'] = self.rule_data['vote_rule']
3867
3872
3868 return user_group_data
3873 return user_group_data
3869
3874
3870 def __unicode__(self):
3875 def __unicode__(self):
3871 return u"<%s('id:%s')>" % (self.__class__.__name__,
3876 return u"<%s('id:%s')>" % (self.__class__.__name__,
3872 self.pull_requests_reviewers_id)
3877 self.pull_requests_reviewers_id)
3873
3878
3874
3879
3875 class Notification(Base, BaseModel):
3880 class Notification(Base, BaseModel):
3876 __tablename__ = 'notifications'
3881 __tablename__ = 'notifications'
3877 __table_args__ = (
3882 __table_args__ = (
3878 Index('notification_type_idx', 'type'),
3883 Index('notification_type_idx', 'type'),
3879 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3880 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3885 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3881 )
3886 )
3882
3887
3883 TYPE_CHANGESET_COMMENT = u'cs_comment'
3888 TYPE_CHANGESET_COMMENT = u'cs_comment'
3884 TYPE_MESSAGE = u'message'
3889 TYPE_MESSAGE = u'message'
3885 TYPE_MENTION = u'mention'
3890 TYPE_MENTION = u'mention'
3886 TYPE_REGISTRATION = u'registration'
3891 TYPE_REGISTRATION = u'registration'
3887 TYPE_PULL_REQUEST = u'pull_request'
3892 TYPE_PULL_REQUEST = u'pull_request'
3888 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3893 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3889
3894
3890 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3895 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3891 subject = Column('subject', Unicode(512), nullable=True)
3896 subject = Column('subject', Unicode(512), nullable=True)
3892 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3897 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3893 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3898 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3894 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)
3895 type_ = Column('type', Unicode(255))
3900 type_ = Column('type', Unicode(255))
3896
3901
3897 created_by_user = relationship('User')
3902 created_by_user = relationship('User')
3898 notifications_to_users = relationship('UserNotification', lazy='joined',
3903 notifications_to_users = relationship('UserNotification', lazy='joined',
3899 cascade="all, delete, delete-orphan")
3904 cascade="all, delete, delete-orphan")
3900
3905
3901 @property
3906 @property
3902 def recipients(self):
3907 def recipients(self):
3903 return [x.user for x in UserNotification.query()\
3908 return [x.user for x in UserNotification.query()\
3904 .filter(UserNotification.notification == self)\
3909 .filter(UserNotification.notification == self)\
3905 .order_by(UserNotification.user_id.asc()).all()]
3910 .order_by(UserNotification.user_id.asc()).all()]
3906
3911
3907 @classmethod
3912 @classmethod
3908 def create(cls, created_by, subject, body, recipients, type_=None):
3913 def create(cls, created_by, subject, body, recipients, type_=None):
3909 if type_ is None:
3914 if type_ is None:
3910 type_ = Notification.TYPE_MESSAGE
3915 type_ = Notification.TYPE_MESSAGE
3911
3916
3912 notification = cls()
3917 notification = cls()
3913 notification.created_by_user = created_by
3918 notification.created_by_user = created_by
3914 notification.subject = subject
3919 notification.subject = subject
3915 notification.body = body
3920 notification.body = body
3916 notification.type_ = type_
3921 notification.type_ = type_
3917 notification.created_on = datetime.datetime.now()
3922 notification.created_on = datetime.datetime.now()
3918
3923
3919 for u in recipients:
3924 for u in recipients:
3920 assoc = UserNotification()
3925 assoc = UserNotification()
3921 assoc.notification = notification
3926 assoc.notification = notification
3922
3927
3923 # if created_by is inside recipients mark his notification
3928 # if created_by is inside recipients mark his notification
3924 # as read
3929 # as read
3925 if u.user_id == created_by.user_id:
3930 if u.user_id == created_by.user_id:
3926 assoc.read = True
3931 assoc.read = True
3927
3932
3928 u.notifications.append(assoc)
3933 u.notifications.append(assoc)
3929 Session().add(notification)
3934 Session().add(notification)
3930
3935
3931 return notification
3936 return notification
3932
3937
3933
3938
3934 class UserNotification(Base, BaseModel):
3939 class UserNotification(Base, BaseModel):
3935 __tablename__ = 'user_to_notification'
3940 __tablename__ = 'user_to_notification'
3936 __table_args__ = (
3941 __table_args__ = (
3937 UniqueConstraint('user_id', 'notification_id'),
3942 UniqueConstraint('user_id', 'notification_id'),
3938 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3939 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3940 )
3945 )
3941 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3946 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3942 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3947 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3943 read = Column('read', Boolean, default=False)
3948 read = Column('read', Boolean, default=False)
3944 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3949 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3945
3950
3946 user = relationship('User', lazy="joined")
3951 user = relationship('User', lazy="joined")
3947 notification = relationship('Notification', lazy="joined",
3952 notification = relationship('Notification', lazy="joined",
3948 order_by=lambda: Notification.created_on.desc(),)
3953 order_by=lambda: Notification.created_on.desc(),)
3949
3954
3950 def mark_as_read(self):
3955 def mark_as_read(self):
3951 self.read = True
3956 self.read = True
3952 Session().add(self)
3957 Session().add(self)
3953
3958
3954
3959
3955 class Gist(Base, BaseModel):
3960 class Gist(Base, BaseModel):
3956 __tablename__ = 'gists'
3961 __tablename__ = 'gists'
3957 __table_args__ = (
3962 __table_args__ = (
3958 Index('g_gist_access_id_idx', 'gist_access_id'),
3963 Index('g_gist_access_id_idx', 'gist_access_id'),
3959 Index('g_created_on_idx', 'created_on'),
3964 Index('g_created_on_idx', 'created_on'),
3960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3965 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3961 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3966 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3962 )
3967 )
3963 GIST_PUBLIC = u'public'
3968 GIST_PUBLIC = u'public'
3964 GIST_PRIVATE = u'private'
3969 GIST_PRIVATE = u'private'
3965 DEFAULT_FILENAME = u'gistfile1.txt'
3970 DEFAULT_FILENAME = u'gistfile1.txt'
3966
3971
3967 ACL_LEVEL_PUBLIC = u'acl_public'
3972 ACL_LEVEL_PUBLIC = u'acl_public'
3968 ACL_LEVEL_PRIVATE = u'acl_private'
3973 ACL_LEVEL_PRIVATE = u'acl_private'
3969
3974
3970 gist_id = Column('gist_id', Integer(), primary_key=True)
3975 gist_id = Column('gist_id', Integer(), primary_key=True)
3971 gist_access_id = Column('gist_access_id', Unicode(250))
3976 gist_access_id = Column('gist_access_id', Unicode(250))
3972 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3977 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3973 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3978 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3974 gist_expires = Column('gist_expires', Float(53), nullable=False)
3979 gist_expires = Column('gist_expires', Float(53), nullable=False)
3975 gist_type = Column('gist_type', Unicode(128), nullable=False)
3980 gist_type = Column('gist_type', Unicode(128), nullable=False)
3976 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3981 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3977 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3982 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3978 acl_level = Column('acl_level', Unicode(128), nullable=True)
3983 acl_level = Column('acl_level', Unicode(128), nullable=True)
3979
3984
3980 owner = relationship('User')
3985 owner = relationship('User')
3981
3986
3982 def __repr__(self):
3987 def __repr__(self):
3983 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3988 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3984
3989
3985 @hybrid_property
3990 @hybrid_property
3986 def description_safe(self):
3991 def description_safe(self):
3987 from rhodecode.lib import helpers as h
3992 from rhodecode.lib import helpers as h
3988 return h.escape(self.gist_description)
3993 return h.escape(self.gist_description)
3989
3994
3990 @classmethod
3995 @classmethod
3991 def get_or_404(cls, id_):
3996 def get_or_404(cls, id_):
3992 from pyramid.httpexceptions import HTTPNotFound
3997 from pyramid.httpexceptions import HTTPNotFound
3993
3998
3994 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3999 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3995 if not res:
4000 if not res:
3996 raise HTTPNotFound()
4001 raise HTTPNotFound()
3997 return res
4002 return res
3998
4003
3999 @classmethod
4004 @classmethod
4000 def get_by_access_id(cls, gist_access_id):
4005 def get_by_access_id(cls, gist_access_id):
4001 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4006 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4002
4007
4003 def gist_url(self):
4008 def gist_url(self):
4004 from rhodecode.model.gist import GistModel
4009 from rhodecode.model.gist import GistModel
4005 return GistModel().get_url(self)
4010 return GistModel().get_url(self)
4006
4011
4007 @classmethod
4012 @classmethod
4008 def base_path(cls):
4013 def base_path(cls):
4009 """
4014 """
4010 Returns base path when all gists are stored
4015 Returns base path when all gists are stored
4011
4016
4012 :param cls:
4017 :param cls:
4013 """
4018 """
4014 from rhodecode.model.gist import GIST_STORE_LOC
4019 from rhodecode.model.gist import GIST_STORE_LOC
4015 q = Session().query(RhodeCodeUi)\
4020 q = Session().query(RhodeCodeUi)\
4016 .filter(RhodeCodeUi.ui_key == URL_SEP)
4021 .filter(RhodeCodeUi.ui_key == URL_SEP)
4017 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4022 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4018 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4023 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4019
4024
4020 def get_api_data(self):
4025 def get_api_data(self):
4021 """
4026 """
4022 Common function for generating gist related data for API
4027 Common function for generating gist related data for API
4023 """
4028 """
4024 gist = self
4029 gist = self
4025 data = {
4030 data = {
4026 'gist_id': gist.gist_id,
4031 'gist_id': gist.gist_id,
4027 'type': gist.gist_type,
4032 'type': gist.gist_type,
4028 'access_id': gist.gist_access_id,
4033 'access_id': gist.gist_access_id,
4029 'description': gist.gist_description,
4034 'description': gist.gist_description,
4030 'url': gist.gist_url(),
4035 'url': gist.gist_url(),
4031 'expires': gist.gist_expires,
4036 'expires': gist.gist_expires,
4032 'created_on': gist.created_on,
4037 'created_on': gist.created_on,
4033 'modified_at': gist.modified_at,
4038 'modified_at': gist.modified_at,
4034 'content': None,
4039 'content': None,
4035 'acl_level': gist.acl_level,
4040 'acl_level': gist.acl_level,
4036 }
4041 }
4037 return data
4042 return data
4038
4043
4039 def __json__(self):
4044 def __json__(self):
4040 data = dict(
4045 data = dict(
4041 )
4046 )
4042 data.update(self.get_api_data())
4047 data.update(self.get_api_data())
4043 return data
4048 return data
4044 # SCM functions
4049 # SCM functions
4045
4050
4046 def scm_instance(self, **kwargs):
4051 def scm_instance(self, **kwargs):
4047 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4052 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4048 return get_vcs_instance(
4053 return get_vcs_instance(
4049 repo_path=safe_str(full_repo_path), create=False)
4054 repo_path=safe_str(full_repo_path), create=False)
4050
4055
4051
4056
4052 class ExternalIdentity(Base, BaseModel):
4057 class ExternalIdentity(Base, BaseModel):
4053 __tablename__ = 'external_identities'
4058 __tablename__ = 'external_identities'
4054 __table_args__ = (
4059 __table_args__ = (
4055 Index('local_user_id_idx', 'local_user_id'),
4060 Index('local_user_id_idx', 'local_user_id'),
4056 Index('external_id_idx', 'external_id'),
4061 Index('external_id_idx', 'external_id'),
4057 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4062 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4058 'mysql_charset': 'utf8'})
4063 'mysql_charset': 'utf8'})
4059
4064
4060 external_id = Column('external_id', Unicode(255), default=u'',
4065 external_id = Column('external_id', Unicode(255), default=u'',
4061 primary_key=True)
4066 primary_key=True)
4062 external_username = Column('external_username', Unicode(1024), default=u'')
4067 external_username = Column('external_username', Unicode(1024), default=u'')
4063 local_user_id = Column('local_user_id', Integer(),
4068 local_user_id = Column('local_user_id', Integer(),
4064 ForeignKey('users.user_id'), primary_key=True)
4069 ForeignKey('users.user_id'), primary_key=True)
4065 provider_name = Column('provider_name', Unicode(255), default=u'',
4070 provider_name = Column('provider_name', Unicode(255), default=u'',
4066 primary_key=True)
4071 primary_key=True)
4067 access_token = Column('access_token', String(1024), default=u'')
4072 access_token = Column('access_token', String(1024), default=u'')
4068 alt_token = Column('alt_token', String(1024), default=u'')
4073 alt_token = Column('alt_token', String(1024), default=u'')
4069 token_secret = Column('token_secret', String(1024), default=u'')
4074 token_secret = Column('token_secret', String(1024), default=u'')
4070
4075
4071 @classmethod
4076 @classmethod
4072 def by_external_id_and_provider(cls, external_id, provider_name,
4077 def by_external_id_and_provider(cls, external_id, provider_name,
4073 local_user_id=None):
4078 local_user_id=None):
4074 """
4079 """
4075 Returns ExternalIdentity instance based on search params
4080 Returns ExternalIdentity instance based on search params
4076
4081
4077 :param external_id:
4082 :param external_id:
4078 :param provider_name:
4083 :param provider_name:
4079 :return: ExternalIdentity
4084 :return: ExternalIdentity
4080 """
4085 """
4081 query = cls.query()
4086 query = cls.query()
4082 query = query.filter(cls.external_id == external_id)
4087 query = query.filter(cls.external_id == external_id)
4083 query = query.filter(cls.provider_name == provider_name)
4088 query = query.filter(cls.provider_name == provider_name)
4084 if local_user_id:
4089 if local_user_id:
4085 query = query.filter(cls.local_user_id == local_user_id)
4090 query = query.filter(cls.local_user_id == local_user_id)
4086 return query.first()
4091 return query.first()
4087
4092
4088 @classmethod
4093 @classmethod
4089 def user_by_external_id_and_provider(cls, external_id, provider_name):
4094 def user_by_external_id_and_provider(cls, external_id, provider_name):
4090 """
4095 """
4091 Returns User instance based on search params
4096 Returns User instance based on search params
4092
4097
4093 :param external_id:
4098 :param external_id:
4094 :param provider_name:
4099 :param provider_name:
4095 :return: User
4100 :return: User
4096 """
4101 """
4097 query = User.query()
4102 query = User.query()
4098 query = query.filter(cls.external_id == external_id)
4103 query = query.filter(cls.external_id == external_id)
4099 query = query.filter(cls.provider_name == provider_name)
4104 query = query.filter(cls.provider_name == provider_name)
4100 query = query.filter(User.user_id == cls.local_user_id)
4105 query = query.filter(User.user_id == cls.local_user_id)
4101 return query.first()
4106 return query.first()
4102
4107
4103 @classmethod
4108 @classmethod
4104 def by_local_user_id(cls, local_user_id):
4109 def by_local_user_id(cls, local_user_id):
4105 """
4110 """
4106 Returns all tokens for user
4111 Returns all tokens for user
4107
4112
4108 :param local_user_id:
4113 :param local_user_id:
4109 :return: ExternalIdentity
4114 :return: ExternalIdentity
4110 """
4115 """
4111 query = cls.query()
4116 query = cls.query()
4112 query = query.filter(cls.local_user_id == local_user_id)
4117 query = query.filter(cls.local_user_id == local_user_id)
4113 return query
4118 return query
4114
4119
4115
4120
4116 class Integration(Base, BaseModel):
4121 class Integration(Base, BaseModel):
4117 __tablename__ = 'integrations'
4122 __tablename__ = 'integrations'
4118 __table_args__ = (
4123 __table_args__ = (
4119 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4124 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4120 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4125 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
4121 )
4126 )
4122
4127
4123 integration_id = Column('integration_id', Integer(), primary_key=True)
4128 integration_id = Column('integration_id', Integer(), primary_key=True)
4124 integration_type = Column('integration_type', String(255))
4129 integration_type = Column('integration_type', String(255))
4125 enabled = Column('enabled', Boolean(), nullable=False)
4130 enabled = Column('enabled', Boolean(), nullable=False)
4126 name = Column('name', String(255), nullable=False)
4131 name = Column('name', String(255), nullable=False)
4127 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4132 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4128 default=False)
4133 default=False)
4129
4134
4130 settings = Column(
4135 settings = Column(
4131 'settings_json', MutationObj.as_mutable(
4136 'settings_json', MutationObj.as_mutable(
4132 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4137 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4133 repo_id = Column(
4138 repo_id = Column(
4134 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4139 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4135 nullable=True, unique=None, default=None)
4140 nullable=True, unique=None, default=None)
4136 repo = relationship('Repository', lazy='joined')
4141 repo = relationship('Repository', lazy='joined')
4137
4142
4138 repo_group_id = Column(
4143 repo_group_id = Column(
4139 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4144 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4140 nullable=True, unique=None, default=None)
4145 nullable=True, unique=None, default=None)
4141 repo_group = relationship('RepoGroup', lazy='joined')
4146 repo_group = relationship('RepoGroup', lazy='joined')
4142
4147
4143 @property
4148 @property
4144 def scope(self):
4149 def scope(self):
4145 if self.repo:
4150 if self.repo:
4146 return repr(self.repo)
4151 return repr(self.repo)
4147 if self.repo_group:
4152 if self.repo_group:
4148 if self.child_repos_only:
4153 if self.child_repos_only:
4149 return repr(self.repo_group) + ' (child repos only)'
4154 return repr(self.repo_group) + ' (child repos only)'
4150 else:
4155 else:
4151 return repr(self.repo_group) + ' (recursive)'
4156 return repr(self.repo_group) + ' (recursive)'
4152 if self.child_repos_only:
4157 if self.child_repos_only:
4153 return 'root_repos'
4158 return 'root_repos'
4154 return 'global'
4159 return 'global'
4155
4160
4156 def __repr__(self):
4161 def __repr__(self):
4157 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4162 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4158
4163
4159
4164
4160 class RepoReviewRuleUser(Base, BaseModel):
4165 class RepoReviewRuleUser(Base, BaseModel):
4161 __tablename__ = 'repo_review_rules_users'
4166 __tablename__ = 'repo_review_rules_users'
4162 __table_args__ = (
4167 __table_args__ = (
4163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4164 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4165 )
4170 )
4166
4171
4167 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4172 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4168 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4173 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4169 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4174 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4170 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4175 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4171 user = relationship('User')
4176 user = relationship('User')
4172
4177
4173 def rule_data(self):
4178 def rule_data(self):
4174 return {
4179 return {
4175 'mandatory': self.mandatory
4180 'mandatory': self.mandatory
4176 }
4181 }
4177
4182
4178
4183
4179 class RepoReviewRuleUserGroup(Base, BaseModel):
4184 class RepoReviewRuleUserGroup(Base, BaseModel):
4180 __tablename__ = 'repo_review_rules_users_groups'
4185 __tablename__ = 'repo_review_rules_users_groups'
4181 __table_args__ = (
4186 __table_args__ = (
4182 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4183 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4188 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4184 )
4189 )
4185 VOTE_RULE_ALL = -1
4190 VOTE_RULE_ALL = -1
4186
4191
4187 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4192 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4188 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4193 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4189 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4194 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4190 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4195 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4191 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4196 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4192 users_group = relationship('UserGroup')
4197 users_group = relationship('UserGroup')
4193
4198
4194 def rule_data(self):
4199 def rule_data(self):
4195 return {
4200 return {
4196 'mandatory': self.mandatory,
4201 'mandatory': self.mandatory,
4197 'vote_rule': self.vote_rule
4202 'vote_rule': self.vote_rule
4198 }
4203 }
4199
4204
4200 @property
4205 @property
4201 def vote_rule_label(self):
4206 def vote_rule_label(self):
4202 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4207 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4203 return 'all must vote'
4208 return 'all must vote'
4204 else:
4209 else:
4205 return 'min. vote {}'.format(self.vote_rule)
4210 return 'min. vote {}'.format(self.vote_rule)
4206
4211
4207
4212
4208 class RepoReviewRule(Base, BaseModel):
4213 class RepoReviewRule(Base, BaseModel):
4209 __tablename__ = 'repo_review_rules'
4214 __tablename__ = 'repo_review_rules'
4210 __table_args__ = (
4215 __table_args__ = (
4211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4217 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4213 )
4218 )
4214
4219
4215 repo_review_rule_id = Column(
4220 repo_review_rule_id = Column(
4216 'repo_review_rule_id', Integer(), primary_key=True)
4221 'repo_review_rule_id', Integer(), primary_key=True)
4217 repo_id = Column(
4222 repo_id = Column(
4218 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4223 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4219 repo = relationship('Repository', backref='review_rules')
4224 repo = relationship('Repository', backref='review_rules')
4220
4225
4221 review_rule_name = Column('review_rule_name', String(255))
4226 review_rule_name = Column('review_rule_name', String(255))
4222 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4227 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4223 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4228 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4224 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4229 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4225
4230
4226 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4231 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4227 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4232 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4228 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4233 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4229 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4234 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4230
4235
4231 rule_users = relationship('RepoReviewRuleUser')
4236 rule_users = relationship('RepoReviewRuleUser')
4232 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4237 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4233
4238
4234 def _validate_glob(self, value):
4239 def _validate_glob(self, value):
4235 re.compile('^' + glob2re(value) + '$')
4240 re.compile('^' + glob2re(value) + '$')
4236
4241
4237 @hybrid_property
4242 @hybrid_property
4238 def source_branch_pattern(self):
4243 def source_branch_pattern(self):
4239 return self._branch_pattern or '*'
4244 return self._branch_pattern or '*'
4240
4245
4241 @source_branch_pattern.setter
4246 @source_branch_pattern.setter
4242 def source_branch_pattern(self, value):
4247 def source_branch_pattern(self, value):
4243 self._validate_glob(value)
4248 self._validate_glob(value)
4244 self._branch_pattern = value or '*'
4249 self._branch_pattern = value or '*'
4245
4250
4246 @hybrid_property
4251 @hybrid_property
4247 def target_branch_pattern(self):
4252 def target_branch_pattern(self):
4248 return self._target_branch_pattern or '*'
4253 return self._target_branch_pattern or '*'
4249
4254
4250 @target_branch_pattern.setter
4255 @target_branch_pattern.setter
4251 def target_branch_pattern(self, value):
4256 def target_branch_pattern(self, value):
4252 self._validate_glob(value)
4257 self._validate_glob(value)
4253 self._target_branch_pattern = value or '*'
4258 self._target_branch_pattern = value or '*'
4254
4259
4255 @hybrid_property
4260 @hybrid_property
4256 def file_pattern(self):
4261 def file_pattern(self):
4257 return self._file_pattern or '*'
4262 return self._file_pattern or '*'
4258
4263
4259 @file_pattern.setter
4264 @file_pattern.setter
4260 def file_pattern(self, value):
4265 def file_pattern(self, value):
4261 self._validate_glob(value)
4266 self._validate_glob(value)
4262 self._file_pattern = value or '*'
4267 self._file_pattern = value or '*'
4263
4268
4264 def matches(self, source_branch, target_branch, files_changed):
4269 def matches(self, source_branch, target_branch, files_changed):
4265 """
4270 """
4266 Check if this review rule matches a branch/files in a pull request
4271 Check if this review rule matches a branch/files in a pull request
4267
4272
4268 :param source_branch: source branch name for the commit
4273 :param source_branch: source branch name for the commit
4269 :param target_branch: target branch name for the commit
4274 :param target_branch: target branch name for the commit
4270 :param files_changed: list of file paths changed in the pull request
4275 :param files_changed: list of file paths changed in the pull request
4271 """
4276 """
4272
4277
4273 source_branch = source_branch or ''
4278 source_branch = source_branch or ''
4274 target_branch = target_branch or ''
4279 target_branch = target_branch or ''
4275 files_changed = files_changed or []
4280 files_changed = files_changed or []
4276
4281
4277 branch_matches = True
4282 branch_matches = True
4278 if source_branch or target_branch:
4283 if source_branch or target_branch:
4279 if self.source_branch_pattern == '*':
4284 if self.source_branch_pattern == '*':
4280 source_branch_match = True
4285 source_branch_match = True
4281 else:
4286 else:
4282 source_branch_regex = re.compile(
4287 source_branch_regex = re.compile(
4283 '^' + glob2re(self.source_branch_pattern) + '$')
4288 '^' + glob2re(self.source_branch_pattern) + '$')
4284 source_branch_match = bool(source_branch_regex.search(source_branch))
4289 source_branch_match = bool(source_branch_regex.search(source_branch))
4285 if self.target_branch_pattern == '*':
4290 if self.target_branch_pattern == '*':
4286 target_branch_match = True
4291 target_branch_match = True
4287 else:
4292 else:
4288 target_branch_regex = re.compile(
4293 target_branch_regex = re.compile(
4289 '^' + glob2re(self.target_branch_pattern) + '$')
4294 '^' + glob2re(self.target_branch_pattern) + '$')
4290 target_branch_match = bool(target_branch_regex.search(target_branch))
4295 target_branch_match = bool(target_branch_regex.search(target_branch))
4291
4296
4292 branch_matches = source_branch_match and target_branch_match
4297 branch_matches = source_branch_match and target_branch_match
4293
4298
4294 files_matches = True
4299 files_matches = True
4295 if self.file_pattern != '*':
4300 if self.file_pattern != '*':
4296 files_matches = False
4301 files_matches = False
4297 file_regex = re.compile(glob2re(self.file_pattern))
4302 file_regex = re.compile(glob2re(self.file_pattern))
4298 for filename in files_changed:
4303 for filename in files_changed:
4299 if file_regex.search(filename):
4304 if file_regex.search(filename):
4300 files_matches = True
4305 files_matches = True
4301 break
4306 break
4302
4307
4303 return branch_matches and files_matches
4308 return branch_matches and files_matches
4304
4309
4305 @property
4310 @property
4306 def review_users(self):
4311 def review_users(self):
4307 """ Returns the users which this rule applies to """
4312 """ Returns the users which this rule applies to """
4308
4313
4309 users = collections.OrderedDict()
4314 users = collections.OrderedDict()
4310
4315
4311 for rule_user in self.rule_users:
4316 for rule_user in self.rule_users:
4312 if rule_user.user.active:
4317 if rule_user.user.active:
4313 if rule_user.user not in users:
4318 if rule_user.user not in users:
4314 users[rule_user.user.username] = {
4319 users[rule_user.user.username] = {
4315 'user': rule_user.user,
4320 'user': rule_user.user,
4316 'source': 'user',
4321 'source': 'user',
4317 'source_data': {},
4322 'source_data': {},
4318 'data': rule_user.rule_data()
4323 'data': rule_user.rule_data()
4319 }
4324 }
4320
4325
4321 for rule_user_group in self.rule_user_groups:
4326 for rule_user_group in self.rule_user_groups:
4322 source_data = {
4327 source_data = {
4323 'user_group_id': rule_user_group.users_group.users_group_id,
4328 'user_group_id': rule_user_group.users_group.users_group_id,
4324 'name': rule_user_group.users_group.users_group_name,
4329 'name': rule_user_group.users_group.users_group_name,
4325 'members': len(rule_user_group.users_group.members)
4330 'members': len(rule_user_group.users_group.members)
4326 }
4331 }
4327 for member in rule_user_group.users_group.members:
4332 for member in rule_user_group.users_group.members:
4328 if member.user.active:
4333 if member.user.active:
4329 key = member.user.username
4334 key = member.user.username
4330 if key in users:
4335 if key in users:
4331 # skip this member as we have him already
4336 # skip this member as we have him already
4332 # this prevents from override the "first" matched
4337 # this prevents from override the "first" matched
4333 # users with duplicates in multiple groups
4338 # users with duplicates in multiple groups
4334 continue
4339 continue
4335
4340
4336 users[key] = {
4341 users[key] = {
4337 'user': member.user,
4342 'user': member.user,
4338 'source': 'user_group',
4343 'source': 'user_group',
4339 'source_data': source_data,
4344 'source_data': source_data,
4340 'data': rule_user_group.rule_data()
4345 'data': rule_user_group.rule_data()
4341 }
4346 }
4342
4347
4343 return users
4348 return users
4344
4349
4345 def user_group_vote_rule(self):
4350 def user_group_vote_rule(self):
4346 rules = []
4351 rules = []
4347 if self.rule_user_groups:
4352 if self.rule_user_groups:
4348 for user_group in self.rule_user_groups:
4353 for user_group in self.rule_user_groups:
4349 rules.append(user_group)
4354 rules.append(user_group)
4350 return rules
4355 return rules
4351
4356
4352 def __repr__(self):
4357 def __repr__(self):
4353 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4358 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4354 self.repo_review_rule_id, self.repo)
4359 self.repo_review_rule_id, self.repo)
4355
4360
4356
4361
4357 class ScheduleEntry(Base, BaseModel):
4362 class ScheduleEntry(Base, BaseModel):
4358 __tablename__ = 'schedule_entries'
4363 __tablename__ = 'schedule_entries'
4359 __table_args__ = (
4364 __table_args__ = (
4360 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4365 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4361 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4366 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4362 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4367 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4363 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4368 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4364 )
4369 )
4365 schedule_types = ['crontab', 'timedelta', 'integer']
4370 schedule_types = ['crontab', 'timedelta', 'integer']
4366 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4371 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4367
4372
4368 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4373 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4369 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4374 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4370 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4375 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4371
4376
4372 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4377 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4373 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4378 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4374
4379
4375 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4380 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4376 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4381 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4377
4382
4378 # task
4383 # task
4379 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4384 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4380 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4385 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4381 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4386 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4382 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4387 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4383
4388
4384 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4389 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4385 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4390 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4386
4391
4387 @hybrid_property
4392 @hybrid_property
4388 def schedule_type(self):
4393 def schedule_type(self):
4389 return self._schedule_type
4394 return self._schedule_type
4390
4395
4391 @schedule_type.setter
4396 @schedule_type.setter
4392 def schedule_type(self, val):
4397 def schedule_type(self, val):
4393 if val not in self.schedule_types:
4398 if val not in self.schedule_types:
4394 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4399 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4395 val, self.schedule_type))
4400 val, self.schedule_type))
4396
4401
4397 self._schedule_type = val
4402 self._schedule_type = val
4398
4403
4399 @classmethod
4404 @classmethod
4400 def get_uid(cls, obj):
4405 def get_uid(cls, obj):
4401 args = obj.task_args
4406 args = obj.task_args
4402 kwargs = obj.task_kwargs
4407 kwargs = obj.task_kwargs
4403 if isinstance(args, JsonRaw):
4408 if isinstance(args, JsonRaw):
4404 try:
4409 try:
4405 args = json.loads(args)
4410 args = json.loads(args)
4406 except ValueError:
4411 except ValueError:
4407 args = tuple()
4412 args = tuple()
4408
4413
4409 if isinstance(kwargs, JsonRaw):
4414 if isinstance(kwargs, JsonRaw):
4410 try:
4415 try:
4411 kwargs = json.loads(kwargs)
4416 kwargs = json.loads(kwargs)
4412 except ValueError:
4417 except ValueError:
4413 kwargs = dict()
4418 kwargs = dict()
4414
4419
4415 dot_notation = obj.task_dot_notation
4420 dot_notation = obj.task_dot_notation
4416 val = '.'.join(map(safe_str, [
4421 val = '.'.join(map(safe_str, [
4417 sorted(dot_notation), args, sorted(kwargs.items())]))
4422 sorted(dot_notation), args, sorted(kwargs.items())]))
4418 return hashlib.sha1(val).hexdigest()
4423 return hashlib.sha1(val).hexdigest()
4419
4424
4420 @classmethod
4425 @classmethod
4421 def get_by_schedule_name(cls, schedule_name):
4426 def get_by_schedule_name(cls, schedule_name):
4422 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4427 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4423
4428
4424 @classmethod
4429 @classmethod
4425 def get_by_schedule_id(cls, schedule_id):
4430 def get_by_schedule_id(cls, schedule_id):
4426 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4431 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4427
4432
4428 @property
4433 @property
4429 def task(self):
4434 def task(self):
4430 return self.task_dot_notation
4435 return self.task_dot_notation
4431
4436
4432 @property
4437 @property
4433 def schedule(self):
4438 def schedule(self):
4434 from rhodecode.lib.celerylib.utils import raw_2_schedule
4439 from rhodecode.lib.celerylib.utils import raw_2_schedule
4435 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4440 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4436 return schedule
4441 return schedule
4437
4442
4438 @property
4443 @property
4439 def args(self):
4444 def args(self):
4440 try:
4445 try:
4441 return list(self.task_args or [])
4446 return list(self.task_args or [])
4442 except ValueError:
4447 except ValueError:
4443 return list()
4448 return list()
4444
4449
4445 @property
4450 @property
4446 def kwargs(self):
4451 def kwargs(self):
4447 try:
4452 try:
4448 return dict(self.task_kwargs or {})
4453 return dict(self.task_kwargs or {})
4449 except ValueError:
4454 except ValueError:
4450 return dict()
4455 return dict()
4451
4456
4452 def _as_raw(self, val):
4457 def _as_raw(self, val):
4453 if hasattr(val, 'de_coerce'):
4458 if hasattr(val, 'de_coerce'):
4454 val = val.de_coerce()
4459 val = val.de_coerce()
4455 if val:
4460 if val:
4456 val = json.dumps(val)
4461 val = json.dumps(val)
4457
4462
4458 return val
4463 return val
4459
4464
4460 @property
4465 @property
4461 def schedule_definition_raw(self):
4466 def schedule_definition_raw(self):
4462 return self._as_raw(self.schedule_definition)
4467 return self._as_raw(self.schedule_definition)
4463
4468
4464 @property
4469 @property
4465 def args_raw(self):
4470 def args_raw(self):
4466 return self._as_raw(self.task_args)
4471 return self._as_raw(self.task_args)
4467
4472
4468 @property
4473 @property
4469 def kwargs_raw(self):
4474 def kwargs_raw(self):
4470 return self._as_raw(self.task_kwargs)
4475 return self._as_raw(self.task_kwargs)
4471
4476
4472 def __repr__(self):
4477 def __repr__(self):
4473 return '<DB:ScheduleEntry({}:{})>'.format(
4478 return '<DB:ScheduleEntry({}:{})>'.format(
4474 self.schedule_entry_id, self.schedule_name)
4479 self.schedule_entry_id, self.schedule_name)
4475
4480
4476
4481
4477 @event.listens_for(ScheduleEntry, 'before_update')
4482 @event.listens_for(ScheduleEntry, 'before_update')
4478 def update_task_uid(mapper, connection, target):
4483 def update_task_uid(mapper, connection, target):
4479 target.task_uid = ScheduleEntry.get_uid(target)
4484 target.task_uid = ScheduleEntry.get_uid(target)
4480
4485
4481
4486
4482 @event.listens_for(ScheduleEntry, 'before_insert')
4487 @event.listens_for(ScheduleEntry, 'before_insert')
4483 def set_task_uid(mapper, connection, target):
4488 def set_task_uid(mapper, connection, target):
4484 target.task_uid = ScheduleEntry.get_uid(target)
4489 target.task_uid = ScheduleEntry.get_uid(target)
4485
4490
4486
4491
4487 class DbMigrateVersion(Base, BaseModel):
4492 class DbMigrateVersion(Base, BaseModel):
4488 __tablename__ = 'db_migrate_version'
4493 __tablename__ = 'db_migrate_version'
4489 __table_args__ = (
4494 __table_args__ = (
4490 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4491 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4496 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4492 )
4497 )
4493 repository_id = Column('repository_id', String(250), primary_key=True)
4498 repository_id = Column('repository_id', String(250), primary_key=True)
4494 repository_path = Column('repository_path', Text)
4499 repository_path = Column('repository_path', Text)
4495 version = Column('version', Integer)
4500 version = Column('version', Integer)
4496
4501
4497
4502
4498 class DbSession(Base, BaseModel):
4503 class DbSession(Base, BaseModel):
4499 __tablename__ = 'db_session'
4504 __tablename__ = 'db_session'
4500 __table_args__ = (
4505 __table_args__ = (
4501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4506 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4507 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4503 )
4508 )
4504
4509
4505 def __repr__(self):
4510 def __repr__(self):
4506 return '<DB:DbSession({})>'.format(self.id)
4511 return '<DB:DbSession({})>'.format(self.id)
4507
4512
4508 id = Column('id', Integer())
4513 id = Column('id', Integer())
4509 namespace = Column('namespace', String(255), primary_key=True)
4514 namespace = Column('namespace', String(255), primary_key=True)
4510 accessed = Column('accessed', DateTime, nullable=False)
4515 accessed = Column('accessed', DateTime, nullable=False)
4511 created = Column('created', DateTime, nullable=False)
4516 created = Column('created', DateTime, nullable=False)
4512 data = Column('data', PickleType, nullable=False)
4517 data = Column('data', PickleType, nullable=False)
4513
4518
4514
4519
4515
4520
4516 class BeakerCache(Base, BaseModel):
4521 class BeakerCache(Base, BaseModel):
4517 __tablename__ = 'beaker_cache'
4522 __tablename__ = 'beaker_cache'
4518 __table_args__ = (
4523 __table_args__ = (
4519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4524 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4520 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4525 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4521 )
4526 )
4522
4527
4523 def __repr__(self):
4528 def __repr__(self):
4524 return '<DB:DbSession({})>'.format(self.id)
4529 return '<DB:DbSession({})>'.format(self.id)
4525
4530
4526 id = Column('id', Integer())
4531 id = Column('id', Integer())
4527 namespace = Column('namespace', String(255), primary_key=True)
4532 namespace = Column('namespace', String(255), primary_key=True)
4528 accessed = Column('accessed', DateTime, nullable=False)
4533 accessed = Column('accessed', DateTime, nullable=False)
4529 created = Column('created', DateTime, nullable=False)
4534 created = Column('created', DateTime, nullable=False)
4530 data = Column('data', PickleType, nullable=False)
4535 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now