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