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