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