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