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