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