##// END OF EJS Templates
repository-groups: introduce last change for repository groups.
marcink -
r1940:04fca2a9 default
parent child Browse files
Show More
@@ -0,0 +1,35 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from rhodecode.model import meta
6 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
7
8 log = logging.getLogger(__name__)
9
10
11 def upgrade(migrate_engine):
12 """
13 Upgrade operations go here.
14 Don't create your own engine; bind migrate_engine to your metadata
15 """
16 _reset_base(migrate_engine)
17 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
18
19 repo_group_table = db.RepoGroup.__table__
20
21 updated_on = Column(
22 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
23 default=datetime.datetime.now)
24 updated_on.create(table=repo_group_table)
25
26 fixups(db, meta.Session)
27
28
29 def downgrade(migrate_engine):
30 meta = MetaData()
31 meta.bind = migrate_engine
32
33
34 def fixups(models, _SESSION):
35 pass
@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 78 # defines current db version for migrations
54 __dbversion__ = 79 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,4119 +1,4124 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pyramid.threadlocal import get_current_request
47 from pyramid.threadlocal import get_current_request
48
48
49 from rhodecode.translation import _
49 from rhodecode.translation import _
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
361 HOOK_PUSH_KEY = 'pushkey.key_push'
362
362
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # TODO: johbo: Unify way how hooks are configured for git and hg,
364 # git part is currently hardcoded.
364 # git part is currently hardcoded.
365
365
366 # SVN PATTERNS
366 # SVN PATTERNS
367 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_BRANCH_ID = 'vcs_svn_branch'
368 SVN_TAG_ID = 'vcs_svn_tag'
368 SVN_TAG_ID = 'vcs_svn_tag'
369
369
370 ui_id = Column(
370 ui_id = Column(
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 "ui_id", Integer(), nullable=False, unique=True, default=None,
372 primary_key=True)
372 primary_key=True)
373 ui_section = Column(
373 ui_section = Column(
374 "ui_section", String(255), nullable=True, unique=None, default=None)
374 "ui_section", String(255), nullable=True, unique=None, default=None)
375 ui_key = Column(
375 ui_key = Column(
376 "ui_key", String(255), nullable=True, unique=None, default=None)
376 "ui_key", String(255), nullable=True, unique=None, default=None)
377 ui_value = Column(
377 ui_value = Column(
378 "ui_value", String(255), nullable=True, unique=None, default=None)
378 "ui_value", String(255), nullable=True, unique=None, default=None)
379 ui_active = Column(
379 ui_active = Column(
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380 "ui_active", Boolean(), nullable=True, unique=None, default=True)
381
381
382 def __repr__(self):
382 def __repr__(self):
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
384 self.ui_key, self.ui_value)
384 self.ui_key, self.ui_value)
385
385
386
386
387 class RepoRhodeCodeSetting(Base, BaseModel):
387 class RepoRhodeCodeSetting(Base, BaseModel):
388 __tablename__ = 'repo_rhodecode_settings'
388 __tablename__ = 'repo_rhodecode_settings'
389 __table_args__ = (
389 __table_args__ = (
390 UniqueConstraint(
390 UniqueConstraint(
391 'app_settings_name', 'repository_id',
391 'app_settings_name', 'repository_id',
392 name='uq_repo_rhodecode_setting_name_repo_id'),
392 name='uq_repo_rhodecode_setting_name_repo_id'),
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
395 )
395 )
396
396
397 repository_id = Column(
397 repository_id = Column(
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
399 nullable=False)
399 nullable=False)
400 app_settings_id = Column(
400 app_settings_id = Column(
401 "app_settings_id", Integer(), nullable=False, unique=True,
401 "app_settings_id", Integer(), nullable=False, unique=True,
402 default=None, primary_key=True)
402 default=None, primary_key=True)
403 app_settings_name = Column(
403 app_settings_name = Column(
404 "app_settings_name", String(255), nullable=True, unique=None,
404 "app_settings_name", String(255), nullable=True, unique=None,
405 default=None)
405 default=None)
406 _app_settings_value = Column(
406 _app_settings_value = Column(
407 "app_settings_value", String(4096), nullable=True, unique=None,
407 "app_settings_value", String(4096), nullable=True, unique=None,
408 default=None)
408 default=None)
409 _app_settings_type = Column(
409 _app_settings_type = Column(
410 "app_settings_type", String(255), nullable=True, unique=None,
410 "app_settings_type", String(255), nullable=True, unique=None,
411 default=None)
411 default=None)
412
412
413 repository = relationship('Repository')
413 repository = relationship('Repository')
414
414
415 def __init__(self, repository_id, key='', val='', type='unicode'):
415 def __init__(self, repository_id, key='', val='', type='unicode'):
416 self.repository_id = repository_id
416 self.repository_id = repository_id
417 self.app_settings_name = key
417 self.app_settings_name = key
418 self.app_settings_type = type
418 self.app_settings_type = type
419 self.app_settings_value = val
419 self.app_settings_value = val
420
420
421 @validates('_app_settings_value')
421 @validates('_app_settings_value')
422 def validate_settings_value(self, key, val):
422 def validate_settings_value(self, key, val):
423 assert type(val) == unicode
423 assert type(val) == unicode
424 return val
424 return val
425
425
426 @hybrid_property
426 @hybrid_property
427 def app_settings_value(self):
427 def app_settings_value(self):
428 v = self._app_settings_value
428 v = self._app_settings_value
429 type_ = self.app_settings_type
429 type_ = self.app_settings_type
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
432 return converter(v)
432 return converter(v)
433
433
434 @app_settings_value.setter
434 @app_settings_value.setter
435 def app_settings_value(self, val):
435 def app_settings_value(self, val):
436 """
436 """
437 Setter that will always make sure we use unicode in app_settings_value
437 Setter that will always make sure we use unicode in app_settings_value
438
438
439 :param val:
439 :param val:
440 """
440 """
441 self._app_settings_value = safe_unicode(val)
441 self._app_settings_value = safe_unicode(val)
442
442
443 @hybrid_property
443 @hybrid_property
444 def app_settings_type(self):
444 def app_settings_type(self):
445 return self._app_settings_type
445 return self._app_settings_type
446
446
447 @app_settings_type.setter
447 @app_settings_type.setter
448 def app_settings_type(self, val):
448 def app_settings_type(self, val):
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
450 if val not in SETTINGS_TYPES:
450 if val not in SETTINGS_TYPES:
451 raise Exception('type must be one of %s got %s'
451 raise Exception('type must be one of %s got %s'
452 % (SETTINGS_TYPES.keys(), val))
452 % (SETTINGS_TYPES.keys(), val))
453 self._app_settings_type = val
453 self._app_settings_type = val
454
454
455 def __unicode__(self):
455 def __unicode__(self):
456 return u"<%s('%s:%s:%s[%s]')>" % (
456 return u"<%s('%s:%s:%s[%s]')>" % (
457 self.__class__.__name__, self.repository.repo_name,
457 self.__class__.__name__, self.repository.repo_name,
458 self.app_settings_name, self.app_settings_value,
458 self.app_settings_name, self.app_settings_value,
459 self.app_settings_type
459 self.app_settings_type
460 )
460 )
461
461
462
462
463 class RepoRhodeCodeUi(Base, BaseModel):
463 class RepoRhodeCodeUi(Base, BaseModel):
464 __tablename__ = 'repo_rhodecode_ui'
464 __tablename__ = 'repo_rhodecode_ui'
465 __table_args__ = (
465 __table_args__ = (
466 UniqueConstraint(
466 UniqueConstraint(
467 'repository_id', 'ui_section', 'ui_key',
467 'repository_id', 'ui_section', 'ui_key',
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 name='uq_repo_rhodecode_ui_repository_id_section_key'),
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
471 )
471 )
472
472
473 repository_id = Column(
473 repository_id = Column(
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
475 nullable=False)
475 nullable=False)
476 ui_id = Column(
476 ui_id = Column(
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 "ui_id", Integer(), nullable=False, unique=True, default=None,
478 primary_key=True)
478 primary_key=True)
479 ui_section = Column(
479 ui_section = Column(
480 "ui_section", String(255), nullable=True, unique=None, default=None)
480 "ui_section", String(255), nullable=True, unique=None, default=None)
481 ui_key = Column(
481 ui_key = Column(
482 "ui_key", String(255), nullable=True, unique=None, default=None)
482 "ui_key", String(255), nullable=True, unique=None, default=None)
483 ui_value = Column(
483 ui_value = Column(
484 "ui_value", String(255), nullable=True, unique=None, default=None)
484 "ui_value", String(255), nullable=True, unique=None, default=None)
485 ui_active = Column(
485 ui_active = Column(
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486 "ui_active", Boolean(), nullable=True, unique=None, default=True)
487
487
488 repository = relationship('Repository')
488 repository = relationship('Repository')
489
489
490 def __repr__(self):
490 def __repr__(self):
491 return '<%s[%s:%s]%s=>%s]>' % (
491 return '<%s[%s:%s]%s=>%s]>' % (
492 self.__class__.__name__, self.repository.repo_name,
492 self.__class__.__name__, self.repository.repo_name,
493 self.ui_section, self.ui_key, self.ui_value)
493 self.ui_section, self.ui_key, self.ui_value)
494
494
495
495
496 class User(Base, BaseModel):
496 class User(Base, BaseModel):
497 __tablename__ = 'users'
497 __tablename__ = 'users'
498 __table_args__ = (
498 __table_args__ = (
499 UniqueConstraint('username'), UniqueConstraint('email'),
499 UniqueConstraint('username'), UniqueConstraint('email'),
500 Index('u_username_idx', 'username'),
500 Index('u_username_idx', 'username'),
501 Index('u_email_idx', 'email'),
501 Index('u_email_idx', 'email'),
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 {'extend_existing': True, 'mysql_engine': 'InnoDB',
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
504 )
504 )
505 DEFAULT_USER = 'default'
505 DEFAULT_USER = 'default'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
508
508
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 username = Column("username", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 password = Column("password", String(255), nullable=True, unique=None, default=None)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 _email = Column("email", String(255), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
518 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
519
519
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
526
526
527 user_log = relationship('UserLog')
527 user_log = relationship('UserLog')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
529
529
530 repositories = relationship('Repository')
530 repositories = relationship('Repository')
531 repository_groups = relationship('RepoGroup')
531 repository_groups = relationship('RepoGroup')
532 user_groups = relationship('UserGroup')
532 user_groups = relationship('UserGroup')
533
533
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
536
536
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
540
540
541 group_member = relationship('UserGroupMember', cascade='all')
541 group_member = relationship('UserGroupMember', cascade='all')
542
542
543 notifications = relationship('UserNotification', cascade='all')
543 notifications = relationship('UserNotification', cascade='all')
544 # notifications assigned to this user
544 # notifications assigned to this user
545 user_created_notifications = relationship('Notification', cascade='all')
545 user_created_notifications = relationship('Notification', cascade='all')
546 # comments created by this user
546 # comments created by this user
547 user_comments = relationship('ChangesetComment', cascade='all')
547 user_comments = relationship('ChangesetComment', cascade='all')
548 # user profile extra info
548 # user profile extra info
549 user_emails = relationship('UserEmailMap', cascade='all')
549 user_emails = relationship('UserEmailMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_ip_map = relationship('UserIpMap', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 user_auth_tokens = relationship('UserApiKeys', cascade='all')
552 # gists
552 # gists
553 user_gists = relationship('Gist', cascade='all')
553 user_gists = relationship('Gist', cascade='all')
554 # user pull requests
554 # user pull requests
555 user_pull_requests = relationship('PullRequest', cascade='all')
555 user_pull_requests = relationship('PullRequest', cascade='all')
556 # external identities
556 # external identities
557 extenal_identities = relationship(
557 extenal_identities = relationship(
558 'ExternalIdentity',
558 'ExternalIdentity',
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
560 cascade='all')
560 cascade='all')
561
561
562 def __unicode__(self):
562 def __unicode__(self):
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
564 self.user_id, self.username)
564 self.user_id, self.username)
565
565
566 @hybrid_property
566 @hybrid_property
567 def email(self):
567 def email(self):
568 return self._email
568 return self._email
569
569
570 @email.setter
570 @email.setter
571 def email(self, val):
571 def email(self, val):
572 self._email = val.lower() if val else None
572 self._email = val.lower() if val else None
573
573
574 @hybrid_property
574 @hybrid_property
575 def first_name(self):
575 def first_name(self):
576 from rhodecode.lib import helpers as h
576 from rhodecode.lib import helpers as h
577 if self.name:
577 if self.name:
578 return h.escape(self.name)
578 return h.escape(self.name)
579 return self.name
579 return self.name
580
580
581 @hybrid_property
581 @hybrid_property
582 def last_name(self):
582 def last_name(self):
583 from rhodecode.lib import helpers as h
583 from rhodecode.lib import helpers as h
584 if self.lastname:
584 if self.lastname:
585 return h.escape(self.lastname)
585 return h.escape(self.lastname)
586 return self.lastname
586 return self.lastname
587
587
588 @hybrid_property
588 @hybrid_property
589 def api_key(self):
589 def api_key(self):
590 """
590 """
591 Fetch if exist an auth-token with role ALL connected to this user
591 Fetch if exist an auth-token with role ALL connected to this user
592 """
592 """
593 user_auth_token = UserApiKeys.query()\
593 user_auth_token = UserApiKeys.query()\
594 .filter(UserApiKeys.user_id == self.user_id)\
594 .filter(UserApiKeys.user_id == self.user_id)\
595 .filter(or_(UserApiKeys.expires == -1,
595 .filter(or_(UserApiKeys.expires == -1,
596 UserApiKeys.expires >= time.time()))\
596 UserApiKeys.expires >= time.time()))\
597 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
597 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
598 if user_auth_token:
598 if user_auth_token:
599 user_auth_token = user_auth_token.api_key
599 user_auth_token = user_auth_token.api_key
600
600
601 return user_auth_token
601 return user_auth_token
602
602
603 @api_key.setter
603 @api_key.setter
604 def api_key(self, val):
604 def api_key(self, val):
605 # don't allow to set API key this is deprecated for now
605 # don't allow to set API key this is deprecated for now
606 self._api_key = None
606 self._api_key = None
607
607
608 @property
608 @property
609 def reviewer_pull_requests(self):
609 def reviewer_pull_requests(self):
610 return PullRequestReviewers.query() \
610 return PullRequestReviewers.query() \
611 .options(joinedload(PullRequestReviewers.pull_request)) \
611 .options(joinedload(PullRequestReviewers.pull_request)) \
612 .filter(PullRequestReviewers.user_id == self.user_id) \
612 .filter(PullRequestReviewers.user_id == self.user_id) \
613 .all()
613 .all()
614
614
615 @property
615 @property
616 def firstname(self):
616 def firstname(self):
617 # alias for future
617 # alias for future
618 return self.name
618 return self.name
619
619
620 @property
620 @property
621 def emails(self):
621 def emails(self):
622 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
622 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
623 return [self.email] + [x.email for x in other]
623 return [self.email] + [x.email for x in other]
624
624
625 @property
625 @property
626 def auth_tokens(self):
626 def auth_tokens(self):
627 return [x.api_key for x in self.extra_auth_tokens]
627 return [x.api_key for x in self.extra_auth_tokens]
628
628
629 @property
629 @property
630 def extra_auth_tokens(self):
630 def extra_auth_tokens(self):
631 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
631 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
632
632
633 @property
633 @property
634 def feed_token(self):
634 def feed_token(self):
635 return self.get_feed_token()
635 return self.get_feed_token()
636
636
637 def get_feed_token(self):
637 def get_feed_token(self):
638 feed_tokens = UserApiKeys.query()\
638 feed_tokens = UserApiKeys.query()\
639 .filter(UserApiKeys.user == self)\
639 .filter(UserApiKeys.user == self)\
640 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
640 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
641 .all()
641 .all()
642 if feed_tokens:
642 if feed_tokens:
643 return feed_tokens[0].api_key
643 return feed_tokens[0].api_key
644 return 'NO_FEED_TOKEN_AVAILABLE'
644 return 'NO_FEED_TOKEN_AVAILABLE'
645
645
646 @classmethod
646 @classmethod
647 def extra_valid_auth_tokens(cls, user, role=None):
647 def extra_valid_auth_tokens(cls, user, role=None):
648 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
648 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
649 .filter(or_(UserApiKeys.expires == -1,
649 .filter(or_(UserApiKeys.expires == -1,
650 UserApiKeys.expires >= time.time()))
650 UserApiKeys.expires >= time.time()))
651 if role:
651 if role:
652 tokens = tokens.filter(or_(UserApiKeys.role == role,
652 tokens = tokens.filter(or_(UserApiKeys.role == role,
653 UserApiKeys.role == UserApiKeys.ROLE_ALL))
653 UserApiKeys.role == UserApiKeys.ROLE_ALL))
654 return tokens.all()
654 return tokens.all()
655
655
656 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
656 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
657 from rhodecode.lib import auth
657 from rhodecode.lib import auth
658
658
659 log.debug('Trying to authenticate user: %s via auth-token, '
659 log.debug('Trying to authenticate user: %s via auth-token, '
660 'and roles: %s', self, roles)
660 'and roles: %s', self, roles)
661
661
662 if not auth_token:
662 if not auth_token:
663 return False
663 return False
664
664
665 crypto_backend = auth.crypto_backend()
665 crypto_backend = auth.crypto_backend()
666
666
667 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
667 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
668 tokens_q = UserApiKeys.query()\
668 tokens_q = UserApiKeys.query()\
669 .filter(UserApiKeys.user_id == self.user_id)\
669 .filter(UserApiKeys.user_id == self.user_id)\
670 .filter(or_(UserApiKeys.expires == -1,
670 .filter(or_(UserApiKeys.expires == -1,
671 UserApiKeys.expires >= time.time()))
671 UserApiKeys.expires >= time.time()))
672
672
673 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
673 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
674
674
675 plain_tokens = []
675 plain_tokens = []
676 hash_tokens = []
676 hash_tokens = []
677
677
678 for token in tokens_q.all():
678 for token in tokens_q.all():
679 # verify scope first
679 # verify scope first
680 if token.repo_id:
680 if token.repo_id:
681 # token has a scope, we need to verify it
681 # token has a scope, we need to verify it
682 if scope_repo_id != token.repo_id:
682 if scope_repo_id != token.repo_id:
683 log.debug(
683 log.debug(
684 'Scope mismatch: token has a set repo scope: %s, '
684 'Scope mismatch: token has a set repo scope: %s, '
685 'and calling scope is:%s, skipping further checks',
685 'and calling scope is:%s, skipping further checks',
686 token.repo, scope_repo_id)
686 token.repo, scope_repo_id)
687 # token has a scope, and it doesn't match, skip token
687 # token has a scope, and it doesn't match, skip token
688 continue
688 continue
689
689
690 if token.api_key.startswith(crypto_backend.ENC_PREF):
690 if token.api_key.startswith(crypto_backend.ENC_PREF):
691 hash_tokens.append(token.api_key)
691 hash_tokens.append(token.api_key)
692 else:
692 else:
693 plain_tokens.append(token.api_key)
693 plain_tokens.append(token.api_key)
694
694
695 is_plain_match = auth_token in plain_tokens
695 is_plain_match = auth_token in plain_tokens
696 if is_plain_match:
696 if is_plain_match:
697 return True
697 return True
698
698
699 for hashed in hash_tokens:
699 for hashed in hash_tokens:
700 # TODO(marcink): this is expensive to calculate, but most secure
700 # TODO(marcink): this is expensive to calculate, but most secure
701 match = crypto_backend.hash_check(auth_token, hashed)
701 match = crypto_backend.hash_check(auth_token, hashed)
702 if match:
702 if match:
703 return True
703 return True
704
704
705 return False
705 return False
706
706
707 @property
707 @property
708 def ip_addresses(self):
708 def ip_addresses(self):
709 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
709 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
710 return [x.ip_addr for x in ret]
710 return [x.ip_addr for x in ret]
711
711
712 @property
712 @property
713 def username_and_name(self):
713 def username_and_name(self):
714 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
714 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
715
715
716 @property
716 @property
717 def username_or_name_or_email(self):
717 def username_or_name_or_email(self):
718 full_name = self.full_name if self.full_name is not ' ' else None
718 full_name = self.full_name if self.full_name is not ' ' else None
719 return self.username or full_name or self.email
719 return self.username or full_name or self.email
720
720
721 @property
721 @property
722 def full_name(self):
722 def full_name(self):
723 return '%s %s' % (self.first_name, self.last_name)
723 return '%s %s' % (self.first_name, self.last_name)
724
724
725 @property
725 @property
726 def full_name_or_username(self):
726 def full_name_or_username(self):
727 return ('%s %s' % (self.first_name, self.last_name)
727 return ('%s %s' % (self.first_name, self.last_name)
728 if (self.first_name and self.last_name) else self.username)
728 if (self.first_name and self.last_name) else self.username)
729
729
730 @property
730 @property
731 def full_contact(self):
731 def full_contact(self):
732 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
732 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
733
733
734 @property
734 @property
735 def short_contact(self):
735 def short_contact(self):
736 return '%s %s' % (self.first_name, self.last_name)
736 return '%s %s' % (self.first_name, self.last_name)
737
737
738 @property
738 @property
739 def is_admin(self):
739 def is_admin(self):
740 return self.admin
740 return self.admin
741
741
742 @property
742 @property
743 def AuthUser(self):
743 def AuthUser(self):
744 """
744 """
745 Returns instance of AuthUser for this user
745 Returns instance of AuthUser for this user
746 """
746 """
747 from rhodecode.lib.auth import AuthUser
747 from rhodecode.lib.auth import AuthUser
748 return AuthUser(user_id=self.user_id, username=self.username)
748 return AuthUser(user_id=self.user_id, username=self.username)
749
749
750 @hybrid_property
750 @hybrid_property
751 def user_data(self):
751 def user_data(self):
752 if not self._user_data:
752 if not self._user_data:
753 return {}
753 return {}
754
754
755 try:
755 try:
756 return json.loads(self._user_data)
756 return json.loads(self._user_data)
757 except TypeError:
757 except TypeError:
758 return {}
758 return {}
759
759
760 @user_data.setter
760 @user_data.setter
761 def user_data(self, val):
761 def user_data(self, val):
762 if not isinstance(val, dict):
762 if not isinstance(val, dict):
763 raise Exception('user_data must be dict, got %s' % type(val))
763 raise Exception('user_data must be dict, got %s' % type(val))
764 try:
764 try:
765 self._user_data = json.dumps(val)
765 self._user_data = json.dumps(val)
766 except Exception:
766 except Exception:
767 log.error(traceback.format_exc())
767 log.error(traceback.format_exc())
768
768
769 @classmethod
769 @classmethod
770 def get_by_username(cls, username, case_insensitive=False,
770 def get_by_username(cls, username, case_insensitive=False,
771 cache=False, identity_cache=False):
771 cache=False, identity_cache=False):
772 session = Session()
772 session = Session()
773
773
774 if case_insensitive:
774 if case_insensitive:
775 q = cls.query().filter(
775 q = cls.query().filter(
776 func.lower(cls.username) == func.lower(username))
776 func.lower(cls.username) == func.lower(username))
777 else:
777 else:
778 q = cls.query().filter(cls.username == username)
778 q = cls.query().filter(cls.username == username)
779
779
780 if cache:
780 if cache:
781 if identity_cache:
781 if identity_cache:
782 val = cls.identity_cache(session, 'username', username)
782 val = cls.identity_cache(session, 'username', username)
783 if val:
783 if val:
784 return val
784 return val
785 else:
785 else:
786 cache_key = "get_user_by_name_%s" % _hash_key(username)
786 cache_key = "get_user_by_name_%s" % _hash_key(username)
787 q = q.options(
787 q = q.options(
788 FromCache("sql_cache_short", cache_key))
788 FromCache("sql_cache_short", cache_key))
789
789
790 return q.scalar()
790 return q.scalar()
791
791
792 @classmethod
792 @classmethod
793 def get_by_auth_token(cls, auth_token, cache=False):
793 def get_by_auth_token(cls, auth_token, cache=False):
794 q = UserApiKeys.query()\
794 q = UserApiKeys.query()\
795 .filter(UserApiKeys.api_key == auth_token)\
795 .filter(UserApiKeys.api_key == auth_token)\
796 .filter(or_(UserApiKeys.expires == -1,
796 .filter(or_(UserApiKeys.expires == -1,
797 UserApiKeys.expires >= time.time()))
797 UserApiKeys.expires >= time.time()))
798 if cache:
798 if cache:
799 q = q.options(
799 q = q.options(
800 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
800 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
801
801
802 match = q.first()
802 match = q.first()
803 if match:
803 if match:
804 return match.user
804 return match.user
805
805
806 @classmethod
806 @classmethod
807 def get_by_email(cls, email, case_insensitive=False, cache=False):
807 def get_by_email(cls, email, case_insensitive=False, cache=False):
808
808
809 if case_insensitive:
809 if case_insensitive:
810 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
810 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
811
811
812 else:
812 else:
813 q = cls.query().filter(cls.email == email)
813 q = cls.query().filter(cls.email == email)
814
814
815 email_key = _hash_key(email)
815 email_key = _hash_key(email)
816 if cache:
816 if cache:
817 q = q.options(
817 q = q.options(
818 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
818 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
819
819
820 ret = q.scalar()
820 ret = q.scalar()
821 if ret is None:
821 if ret is None:
822 q = UserEmailMap.query()
822 q = UserEmailMap.query()
823 # try fetching in alternate email map
823 # try fetching in alternate email map
824 if case_insensitive:
824 if case_insensitive:
825 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
825 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
826 else:
826 else:
827 q = q.filter(UserEmailMap.email == email)
827 q = q.filter(UserEmailMap.email == email)
828 q = q.options(joinedload(UserEmailMap.user))
828 q = q.options(joinedload(UserEmailMap.user))
829 if cache:
829 if cache:
830 q = q.options(
830 q = q.options(
831 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
831 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
832 ret = getattr(q.scalar(), 'user', None)
832 ret = getattr(q.scalar(), 'user', None)
833
833
834 return ret
834 return ret
835
835
836 @classmethod
836 @classmethod
837 def get_from_cs_author(cls, author):
837 def get_from_cs_author(cls, author):
838 """
838 """
839 Tries to get User objects out of commit author string
839 Tries to get User objects out of commit author string
840
840
841 :param author:
841 :param author:
842 """
842 """
843 from rhodecode.lib.helpers import email, author_name
843 from rhodecode.lib.helpers import email, author_name
844 # Valid email in the attribute passed, see if they're in the system
844 # Valid email in the attribute passed, see if they're in the system
845 _email = email(author)
845 _email = email(author)
846 if _email:
846 if _email:
847 user = cls.get_by_email(_email, case_insensitive=True)
847 user = cls.get_by_email(_email, case_insensitive=True)
848 if user:
848 if user:
849 return user
849 return user
850 # Maybe we can match by username?
850 # Maybe we can match by username?
851 _author = author_name(author)
851 _author = author_name(author)
852 user = cls.get_by_username(_author, case_insensitive=True)
852 user = cls.get_by_username(_author, case_insensitive=True)
853 if user:
853 if user:
854 return user
854 return user
855
855
856 def update_userdata(self, **kwargs):
856 def update_userdata(self, **kwargs):
857 usr = self
857 usr = self
858 old = usr.user_data
858 old = usr.user_data
859 old.update(**kwargs)
859 old.update(**kwargs)
860 usr.user_data = old
860 usr.user_data = old
861 Session().add(usr)
861 Session().add(usr)
862 log.debug('updated userdata with ', kwargs)
862 log.debug('updated userdata with ', kwargs)
863
863
864 def update_lastlogin(self):
864 def update_lastlogin(self):
865 """Update user lastlogin"""
865 """Update user lastlogin"""
866 self.last_login = datetime.datetime.now()
866 self.last_login = datetime.datetime.now()
867 Session().add(self)
867 Session().add(self)
868 log.debug('updated user %s lastlogin', self.username)
868 log.debug('updated user %s lastlogin', self.username)
869
869
870 def update_lastactivity(self):
870 def update_lastactivity(self):
871 """Update user lastactivity"""
871 """Update user lastactivity"""
872 self.last_activity = datetime.datetime.now()
872 self.last_activity = datetime.datetime.now()
873 Session().add(self)
873 Session().add(self)
874 log.debug('updated user %s lastactivity', self.username)
874 log.debug('updated user %s lastactivity', self.username)
875
875
876 def update_password(self, new_password):
876 def update_password(self, new_password):
877 from rhodecode.lib.auth import get_crypt_password
877 from rhodecode.lib.auth import get_crypt_password
878
878
879 self.password = get_crypt_password(new_password)
879 self.password = get_crypt_password(new_password)
880 Session().add(self)
880 Session().add(self)
881
881
882 @classmethod
882 @classmethod
883 def get_first_super_admin(cls):
883 def get_first_super_admin(cls):
884 user = User.query().filter(User.admin == true()).first()
884 user = User.query().filter(User.admin == true()).first()
885 if user is None:
885 if user is None:
886 raise Exception('FATAL: Missing administrative account!')
886 raise Exception('FATAL: Missing administrative account!')
887 return user
887 return user
888
888
889 @classmethod
889 @classmethod
890 def get_all_super_admins(cls):
890 def get_all_super_admins(cls):
891 """
891 """
892 Returns all admin accounts sorted by username
892 Returns all admin accounts sorted by username
893 """
893 """
894 return User.query().filter(User.admin == true())\
894 return User.query().filter(User.admin == true())\
895 .order_by(User.username.asc()).all()
895 .order_by(User.username.asc()).all()
896
896
897 @classmethod
897 @classmethod
898 def get_default_user(cls, cache=False, refresh=False):
898 def get_default_user(cls, cache=False, refresh=False):
899 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
899 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
900 if user is None:
900 if user is None:
901 raise Exception('FATAL: Missing default account!')
901 raise Exception('FATAL: Missing default account!')
902 if refresh:
902 if refresh:
903 # The default user might be based on outdated state which
903 # The default user might be based on outdated state which
904 # has been loaded from the cache.
904 # has been loaded from the cache.
905 # A call to refresh() ensures that the
905 # A call to refresh() ensures that the
906 # latest state from the database is used.
906 # latest state from the database is used.
907 Session().refresh(user)
907 Session().refresh(user)
908 return user
908 return user
909
909
910 def _get_default_perms(self, user, suffix=''):
910 def _get_default_perms(self, user, suffix=''):
911 from rhodecode.model.permission import PermissionModel
911 from rhodecode.model.permission import PermissionModel
912 return PermissionModel().get_default_perms(user.user_perms, suffix)
912 return PermissionModel().get_default_perms(user.user_perms, suffix)
913
913
914 def get_default_perms(self, suffix=''):
914 def get_default_perms(self, suffix=''):
915 return self._get_default_perms(self, suffix)
915 return self._get_default_perms(self, suffix)
916
916
917 def get_api_data(self, include_secrets=False, details='full'):
917 def get_api_data(self, include_secrets=False, details='full'):
918 """
918 """
919 Common function for generating user related data for API
919 Common function for generating user related data for API
920
920
921 :param include_secrets: By default secrets in the API data will be replaced
921 :param include_secrets: By default secrets in the API data will be replaced
922 by a placeholder value to prevent exposing this data by accident. In case
922 by a placeholder value to prevent exposing this data by accident. In case
923 this data shall be exposed, set this flag to ``True``.
923 this data shall be exposed, set this flag to ``True``.
924
924
925 :param details: details can be 'basic|full' basic gives only a subset of
925 :param details: details can be 'basic|full' basic gives only a subset of
926 the available user information that includes user_id, name and emails.
926 the available user information that includes user_id, name and emails.
927 """
927 """
928 user = self
928 user = self
929 user_data = self.user_data
929 user_data = self.user_data
930 data = {
930 data = {
931 'user_id': user.user_id,
931 'user_id': user.user_id,
932 'username': user.username,
932 'username': user.username,
933 'firstname': user.name,
933 'firstname': user.name,
934 'lastname': user.lastname,
934 'lastname': user.lastname,
935 'email': user.email,
935 'email': user.email,
936 'emails': user.emails,
936 'emails': user.emails,
937 }
937 }
938 if details == 'basic':
938 if details == 'basic':
939 return data
939 return data
940
940
941 api_key_length = 40
941 api_key_length = 40
942 api_key_replacement = '*' * api_key_length
942 api_key_replacement = '*' * api_key_length
943
943
944 extras = {
944 extras = {
945 'api_keys': [api_key_replacement],
945 'api_keys': [api_key_replacement],
946 'auth_tokens': [api_key_replacement],
946 'auth_tokens': [api_key_replacement],
947 'active': user.active,
947 'active': user.active,
948 'admin': user.admin,
948 'admin': user.admin,
949 'extern_type': user.extern_type,
949 'extern_type': user.extern_type,
950 'extern_name': user.extern_name,
950 'extern_name': user.extern_name,
951 'last_login': user.last_login,
951 'last_login': user.last_login,
952 'last_activity': user.last_activity,
952 'last_activity': user.last_activity,
953 'ip_addresses': user.ip_addresses,
953 'ip_addresses': user.ip_addresses,
954 'language': user_data.get('language')
954 'language': user_data.get('language')
955 }
955 }
956 data.update(extras)
956 data.update(extras)
957
957
958 if include_secrets:
958 if include_secrets:
959 data['api_keys'] = user.auth_tokens
959 data['api_keys'] = user.auth_tokens
960 data['auth_tokens'] = user.extra_auth_tokens
960 data['auth_tokens'] = user.extra_auth_tokens
961 return data
961 return data
962
962
963 def __json__(self):
963 def __json__(self):
964 data = {
964 data = {
965 'full_name': self.full_name,
965 'full_name': self.full_name,
966 'full_name_or_username': self.full_name_or_username,
966 'full_name_or_username': self.full_name_or_username,
967 'short_contact': self.short_contact,
967 'short_contact': self.short_contact,
968 'full_contact': self.full_contact,
968 'full_contact': self.full_contact,
969 }
969 }
970 data.update(self.get_api_data())
970 data.update(self.get_api_data())
971 return data
971 return data
972
972
973
973
974 class UserApiKeys(Base, BaseModel):
974 class UserApiKeys(Base, BaseModel):
975 __tablename__ = 'user_api_keys'
975 __tablename__ = 'user_api_keys'
976 __table_args__ = (
976 __table_args__ = (
977 Index('uak_api_key_idx', 'api_key'),
977 Index('uak_api_key_idx', 'api_key'),
978 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
978 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
979 UniqueConstraint('api_key'),
979 UniqueConstraint('api_key'),
980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
982 )
982 )
983 __mapper_args__ = {}
983 __mapper_args__ = {}
984
984
985 # ApiKey role
985 # ApiKey role
986 ROLE_ALL = 'token_role_all'
986 ROLE_ALL = 'token_role_all'
987 ROLE_HTTP = 'token_role_http'
987 ROLE_HTTP = 'token_role_http'
988 ROLE_VCS = 'token_role_vcs'
988 ROLE_VCS = 'token_role_vcs'
989 ROLE_API = 'token_role_api'
989 ROLE_API = 'token_role_api'
990 ROLE_FEED = 'token_role_feed'
990 ROLE_FEED = 'token_role_feed'
991 ROLE_PASSWORD_RESET = 'token_password_reset'
991 ROLE_PASSWORD_RESET = 'token_password_reset'
992
992
993 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
993 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
994
994
995 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
995 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
996 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
997 api_key = Column("api_key", String(255), nullable=False, unique=True)
997 api_key = Column("api_key", String(255), nullable=False, unique=True)
998 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
998 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
999 expires = Column('expires', Float(53), nullable=False)
999 expires = Column('expires', Float(53), nullable=False)
1000 role = Column('role', String(255), nullable=True)
1000 role = Column('role', String(255), nullable=True)
1001 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1001 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1002
1002
1003 # scope columns
1003 # scope columns
1004 repo_id = Column(
1004 repo_id = Column(
1005 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1005 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1006 nullable=True, unique=None, default=None)
1006 nullable=True, unique=None, default=None)
1007 repo = relationship('Repository', lazy='joined')
1007 repo = relationship('Repository', lazy='joined')
1008
1008
1009 repo_group_id = Column(
1009 repo_group_id = Column(
1010 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1010 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1011 nullable=True, unique=None, default=None)
1011 nullable=True, unique=None, default=None)
1012 repo_group = relationship('RepoGroup', lazy='joined')
1012 repo_group = relationship('RepoGroup', lazy='joined')
1013
1013
1014 user = relationship('User', lazy='joined')
1014 user = relationship('User', lazy='joined')
1015
1015
1016 def __unicode__(self):
1016 def __unicode__(self):
1017 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1017 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1018
1018
1019 def __json__(self):
1019 def __json__(self):
1020 data = {
1020 data = {
1021 'auth_token': self.api_key,
1021 'auth_token': self.api_key,
1022 'role': self.role,
1022 'role': self.role,
1023 'scope': self.scope_humanized,
1023 'scope': self.scope_humanized,
1024 'expired': self.expired
1024 'expired': self.expired
1025 }
1025 }
1026 return data
1026 return data
1027
1027
1028 def get_api_data(self, include_secrets=False):
1028 def get_api_data(self, include_secrets=False):
1029 data = self.__json__()
1029 data = self.__json__()
1030 if include_secrets:
1030 if include_secrets:
1031 return data
1031 return data
1032 else:
1032 else:
1033 data['auth_token'] = self.token_obfuscated
1033 data['auth_token'] = self.token_obfuscated
1034 return data
1034 return data
1035
1035
1036 @hybrid_property
1036 @hybrid_property
1037 def description_safe(self):
1037 def description_safe(self):
1038 from rhodecode.lib import helpers as h
1038 from rhodecode.lib import helpers as h
1039 return h.escape(self.description)
1039 return h.escape(self.description)
1040
1040
1041 @property
1041 @property
1042 def expired(self):
1042 def expired(self):
1043 if self.expires == -1:
1043 if self.expires == -1:
1044 return False
1044 return False
1045 return time.time() > self.expires
1045 return time.time() > self.expires
1046
1046
1047 @classmethod
1047 @classmethod
1048 def _get_role_name(cls, role):
1048 def _get_role_name(cls, role):
1049 return {
1049 return {
1050 cls.ROLE_ALL: _('all'),
1050 cls.ROLE_ALL: _('all'),
1051 cls.ROLE_HTTP: _('http/web interface'),
1051 cls.ROLE_HTTP: _('http/web interface'),
1052 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1052 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1053 cls.ROLE_API: _('api calls'),
1053 cls.ROLE_API: _('api calls'),
1054 cls.ROLE_FEED: _('feed access'),
1054 cls.ROLE_FEED: _('feed access'),
1055 }.get(role, role)
1055 }.get(role, role)
1056
1056
1057 @property
1057 @property
1058 def role_humanized(self):
1058 def role_humanized(self):
1059 return self._get_role_name(self.role)
1059 return self._get_role_name(self.role)
1060
1060
1061 def _get_scope(self):
1061 def _get_scope(self):
1062 if self.repo:
1062 if self.repo:
1063 return repr(self.repo)
1063 return repr(self.repo)
1064 if self.repo_group:
1064 if self.repo_group:
1065 return repr(self.repo_group) + ' (recursive)'
1065 return repr(self.repo_group) + ' (recursive)'
1066 return 'global'
1066 return 'global'
1067
1067
1068 @property
1068 @property
1069 def scope_humanized(self):
1069 def scope_humanized(self):
1070 return self._get_scope()
1070 return self._get_scope()
1071
1071
1072 @property
1072 @property
1073 def token_obfuscated(self):
1073 def token_obfuscated(self):
1074 if self.api_key:
1074 if self.api_key:
1075 return self.api_key[:4] + "****"
1075 return self.api_key[:4] + "****"
1076
1076
1077
1077
1078 class UserEmailMap(Base, BaseModel):
1078 class UserEmailMap(Base, BaseModel):
1079 __tablename__ = 'user_email_map'
1079 __tablename__ = 'user_email_map'
1080 __table_args__ = (
1080 __table_args__ = (
1081 Index('uem_email_idx', 'email'),
1081 Index('uem_email_idx', 'email'),
1082 UniqueConstraint('email'),
1082 UniqueConstraint('email'),
1083 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1083 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1084 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1084 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1085 )
1085 )
1086 __mapper_args__ = {}
1086 __mapper_args__ = {}
1087
1087
1088 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1088 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1089 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1090 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1090 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1091 user = relationship('User', lazy='joined')
1091 user = relationship('User', lazy='joined')
1092
1092
1093 @validates('_email')
1093 @validates('_email')
1094 def validate_email(self, key, email):
1094 def validate_email(self, key, email):
1095 # check if this email is not main one
1095 # check if this email is not main one
1096 main_email = Session().query(User).filter(User.email == email).scalar()
1096 main_email = Session().query(User).filter(User.email == email).scalar()
1097 if main_email is not None:
1097 if main_email is not None:
1098 raise AttributeError('email %s is present is user table' % email)
1098 raise AttributeError('email %s is present is user table' % email)
1099 return email
1099 return email
1100
1100
1101 @hybrid_property
1101 @hybrid_property
1102 def email(self):
1102 def email(self):
1103 return self._email
1103 return self._email
1104
1104
1105 @email.setter
1105 @email.setter
1106 def email(self, val):
1106 def email(self, val):
1107 self._email = val.lower() if val else None
1107 self._email = val.lower() if val else None
1108
1108
1109
1109
1110 class UserIpMap(Base, BaseModel):
1110 class UserIpMap(Base, BaseModel):
1111 __tablename__ = 'user_ip_map'
1111 __tablename__ = 'user_ip_map'
1112 __table_args__ = (
1112 __table_args__ = (
1113 UniqueConstraint('user_id', 'ip_addr'),
1113 UniqueConstraint('user_id', 'ip_addr'),
1114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1114 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1115 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1116 )
1116 )
1117 __mapper_args__ = {}
1117 __mapper_args__ = {}
1118
1118
1119 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1119 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1120 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1120 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1121 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1121 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1122 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1122 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1123 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1123 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1124 user = relationship('User', lazy='joined')
1124 user = relationship('User', lazy='joined')
1125
1125
1126 @hybrid_property
1126 @hybrid_property
1127 def description_safe(self):
1127 def description_safe(self):
1128 from rhodecode.lib import helpers as h
1128 from rhodecode.lib import helpers as h
1129 return h.escape(self.description)
1129 return h.escape(self.description)
1130
1130
1131 @classmethod
1131 @classmethod
1132 def _get_ip_range(cls, ip_addr):
1132 def _get_ip_range(cls, ip_addr):
1133 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1133 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1134 return [str(net.network_address), str(net.broadcast_address)]
1134 return [str(net.network_address), str(net.broadcast_address)]
1135
1135
1136 def __json__(self):
1136 def __json__(self):
1137 return {
1137 return {
1138 'ip_addr': self.ip_addr,
1138 'ip_addr': self.ip_addr,
1139 'ip_range': self._get_ip_range(self.ip_addr),
1139 'ip_range': self._get_ip_range(self.ip_addr),
1140 }
1140 }
1141
1141
1142 def __unicode__(self):
1142 def __unicode__(self):
1143 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1143 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1144 self.user_id, self.ip_addr)
1144 self.user_id, self.ip_addr)
1145
1145
1146
1146
1147 class UserLog(Base, BaseModel):
1147 class UserLog(Base, BaseModel):
1148 __tablename__ = 'user_logs'
1148 __tablename__ = 'user_logs'
1149 __table_args__ = (
1149 __table_args__ = (
1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1151 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1151 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1152 )
1152 )
1153 VERSION_1 = 'v1'
1153 VERSION_1 = 'v1'
1154 VERSION_2 = 'v2'
1154 VERSION_2 = 'v2'
1155 VERSIONS = [VERSION_1, VERSION_2]
1155 VERSIONS = [VERSION_1, VERSION_2]
1156
1156
1157 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1157 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1158 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1158 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1159 username = Column("username", String(255), nullable=True, unique=None, default=None)
1159 username = Column("username", String(255), nullable=True, unique=None, default=None)
1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1161 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1161 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1162 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1162 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1163 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1163 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1164 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1164 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1165
1165
1166 version = Column("version", String(255), nullable=True, default=VERSION_1)
1166 version = Column("version", String(255), nullable=True, default=VERSION_1)
1167 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1167 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1168 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1168 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1169
1169
1170 def __unicode__(self):
1170 def __unicode__(self):
1171 return u"<%s('id:%s:%s')>" % (
1171 return u"<%s('id:%s:%s')>" % (
1172 self.__class__.__name__, self.repository_name, self.action)
1172 self.__class__.__name__, self.repository_name, self.action)
1173
1173
1174 def __json__(self):
1174 def __json__(self):
1175 return {
1175 return {
1176 'user_id': self.user_id,
1176 'user_id': self.user_id,
1177 'username': self.username,
1177 'username': self.username,
1178 'repository_id': self.repository_id,
1178 'repository_id': self.repository_id,
1179 'repository_name': self.repository_name,
1179 'repository_name': self.repository_name,
1180 'user_ip': self.user_ip,
1180 'user_ip': self.user_ip,
1181 'action_date': self.action_date,
1181 'action_date': self.action_date,
1182 'action': self.action,
1182 'action': self.action,
1183 }
1183 }
1184
1184
1185 @property
1185 @property
1186 def action_as_day(self):
1186 def action_as_day(self):
1187 return datetime.date(*self.action_date.timetuple()[:3])
1187 return datetime.date(*self.action_date.timetuple()[:3])
1188
1188
1189 user = relationship('User')
1189 user = relationship('User')
1190 repository = relationship('Repository', cascade='')
1190 repository = relationship('Repository', cascade='')
1191
1191
1192
1192
1193 class UserGroup(Base, BaseModel):
1193 class UserGroup(Base, BaseModel):
1194 __tablename__ = 'users_groups'
1194 __tablename__ = 'users_groups'
1195 __table_args__ = (
1195 __table_args__ = (
1196 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1196 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1197 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1197 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1198 )
1198 )
1199
1199
1200 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1200 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1201 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1202 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1202 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1203 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1203 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1204 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1204 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1205 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1205 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1206 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1207 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1207 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1208
1208
1209 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1209 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1210 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1210 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1211 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1211 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1212 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1212 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1213 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1213 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1214 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1214 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1215
1215
1216 user = relationship('User')
1216 user = relationship('User')
1217
1217
1218 @hybrid_property
1218 @hybrid_property
1219 def description_safe(self):
1219 def description_safe(self):
1220 from rhodecode.lib import helpers as h
1220 from rhodecode.lib import helpers as h
1221 return h.escape(self.description)
1221 return h.escape(self.description)
1222
1222
1223 @hybrid_property
1223 @hybrid_property
1224 def group_data(self):
1224 def group_data(self):
1225 if not self._group_data:
1225 if not self._group_data:
1226 return {}
1226 return {}
1227
1227
1228 try:
1228 try:
1229 return json.loads(self._group_data)
1229 return json.loads(self._group_data)
1230 except TypeError:
1230 except TypeError:
1231 return {}
1231 return {}
1232
1232
1233 @group_data.setter
1233 @group_data.setter
1234 def group_data(self, val):
1234 def group_data(self, val):
1235 try:
1235 try:
1236 self._group_data = json.dumps(val)
1236 self._group_data = json.dumps(val)
1237 except Exception:
1237 except Exception:
1238 log.error(traceback.format_exc())
1238 log.error(traceback.format_exc())
1239
1239
1240 def __unicode__(self):
1240 def __unicode__(self):
1241 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1241 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1242 self.users_group_id,
1242 self.users_group_id,
1243 self.users_group_name)
1243 self.users_group_name)
1244
1244
1245 @classmethod
1245 @classmethod
1246 def get_by_group_name(cls, group_name, cache=False,
1246 def get_by_group_name(cls, group_name, cache=False,
1247 case_insensitive=False):
1247 case_insensitive=False):
1248 if case_insensitive:
1248 if case_insensitive:
1249 q = cls.query().filter(func.lower(cls.users_group_name) ==
1249 q = cls.query().filter(func.lower(cls.users_group_name) ==
1250 func.lower(group_name))
1250 func.lower(group_name))
1251
1251
1252 else:
1252 else:
1253 q = cls.query().filter(cls.users_group_name == group_name)
1253 q = cls.query().filter(cls.users_group_name == group_name)
1254 if cache:
1254 if cache:
1255 q = q.options(
1255 q = q.options(
1256 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1256 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1257 return q.scalar()
1257 return q.scalar()
1258
1258
1259 @classmethod
1259 @classmethod
1260 def get(cls, user_group_id, cache=False):
1260 def get(cls, user_group_id, cache=False):
1261 user_group = cls.query()
1261 user_group = cls.query()
1262 if cache:
1262 if cache:
1263 user_group = user_group.options(
1263 user_group = user_group.options(
1264 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1264 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1265 return user_group.get(user_group_id)
1265 return user_group.get(user_group_id)
1266
1266
1267 def permissions(self, with_admins=True, with_owner=True):
1267 def permissions(self, with_admins=True, with_owner=True):
1268 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1268 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1269 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1269 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1270 joinedload(UserUserGroupToPerm.user),
1270 joinedload(UserUserGroupToPerm.user),
1271 joinedload(UserUserGroupToPerm.permission),)
1271 joinedload(UserUserGroupToPerm.permission),)
1272
1272
1273 # get owners and admins and permissions. We do a trick of re-writing
1273 # get owners and admins and permissions. We do a trick of re-writing
1274 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1274 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1275 # has a global reference and changing one object propagates to all
1275 # has a global reference and changing one object propagates to all
1276 # others. This means if admin is also an owner admin_row that change
1276 # others. This means if admin is also an owner admin_row that change
1277 # would propagate to both objects
1277 # would propagate to both objects
1278 perm_rows = []
1278 perm_rows = []
1279 for _usr in q.all():
1279 for _usr in q.all():
1280 usr = AttributeDict(_usr.user.get_dict())
1280 usr = AttributeDict(_usr.user.get_dict())
1281 usr.permission = _usr.permission.permission_name
1281 usr.permission = _usr.permission.permission_name
1282 perm_rows.append(usr)
1282 perm_rows.append(usr)
1283
1283
1284 # filter the perm rows by 'default' first and then sort them by
1284 # filter the perm rows by 'default' first and then sort them by
1285 # admin,write,read,none permissions sorted again alphabetically in
1285 # admin,write,read,none permissions sorted again alphabetically in
1286 # each group
1286 # each group
1287 perm_rows = sorted(perm_rows, key=display_sort)
1287 perm_rows = sorted(perm_rows, key=display_sort)
1288
1288
1289 _admin_perm = 'usergroup.admin'
1289 _admin_perm = 'usergroup.admin'
1290 owner_row = []
1290 owner_row = []
1291 if with_owner:
1291 if with_owner:
1292 usr = AttributeDict(self.user.get_dict())
1292 usr = AttributeDict(self.user.get_dict())
1293 usr.owner_row = True
1293 usr.owner_row = True
1294 usr.permission = _admin_perm
1294 usr.permission = _admin_perm
1295 owner_row.append(usr)
1295 owner_row.append(usr)
1296
1296
1297 super_admin_rows = []
1297 super_admin_rows = []
1298 if with_admins:
1298 if with_admins:
1299 for usr in User.get_all_super_admins():
1299 for usr in User.get_all_super_admins():
1300 # if this admin is also owner, don't double the record
1300 # if this admin is also owner, don't double the record
1301 if usr.user_id == owner_row[0].user_id:
1301 if usr.user_id == owner_row[0].user_id:
1302 owner_row[0].admin_row = True
1302 owner_row[0].admin_row = True
1303 else:
1303 else:
1304 usr = AttributeDict(usr.get_dict())
1304 usr = AttributeDict(usr.get_dict())
1305 usr.admin_row = True
1305 usr.admin_row = True
1306 usr.permission = _admin_perm
1306 usr.permission = _admin_perm
1307 super_admin_rows.append(usr)
1307 super_admin_rows.append(usr)
1308
1308
1309 return super_admin_rows + owner_row + perm_rows
1309 return super_admin_rows + owner_row + perm_rows
1310
1310
1311 def permission_user_groups(self):
1311 def permission_user_groups(self):
1312 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1312 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1313 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1313 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1314 joinedload(UserGroupUserGroupToPerm.target_user_group),
1314 joinedload(UserGroupUserGroupToPerm.target_user_group),
1315 joinedload(UserGroupUserGroupToPerm.permission),)
1315 joinedload(UserGroupUserGroupToPerm.permission),)
1316
1316
1317 perm_rows = []
1317 perm_rows = []
1318 for _user_group in q.all():
1318 for _user_group in q.all():
1319 usr = AttributeDict(_user_group.user_group.get_dict())
1319 usr = AttributeDict(_user_group.user_group.get_dict())
1320 usr.permission = _user_group.permission.permission_name
1320 usr.permission = _user_group.permission.permission_name
1321 perm_rows.append(usr)
1321 perm_rows.append(usr)
1322
1322
1323 return perm_rows
1323 return perm_rows
1324
1324
1325 def _get_default_perms(self, user_group, suffix=''):
1325 def _get_default_perms(self, user_group, suffix=''):
1326 from rhodecode.model.permission import PermissionModel
1326 from rhodecode.model.permission import PermissionModel
1327 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1327 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1328
1328
1329 def get_default_perms(self, suffix=''):
1329 def get_default_perms(self, suffix=''):
1330 return self._get_default_perms(self, suffix)
1330 return self._get_default_perms(self, suffix)
1331
1331
1332 def get_api_data(self, with_group_members=True, include_secrets=False):
1332 def get_api_data(self, with_group_members=True, include_secrets=False):
1333 """
1333 """
1334 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1334 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1335 basically forwarded.
1335 basically forwarded.
1336
1336
1337 """
1337 """
1338 user_group = self
1338 user_group = self
1339 data = {
1339 data = {
1340 'users_group_id': user_group.users_group_id,
1340 'users_group_id': user_group.users_group_id,
1341 'group_name': user_group.users_group_name,
1341 'group_name': user_group.users_group_name,
1342 'group_description': user_group.user_group_description,
1342 'group_description': user_group.user_group_description,
1343 'active': user_group.users_group_active,
1343 'active': user_group.users_group_active,
1344 'owner': user_group.user.username,
1344 'owner': user_group.user.username,
1345 'owner_email': user_group.user.email,
1345 'owner_email': user_group.user.email,
1346 }
1346 }
1347
1347
1348 if with_group_members:
1348 if with_group_members:
1349 users = []
1349 users = []
1350 for user in user_group.members:
1350 for user in user_group.members:
1351 user = user.user
1351 user = user.user
1352 users.append(user.get_api_data(include_secrets=include_secrets))
1352 users.append(user.get_api_data(include_secrets=include_secrets))
1353 data['users'] = users
1353 data['users'] = users
1354
1354
1355 return data
1355 return data
1356
1356
1357
1357
1358 class UserGroupMember(Base, BaseModel):
1358 class UserGroupMember(Base, BaseModel):
1359 __tablename__ = 'users_groups_members'
1359 __tablename__ = 'users_groups_members'
1360 __table_args__ = (
1360 __table_args__ = (
1361 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1361 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1362 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1362 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1363 )
1363 )
1364
1364
1365 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1365 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1366 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1368
1368
1369 user = relationship('User', lazy='joined')
1369 user = relationship('User', lazy='joined')
1370 users_group = relationship('UserGroup')
1370 users_group = relationship('UserGroup')
1371
1371
1372 def __init__(self, gr_id='', u_id=''):
1372 def __init__(self, gr_id='', u_id=''):
1373 self.users_group_id = gr_id
1373 self.users_group_id = gr_id
1374 self.user_id = u_id
1374 self.user_id = u_id
1375
1375
1376
1376
1377 class RepositoryField(Base, BaseModel):
1377 class RepositoryField(Base, BaseModel):
1378 __tablename__ = 'repositories_fields'
1378 __tablename__ = 'repositories_fields'
1379 __table_args__ = (
1379 __table_args__ = (
1380 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1380 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1381 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1381 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1382 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1382 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1383 )
1383 )
1384 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1384 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1385
1385
1386 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1386 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1387 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1387 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1388 field_key = Column("field_key", String(250))
1388 field_key = Column("field_key", String(250))
1389 field_label = Column("field_label", String(1024), nullable=False)
1389 field_label = Column("field_label", String(1024), nullable=False)
1390 field_value = Column("field_value", String(10000), nullable=False)
1390 field_value = Column("field_value", String(10000), nullable=False)
1391 field_desc = Column("field_desc", String(1024), nullable=False)
1391 field_desc = Column("field_desc", String(1024), nullable=False)
1392 field_type = Column("field_type", String(255), nullable=False, unique=None)
1392 field_type = Column("field_type", String(255), nullable=False, unique=None)
1393 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1393 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1394
1394
1395 repository = relationship('Repository')
1395 repository = relationship('Repository')
1396
1396
1397 @property
1397 @property
1398 def field_key_prefixed(self):
1398 def field_key_prefixed(self):
1399 return 'ex_%s' % self.field_key
1399 return 'ex_%s' % self.field_key
1400
1400
1401 @classmethod
1401 @classmethod
1402 def un_prefix_key(cls, key):
1402 def un_prefix_key(cls, key):
1403 if key.startswith(cls.PREFIX):
1403 if key.startswith(cls.PREFIX):
1404 return key[len(cls.PREFIX):]
1404 return key[len(cls.PREFIX):]
1405 return key
1405 return key
1406
1406
1407 @classmethod
1407 @classmethod
1408 def get_by_key_name(cls, key, repo):
1408 def get_by_key_name(cls, key, repo):
1409 row = cls.query()\
1409 row = cls.query()\
1410 .filter(cls.repository == repo)\
1410 .filter(cls.repository == repo)\
1411 .filter(cls.field_key == key).scalar()
1411 .filter(cls.field_key == key).scalar()
1412 return row
1412 return row
1413
1413
1414
1414
1415 class Repository(Base, BaseModel):
1415 class Repository(Base, BaseModel):
1416 __tablename__ = 'repositories'
1416 __tablename__ = 'repositories'
1417 __table_args__ = (
1417 __table_args__ = (
1418 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1418 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1420 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1420 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1421 )
1421 )
1422 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1422 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1423 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1423 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1424
1424
1425 STATE_CREATED = 'repo_state_created'
1425 STATE_CREATED = 'repo_state_created'
1426 STATE_PENDING = 'repo_state_pending'
1426 STATE_PENDING = 'repo_state_pending'
1427 STATE_ERROR = 'repo_state_error'
1427 STATE_ERROR = 'repo_state_error'
1428
1428
1429 LOCK_AUTOMATIC = 'lock_auto'
1429 LOCK_AUTOMATIC = 'lock_auto'
1430 LOCK_API = 'lock_api'
1430 LOCK_API = 'lock_api'
1431 LOCK_WEB = 'lock_web'
1431 LOCK_WEB = 'lock_web'
1432 LOCK_PULL = 'lock_pull'
1432 LOCK_PULL = 'lock_pull'
1433
1433
1434 NAME_SEP = URL_SEP
1434 NAME_SEP = URL_SEP
1435
1435
1436 repo_id = Column(
1436 repo_id = Column(
1437 "repo_id", Integer(), nullable=False, unique=True, default=None,
1437 "repo_id", Integer(), nullable=False, unique=True, default=None,
1438 primary_key=True)
1438 primary_key=True)
1439 _repo_name = Column(
1439 _repo_name = Column(
1440 "repo_name", Text(), nullable=False, default=None)
1440 "repo_name", Text(), nullable=False, default=None)
1441 _repo_name_hash = Column(
1441 _repo_name_hash = Column(
1442 "repo_name_hash", String(255), nullable=False, unique=True)
1442 "repo_name_hash", String(255), nullable=False, unique=True)
1443 repo_state = Column("repo_state", String(255), nullable=True)
1443 repo_state = Column("repo_state", String(255), nullable=True)
1444
1444
1445 clone_uri = Column(
1445 clone_uri = Column(
1446 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1446 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1447 default=None)
1447 default=None)
1448 repo_type = Column(
1448 repo_type = Column(
1449 "repo_type", String(255), nullable=False, unique=False, default=None)
1449 "repo_type", String(255), nullable=False, unique=False, default=None)
1450 user_id = Column(
1450 user_id = Column(
1451 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1451 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1452 unique=False, default=None)
1452 unique=False, default=None)
1453 private = Column(
1453 private = Column(
1454 "private", Boolean(), nullable=True, unique=None, default=None)
1454 "private", Boolean(), nullable=True, unique=None, default=None)
1455 enable_statistics = Column(
1455 enable_statistics = Column(
1456 "statistics", Boolean(), nullable=True, unique=None, default=True)
1456 "statistics", Boolean(), nullable=True, unique=None, default=True)
1457 enable_downloads = Column(
1457 enable_downloads = Column(
1458 "downloads", Boolean(), nullable=True, unique=None, default=True)
1458 "downloads", Boolean(), nullable=True, unique=None, default=True)
1459 description = Column(
1459 description = Column(
1460 "description", String(10000), nullable=True, unique=None, default=None)
1460 "description", String(10000), nullable=True, unique=None, default=None)
1461 created_on = Column(
1461 created_on = Column(
1462 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1462 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1463 default=datetime.datetime.now)
1463 default=datetime.datetime.now)
1464 updated_on = Column(
1464 updated_on = Column(
1465 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1465 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1466 default=datetime.datetime.now)
1466 default=datetime.datetime.now)
1467 _landing_revision = Column(
1467 _landing_revision = Column(
1468 "landing_revision", String(255), nullable=False, unique=False,
1468 "landing_revision", String(255), nullable=False, unique=False,
1469 default=None)
1469 default=None)
1470 enable_locking = Column(
1470 enable_locking = Column(
1471 "enable_locking", Boolean(), nullable=False, unique=None,
1471 "enable_locking", Boolean(), nullable=False, unique=None,
1472 default=False)
1472 default=False)
1473 _locked = Column(
1473 _locked = Column(
1474 "locked", String(255), nullable=True, unique=False, default=None)
1474 "locked", String(255), nullable=True, unique=False, default=None)
1475 _changeset_cache = Column(
1475 _changeset_cache = Column(
1476 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1476 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1477
1477
1478 fork_id = Column(
1478 fork_id = Column(
1479 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1479 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1480 nullable=True, unique=False, default=None)
1480 nullable=True, unique=False, default=None)
1481 group_id = Column(
1481 group_id = Column(
1482 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1482 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1483 unique=False, default=None)
1483 unique=False, default=None)
1484
1484
1485 user = relationship('User', lazy='joined')
1485 user = relationship('User', lazy='joined')
1486 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1486 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1487 group = relationship('RepoGroup', lazy='joined')
1487 group = relationship('RepoGroup', lazy='joined')
1488 repo_to_perm = relationship(
1488 repo_to_perm = relationship(
1489 'UserRepoToPerm', cascade='all',
1489 'UserRepoToPerm', cascade='all',
1490 order_by='UserRepoToPerm.repo_to_perm_id')
1490 order_by='UserRepoToPerm.repo_to_perm_id')
1491 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1491 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1492 stats = relationship('Statistics', cascade='all', uselist=False)
1492 stats = relationship('Statistics', cascade='all', uselist=False)
1493
1493
1494 followers = relationship(
1494 followers = relationship(
1495 'UserFollowing',
1495 'UserFollowing',
1496 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1496 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1497 cascade='all')
1497 cascade='all')
1498 extra_fields = relationship(
1498 extra_fields = relationship(
1499 'RepositoryField', cascade="all, delete, delete-orphan")
1499 'RepositoryField', cascade="all, delete, delete-orphan")
1500 logs = relationship('UserLog')
1500 logs = relationship('UserLog')
1501 comments = relationship(
1501 comments = relationship(
1502 'ChangesetComment', cascade="all, delete, delete-orphan")
1502 'ChangesetComment', cascade="all, delete, delete-orphan")
1503 pull_requests_source = relationship(
1503 pull_requests_source = relationship(
1504 'PullRequest',
1504 'PullRequest',
1505 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1505 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1506 cascade="all, delete, delete-orphan")
1506 cascade="all, delete, delete-orphan")
1507 pull_requests_target = relationship(
1507 pull_requests_target = relationship(
1508 'PullRequest',
1508 'PullRequest',
1509 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1509 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1510 cascade="all, delete, delete-orphan")
1510 cascade="all, delete, delete-orphan")
1511 ui = relationship('RepoRhodeCodeUi', cascade="all")
1511 ui = relationship('RepoRhodeCodeUi', cascade="all")
1512 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1512 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1513 integrations = relationship('Integration',
1513 integrations = relationship('Integration',
1514 cascade="all, delete, delete-orphan")
1514 cascade="all, delete, delete-orphan")
1515
1515
1516 def __unicode__(self):
1516 def __unicode__(self):
1517 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1517 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1518 safe_unicode(self.repo_name))
1518 safe_unicode(self.repo_name))
1519
1519
1520 @hybrid_property
1520 @hybrid_property
1521 def description_safe(self):
1521 def description_safe(self):
1522 from rhodecode.lib import helpers as h
1522 from rhodecode.lib import helpers as h
1523 return h.escape(self.description)
1523 return h.escape(self.description)
1524
1524
1525 @hybrid_property
1525 @hybrid_property
1526 def landing_rev(self):
1526 def landing_rev(self):
1527 # always should return [rev_type, rev]
1527 # always should return [rev_type, rev]
1528 if self._landing_revision:
1528 if self._landing_revision:
1529 _rev_info = self._landing_revision.split(':')
1529 _rev_info = self._landing_revision.split(':')
1530 if len(_rev_info) < 2:
1530 if len(_rev_info) < 2:
1531 _rev_info.insert(0, 'rev')
1531 _rev_info.insert(0, 'rev')
1532 return [_rev_info[0], _rev_info[1]]
1532 return [_rev_info[0], _rev_info[1]]
1533 return [None, None]
1533 return [None, None]
1534
1534
1535 @landing_rev.setter
1535 @landing_rev.setter
1536 def landing_rev(self, val):
1536 def landing_rev(self, val):
1537 if ':' not in val:
1537 if ':' not in val:
1538 raise ValueError('value must be delimited with `:` and consist '
1538 raise ValueError('value must be delimited with `:` and consist '
1539 'of <rev_type>:<rev>, got %s instead' % val)
1539 'of <rev_type>:<rev>, got %s instead' % val)
1540 self._landing_revision = val
1540 self._landing_revision = val
1541
1541
1542 @hybrid_property
1542 @hybrid_property
1543 def locked(self):
1543 def locked(self):
1544 if self._locked:
1544 if self._locked:
1545 user_id, timelocked, reason = self._locked.split(':')
1545 user_id, timelocked, reason = self._locked.split(':')
1546 lock_values = int(user_id), timelocked, reason
1546 lock_values = int(user_id), timelocked, reason
1547 else:
1547 else:
1548 lock_values = [None, None, None]
1548 lock_values = [None, None, None]
1549 return lock_values
1549 return lock_values
1550
1550
1551 @locked.setter
1551 @locked.setter
1552 def locked(self, val):
1552 def locked(self, val):
1553 if val and isinstance(val, (list, tuple)):
1553 if val and isinstance(val, (list, tuple)):
1554 self._locked = ':'.join(map(str, val))
1554 self._locked = ':'.join(map(str, val))
1555 else:
1555 else:
1556 self._locked = None
1556 self._locked = None
1557
1557
1558 @hybrid_property
1558 @hybrid_property
1559 def changeset_cache(self):
1559 def changeset_cache(self):
1560 from rhodecode.lib.vcs.backends.base import EmptyCommit
1560 from rhodecode.lib.vcs.backends.base import EmptyCommit
1561 dummy = EmptyCommit().__json__()
1561 dummy = EmptyCommit().__json__()
1562 if not self._changeset_cache:
1562 if not self._changeset_cache:
1563 return dummy
1563 return dummy
1564 try:
1564 try:
1565 return json.loads(self._changeset_cache)
1565 return json.loads(self._changeset_cache)
1566 except TypeError:
1566 except TypeError:
1567 return dummy
1567 return dummy
1568 except Exception:
1568 except Exception:
1569 log.error(traceback.format_exc())
1569 log.error(traceback.format_exc())
1570 return dummy
1570 return dummy
1571
1571
1572 @changeset_cache.setter
1572 @changeset_cache.setter
1573 def changeset_cache(self, val):
1573 def changeset_cache(self, val):
1574 try:
1574 try:
1575 self._changeset_cache = json.dumps(val)
1575 self._changeset_cache = json.dumps(val)
1576 except Exception:
1576 except Exception:
1577 log.error(traceback.format_exc())
1577 log.error(traceback.format_exc())
1578
1578
1579 @hybrid_property
1579 @hybrid_property
1580 def repo_name(self):
1580 def repo_name(self):
1581 return self._repo_name
1581 return self._repo_name
1582
1582
1583 @repo_name.setter
1583 @repo_name.setter
1584 def repo_name(self, value):
1584 def repo_name(self, value):
1585 self._repo_name = value
1585 self._repo_name = value
1586 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1586 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1587
1587
1588 @classmethod
1588 @classmethod
1589 def normalize_repo_name(cls, repo_name):
1589 def normalize_repo_name(cls, repo_name):
1590 """
1590 """
1591 Normalizes os specific repo_name to the format internally stored inside
1591 Normalizes os specific repo_name to the format internally stored inside
1592 database using URL_SEP
1592 database using URL_SEP
1593
1593
1594 :param cls:
1594 :param cls:
1595 :param repo_name:
1595 :param repo_name:
1596 """
1596 """
1597 return cls.NAME_SEP.join(repo_name.split(os.sep))
1597 return cls.NAME_SEP.join(repo_name.split(os.sep))
1598
1598
1599 @classmethod
1599 @classmethod
1600 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1600 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1601 session = Session()
1601 session = Session()
1602 q = session.query(cls).filter(cls.repo_name == repo_name)
1602 q = session.query(cls).filter(cls.repo_name == repo_name)
1603
1603
1604 if cache:
1604 if cache:
1605 if identity_cache:
1605 if identity_cache:
1606 val = cls.identity_cache(session, 'repo_name', repo_name)
1606 val = cls.identity_cache(session, 'repo_name', repo_name)
1607 if val:
1607 if val:
1608 return val
1608 return val
1609 else:
1609 else:
1610 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1610 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1611 q = q.options(
1611 q = q.options(
1612 FromCache("sql_cache_short", cache_key))
1612 FromCache("sql_cache_short", cache_key))
1613
1613
1614 return q.scalar()
1614 return q.scalar()
1615
1615
1616 @classmethod
1616 @classmethod
1617 def get_by_full_path(cls, repo_full_path):
1617 def get_by_full_path(cls, repo_full_path):
1618 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1618 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1619 repo_name = cls.normalize_repo_name(repo_name)
1619 repo_name = cls.normalize_repo_name(repo_name)
1620 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1620 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1621
1621
1622 @classmethod
1622 @classmethod
1623 def get_repo_forks(cls, repo_id):
1623 def get_repo_forks(cls, repo_id):
1624 return cls.query().filter(Repository.fork_id == repo_id)
1624 return cls.query().filter(Repository.fork_id == repo_id)
1625
1625
1626 @classmethod
1626 @classmethod
1627 def base_path(cls):
1627 def base_path(cls):
1628 """
1628 """
1629 Returns base path when all repos are stored
1629 Returns base path when all repos are stored
1630
1630
1631 :param cls:
1631 :param cls:
1632 """
1632 """
1633 q = Session().query(RhodeCodeUi)\
1633 q = Session().query(RhodeCodeUi)\
1634 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1634 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1635 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1635 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1636 return q.one().ui_value
1636 return q.one().ui_value
1637
1637
1638 @classmethod
1638 @classmethod
1639 def is_valid(cls, repo_name):
1639 def is_valid(cls, repo_name):
1640 """
1640 """
1641 returns True if given repo name is a valid filesystem repository
1641 returns True if given repo name is a valid filesystem repository
1642
1642
1643 :param cls:
1643 :param cls:
1644 :param repo_name:
1644 :param repo_name:
1645 """
1645 """
1646 from rhodecode.lib.utils import is_valid_repo
1646 from rhodecode.lib.utils import is_valid_repo
1647
1647
1648 return is_valid_repo(repo_name, cls.base_path())
1648 return is_valid_repo(repo_name, cls.base_path())
1649
1649
1650 @classmethod
1650 @classmethod
1651 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1651 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1652 case_insensitive=True):
1652 case_insensitive=True):
1653 q = Repository.query()
1653 q = Repository.query()
1654
1654
1655 if not isinstance(user_id, Optional):
1655 if not isinstance(user_id, Optional):
1656 q = q.filter(Repository.user_id == user_id)
1656 q = q.filter(Repository.user_id == user_id)
1657
1657
1658 if not isinstance(group_id, Optional):
1658 if not isinstance(group_id, Optional):
1659 q = q.filter(Repository.group_id == group_id)
1659 q = q.filter(Repository.group_id == group_id)
1660
1660
1661 if case_insensitive:
1661 if case_insensitive:
1662 q = q.order_by(func.lower(Repository.repo_name))
1662 q = q.order_by(func.lower(Repository.repo_name))
1663 else:
1663 else:
1664 q = q.order_by(Repository.repo_name)
1664 q = q.order_by(Repository.repo_name)
1665 return q.all()
1665 return q.all()
1666
1666
1667 @property
1667 @property
1668 def forks(self):
1668 def forks(self):
1669 """
1669 """
1670 Return forks of this repo
1670 Return forks of this repo
1671 """
1671 """
1672 return Repository.get_repo_forks(self.repo_id)
1672 return Repository.get_repo_forks(self.repo_id)
1673
1673
1674 @property
1674 @property
1675 def parent(self):
1675 def parent(self):
1676 """
1676 """
1677 Returns fork parent
1677 Returns fork parent
1678 """
1678 """
1679 return self.fork
1679 return self.fork
1680
1680
1681 @property
1681 @property
1682 def just_name(self):
1682 def just_name(self):
1683 return self.repo_name.split(self.NAME_SEP)[-1]
1683 return self.repo_name.split(self.NAME_SEP)[-1]
1684
1684
1685 @property
1685 @property
1686 def groups_with_parents(self):
1686 def groups_with_parents(self):
1687 groups = []
1687 groups = []
1688 if self.group is None:
1688 if self.group is None:
1689 return groups
1689 return groups
1690
1690
1691 cur_gr = self.group
1691 cur_gr = self.group
1692 groups.insert(0, cur_gr)
1692 groups.insert(0, cur_gr)
1693 while 1:
1693 while 1:
1694 gr = getattr(cur_gr, 'parent_group', None)
1694 gr = getattr(cur_gr, 'parent_group', None)
1695 cur_gr = cur_gr.parent_group
1695 cur_gr = cur_gr.parent_group
1696 if gr is None:
1696 if gr is None:
1697 break
1697 break
1698 groups.insert(0, gr)
1698 groups.insert(0, gr)
1699
1699
1700 return groups
1700 return groups
1701
1701
1702 @property
1702 @property
1703 def groups_and_repo(self):
1703 def groups_and_repo(self):
1704 return self.groups_with_parents, self
1704 return self.groups_with_parents, self
1705
1705
1706 @LazyProperty
1706 @LazyProperty
1707 def repo_path(self):
1707 def repo_path(self):
1708 """
1708 """
1709 Returns base full path for that repository means where it actually
1709 Returns base full path for that repository means where it actually
1710 exists on a filesystem
1710 exists on a filesystem
1711 """
1711 """
1712 q = Session().query(RhodeCodeUi).filter(
1712 q = Session().query(RhodeCodeUi).filter(
1713 RhodeCodeUi.ui_key == self.NAME_SEP)
1713 RhodeCodeUi.ui_key == self.NAME_SEP)
1714 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1714 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1715 return q.one().ui_value
1715 return q.one().ui_value
1716
1716
1717 @property
1717 @property
1718 def repo_full_path(self):
1718 def repo_full_path(self):
1719 p = [self.repo_path]
1719 p = [self.repo_path]
1720 # we need to split the name by / since this is how we store the
1720 # we need to split the name by / since this is how we store the
1721 # names in the database, but that eventually needs to be converted
1721 # names in the database, but that eventually needs to be converted
1722 # into a valid system path
1722 # into a valid system path
1723 p += self.repo_name.split(self.NAME_SEP)
1723 p += self.repo_name.split(self.NAME_SEP)
1724 return os.path.join(*map(safe_unicode, p))
1724 return os.path.join(*map(safe_unicode, p))
1725
1725
1726 @property
1726 @property
1727 def cache_keys(self):
1727 def cache_keys(self):
1728 """
1728 """
1729 Returns associated cache keys for that repo
1729 Returns associated cache keys for that repo
1730 """
1730 """
1731 return CacheKey.query()\
1731 return CacheKey.query()\
1732 .filter(CacheKey.cache_args == self.repo_name)\
1732 .filter(CacheKey.cache_args == self.repo_name)\
1733 .order_by(CacheKey.cache_key)\
1733 .order_by(CacheKey.cache_key)\
1734 .all()
1734 .all()
1735
1735
1736 def get_new_name(self, repo_name):
1736 def get_new_name(self, repo_name):
1737 """
1737 """
1738 returns new full repository name based on assigned group and new new
1738 returns new full repository name based on assigned group and new new
1739
1739
1740 :param group_name:
1740 :param group_name:
1741 """
1741 """
1742 path_prefix = self.group.full_path_splitted if self.group else []
1742 path_prefix = self.group.full_path_splitted if self.group else []
1743 return self.NAME_SEP.join(path_prefix + [repo_name])
1743 return self.NAME_SEP.join(path_prefix + [repo_name])
1744
1744
1745 @property
1745 @property
1746 def _config(self):
1746 def _config(self):
1747 """
1747 """
1748 Returns db based config object.
1748 Returns db based config object.
1749 """
1749 """
1750 from rhodecode.lib.utils import make_db_config
1750 from rhodecode.lib.utils import make_db_config
1751 return make_db_config(clear_session=False, repo=self)
1751 return make_db_config(clear_session=False, repo=self)
1752
1752
1753 def permissions(self, with_admins=True, with_owner=True):
1753 def permissions(self, with_admins=True, with_owner=True):
1754 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1754 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1755 q = q.options(joinedload(UserRepoToPerm.repository),
1755 q = q.options(joinedload(UserRepoToPerm.repository),
1756 joinedload(UserRepoToPerm.user),
1756 joinedload(UserRepoToPerm.user),
1757 joinedload(UserRepoToPerm.permission),)
1757 joinedload(UserRepoToPerm.permission),)
1758
1758
1759 # get owners and admins and permissions. We do a trick of re-writing
1759 # get owners and admins and permissions. We do a trick of re-writing
1760 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1760 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1761 # has a global reference and changing one object propagates to all
1761 # has a global reference and changing one object propagates to all
1762 # others. This means if admin is also an owner admin_row that change
1762 # others. This means if admin is also an owner admin_row that change
1763 # would propagate to both objects
1763 # would propagate to both objects
1764 perm_rows = []
1764 perm_rows = []
1765 for _usr in q.all():
1765 for _usr in q.all():
1766 usr = AttributeDict(_usr.user.get_dict())
1766 usr = AttributeDict(_usr.user.get_dict())
1767 usr.permission = _usr.permission.permission_name
1767 usr.permission = _usr.permission.permission_name
1768 perm_rows.append(usr)
1768 perm_rows.append(usr)
1769
1769
1770 # filter the perm rows by 'default' first and then sort them by
1770 # filter the perm rows by 'default' first and then sort them by
1771 # admin,write,read,none permissions sorted again alphabetically in
1771 # admin,write,read,none permissions sorted again alphabetically in
1772 # each group
1772 # each group
1773 perm_rows = sorted(perm_rows, key=display_sort)
1773 perm_rows = sorted(perm_rows, key=display_sort)
1774
1774
1775 _admin_perm = 'repository.admin'
1775 _admin_perm = 'repository.admin'
1776 owner_row = []
1776 owner_row = []
1777 if with_owner:
1777 if with_owner:
1778 usr = AttributeDict(self.user.get_dict())
1778 usr = AttributeDict(self.user.get_dict())
1779 usr.owner_row = True
1779 usr.owner_row = True
1780 usr.permission = _admin_perm
1780 usr.permission = _admin_perm
1781 owner_row.append(usr)
1781 owner_row.append(usr)
1782
1782
1783 super_admin_rows = []
1783 super_admin_rows = []
1784 if with_admins:
1784 if with_admins:
1785 for usr in User.get_all_super_admins():
1785 for usr in User.get_all_super_admins():
1786 # if this admin is also owner, don't double the record
1786 # if this admin is also owner, don't double the record
1787 if usr.user_id == owner_row[0].user_id:
1787 if usr.user_id == owner_row[0].user_id:
1788 owner_row[0].admin_row = True
1788 owner_row[0].admin_row = True
1789 else:
1789 else:
1790 usr = AttributeDict(usr.get_dict())
1790 usr = AttributeDict(usr.get_dict())
1791 usr.admin_row = True
1791 usr.admin_row = True
1792 usr.permission = _admin_perm
1792 usr.permission = _admin_perm
1793 super_admin_rows.append(usr)
1793 super_admin_rows.append(usr)
1794
1794
1795 return super_admin_rows + owner_row + perm_rows
1795 return super_admin_rows + owner_row + perm_rows
1796
1796
1797 def permission_user_groups(self):
1797 def permission_user_groups(self):
1798 q = UserGroupRepoToPerm.query().filter(
1798 q = UserGroupRepoToPerm.query().filter(
1799 UserGroupRepoToPerm.repository == self)
1799 UserGroupRepoToPerm.repository == self)
1800 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1800 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1801 joinedload(UserGroupRepoToPerm.users_group),
1801 joinedload(UserGroupRepoToPerm.users_group),
1802 joinedload(UserGroupRepoToPerm.permission),)
1802 joinedload(UserGroupRepoToPerm.permission),)
1803
1803
1804 perm_rows = []
1804 perm_rows = []
1805 for _user_group in q.all():
1805 for _user_group in q.all():
1806 usr = AttributeDict(_user_group.users_group.get_dict())
1806 usr = AttributeDict(_user_group.users_group.get_dict())
1807 usr.permission = _user_group.permission.permission_name
1807 usr.permission = _user_group.permission.permission_name
1808 perm_rows.append(usr)
1808 perm_rows.append(usr)
1809
1809
1810 return perm_rows
1810 return perm_rows
1811
1811
1812 def get_api_data(self, include_secrets=False):
1812 def get_api_data(self, include_secrets=False):
1813 """
1813 """
1814 Common function for generating repo api data
1814 Common function for generating repo api data
1815
1815
1816 :param include_secrets: See :meth:`User.get_api_data`.
1816 :param include_secrets: See :meth:`User.get_api_data`.
1817
1817
1818 """
1818 """
1819 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1819 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1820 # move this methods on models level.
1820 # move this methods on models level.
1821 from rhodecode.model.settings import SettingsModel
1821 from rhodecode.model.settings import SettingsModel
1822 from rhodecode.model.repo import RepoModel
1822 from rhodecode.model.repo import RepoModel
1823
1823
1824 repo = self
1824 repo = self
1825 _user_id, _time, _reason = self.locked
1825 _user_id, _time, _reason = self.locked
1826
1826
1827 data = {
1827 data = {
1828 'repo_id': repo.repo_id,
1828 'repo_id': repo.repo_id,
1829 'repo_name': repo.repo_name,
1829 'repo_name': repo.repo_name,
1830 'repo_type': repo.repo_type,
1830 'repo_type': repo.repo_type,
1831 'clone_uri': repo.clone_uri or '',
1831 'clone_uri': repo.clone_uri or '',
1832 'url': RepoModel().get_url(self),
1832 'url': RepoModel().get_url(self),
1833 'private': repo.private,
1833 'private': repo.private,
1834 'created_on': repo.created_on,
1834 'created_on': repo.created_on,
1835 'description': repo.description_safe,
1835 'description': repo.description_safe,
1836 'landing_rev': repo.landing_rev,
1836 'landing_rev': repo.landing_rev,
1837 'owner': repo.user.username,
1837 'owner': repo.user.username,
1838 'fork_of': repo.fork.repo_name if repo.fork else None,
1838 'fork_of': repo.fork.repo_name if repo.fork else None,
1839 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1839 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1840 'enable_statistics': repo.enable_statistics,
1840 'enable_statistics': repo.enable_statistics,
1841 'enable_locking': repo.enable_locking,
1841 'enable_locking': repo.enable_locking,
1842 'enable_downloads': repo.enable_downloads,
1842 'enable_downloads': repo.enable_downloads,
1843 'last_changeset': repo.changeset_cache,
1843 'last_changeset': repo.changeset_cache,
1844 'locked_by': User.get(_user_id).get_api_data(
1844 'locked_by': User.get(_user_id).get_api_data(
1845 include_secrets=include_secrets) if _user_id else None,
1845 include_secrets=include_secrets) if _user_id else None,
1846 'locked_date': time_to_datetime(_time) if _time else None,
1846 'locked_date': time_to_datetime(_time) if _time else None,
1847 'lock_reason': _reason if _reason else None,
1847 'lock_reason': _reason if _reason else None,
1848 }
1848 }
1849
1849
1850 # TODO: mikhail: should be per-repo settings here
1850 # TODO: mikhail: should be per-repo settings here
1851 rc_config = SettingsModel().get_all_settings()
1851 rc_config = SettingsModel().get_all_settings()
1852 repository_fields = str2bool(
1852 repository_fields = str2bool(
1853 rc_config.get('rhodecode_repository_fields'))
1853 rc_config.get('rhodecode_repository_fields'))
1854 if repository_fields:
1854 if repository_fields:
1855 for f in self.extra_fields:
1855 for f in self.extra_fields:
1856 data[f.field_key_prefixed] = f.field_value
1856 data[f.field_key_prefixed] = f.field_value
1857
1857
1858 return data
1858 return data
1859
1859
1860 @classmethod
1860 @classmethod
1861 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1861 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1862 if not lock_time:
1862 if not lock_time:
1863 lock_time = time.time()
1863 lock_time = time.time()
1864 if not lock_reason:
1864 if not lock_reason:
1865 lock_reason = cls.LOCK_AUTOMATIC
1865 lock_reason = cls.LOCK_AUTOMATIC
1866 repo.locked = [user_id, lock_time, lock_reason]
1866 repo.locked = [user_id, lock_time, lock_reason]
1867 Session().add(repo)
1867 Session().add(repo)
1868 Session().commit()
1868 Session().commit()
1869
1869
1870 @classmethod
1870 @classmethod
1871 def unlock(cls, repo):
1871 def unlock(cls, repo):
1872 repo.locked = None
1872 repo.locked = None
1873 Session().add(repo)
1873 Session().add(repo)
1874 Session().commit()
1874 Session().commit()
1875
1875
1876 @classmethod
1876 @classmethod
1877 def getlock(cls, repo):
1877 def getlock(cls, repo):
1878 return repo.locked
1878 return repo.locked
1879
1879
1880 def is_user_lock(self, user_id):
1880 def is_user_lock(self, user_id):
1881 if self.lock[0]:
1881 if self.lock[0]:
1882 lock_user_id = safe_int(self.lock[0])
1882 lock_user_id = safe_int(self.lock[0])
1883 user_id = safe_int(user_id)
1883 user_id = safe_int(user_id)
1884 # both are ints, and they are equal
1884 # both are ints, and they are equal
1885 return all([lock_user_id, user_id]) and lock_user_id == user_id
1885 return all([lock_user_id, user_id]) and lock_user_id == user_id
1886
1886
1887 return False
1887 return False
1888
1888
1889 def get_locking_state(self, action, user_id, only_when_enabled=True):
1889 def get_locking_state(self, action, user_id, only_when_enabled=True):
1890 """
1890 """
1891 Checks locking on this repository, if locking is enabled and lock is
1891 Checks locking on this repository, if locking is enabled and lock is
1892 present returns a tuple of make_lock, locked, locked_by.
1892 present returns a tuple of make_lock, locked, locked_by.
1893 make_lock can have 3 states None (do nothing) True, make lock
1893 make_lock can have 3 states None (do nothing) True, make lock
1894 False release lock, This value is later propagated to hooks, which
1894 False release lock, This value is later propagated to hooks, which
1895 do the locking. Think about this as signals passed to hooks what to do.
1895 do the locking. Think about this as signals passed to hooks what to do.
1896
1896
1897 """
1897 """
1898 # TODO: johbo: This is part of the business logic and should be moved
1898 # TODO: johbo: This is part of the business logic and should be moved
1899 # into the RepositoryModel.
1899 # into the RepositoryModel.
1900
1900
1901 if action not in ('push', 'pull'):
1901 if action not in ('push', 'pull'):
1902 raise ValueError("Invalid action value: %s" % repr(action))
1902 raise ValueError("Invalid action value: %s" % repr(action))
1903
1903
1904 # defines if locked error should be thrown to user
1904 # defines if locked error should be thrown to user
1905 currently_locked = False
1905 currently_locked = False
1906 # defines if new lock should be made, tri-state
1906 # defines if new lock should be made, tri-state
1907 make_lock = None
1907 make_lock = None
1908 repo = self
1908 repo = self
1909 user = User.get(user_id)
1909 user = User.get(user_id)
1910
1910
1911 lock_info = repo.locked
1911 lock_info = repo.locked
1912
1912
1913 if repo and (repo.enable_locking or not only_when_enabled):
1913 if repo and (repo.enable_locking or not only_when_enabled):
1914 if action == 'push':
1914 if action == 'push':
1915 # check if it's already locked !, if it is compare users
1915 # check if it's already locked !, if it is compare users
1916 locked_by_user_id = lock_info[0]
1916 locked_by_user_id = lock_info[0]
1917 if user.user_id == locked_by_user_id:
1917 if user.user_id == locked_by_user_id:
1918 log.debug(
1918 log.debug(
1919 'Got `push` action from user %s, now unlocking', user)
1919 'Got `push` action from user %s, now unlocking', user)
1920 # unlock if we have push from user who locked
1920 # unlock if we have push from user who locked
1921 make_lock = False
1921 make_lock = False
1922 else:
1922 else:
1923 # we're not the same user who locked, ban with
1923 # we're not the same user who locked, ban with
1924 # code defined in settings (default is 423 HTTP Locked) !
1924 # code defined in settings (default is 423 HTTP Locked) !
1925 log.debug('Repo %s is currently locked by %s', repo, user)
1925 log.debug('Repo %s is currently locked by %s', repo, user)
1926 currently_locked = True
1926 currently_locked = True
1927 elif action == 'pull':
1927 elif action == 'pull':
1928 # [0] user [1] date
1928 # [0] user [1] date
1929 if lock_info[0] and lock_info[1]:
1929 if lock_info[0] and lock_info[1]:
1930 log.debug('Repo %s is currently locked by %s', repo, user)
1930 log.debug('Repo %s is currently locked by %s', repo, user)
1931 currently_locked = True
1931 currently_locked = True
1932 else:
1932 else:
1933 log.debug('Setting lock on repo %s by %s', repo, user)
1933 log.debug('Setting lock on repo %s by %s', repo, user)
1934 make_lock = True
1934 make_lock = True
1935
1935
1936 else:
1936 else:
1937 log.debug('Repository %s do not have locking enabled', repo)
1937 log.debug('Repository %s do not have locking enabled', repo)
1938
1938
1939 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1939 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1940 make_lock, currently_locked, lock_info)
1940 make_lock, currently_locked, lock_info)
1941
1941
1942 from rhodecode.lib.auth import HasRepoPermissionAny
1942 from rhodecode.lib.auth import HasRepoPermissionAny
1943 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1943 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1944 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1944 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1945 # if we don't have at least write permission we cannot make a lock
1945 # if we don't have at least write permission we cannot make a lock
1946 log.debug('lock state reset back to FALSE due to lack '
1946 log.debug('lock state reset back to FALSE due to lack '
1947 'of at least read permission')
1947 'of at least read permission')
1948 make_lock = False
1948 make_lock = False
1949
1949
1950 return make_lock, currently_locked, lock_info
1950 return make_lock, currently_locked, lock_info
1951
1951
1952 @property
1952 @property
1953 def last_db_change(self):
1953 def last_db_change(self):
1954 return self.updated_on
1954 return self.updated_on
1955
1955
1956 @property
1956 @property
1957 def clone_uri_hidden(self):
1957 def clone_uri_hidden(self):
1958 clone_uri = self.clone_uri
1958 clone_uri = self.clone_uri
1959 if clone_uri:
1959 if clone_uri:
1960 import urlobject
1960 import urlobject
1961 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1961 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1962 if url_obj.password:
1962 if url_obj.password:
1963 clone_uri = url_obj.with_password('*****')
1963 clone_uri = url_obj.with_password('*****')
1964 return clone_uri
1964 return clone_uri
1965
1965
1966 def clone_url(self, **override):
1966 def clone_url(self, **override):
1967 from rhodecode.model.settings import SettingsModel
1967 from rhodecode.model.settings import SettingsModel
1968
1968
1969 uri_tmpl = None
1969 uri_tmpl = None
1970 if 'with_id' in override:
1970 if 'with_id' in override:
1971 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1971 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1972 del override['with_id']
1972 del override['with_id']
1973
1973
1974 if 'uri_tmpl' in override:
1974 if 'uri_tmpl' in override:
1975 uri_tmpl = override['uri_tmpl']
1975 uri_tmpl = override['uri_tmpl']
1976 del override['uri_tmpl']
1976 del override['uri_tmpl']
1977
1977
1978 # we didn't override our tmpl from **overrides
1978 # we didn't override our tmpl from **overrides
1979 if not uri_tmpl:
1979 if not uri_tmpl:
1980 rc_config = SettingsModel().get_all_settings(cache=True)
1980 rc_config = SettingsModel().get_all_settings(cache=True)
1981 uri_tmpl = rc_config.get(
1981 uri_tmpl = rc_config.get(
1982 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
1982 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
1983
1983
1984 request = get_current_request()
1984 request = get_current_request()
1985 return get_clone_url(request=request,
1985 return get_clone_url(request=request,
1986 uri_tmpl=uri_tmpl,
1986 uri_tmpl=uri_tmpl,
1987 repo_name=self.repo_name,
1987 repo_name=self.repo_name,
1988 repo_id=self.repo_id, **override)
1988 repo_id=self.repo_id, **override)
1989
1989
1990 def set_state(self, state):
1990 def set_state(self, state):
1991 self.repo_state = state
1991 self.repo_state = state
1992 Session().add(self)
1992 Session().add(self)
1993 #==========================================================================
1993 #==========================================================================
1994 # SCM PROPERTIES
1994 # SCM PROPERTIES
1995 #==========================================================================
1995 #==========================================================================
1996
1996
1997 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1997 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1998 return get_commit_safe(
1998 return get_commit_safe(
1999 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1999 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2000
2000
2001 def get_changeset(self, rev=None, pre_load=None):
2001 def get_changeset(self, rev=None, pre_load=None):
2002 warnings.warn("Use get_commit", DeprecationWarning)
2002 warnings.warn("Use get_commit", DeprecationWarning)
2003 commit_id = None
2003 commit_id = None
2004 commit_idx = None
2004 commit_idx = None
2005 if isinstance(rev, basestring):
2005 if isinstance(rev, basestring):
2006 commit_id = rev
2006 commit_id = rev
2007 else:
2007 else:
2008 commit_idx = rev
2008 commit_idx = rev
2009 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2009 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2010 pre_load=pre_load)
2010 pre_load=pre_load)
2011
2011
2012 def get_landing_commit(self):
2012 def get_landing_commit(self):
2013 """
2013 """
2014 Returns landing commit, or if that doesn't exist returns the tip
2014 Returns landing commit, or if that doesn't exist returns the tip
2015 """
2015 """
2016 _rev_type, _rev = self.landing_rev
2016 _rev_type, _rev = self.landing_rev
2017 commit = self.get_commit(_rev)
2017 commit = self.get_commit(_rev)
2018 if isinstance(commit, EmptyCommit):
2018 if isinstance(commit, EmptyCommit):
2019 return self.get_commit()
2019 return self.get_commit()
2020 return commit
2020 return commit
2021
2021
2022 def update_commit_cache(self, cs_cache=None, config=None):
2022 def update_commit_cache(self, cs_cache=None, config=None):
2023 """
2023 """
2024 Update cache of last changeset for repository, keys should be::
2024 Update cache of last changeset for repository, keys should be::
2025
2025
2026 short_id
2026 short_id
2027 raw_id
2027 raw_id
2028 revision
2028 revision
2029 parents
2029 parents
2030 message
2030 message
2031 date
2031 date
2032 author
2032 author
2033
2033
2034 :param cs_cache:
2034 :param cs_cache:
2035 """
2035 """
2036 from rhodecode.lib.vcs.backends.base import BaseChangeset
2036 from rhodecode.lib.vcs.backends.base import BaseChangeset
2037 if cs_cache is None:
2037 if cs_cache is None:
2038 # use no-cache version here
2038 # use no-cache version here
2039 scm_repo = self.scm_instance(cache=False, config=config)
2039 scm_repo = self.scm_instance(cache=False, config=config)
2040 if scm_repo:
2040 if scm_repo:
2041 cs_cache = scm_repo.get_commit(
2041 cs_cache = scm_repo.get_commit(
2042 pre_load=["author", "date", "message", "parents"])
2042 pre_load=["author", "date", "message", "parents"])
2043 else:
2043 else:
2044 cs_cache = EmptyCommit()
2044 cs_cache = EmptyCommit()
2045
2045
2046 if isinstance(cs_cache, BaseChangeset):
2046 if isinstance(cs_cache, BaseChangeset):
2047 cs_cache = cs_cache.__json__()
2047 cs_cache = cs_cache.__json__()
2048
2048
2049 def is_outdated(new_cs_cache):
2049 def is_outdated(new_cs_cache):
2050 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2050 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2051 new_cs_cache['revision'] != self.changeset_cache['revision']):
2051 new_cs_cache['revision'] != self.changeset_cache['revision']):
2052 return True
2052 return True
2053 return False
2053 return False
2054
2054
2055 # check if we have maybe already latest cached revision
2055 # check if we have maybe already latest cached revision
2056 if is_outdated(cs_cache) or not self.changeset_cache:
2056 if is_outdated(cs_cache) or not self.changeset_cache:
2057 _default = datetime.datetime.fromtimestamp(0)
2057 _default = datetime.datetime.fromtimestamp(0)
2058 last_change = cs_cache.get('date') or _default
2058 last_change = cs_cache.get('date') or _default
2059 log.debug('updated repo %s with new cs cache %s',
2059 log.debug('updated repo %s with new cs cache %s',
2060 self.repo_name, cs_cache)
2060 self.repo_name, cs_cache)
2061 self.updated_on = last_change
2061 self.updated_on = last_change
2062 self.changeset_cache = cs_cache
2062 self.changeset_cache = cs_cache
2063 Session().add(self)
2063 Session().add(self)
2064 Session().commit()
2064 Session().commit()
2065 else:
2065 else:
2066 log.debug('Skipping update_commit_cache for repo:`%s` '
2066 log.debug('Skipping update_commit_cache for repo:`%s` '
2067 'commit already with latest changes', self.repo_name)
2067 'commit already with latest changes', self.repo_name)
2068
2068
2069 @property
2069 @property
2070 def tip(self):
2070 def tip(self):
2071 return self.get_commit('tip')
2071 return self.get_commit('tip')
2072
2072
2073 @property
2073 @property
2074 def author(self):
2074 def author(self):
2075 return self.tip.author
2075 return self.tip.author
2076
2076
2077 @property
2077 @property
2078 def last_change(self):
2078 def last_change(self):
2079 return self.scm_instance().last_change
2079 return self.scm_instance().last_change
2080
2080
2081 def get_comments(self, revisions=None):
2081 def get_comments(self, revisions=None):
2082 """
2082 """
2083 Returns comments for this repository grouped by revisions
2083 Returns comments for this repository grouped by revisions
2084
2084
2085 :param revisions: filter query by revisions only
2085 :param revisions: filter query by revisions only
2086 """
2086 """
2087 cmts = ChangesetComment.query()\
2087 cmts = ChangesetComment.query()\
2088 .filter(ChangesetComment.repo == self)
2088 .filter(ChangesetComment.repo == self)
2089 if revisions:
2089 if revisions:
2090 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2090 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2091 grouped = collections.defaultdict(list)
2091 grouped = collections.defaultdict(list)
2092 for cmt in cmts.all():
2092 for cmt in cmts.all():
2093 grouped[cmt.revision].append(cmt)
2093 grouped[cmt.revision].append(cmt)
2094 return grouped
2094 return grouped
2095
2095
2096 def statuses(self, revisions=None):
2096 def statuses(self, revisions=None):
2097 """
2097 """
2098 Returns statuses for this repository
2098 Returns statuses for this repository
2099
2099
2100 :param revisions: list of revisions to get statuses for
2100 :param revisions: list of revisions to get statuses for
2101 """
2101 """
2102 statuses = ChangesetStatus.query()\
2102 statuses = ChangesetStatus.query()\
2103 .filter(ChangesetStatus.repo == self)\
2103 .filter(ChangesetStatus.repo == self)\
2104 .filter(ChangesetStatus.version == 0)
2104 .filter(ChangesetStatus.version == 0)
2105
2105
2106 if revisions:
2106 if revisions:
2107 # Try doing the filtering in chunks to avoid hitting limits
2107 # Try doing the filtering in chunks to avoid hitting limits
2108 size = 500
2108 size = 500
2109 status_results = []
2109 status_results = []
2110 for chunk in xrange(0, len(revisions), size):
2110 for chunk in xrange(0, len(revisions), size):
2111 status_results += statuses.filter(
2111 status_results += statuses.filter(
2112 ChangesetStatus.revision.in_(
2112 ChangesetStatus.revision.in_(
2113 revisions[chunk: chunk+size])
2113 revisions[chunk: chunk+size])
2114 ).all()
2114 ).all()
2115 else:
2115 else:
2116 status_results = statuses.all()
2116 status_results = statuses.all()
2117
2117
2118 grouped = {}
2118 grouped = {}
2119
2119
2120 # maybe we have open new pullrequest without a status?
2120 # maybe we have open new pullrequest without a status?
2121 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2121 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2122 status_lbl = ChangesetStatus.get_status_lbl(stat)
2122 status_lbl = ChangesetStatus.get_status_lbl(stat)
2123 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2123 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2124 for rev in pr.revisions:
2124 for rev in pr.revisions:
2125 pr_id = pr.pull_request_id
2125 pr_id = pr.pull_request_id
2126 pr_repo = pr.target_repo.repo_name
2126 pr_repo = pr.target_repo.repo_name
2127 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2127 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2128
2128
2129 for stat in status_results:
2129 for stat in status_results:
2130 pr_id = pr_repo = None
2130 pr_id = pr_repo = None
2131 if stat.pull_request:
2131 if stat.pull_request:
2132 pr_id = stat.pull_request.pull_request_id
2132 pr_id = stat.pull_request.pull_request_id
2133 pr_repo = stat.pull_request.target_repo.repo_name
2133 pr_repo = stat.pull_request.target_repo.repo_name
2134 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2134 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2135 pr_id, pr_repo]
2135 pr_id, pr_repo]
2136 return grouped
2136 return grouped
2137
2137
2138 # ==========================================================================
2138 # ==========================================================================
2139 # SCM CACHE INSTANCE
2139 # SCM CACHE INSTANCE
2140 # ==========================================================================
2140 # ==========================================================================
2141
2141
2142 def scm_instance(self, **kwargs):
2142 def scm_instance(self, **kwargs):
2143 import rhodecode
2143 import rhodecode
2144
2144
2145 # Passing a config will not hit the cache currently only used
2145 # Passing a config will not hit the cache currently only used
2146 # for repo2dbmapper
2146 # for repo2dbmapper
2147 config = kwargs.pop('config', None)
2147 config = kwargs.pop('config', None)
2148 cache = kwargs.pop('cache', None)
2148 cache = kwargs.pop('cache', None)
2149 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2149 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2150 # if cache is NOT defined use default global, else we have a full
2150 # if cache is NOT defined use default global, else we have a full
2151 # control over cache behaviour
2151 # control over cache behaviour
2152 if cache is None and full_cache and not config:
2152 if cache is None and full_cache and not config:
2153 return self._get_instance_cached()
2153 return self._get_instance_cached()
2154 return self._get_instance(cache=bool(cache), config=config)
2154 return self._get_instance(cache=bool(cache), config=config)
2155
2155
2156 def _get_instance_cached(self):
2156 def _get_instance_cached(self):
2157 @cache_region('long_term')
2157 @cache_region('long_term')
2158 def _get_repo(cache_key):
2158 def _get_repo(cache_key):
2159 return self._get_instance()
2159 return self._get_instance()
2160
2160
2161 invalidator_context = CacheKey.repo_context_cache(
2161 invalidator_context = CacheKey.repo_context_cache(
2162 _get_repo, self.repo_name, None, thread_scoped=True)
2162 _get_repo, self.repo_name, None, thread_scoped=True)
2163
2163
2164 with invalidator_context as context:
2164 with invalidator_context as context:
2165 context.invalidate()
2165 context.invalidate()
2166 repo = context.compute()
2166 repo = context.compute()
2167
2167
2168 return repo
2168 return repo
2169
2169
2170 def _get_instance(self, cache=True, config=None):
2170 def _get_instance(self, cache=True, config=None):
2171 config = config or self._config
2171 config = config or self._config
2172 custom_wire = {
2172 custom_wire = {
2173 'cache': cache # controls the vcs.remote cache
2173 'cache': cache # controls the vcs.remote cache
2174 }
2174 }
2175 repo = get_vcs_instance(
2175 repo = get_vcs_instance(
2176 repo_path=safe_str(self.repo_full_path),
2176 repo_path=safe_str(self.repo_full_path),
2177 config=config,
2177 config=config,
2178 with_wire=custom_wire,
2178 with_wire=custom_wire,
2179 create=False,
2179 create=False,
2180 _vcs_alias=self.repo_type)
2180 _vcs_alias=self.repo_type)
2181
2181
2182 return repo
2182 return repo
2183
2183
2184 def __json__(self):
2184 def __json__(self):
2185 return {'landing_rev': self.landing_rev}
2185 return {'landing_rev': self.landing_rev}
2186
2186
2187 def get_dict(self):
2187 def get_dict(self):
2188
2188
2189 # Since we transformed `repo_name` to a hybrid property, we need to
2189 # Since we transformed `repo_name` to a hybrid property, we need to
2190 # keep compatibility with the code which uses `repo_name` field.
2190 # keep compatibility with the code which uses `repo_name` field.
2191
2191
2192 result = super(Repository, self).get_dict()
2192 result = super(Repository, self).get_dict()
2193 result['repo_name'] = result.pop('_repo_name', None)
2193 result['repo_name'] = result.pop('_repo_name', None)
2194 return result
2194 return result
2195
2195
2196
2196
2197 class RepoGroup(Base, BaseModel):
2197 class RepoGroup(Base, BaseModel):
2198 __tablename__ = 'groups'
2198 __tablename__ = 'groups'
2199 __table_args__ = (
2199 __table_args__ = (
2200 UniqueConstraint('group_name', 'group_parent_id'),
2200 UniqueConstraint('group_name', 'group_parent_id'),
2201 CheckConstraint('group_id != group_parent_id'),
2201 CheckConstraint('group_id != group_parent_id'),
2202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2202 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2203 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2204 )
2204 )
2205 __mapper_args__ = {'order_by': 'group_name'}
2205 __mapper_args__ = {'order_by': 'group_name'}
2206
2206
2207 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2207 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2208
2208
2209 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2209 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2210 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2210 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2211 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2211 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2212 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2212 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2213 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2213 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2214 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2214 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2215 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2215 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2216 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2216 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2217 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2217
2218
2218 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2219 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2219 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2220 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2220 parent_group = relationship('RepoGroup', remote_side=group_id)
2221 parent_group = relationship('RepoGroup', remote_side=group_id)
2221 user = relationship('User')
2222 user = relationship('User')
2222 integrations = relationship('Integration',
2223 integrations = relationship('Integration',
2223 cascade="all, delete, delete-orphan")
2224 cascade="all, delete, delete-orphan")
2224
2225
2225 def __init__(self, group_name='', parent_group=None):
2226 def __init__(self, group_name='', parent_group=None):
2226 self.group_name = group_name
2227 self.group_name = group_name
2227 self.parent_group = parent_group
2228 self.parent_group = parent_group
2228
2229
2229 def __unicode__(self):
2230 def __unicode__(self):
2230 return u"<%s('id:%s:%s')>" % (
2231 return u"<%s('id:%s:%s')>" % (
2231 self.__class__.__name__, self.group_id, self.group_name)
2232 self.__class__.__name__, self.group_id, self.group_name)
2232
2233
2233 @hybrid_property
2234 @hybrid_property
2234 def description_safe(self):
2235 def description_safe(self):
2235 from rhodecode.lib import helpers as h
2236 from rhodecode.lib import helpers as h
2236 return h.escape(self.group_description)
2237 return h.escape(self.group_description)
2237
2238
2238 @classmethod
2239 @classmethod
2239 def _generate_choice(cls, repo_group):
2240 def _generate_choice(cls, repo_group):
2240 from webhelpers.html import literal as _literal
2241 from webhelpers.html import literal as _literal
2241 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2242 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2242 return repo_group.group_id, _name(repo_group.full_path_splitted)
2243 return repo_group.group_id, _name(repo_group.full_path_splitted)
2243
2244
2244 @classmethod
2245 @classmethod
2245 def groups_choices(cls, groups=None, show_empty_group=True):
2246 def groups_choices(cls, groups=None, show_empty_group=True):
2246 if not groups:
2247 if not groups:
2247 groups = cls.query().all()
2248 groups = cls.query().all()
2248
2249
2249 repo_groups = []
2250 repo_groups = []
2250 if show_empty_group:
2251 if show_empty_group:
2251 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2252 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2252
2253
2253 repo_groups.extend([cls._generate_choice(x) for x in groups])
2254 repo_groups.extend([cls._generate_choice(x) for x in groups])
2254
2255
2255 repo_groups = sorted(
2256 repo_groups = sorted(
2256 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2257 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2257 return repo_groups
2258 return repo_groups
2258
2259
2259 @classmethod
2260 @classmethod
2260 def url_sep(cls):
2261 def url_sep(cls):
2261 return URL_SEP
2262 return URL_SEP
2262
2263
2263 @classmethod
2264 @classmethod
2264 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2265 if case_insensitive:
2266 if case_insensitive:
2266 gr = cls.query().filter(func.lower(cls.group_name)
2267 gr = cls.query().filter(func.lower(cls.group_name)
2267 == func.lower(group_name))
2268 == func.lower(group_name))
2268 else:
2269 else:
2269 gr = cls.query().filter(cls.group_name == group_name)
2270 gr = cls.query().filter(cls.group_name == group_name)
2270 if cache:
2271 if cache:
2271 name_key = _hash_key(group_name)
2272 name_key = _hash_key(group_name)
2272 gr = gr.options(
2273 gr = gr.options(
2273 FromCache("sql_cache_short", "get_group_%s" % name_key))
2274 FromCache("sql_cache_short", "get_group_%s" % name_key))
2274 return gr.scalar()
2275 return gr.scalar()
2275
2276
2276 @classmethod
2277 @classmethod
2277 def get_user_personal_repo_group(cls, user_id):
2278 def get_user_personal_repo_group(cls, user_id):
2278 user = User.get(user_id)
2279 user = User.get(user_id)
2279 if user.username == User.DEFAULT_USER:
2280 if user.username == User.DEFAULT_USER:
2280 return None
2281 return None
2281
2282
2282 return cls.query()\
2283 return cls.query()\
2283 .filter(cls.personal == true()) \
2284 .filter(cls.personal == true()) \
2284 .filter(cls.user == user).scalar()
2285 .filter(cls.user == user).scalar()
2285
2286
2286 @classmethod
2287 @classmethod
2287 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2288 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2288 case_insensitive=True):
2289 case_insensitive=True):
2289 q = RepoGroup.query()
2290 q = RepoGroup.query()
2290
2291
2291 if not isinstance(user_id, Optional):
2292 if not isinstance(user_id, Optional):
2292 q = q.filter(RepoGroup.user_id == user_id)
2293 q = q.filter(RepoGroup.user_id == user_id)
2293
2294
2294 if not isinstance(group_id, Optional):
2295 if not isinstance(group_id, Optional):
2295 q = q.filter(RepoGroup.group_parent_id == group_id)
2296 q = q.filter(RepoGroup.group_parent_id == group_id)
2296
2297
2297 if case_insensitive:
2298 if case_insensitive:
2298 q = q.order_by(func.lower(RepoGroup.group_name))
2299 q = q.order_by(func.lower(RepoGroup.group_name))
2299 else:
2300 else:
2300 q = q.order_by(RepoGroup.group_name)
2301 q = q.order_by(RepoGroup.group_name)
2301 return q.all()
2302 return q.all()
2302
2303
2303 @property
2304 @property
2304 def parents(self):
2305 def parents(self):
2305 parents_recursion_limit = 10
2306 parents_recursion_limit = 10
2306 groups = []
2307 groups = []
2307 if self.parent_group is None:
2308 if self.parent_group is None:
2308 return groups
2309 return groups
2309 cur_gr = self.parent_group
2310 cur_gr = self.parent_group
2310 groups.insert(0, cur_gr)
2311 groups.insert(0, cur_gr)
2311 cnt = 0
2312 cnt = 0
2312 while 1:
2313 while 1:
2313 cnt += 1
2314 cnt += 1
2314 gr = getattr(cur_gr, 'parent_group', None)
2315 gr = getattr(cur_gr, 'parent_group', None)
2315 cur_gr = cur_gr.parent_group
2316 cur_gr = cur_gr.parent_group
2316 if gr is None:
2317 if gr is None:
2317 break
2318 break
2318 if cnt == parents_recursion_limit:
2319 if cnt == parents_recursion_limit:
2319 # this will prevent accidental infinit loops
2320 # this will prevent accidental infinit loops
2320 log.error(('more than %s parents found for group %s, stopping '
2321 log.error(('more than %s parents found for group %s, stopping '
2321 'recursive parent fetching' % (parents_recursion_limit, self)))
2322 'recursive parent fetching' % (parents_recursion_limit, self)))
2322 break
2323 break
2323
2324
2324 groups.insert(0, gr)
2325 groups.insert(0, gr)
2325 return groups
2326 return groups
2326
2327
2327 @property
2328 @property
2329 def last_db_change(self):
2330 return self.updated_on
2331
2332 @property
2328 def children(self):
2333 def children(self):
2329 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2334 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2330
2335
2331 @property
2336 @property
2332 def name(self):
2337 def name(self):
2333 return self.group_name.split(RepoGroup.url_sep())[-1]
2338 return self.group_name.split(RepoGroup.url_sep())[-1]
2334
2339
2335 @property
2340 @property
2336 def full_path(self):
2341 def full_path(self):
2337 return self.group_name
2342 return self.group_name
2338
2343
2339 @property
2344 @property
2340 def full_path_splitted(self):
2345 def full_path_splitted(self):
2341 return self.group_name.split(RepoGroup.url_sep())
2346 return self.group_name.split(RepoGroup.url_sep())
2342
2347
2343 @property
2348 @property
2344 def repositories(self):
2349 def repositories(self):
2345 return Repository.query()\
2350 return Repository.query()\
2346 .filter(Repository.group == self)\
2351 .filter(Repository.group == self)\
2347 .order_by(Repository.repo_name)
2352 .order_by(Repository.repo_name)
2348
2353
2349 @property
2354 @property
2350 def repositories_recursive_count(self):
2355 def repositories_recursive_count(self):
2351 cnt = self.repositories.count()
2356 cnt = self.repositories.count()
2352
2357
2353 def children_count(group):
2358 def children_count(group):
2354 cnt = 0
2359 cnt = 0
2355 for child in group.children:
2360 for child in group.children:
2356 cnt += child.repositories.count()
2361 cnt += child.repositories.count()
2357 cnt += children_count(child)
2362 cnt += children_count(child)
2358 return cnt
2363 return cnt
2359
2364
2360 return cnt + children_count(self)
2365 return cnt + children_count(self)
2361
2366
2362 def _recursive_objects(self, include_repos=True):
2367 def _recursive_objects(self, include_repos=True):
2363 all_ = []
2368 all_ = []
2364
2369
2365 def _get_members(root_gr):
2370 def _get_members(root_gr):
2366 if include_repos:
2371 if include_repos:
2367 for r in root_gr.repositories:
2372 for r in root_gr.repositories:
2368 all_.append(r)
2373 all_.append(r)
2369 childs = root_gr.children.all()
2374 childs = root_gr.children.all()
2370 if childs:
2375 if childs:
2371 for gr in childs:
2376 for gr in childs:
2372 all_.append(gr)
2377 all_.append(gr)
2373 _get_members(gr)
2378 _get_members(gr)
2374
2379
2375 _get_members(self)
2380 _get_members(self)
2376 return [self] + all_
2381 return [self] + all_
2377
2382
2378 def recursive_groups_and_repos(self):
2383 def recursive_groups_and_repos(self):
2379 """
2384 """
2380 Recursive return all groups, with repositories in those groups
2385 Recursive return all groups, with repositories in those groups
2381 """
2386 """
2382 return self._recursive_objects()
2387 return self._recursive_objects()
2383
2388
2384 def recursive_groups(self):
2389 def recursive_groups(self):
2385 """
2390 """
2386 Returns all children groups for this group including children of children
2391 Returns all children groups for this group including children of children
2387 """
2392 """
2388 return self._recursive_objects(include_repos=False)
2393 return self._recursive_objects(include_repos=False)
2389
2394
2390 def get_new_name(self, group_name):
2395 def get_new_name(self, group_name):
2391 """
2396 """
2392 returns new full group name based on parent and new name
2397 returns new full group name based on parent and new name
2393
2398
2394 :param group_name:
2399 :param group_name:
2395 """
2400 """
2396 path_prefix = (self.parent_group.full_path_splitted if
2401 path_prefix = (self.parent_group.full_path_splitted if
2397 self.parent_group else [])
2402 self.parent_group else [])
2398 return RepoGroup.url_sep().join(path_prefix + [group_name])
2403 return RepoGroup.url_sep().join(path_prefix + [group_name])
2399
2404
2400 def permissions(self, with_admins=True, with_owner=True):
2405 def permissions(self, with_admins=True, with_owner=True):
2401 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2406 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2402 q = q.options(joinedload(UserRepoGroupToPerm.group),
2407 q = q.options(joinedload(UserRepoGroupToPerm.group),
2403 joinedload(UserRepoGroupToPerm.user),
2408 joinedload(UserRepoGroupToPerm.user),
2404 joinedload(UserRepoGroupToPerm.permission),)
2409 joinedload(UserRepoGroupToPerm.permission),)
2405
2410
2406 # get owners and admins and permissions. We do a trick of re-writing
2411 # get owners and admins and permissions. We do a trick of re-writing
2407 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2412 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2408 # has a global reference and changing one object propagates to all
2413 # has a global reference and changing one object propagates to all
2409 # others. This means if admin is also an owner admin_row that change
2414 # others. This means if admin is also an owner admin_row that change
2410 # would propagate to both objects
2415 # would propagate to both objects
2411 perm_rows = []
2416 perm_rows = []
2412 for _usr in q.all():
2417 for _usr in q.all():
2413 usr = AttributeDict(_usr.user.get_dict())
2418 usr = AttributeDict(_usr.user.get_dict())
2414 usr.permission = _usr.permission.permission_name
2419 usr.permission = _usr.permission.permission_name
2415 perm_rows.append(usr)
2420 perm_rows.append(usr)
2416
2421
2417 # filter the perm rows by 'default' first and then sort them by
2422 # filter the perm rows by 'default' first and then sort them by
2418 # admin,write,read,none permissions sorted again alphabetically in
2423 # admin,write,read,none permissions sorted again alphabetically in
2419 # each group
2424 # each group
2420 perm_rows = sorted(perm_rows, key=display_sort)
2425 perm_rows = sorted(perm_rows, key=display_sort)
2421
2426
2422 _admin_perm = 'group.admin'
2427 _admin_perm = 'group.admin'
2423 owner_row = []
2428 owner_row = []
2424 if with_owner:
2429 if with_owner:
2425 usr = AttributeDict(self.user.get_dict())
2430 usr = AttributeDict(self.user.get_dict())
2426 usr.owner_row = True
2431 usr.owner_row = True
2427 usr.permission = _admin_perm
2432 usr.permission = _admin_perm
2428 owner_row.append(usr)
2433 owner_row.append(usr)
2429
2434
2430 super_admin_rows = []
2435 super_admin_rows = []
2431 if with_admins:
2436 if with_admins:
2432 for usr in User.get_all_super_admins():
2437 for usr in User.get_all_super_admins():
2433 # if this admin is also owner, don't double the record
2438 # if this admin is also owner, don't double the record
2434 if usr.user_id == owner_row[0].user_id:
2439 if usr.user_id == owner_row[0].user_id:
2435 owner_row[0].admin_row = True
2440 owner_row[0].admin_row = True
2436 else:
2441 else:
2437 usr = AttributeDict(usr.get_dict())
2442 usr = AttributeDict(usr.get_dict())
2438 usr.admin_row = True
2443 usr.admin_row = True
2439 usr.permission = _admin_perm
2444 usr.permission = _admin_perm
2440 super_admin_rows.append(usr)
2445 super_admin_rows.append(usr)
2441
2446
2442 return super_admin_rows + owner_row + perm_rows
2447 return super_admin_rows + owner_row + perm_rows
2443
2448
2444 def permission_user_groups(self):
2449 def permission_user_groups(self):
2445 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2450 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2446 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2451 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2447 joinedload(UserGroupRepoGroupToPerm.users_group),
2452 joinedload(UserGroupRepoGroupToPerm.users_group),
2448 joinedload(UserGroupRepoGroupToPerm.permission),)
2453 joinedload(UserGroupRepoGroupToPerm.permission),)
2449
2454
2450 perm_rows = []
2455 perm_rows = []
2451 for _user_group in q.all():
2456 for _user_group in q.all():
2452 usr = AttributeDict(_user_group.users_group.get_dict())
2457 usr = AttributeDict(_user_group.users_group.get_dict())
2453 usr.permission = _user_group.permission.permission_name
2458 usr.permission = _user_group.permission.permission_name
2454 perm_rows.append(usr)
2459 perm_rows.append(usr)
2455
2460
2456 return perm_rows
2461 return perm_rows
2457
2462
2458 def get_api_data(self):
2463 def get_api_data(self):
2459 """
2464 """
2460 Common function for generating api data
2465 Common function for generating api data
2461
2466
2462 """
2467 """
2463 group = self
2468 group = self
2464 data = {
2469 data = {
2465 'group_id': group.group_id,
2470 'group_id': group.group_id,
2466 'group_name': group.group_name,
2471 'group_name': group.group_name,
2467 'group_description': group.description_safe,
2472 'group_description': group.description_safe,
2468 'parent_group': group.parent_group.group_name if group.parent_group else None,
2473 'parent_group': group.parent_group.group_name if group.parent_group else None,
2469 'repositories': [x.repo_name for x in group.repositories],
2474 'repositories': [x.repo_name for x in group.repositories],
2470 'owner': group.user.username,
2475 'owner': group.user.username,
2471 }
2476 }
2472 return data
2477 return data
2473
2478
2474
2479
2475 class Permission(Base, BaseModel):
2480 class Permission(Base, BaseModel):
2476 __tablename__ = 'permissions'
2481 __tablename__ = 'permissions'
2477 __table_args__ = (
2482 __table_args__ = (
2478 Index('p_perm_name_idx', 'permission_name'),
2483 Index('p_perm_name_idx', 'permission_name'),
2479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2484 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2485 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2481 )
2486 )
2482 PERMS = [
2487 PERMS = [
2483 ('hg.admin', _('RhodeCode Super Administrator')),
2488 ('hg.admin', _('RhodeCode Super Administrator')),
2484
2489
2485 ('repository.none', _('Repository no access')),
2490 ('repository.none', _('Repository no access')),
2486 ('repository.read', _('Repository read access')),
2491 ('repository.read', _('Repository read access')),
2487 ('repository.write', _('Repository write access')),
2492 ('repository.write', _('Repository write access')),
2488 ('repository.admin', _('Repository admin access')),
2493 ('repository.admin', _('Repository admin access')),
2489
2494
2490 ('group.none', _('Repository group no access')),
2495 ('group.none', _('Repository group no access')),
2491 ('group.read', _('Repository group read access')),
2496 ('group.read', _('Repository group read access')),
2492 ('group.write', _('Repository group write access')),
2497 ('group.write', _('Repository group write access')),
2493 ('group.admin', _('Repository group admin access')),
2498 ('group.admin', _('Repository group admin access')),
2494
2499
2495 ('usergroup.none', _('User group no access')),
2500 ('usergroup.none', _('User group no access')),
2496 ('usergroup.read', _('User group read access')),
2501 ('usergroup.read', _('User group read access')),
2497 ('usergroup.write', _('User group write access')),
2502 ('usergroup.write', _('User group write access')),
2498 ('usergroup.admin', _('User group admin access')),
2503 ('usergroup.admin', _('User group admin access')),
2499
2504
2500 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2505 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2501 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2506 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2502
2507
2503 ('hg.usergroup.create.false', _('User Group creation disabled')),
2508 ('hg.usergroup.create.false', _('User Group creation disabled')),
2504 ('hg.usergroup.create.true', _('User Group creation enabled')),
2509 ('hg.usergroup.create.true', _('User Group creation enabled')),
2505
2510
2506 ('hg.create.none', _('Repository creation disabled')),
2511 ('hg.create.none', _('Repository creation disabled')),
2507 ('hg.create.repository', _('Repository creation enabled')),
2512 ('hg.create.repository', _('Repository creation enabled')),
2508 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2513 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2509 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2514 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2510
2515
2511 ('hg.fork.none', _('Repository forking disabled')),
2516 ('hg.fork.none', _('Repository forking disabled')),
2512 ('hg.fork.repository', _('Repository forking enabled')),
2517 ('hg.fork.repository', _('Repository forking enabled')),
2513
2518
2514 ('hg.register.none', _('Registration disabled')),
2519 ('hg.register.none', _('Registration disabled')),
2515 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2520 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2516 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2521 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2517
2522
2518 ('hg.password_reset.enabled', _('Password reset enabled')),
2523 ('hg.password_reset.enabled', _('Password reset enabled')),
2519 ('hg.password_reset.hidden', _('Password reset hidden')),
2524 ('hg.password_reset.hidden', _('Password reset hidden')),
2520 ('hg.password_reset.disabled', _('Password reset disabled')),
2525 ('hg.password_reset.disabled', _('Password reset disabled')),
2521
2526
2522 ('hg.extern_activate.manual', _('Manual activation of external account')),
2527 ('hg.extern_activate.manual', _('Manual activation of external account')),
2523 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2528 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2524
2529
2525 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2530 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2526 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2531 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2527 ]
2532 ]
2528
2533
2529 # definition of system default permissions for DEFAULT user
2534 # definition of system default permissions for DEFAULT user
2530 DEFAULT_USER_PERMISSIONS = [
2535 DEFAULT_USER_PERMISSIONS = [
2531 'repository.read',
2536 'repository.read',
2532 'group.read',
2537 'group.read',
2533 'usergroup.read',
2538 'usergroup.read',
2534 'hg.create.repository',
2539 'hg.create.repository',
2535 'hg.repogroup.create.false',
2540 'hg.repogroup.create.false',
2536 'hg.usergroup.create.false',
2541 'hg.usergroup.create.false',
2537 'hg.create.write_on_repogroup.true',
2542 'hg.create.write_on_repogroup.true',
2538 'hg.fork.repository',
2543 'hg.fork.repository',
2539 'hg.register.manual_activate',
2544 'hg.register.manual_activate',
2540 'hg.password_reset.enabled',
2545 'hg.password_reset.enabled',
2541 'hg.extern_activate.auto',
2546 'hg.extern_activate.auto',
2542 'hg.inherit_default_perms.true',
2547 'hg.inherit_default_perms.true',
2543 ]
2548 ]
2544
2549
2545 # defines which permissions are more important higher the more important
2550 # defines which permissions are more important higher the more important
2546 # Weight defines which permissions are more important.
2551 # Weight defines which permissions are more important.
2547 # The higher number the more important.
2552 # The higher number the more important.
2548 PERM_WEIGHTS = {
2553 PERM_WEIGHTS = {
2549 'repository.none': 0,
2554 'repository.none': 0,
2550 'repository.read': 1,
2555 'repository.read': 1,
2551 'repository.write': 3,
2556 'repository.write': 3,
2552 'repository.admin': 4,
2557 'repository.admin': 4,
2553
2558
2554 'group.none': 0,
2559 'group.none': 0,
2555 'group.read': 1,
2560 'group.read': 1,
2556 'group.write': 3,
2561 'group.write': 3,
2557 'group.admin': 4,
2562 'group.admin': 4,
2558
2563
2559 'usergroup.none': 0,
2564 'usergroup.none': 0,
2560 'usergroup.read': 1,
2565 'usergroup.read': 1,
2561 'usergroup.write': 3,
2566 'usergroup.write': 3,
2562 'usergroup.admin': 4,
2567 'usergroup.admin': 4,
2563
2568
2564 'hg.repogroup.create.false': 0,
2569 'hg.repogroup.create.false': 0,
2565 'hg.repogroup.create.true': 1,
2570 'hg.repogroup.create.true': 1,
2566
2571
2567 'hg.usergroup.create.false': 0,
2572 'hg.usergroup.create.false': 0,
2568 'hg.usergroup.create.true': 1,
2573 'hg.usergroup.create.true': 1,
2569
2574
2570 'hg.fork.none': 0,
2575 'hg.fork.none': 0,
2571 'hg.fork.repository': 1,
2576 'hg.fork.repository': 1,
2572 'hg.create.none': 0,
2577 'hg.create.none': 0,
2573 'hg.create.repository': 1
2578 'hg.create.repository': 1
2574 }
2579 }
2575
2580
2576 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2581 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2577 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2582 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2578 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2583 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2579
2584
2580 def __unicode__(self):
2585 def __unicode__(self):
2581 return u"<%s('%s:%s')>" % (
2586 return u"<%s('%s:%s')>" % (
2582 self.__class__.__name__, self.permission_id, self.permission_name
2587 self.__class__.__name__, self.permission_id, self.permission_name
2583 )
2588 )
2584
2589
2585 @classmethod
2590 @classmethod
2586 def get_by_key(cls, key):
2591 def get_by_key(cls, key):
2587 return cls.query().filter(cls.permission_name == key).scalar()
2592 return cls.query().filter(cls.permission_name == key).scalar()
2588
2593
2589 @classmethod
2594 @classmethod
2590 def get_default_repo_perms(cls, user_id, repo_id=None):
2595 def get_default_repo_perms(cls, user_id, repo_id=None):
2591 q = Session().query(UserRepoToPerm, Repository, Permission)\
2596 q = Session().query(UserRepoToPerm, Repository, Permission)\
2592 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2597 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2593 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2598 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2594 .filter(UserRepoToPerm.user_id == user_id)
2599 .filter(UserRepoToPerm.user_id == user_id)
2595 if repo_id:
2600 if repo_id:
2596 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2601 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2597 return q.all()
2602 return q.all()
2598
2603
2599 @classmethod
2604 @classmethod
2600 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2605 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2601 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2606 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2602 .join(
2607 .join(
2603 Permission,
2608 Permission,
2604 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2609 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2605 .join(
2610 .join(
2606 Repository,
2611 Repository,
2607 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2612 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2608 .join(
2613 .join(
2609 UserGroup,
2614 UserGroup,
2610 UserGroupRepoToPerm.users_group_id ==
2615 UserGroupRepoToPerm.users_group_id ==
2611 UserGroup.users_group_id)\
2616 UserGroup.users_group_id)\
2612 .join(
2617 .join(
2613 UserGroupMember,
2618 UserGroupMember,
2614 UserGroupRepoToPerm.users_group_id ==
2619 UserGroupRepoToPerm.users_group_id ==
2615 UserGroupMember.users_group_id)\
2620 UserGroupMember.users_group_id)\
2616 .filter(
2621 .filter(
2617 UserGroupMember.user_id == user_id,
2622 UserGroupMember.user_id == user_id,
2618 UserGroup.users_group_active == true())
2623 UserGroup.users_group_active == true())
2619 if repo_id:
2624 if repo_id:
2620 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2625 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2621 return q.all()
2626 return q.all()
2622
2627
2623 @classmethod
2628 @classmethod
2624 def get_default_group_perms(cls, user_id, repo_group_id=None):
2629 def get_default_group_perms(cls, user_id, repo_group_id=None):
2625 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2630 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2626 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2631 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2627 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2632 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2628 .filter(UserRepoGroupToPerm.user_id == user_id)
2633 .filter(UserRepoGroupToPerm.user_id == user_id)
2629 if repo_group_id:
2634 if repo_group_id:
2630 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2635 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2631 return q.all()
2636 return q.all()
2632
2637
2633 @classmethod
2638 @classmethod
2634 def get_default_group_perms_from_user_group(
2639 def get_default_group_perms_from_user_group(
2635 cls, user_id, repo_group_id=None):
2640 cls, user_id, repo_group_id=None):
2636 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2641 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2637 .join(
2642 .join(
2638 Permission,
2643 Permission,
2639 UserGroupRepoGroupToPerm.permission_id ==
2644 UserGroupRepoGroupToPerm.permission_id ==
2640 Permission.permission_id)\
2645 Permission.permission_id)\
2641 .join(
2646 .join(
2642 RepoGroup,
2647 RepoGroup,
2643 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2648 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2644 .join(
2649 .join(
2645 UserGroup,
2650 UserGroup,
2646 UserGroupRepoGroupToPerm.users_group_id ==
2651 UserGroupRepoGroupToPerm.users_group_id ==
2647 UserGroup.users_group_id)\
2652 UserGroup.users_group_id)\
2648 .join(
2653 .join(
2649 UserGroupMember,
2654 UserGroupMember,
2650 UserGroupRepoGroupToPerm.users_group_id ==
2655 UserGroupRepoGroupToPerm.users_group_id ==
2651 UserGroupMember.users_group_id)\
2656 UserGroupMember.users_group_id)\
2652 .filter(
2657 .filter(
2653 UserGroupMember.user_id == user_id,
2658 UserGroupMember.user_id == user_id,
2654 UserGroup.users_group_active == true())
2659 UserGroup.users_group_active == true())
2655 if repo_group_id:
2660 if repo_group_id:
2656 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2661 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2657 return q.all()
2662 return q.all()
2658
2663
2659 @classmethod
2664 @classmethod
2660 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2665 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2661 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2666 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2662 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2667 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2663 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2668 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2664 .filter(UserUserGroupToPerm.user_id == user_id)
2669 .filter(UserUserGroupToPerm.user_id == user_id)
2665 if user_group_id:
2670 if user_group_id:
2666 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2671 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2667 return q.all()
2672 return q.all()
2668
2673
2669 @classmethod
2674 @classmethod
2670 def get_default_user_group_perms_from_user_group(
2675 def get_default_user_group_perms_from_user_group(
2671 cls, user_id, user_group_id=None):
2676 cls, user_id, user_group_id=None):
2672 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2677 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2673 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2678 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2674 .join(
2679 .join(
2675 Permission,
2680 Permission,
2676 UserGroupUserGroupToPerm.permission_id ==
2681 UserGroupUserGroupToPerm.permission_id ==
2677 Permission.permission_id)\
2682 Permission.permission_id)\
2678 .join(
2683 .join(
2679 TargetUserGroup,
2684 TargetUserGroup,
2680 UserGroupUserGroupToPerm.target_user_group_id ==
2685 UserGroupUserGroupToPerm.target_user_group_id ==
2681 TargetUserGroup.users_group_id)\
2686 TargetUserGroup.users_group_id)\
2682 .join(
2687 .join(
2683 UserGroup,
2688 UserGroup,
2684 UserGroupUserGroupToPerm.user_group_id ==
2689 UserGroupUserGroupToPerm.user_group_id ==
2685 UserGroup.users_group_id)\
2690 UserGroup.users_group_id)\
2686 .join(
2691 .join(
2687 UserGroupMember,
2692 UserGroupMember,
2688 UserGroupUserGroupToPerm.user_group_id ==
2693 UserGroupUserGroupToPerm.user_group_id ==
2689 UserGroupMember.users_group_id)\
2694 UserGroupMember.users_group_id)\
2690 .filter(
2695 .filter(
2691 UserGroupMember.user_id == user_id,
2696 UserGroupMember.user_id == user_id,
2692 UserGroup.users_group_active == true())
2697 UserGroup.users_group_active == true())
2693 if user_group_id:
2698 if user_group_id:
2694 q = q.filter(
2699 q = q.filter(
2695 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2700 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2696
2701
2697 return q.all()
2702 return q.all()
2698
2703
2699
2704
2700 class UserRepoToPerm(Base, BaseModel):
2705 class UserRepoToPerm(Base, BaseModel):
2701 __tablename__ = 'repo_to_perm'
2706 __tablename__ = 'repo_to_perm'
2702 __table_args__ = (
2707 __table_args__ = (
2703 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2708 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2704 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2705 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2710 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2706 )
2711 )
2707 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2712 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2708 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2713 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2709 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2714 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2710 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2711
2716
2712 user = relationship('User')
2717 user = relationship('User')
2713 repository = relationship('Repository')
2718 repository = relationship('Repository')
2714 permission = relationship('Permission')
2719 permission = relationship('Permission')
2715
2720
2716 @classmethod
2721 @classmethod
2717 def create(cls, user, repository, permission):
2722 def create(cls, user, repository, permission):
2718 n = cls()
2723 n = cls()
2719 n.user = user
2724 n.user = user
2720 n.repository = repository
2725 n.repository = repository
2721 n.permission = permission
2726 n.permission = permission
2722 Session().add(n)
2727 Session().add(n)
2723 return n
2728 return n
2724
2729
2725 def __unicode__(self):
2730 def __unicode__(self):
2726 return u'<%s => %s >' % (self.user, self.repository)
2731 return u'<%s => %s >' % (self.user, self.repository)
2727
2732
2728
2733
2729 class UserUserGroupToPerm(Base, BaseModel):
2734 class UserUserGroupToPerm(Base, BaseModel):
2730 __tablename__ = 'user_user_group_to_perm'
2735 __tablename__ = 'user_user_group_to_perm'
2731 __table_args__ = (
2736 __table_args__ = (
2732 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2737 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2733 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2738 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2734 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2739 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2735 )
2740 )
2736 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2741 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2737 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2742 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2738 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2743 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2739 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2744 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2740
2745
2741 user = relationship('User')
2746 user = relationship('User')
2742 user_group = relationship('UserGroup')
2747 user_group = relationship('UserGroup')
2743 permission = relationship('Permission')
2748 permission = relationship('Permission')
2744
2749
2745 @classmethod
2750 @classmethod
2746 def create(cls, user, user_group, permission):
2751 def create(cls, user, user_group, permission):
2747 n = cls()
2752 n = cls()
2748 n.user = user
2753 n.user = user
2749 n.user_group = user_group
2754 n.user_group = user_group
2750 n.permission = permission
2755 n.permission = permission
2751 Session().add(n)
2756 Session().add(n)
2752 return n
2757 return n
2753
2758
2754 def __unicode__(self):
2759 def __unicode__(self):
2755 return u'<%s => %s >' % (self.user, self.user_group)
2760 return u'<%s => %s >' % (self.user, self.user_group)
2756
2761
2757
2762
2758 class UserToPerm(Base, BaseModel):
2763 class UserToPerm(Base, BaseModel):
2759 __tablename__ = 'user_to_perm'
2764 __tablename__ = 'user_to_perm'
2760 __table_args__ = (
2765 __table_args__ = (
2761 UniqueConstraint('user_id', 'permission_id'),
2766 UniqueConstraint('user_id', 'permission_id'),
2762 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2763 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2764 )
2769 )
2765 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2770 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2766 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2771 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2767 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2772 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2768
2773
2769 user = relationship('User')
2774 user = relationship('User')
2770 permission = relationship('Permission', lazy='joined')
2775 permission = relationship('Permission', lazy='joined')
2771
2776
2772 def __unicode__(self):
2777 def __unicode__(self):
2773 return u'<%s => %s >' % (self.user, self.permission)
2778 return u'<%s => %s >' % (self.user, self.permission)
2774
2779
2775
2780
2776 class UserGroupRepoToPerm(Base, BaseModel):
2781 class UserGroupRepoToPerm(Base, BaseModel):
2777 __tablename__ = 'users_group_repo_to_perm'
2782 __tablename__ = 'users_group_repo_to_perm'
2778 __table_args__ = (
2783 __table_args__ = (
2779 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2784 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2780 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2785 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2781 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2786 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2782 )
2787 )
2783 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2788 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2784 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2789 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2785 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2790 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2786 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2791 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2787
2792
2788 users_group = relationship('UserGroup')
2793 users_group = relationship('UserGroup')
2789 permission = relationship('Permission')
2794 permission = relationship('Permission')
2790 repository = relationship('Repository')
2795 repository = relationship('Repository')
2791
2796
2792 @classmethod
2797 @classmethod
2793 def create(cls, users_group, repository, permission):
2798 def create(cls, users_group, repository, permission):
2794 n = cls()
2799 n = cls()
2795 n.users_group = users_group
2800 n.users_group = users_group
2796 n.repository = repository
2801 n.repository = repository
2797 n.permission = permission
2802 n.permission = permission
2798 Session().add(n)
2803 Session().add(n)
2799 return n
2804 return n
2800
2805
2801 def __unicode__(self):
2806 def __unicode__(self):
2802 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2807 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2803
2808
2804
2809
2805 class UserGroupUserGroupToPerm(Base, BaseModel):
2810 class UserGroupUserGroupToPerm(Base, BaseModel):
2806 __tablename__ = 'user_group_user_group_to_perm'
2811 __tablename__ = 'user_group_user_group_to_perm'
2807 __table_args__ = (
2812 __table_args__ = (
2808 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2813 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2809 CheckConstraint('target_user_group_id != user_group_id'),
2814 CheckConstraint('target_user_group_id != user_group_id'),
2810 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2815 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2811 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2816 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2812 )
2817 )
2813 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)
2818 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)
2814 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2819 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2815 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2820 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2816 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2821 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2817
2822
2818 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2823 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2819 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2824 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2820 permission = relationship('Permission')
2825 permission = relationship('Permission')
2821
2826
2822 @classmethod
2827 @classmethod
2823 def create(cls, target_user_group, user_group, permission):
2828 def create(cls, target_user_group, user_group, permission):
2824 n = cls()
2829 n = cls()
2825 n.target_user_group = target_user_group
2830 n.target_user_group = target_user_group
2826 n.user_group = user_group
2831 n.user_group = user_group
2827 n.permission = permission
2832 n.permission = permission
2828 Session().add(n)
2833 Session().add(n)
2829 return n
2834 return n
2830
2835
2831 def __unicode__(self):
2836 def __unicode__(self):
2832 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2837 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2833
2838
2834
2839
2835 class UserGroupToPerm(Base, BaseModel):
2840 class UserGroupToPerm(Base, BaseModel):
2836 __tablename__ = 'users_group_to_perm'
2841 __tablename__ = 'users_group_to_perm'
2837 __table_args__ = (
2842 __table_args__ = (
2838 UniqueConstraint('users_group_id', 'permission_id',),
2843 UniqueConstraint('users_group_id', 'permission_id',),
2839 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2844 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2840 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2845 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2841 )
2846 )
2842 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2847 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2843 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2848 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2844 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2849 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2845
2850
2846 users_group = relationship('UserGroup')
2851 users_group = relationship('UserGroup')
2847 permission = relationship('Permission')
2852 permission = relationship('Permission')
2848
2853
2849
2854
2850 class UserRepoGroupToPerm(Base, BaseModel):
2855 class UserRepoGroupToPerm(Base, BaseModel):
2851 __tablename__ = 'user_repo_group_to_perm'
2856 __tablename__ = 'user_repo_group_to_perm'
2852 __table_args__ = (
2857 __table_args__ = (
2853 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2858 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2854 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2859 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2855 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2860 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2856 )
2861 )
2857
2862
2858 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2863 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2864 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2860 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2865 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2861 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2862
2867
2863 user = relationship('User')
2868 user = relationship('User')
2864 group = relationship('RepoGroup')
2869 group = relationship('RepoGroup')
2865 permission = relationship('Permission')
2870 permission = relationship('Permission')
2866
2871
2867 @classmethod
2872 @classmethod
2868 def create(cls, user, repository_group, permission):
2873 def create(cls, user, repository_group, permission):
2869 n = cls()
2874 n = cls()
2870 n.user = user
2875 n.user = user
2871 n.group = repository_group
2876 n.group = repository_group
2872 n.permission = permission
2877 n.permission = permission
2873 Session().add(n)
2878 Session().add(n)
2874 return n
2879 return n
2875
2880
2876
2881
2877 class UserGroupRepoGroupToPerm(Base, BaseModel):
2882 class UserGroupRepoGroupToPerm(Base, BaseModel):
2878 __tablename__ = 'users_group_repo_group_to_perm'
2883 __tablename__ = 'users_group_repo_group_to_perm'
2879 __table_args__ = (
2884 __table_args__ = (
2880 UniqueConstraint('users_group_id', 'group_id'),
2885 UniqueConstraint('users_group_id', 'group_id'),
2881 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2882 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2887 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2883 )
2888 )
2884
2889
2885 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)
2890 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)
2886 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2891 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2887 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2892 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2888 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2893 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2889
2894
2890 users_group = relationship('UserGroup')
2895 users_group = relationship('UserGroup')
2891 permission = relationship('Permission')
2896 permission = relationship('Permission')
2892 group = relationship('RepoGroup')
2897 group = relationship('RepoGroup')
2893
2898
2894 @classmethod
2899 @classmethod
2895 def create(cls, user_group, repository_group, permission):
2900 def create(cls, user_group, repository_group, permission):
2896 n = cls()
2901 n = cls()
2897 n.users_group = user_group
2902 n.users_group = user_group
2898 n.group = repository_group
2903 n.group = repository_group
2899 n.permission = permission
2904 n.permission = permission
2900 Session().add(n)
2905 Session().add(n)
2901 return n
2906 return n
2902
2907
2903 def __unicode__(self):
2908 def __unicode__(self):
2904 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2909 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2905
2910
2906
2911
2907 class Statistics(Base, BaseModel):
2912 class Statistics(Base, BaseModel):
2908 __tablename__ = 'statistics'
2913 __tablename__ = 'statistics'
2909 __table_args__ = (
2914 __table_args__ = (
2910 UniqueConstraint('repository_id'),
2915 UniqueConstraint('repository_id'),
2911 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2916 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2912 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2917 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2913 )
2918 )
2914 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2919 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2920 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2916 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2921 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2917 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2922 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2918 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2923 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2919 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2924 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2920
2925
2921 repository = relationship('Repository', single_parent=True)
2926 repository = relationship('Repository', single_parent=True)
2922
2927
2923
2928
2924 class UserFollowing(Base, BaseModel):
2929 class UserFollowing(Base, BaseModel):
2925 __tablename__ = 'user_followings'
2930 __tablename__ = 'user_followings'
2926 __table_args__ = (
2931 __table_args__ = (
2927 UniqueConstraint('user_id', 'follows_repository_id'),
2932 UniqueConstraint('user_id', 'follows_repository_id'),
2928 UniqueConstraint('user_id', 'follows_user_id'),
2933 UniqueConstraint('user_id', 'follows_user_id'),
2929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2934 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2930 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2935 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2931 )
2936 )
2932
2937
2933 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2938 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2934 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2939 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2935 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2940 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2936 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2941 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2937 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2942 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2938
2943
2939 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2944 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2940
2945
2941 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2946 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2942 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2947 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2943
2948
2944 @classmethod
2949 @classmethod
2945 def get_repo_followers(cls, repo_id):
2950 def get_repo_followers(cls, repo_id):
2946 return cls.query().filter(cls.follows_repo_id == repo_id)
2951 return cls.query().filter(cls.follows_repo_id == repo_id)
2947
2952
2948
2953
2949 class CacheKey(Base, BaseModel):
2954 class CacheKey(Base, BaseModel):
2950 __tablename__ = 'cache_invalidation'
2955 __tablename__ = 'cache_invalidation'
2951 __table_args__ = (
2956 __table_args__ = (
2952 UniqueConstraint('cache_key'),
2957 UniqueConstraint('cache_key'),
2953 Index('key_idx', 'cache_key'),
2958 Index('key_idx', 'cache_key'),
2954 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2955 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2956 )
2961 )
2957 CACHE_TYPE_ATOM = 'ATOM'
2962 CACHE_TYPE_ATOM = 'ATOM'
2958 CACHE_TYPE_RSS = 'RSS'
2963 CACHE_TYPE_RSS = 'RSS'
2959 CACHE_TYPE_README = 'README'
2964 CACHE_TYPE_README = 'README'
2960
2965
2961 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2966 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2962 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2967 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2963 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2968 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2964 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2969 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2965
2970
2966 def __init__(self, cache_key, cache_args=''):
2971 def __init__(self, cache_key, cache_args=''):
2967 self.cache_key = cache_key
2972 self.cache_key = cache_key
2968 self.cache_args = cache_args
2973 self.cache_args = cache_args
2969 self.cache_active = False
2974 self.cache_active = False
2970
2975
2971 def __unicode__(self):
2976 def __unicode__(self):
2972 return u"<%s('%s:%s[%s]')>" % (
2977 return u"<%s('%s:%s[%s]')>" % (
2973 self.__class__.__name__,
2978 self.__class__.__name__,
2974 self.cache_id, self.cache_key, self.cache_active)
2979 self.cache_id, self.cache_key, self.cache_active)
2975
2980
2976 def _cache_key_partition(self):
2981 def _cache_key_partition(self):
2977 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2982 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2978 return prefix, repo_name, suffix
2983 return prefix, repo_name, suffix
2979
2984
2980 def get_prefix(self):
2985 def get_prefix(self):
2981 """
2986 """
2982 Try to extract prefix from existing cache key. The key could consist
2987 Try to extract prefix from existing cache key. The key could consist
2983 of prefix, repo_name, suffix
2988 of prefix, repo_name, suffix
2984 """
2989 """
2985 # this returns prefix, repo_name, suffix
2990 # this returns prefix, repo_name, suffix
2986 return self._cache_key_partition()[0]
2991 return self._cache_key_partition()[0]
2987
2992
2988 def get_suffix(self):
2993 def get_suffix(self):
2989 """
2994 """
2990 get suffix that might have been used in _get_cache_key to
2995 get suffix that might have been used in _get_cache_key to
2991 generate self.cache_key. Only used for informational purposes
2996 generate self.cache_key. Only used for informational purposes
2992 in repo_edit.mako.
2997 in repo_edit.mako.
2993 """
2998 """
2994 # prefix, repo_name, suffix
2999 # prefix, repo_name, suffix
2995 return self._cache_key_partition()[2]
3000 return self._cache_key_partition()[2]
2996
3001
2997 @classmethod
3002 @classmethod
2998 def delete_all_cache(cls):
3003 def delete_all_cache(cls):
2999 """
3004 """
3000 Delete all cache keys from database.
3005 Delete all cache keys from database.
3001 Should only be run when all instances are down and all entries
3006 Should only be run when all instances are down and all entries
3002 thus stale.
3007 thus stale.
3003 """
3008 """
3004 cls.query().delete()
3009 cls.query().delete()
3005 Session().commit()
3010 Session().commit()
3006
3011
3007 @classmethod
3012 @classmethod
3008 def get_cache_key(cls, repo_name, cache_type):
3013 def get_cache_key(cls, repo_name, cache_type):
3009 """
3014 """
3010
3015
3011 Generate a cache key for this process of RhodeCode instance.
3016 Generate a cache key for this process of RhodeCode instance.
3012 Prefix most likely will be process id or maybe explicitly set
3017 Prefix most likely will be process id or maybe explicitly set
3013 instance_id from .ini file.
3018 instance_id from .ini file.
3014 """
3019 """
3015 import rhodecode
3020 import rhodecode
3016 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3021 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3017
3022
3018 repo_as_unicode = safe_unicode(repo_name)
3023 repo_as_unicode = safe_unicode(repo_name)
3019 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3024 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3020 if cache_type else repo_as_unicode
3025 if cache_type else repo_as_unicode
3021
3026
3022 return u'{}{}'.format(prefix, key)
3027 return u'{}{}'.format(prefix, key)
3023
3028
3024 @classmethod
3029 @classmethod
3025 def set_invalidate(cls, repo_name, delete=False):
3030 def set_invalidate(cls, repo_name, delete=False):
3026 """
3031 """
3027 Mark all caches of a repo as invalid in the database.
3032 Mark all caches of a repo as invalid in the database.
3028 """
3033 """
3029
3034
3030 try:
3035 try:
3031 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3036 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3032 if delete:
3037 if delete:
3033 log.debug('cache objects deleted for repo %s',
3038 log.debug('cache objects deleted for repo %s',
3034 safe_str(repo_name))
3039 safe_str(repo_name))
3035 qry.delete()
3040 qry.delete()
3036 else:
3041 else:
3037 log.debug('cache objects marked as invalid for repo %s',
3042 log.debug('cache objects marked as invalid for repo %s',
3038 safe_str(repo_name))
3043 safe_str(repo_name))
3039 qry.update({"cache_active": False})
3044 qry.update({"cache_active": False})
3040
3045
3041 Session().commit()
3046 Session().commit()
3042 except Exception:
3047 except Exception:
3043 log.exception(
3048 log.exception(
3044 'Cache key invalidation failed for repository %s',
3049 'Cache key invalidation failed for repository %s',
3045 safe_str(repo_name))
3050 safe_str(repo_name))
3046 Session().rollback()
3051 Session().rollback()
3047
3052
3048 @classmethod
3053 @classmethod
3049 def get_active_cache(cls, cache_key):
3054 def get_active_cache(cls, cache_key):
3050 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3055 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3051 if inv_obj:
3056 if inv_obj:
3052 return inv_obj
3057 return inv_obj
3053 return None
3058 return None
3054
3059
3055 @classmethod
3060 @classmethod
3056 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3061 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3057 thread_scoped=False):
3062 thread_scoped=False):
3058 """
3063 """
3059 @cache_region('long_term')
3064 @cache_region('long_term')
3060 def _heavy_calculation(cache_key):
3065 def _heavy_calculation(cache_key):
3061 return 'result'
3066 return 'result'
3062
3067
3063 cache_context = CacheKey.repo_context_cache(
3068 cache_context = CacheKey.repo_context_cache(
3064 _heavy_calculation, repo_name, cache_type)
3069 _heavy_calculation, repo_name, cache_type)
3065
3070
3066 with cache_context as context:
3071 with cache_context as context:
3067 context.invalidate()
3072 context.invalidate()
3068 computed = context.compute()
3073 computed = context.compute()
3069
3074
3070 assert computed == 'result'
3075 assert computed == 'result'
3071 """
3076 """
3072 from rhodecode.lib import caches
3077 from rhodecode.lib import caches
3073 return caches.InvalidationContext(
3078 return caches.InvalidationContext(
3074 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3079 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3075
3080
3076
3081
3077 class ChangesetComment(Base, BaseModel):
3082 class ChangesetComment(Base, BaseModel):
3078 __tablename__ = 'changeset_comments'
3083 __tablename__ = 'changeset_comments'
3079 __table_args__ = (
3084 __table_args__ = (
3080 Index('cc_revision_idx', 'revision'),
3085 Index('cc_revision_idx', 'revision'),
3081 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3086 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3082 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3087 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3083 )
3088 )
3084
3089
3085 COMMENT_OUTDATED = u'comment_outdated'
3090 COMMENT_OUTDATED = u'comment_outdated'
3086 COMMENT_TYPE_NOTE = u'note'
3091 COMMENT_TYPE_NOTE = u'note'
3087 COMMENT_TYPE_TODO = u'todo'
3092 COMMENT_TYPE_TODO = u'todo'
3088 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3093 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3089
3094
3090 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3095 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3091 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3096 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3092 revision = Column('revision', String(40), nullable=True)
3097 revision = Column('revision', String(40), nullable=True)
3093 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3098 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3094 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3099 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3095 line_no = Column('line_no', Unicode(10), nullable=True)
3100 line_no = Column('line_no', Unicode(10), nullable=True)
3096 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3101 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3097 f_path = Column('f_path', Unicode(1000), nullable=True)
3102 f_path = Column('f_path', Unicode(1000), nullable=True)
3098 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3103 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3099 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3104 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3100 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3105 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3101 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3106 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3102 renderer = Column('renderer', Unicode(64), nullable=True)
3107 renderer = Column('renderer', Unicode(64), nullable=True)
3103 display_state = Column('display_state', Unicode(128), nullable=True)
3108 display_state = Column('display_state', Unicode(128), nullable=True)
3104
3109
3105 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3110 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3106 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3111 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3107 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3112 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3108 author = relationship('User', lazy='joined')
3113 author = relationship('User', lazy='joined')
3109 repo = relationship('Repository')
3114 repo = relationship('Repository')
3110 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3115 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3111 pull_request = relationship('PullRequest', lazy='joined')
3116 pull_request = relationship('PullRequest', lazy='joined')
3112 pull_request_version = relationship('PullRequestVersion')
3117 pull_request_version = relationship('PullRequestVersion')
3113
3118
3114 @classmethod
3119 @classmethod
3115 def get_users(cls, revision=None, pull_request_id=None):
3120 def get_users(cls, revision=None, pull_request_id=None):
3116 """
3121 """
3117 Returns user associated with this ChangesetComment. ie those
3122 Returns user associated with this ChangesetComment. ie those
3118 who actually commented
3123 who actually commented
3119
3124
3120 :param cls:
3125 :param cls:
3121 :param revision:
3126 :param revision:
3122 """
3127 """
3123 q = Session().query(User)\
3128 q = Session().query(User)\
3124 .join(ChangesetComment.author)
3129 .join(ChangesetComment.author)
3125 if revision:
3130 if revision:
3126 q = q.filter(cls.revision == revision)
3131 q = q.filter(cls.revision == revision)
3127 elif pull_request_id:
3132 elif pull_request_id:
3128 q = q.filter(cls.pull_request_id == pull_request_id)
3133 q = q.filter(cls.pull_request_id == pull_request_id)
3129 return q.all()
3134 return q.all()
3130
3135
3131 @classmethod
3136 @classmethod
3132 def get_index_from_version(cls, pr_version, versions):
3137 def get_index_from_version(cls, pr_version, versions):
3133 num_versions = [x.pull_request_version_id for x in versions]
3138 num_versions = [x.pull_request_version_id for x in versions]
3134 try:
3139 try:
3135 return num_versions.index(pr_version) +1
3140 return num_versions.index(pr_version) +1
3136 except (IndexError, ValueError):
3141 except (IndexError, ValueError):
3137 return
3142 return
3138
3143
3139 @property
3144 @property
3140 def outdated(self):
3145 def outdated(self):
3141 return self.display_state == self.COMMENT_OUTDATED
3146 return self.display_state == self.COMMENT_OUTDATED
3142
3147
3143 def outdated_at_version(self, version):
3148 def outdated_at_version(self, version):
3144 """
3149 """
3145 Checks if comment is outdated for given pull request version
3150 Checks if comment is outdated for given pull request version
3146 """
3151 """
3147 return self.outdated and self.pull_request_version_id != version
3152 return self.outdated and self.pull_request_version_id != version
3148
3153
3149 def older_than_version(self, version):
3154 def older_than_version(self, version):
3150 """
3155 """
3151 Checks if comment is made from previous version than given
3156 Checks if comment is made from previous version than given
3152 """
3157 """
3153 if version is None:
3158 if version is None:
3154 return self.pull_request_version_id is not None
3159 return self.pull_request_version_id is not None
3155
3160
3156 return self.pull_request_version_id < version
3161 return self.pull_request_version_id < version
3157
3162
3158 @property
3163 @property
3159 def resolved(self):
3164 def resolved(self):
3160 return self.resolved_by[0] if self.resolved_by else None
3165 return self.resolved_by[0] if self.resolved_by else None
3161
3166
3162 @property
3167 @property
3163 def is_todo(self):
3168 def is_todo(self):
3164 return self.comment_type == self.COMMENT_TYPE_TODO
3169 return self.comment_type == self.COMMENT_TYPE_TODO
3165
3170
3166 @property
3171 @property
3167 def is_inline(self):
3172 def is_inline(self):
3168 return self.line_no and self.f_path
3173 return self.line_no and self.f_path
3169
3174
3170 def get_index_version(self, versions):
3175 def get_index_version(self, versions):
3171 return self.get_index_from_version(
3176 return self.get_index_from_version(
3172 self.pull_request_version_id, versions)
3177 self.pull_request_version_id, versions)
3173
3178
3174 def __repr__(self):
3179 def __repr__(self):
3175 if self.comment_id:
3180 if self.comment_id:
3176 return '<DB:Comment #%s>' % self.comment_id
3181 return '<DB:Comment #%s>' % self.comment_id
3177 else:
3182 else:
3178 return '<DB:Comment at %#x>' % id(self)
3183 return '<DB:Comment at %#x>' % id(self)
3179
3184
3180 def get_api_data(self):
3185 def get_api_data(self):
3181 comment = self
3186 comment = self
3182 data = {
3187 data = {
3183 'comment_id': comment.comment_id,
3188 'comment_id': comment.comment_id,
3184 'comment_type': comment.comment_type,
3189 'comment_type': comment.comment_type,
3185 'comment_text': comment.text,
3190 'comment_text': comment.text,
3186 'comment_status': comment.status_change,
3191 'comment_status': comment.status_change,
3187 'comment_f_path': comment.f_path,
3192 'comment_f_path': comment.f_path,
3188 'comment_lineno': comment.line_no,
3193 'comment_lineno': comment.line_no,
3189 'comment_author': comment.author,
3194 'comment_author': comment.author,
3190 'comment_created_on': comment.created_on
3195 'comment_created_on': comment.created_on
3191 }
3196 }
3192 return data
3197 return data
3193
3198
3194 def __json__(self):
3199 def __json__(self):
3195 data = dict()
3200 data = dict()
3196 data.update(self.get_api_data())
3201 data.update(self.get_api_data())
3197 return data
3202 return data
3198
3203
3199
3204
3200 class ChangesetStatus(Base, BaseModel):
3205 class ChangesetStatus(Base, BaseModel):
3201 __tablename__ = 'changeset_statuses'
3206 __tablename__ = 'changeset_statuses'
3202 __table_args__ = (
3207 __table_args__ = (
3203 Index('cs_revision_idx', 'revision'),
3208 Index('cs_revision_idx', 'revision'),
3204 Index('cs_version_idx', 'version'),
3209 Index('cs_version_idx', 'version'),
3205 UniqueConstraint('repo_id', 'revision', 'version'),
3210 UniqueConstraint('repo_id', 'revision', 'version'),
3206 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3211 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3207 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3212 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3208 )
3213 )
3209 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3214 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3210 STATUS_APPROVED = 'approved'
3215 STATUS_APPROVED = 'approved'
3211 STATUS_REJECTED = 'rejected'
3216 STATUS_REJECTED = 'rejected'
3212 STATUS_UNDER_REVIEW = 'under_review'
3217 STATUS_UNDER_REVIEW = 'under_review'
3213
3218
3214 STATUSES = [
3219 STATUSES = [
3215 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3220 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3216 (STATUS_APPROVED, _("Approved")),
3221 (STATUS_APPROVED, _("Approved")),
3217 (STATUS_REJECTED, _("Rejected")),
3222 (STATUS_REJECTED, _("Rejected")),
3218 (STATUS_UNDER_REVIEW, _("Under Review")),
3223 (STATUS_UNDER_REVIEW, _("Under Review")),
3219 ]
3224 ]
3220
3225
3221 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3226 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3222 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3227 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3223 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3224 revision = Column('revision', String(40), nullable=False)
3229 revision = Column('revision', String(40), nullable=False)
3225 status = Column('status', String(128), nullable=False, default=DEFAULT)
3230 status = Column('status', String(128), nullable=False, default=DEFAULT)
3226 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3231 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3227 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3232 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3228 version = Column('version', Integer(), nullable=False, default=0)
3233 version = Column('version', Integer(), nullable=False, default=0)
3229 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3234 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3230
3235
3231 author = relationship('User', lazy='joined')
3236 author = relationship('User', lazy='joined')
3232 repo = relationship('Repository')
3237 repo = relationship('Repository')
3233 comment = relationship('ChangesetComment', lazy='joined')
3238 comment = relationship('ChangesetComment', lazy='joined')
3234 pull_request = relationship('PullRequest', lazy='joined')
3239 pull_request = relationship('PullRequest', lazy='joined')
3235
3240
3236 def __unicode__(self):
3241 def __unicode__(self):
3237 return u"<%s('%s[v%s]:%s')>" % (
3242 return u"<%s('%s[v%s]:%s')>" % (
3238 self.__class__.__name__,
3243 self.__class__.__name__,
3239 self.status, self.version, self.author
3244 self.status, self.version, self.author
3240 )
3245 )
3241
3246
3242 @classmethod
3247 @classmethod
3243 def get_status_lbl(cls, value):
3248 def get_status_lbl(cls, value):
3244 return dict(cls.STATUSES).get(value)
3249 return dict(cls.STATUSES).get(value)
3245
3250
3246 @property
3251 @property
3247 def status_lbl(self):
3252 def status_lbl(self):
3248 return ChangesetStatus.get_status_lbl(self.status)
3253 return ChangesetStatus.get_status_lbl(self.status)
3249
3254
3250 def get_api_data(self):
3255 def get_api_data(self):
3251 status = self
3256 status = self
3252 data = {
3257 data = {
3253 'status_id': status.changeset_status_id,
3258 'status_id': status.changeset_status_id,
3254 'status': status.status,
3259 'status': status.status,
3255 }
3260 }
3256 return data
3261 return data
3257
3262
3258 def __json__(self):
3263 def __json__(self):
3259 data = dict()
3264 data = dict()
3260 data.update(self.get_api_data())
3265 data.update(self.get_api_data())
3261 return data
3266 return data
3262
3267
3263
3268
3264 class _PullRequestBase(BaseModel):
3269 class _PullRequestBase(BaseModel):
3265 """
3270 """
3266 Common attributes of pull request and version entries.
3271 Common attributes of pull request and version entries.
3267 """
3272 """
3268
3273
3269 # .status values
3274 # .status values
3270 STATUS_NEW = u'new'
3275 STATUS_NEW = u'new'
3271 STATUS_OPEN = u'open'
3276 STATUS_OPEN = u'open'
3272 STATUS_CLOSED = u'closed'
3277 STATUS_CLOSED = u'closed'
3273
3278
3274 title = Column('title', Unicode(255), nullable=True)
3279 title = Column('title', Unicode(255), nullable=True)
3275 description = Column(
3280 description = Column(
3276 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3281 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3277 nullable=True)
3282 nullable=True)
3278 # new/open/closed status of pull request (not approve/reject/etc)
3283 # new/open/closed status of pull request (not approve/reject/etc)
3279 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3284 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3280 created_on = Column(
3285 created_on = Column(
3281 'created_on', DateTime(timezone=False), nullable=False,
3286 'created_on', DateTime(timezone=False), nullable=False,
3282 default=datetime.datetime.now)
3287 default=datetime.datetime.now)
3283 updated_on = Column(
3288 updated_on = Column(
3284 'updated_on', DateTime(timezone=False), nullable=False,
3289 'updated_on', DateTime(timezone=False), nullable=False,
3285 default=datetime.datetime.now)
3290 default=datetime.datetime.now)
3286
3291
3287 @declared_attr
3292 @declared_attr
3288 def user_id(cls):
3293 def user_id(cls):
3289 return Column(
3294 return Column(
3290 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3295 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3291 unique=None)
3296 unique=None)
3292
3297
3293 # 500 revisions max
3298 # 500 revisions max
3294 _revisions = Column(
3299 _revisions = Column(
3295 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3300 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3296
3301
3297 @declared_attr
3302 @declared_attr
3298 def source_repo_id(cls):
3303 def source_repo_id(cls):
3299 # TODO: dan: rename column to source_repo_id
3304 # TODO: dan: rename column to source_repo_id
3300 return Column(
3305 return Column(
3301 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3306 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3302 nullable=False)
3307 nullable=False)
3303
3308
3304 source_ref = Column('org_ref', Unicode(255), nullable=False)
3309 source_ref = Column('org_ref', Unicode(255), nullable=False)
3305
3310
3306 @declared_attr
3311 @declared_attr
3307 def target_repo_id(cls):
3312 def target_repo_id(cls):
3308 # TODO: dan: rename column to target_repo_id
3313 # TODO: dan: rename column to target_repo_id
3309 return Column(
3314 return Column(
3310 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3315 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3311 nullable=False)
3316 nullable=False)
3312
3317
3313 target_ref = Column('other_ref', Unicode(255), nullable=False)
3318 target_ref = Column('other_ref', Unicode(255), nullable=False)
3314 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3319 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3315
3320
3316 # TODO: dan: rename column to last_merge_source_rev
3321 # TODO: dan: rename column to last_merge_source_rev
3317 _last_merge_source_rev = Column(
3322 _last_merge_source_rev = Column(
3318 'last_merge_org_rev', String(40), nullable=True)
3323 'last_merge_org_rev', String(40), nullable=True)
3319 # TODO: dan: rename column to last_merge_target_rev
3324 # TODO: dan: rename column to last_merge_target_rev
3320 _last_merge_target_rev = Column(
3325 _last_merge_target_rev = Column(
3321 'last_merge_other_rev', String(40), nullable=True)
3326 'last_merge_other_rev', String(40), nullable=True)
3322 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3327 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3323 merge_rev = Column('merge_rev', String(40), nullable=True)
3328 merge_rev = Column('merge_rev', String(40), nullable=True)
3324
3329
3325 reviewer_data = Column(
3330 reviewer_data = Column(
3326 'reviewer_data_json', MutationObj.as_mutable(
3331 'reviewer_data_json', MutationObj.as_mutable(
3327 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3332 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3328
3333
3329 @property
3334 @property
3330 def reviewer_data_json(self):
3335 def reviewer_data_json(self):
3331 return json.dumps(self.reviewer_data)
3336 return json.dumps(self.reviewer_data)
3332
3337
3333 @hybrid_property
3338 @hybrid_property
3334 def description_safe(self):
3339 def description_safe(self):
3335 from rhodecode.lib import helpers as h
3340 from rhodecode.lib import helpers as h
3336 return h.escape(self.description)
3341 return h.escape(self.description)
3337
3342
3338 @hybrid_property
3343 @hybrid_property
3339 def revisions(self):
3344 def revisions(self):
3340 return self._revisions.split(':') if self._revisions else []
3345 return self._revisions.split(':') if self._revisions else []
3341
3346
3342 @revisions.setter
3347 @revisions.setter
3343 def revisions(self, val):
3348 def revisions(self, val):
3344 self._revisions = ':'.join(val)
3349 self._revisions = ':'.join(val)
3345
3350
3346 @declared_attr
3351 @declared_attr
3347 def author(cls):
3352 def author(cls):
3348 return relationship('User', lazy='joined')
3353 return relationship('User', lazy='joined')
3349
3354
3350 @declared_attr
3355 @declared_attr
3351 def source_repo(cls):
3356 def source_repo(cls):
3352 return relationship(
3357 return relationship(
3353 'Repository',
3358 'Repository',
3354 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3359 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3355
3360
3356 @property
3361 @property
3357 def source_ref_parts(self):
3362 def source_ref_parts(self):
3358 return self.unicode_to_reference(self.source_ref)
3363 return self.unicode_to_reference(self.source_ref)
3359
3364
3360 @declared_attr
3365 @declared_attr
3361 def target_repo(cls):
3366 def target_repo(cls):
3362 return relationship(
3367 return relationship(
3363 'Repository',
3368 'Repository',
3364 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3369 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3365
3370
3366 @property
3371 @property
3367 def target_ref_parts(self):
3372 def target_ref_parts(self):
3368 return self.unicode_to_reference(self.target_ref)
3373 return self.unicode_to_reference(self.target_ref)
3369
3374
3370 @property
3375 @property
3371 def shadow_merge_ref(self):
3376 def shadow_merge_ref(self):
3372 return self.unicode_to_reference(self._shadow_merge_ref)
3377 return self.unicode_to_reference(self._shadow_merge_ref)
3373
3378
3374 @shadow_merge_ref.setter
3379 @shadow_merge_ref.setter
3375 def shadow_merge_ref(self, ref):
3380 def shadow_merge_ref(self, ref):
3376 self._shadow_merge_ref = self.reference_to_unicode(ref)
3381 self._shadow_merge_ref = self.reference_to_unicode(ref)
3377
3382
3378 def unicode_to_reference(self, raw):
3383 def unicode_to_reference(self, raw):
3379 """
3384 """
3380 Convert a unicode (or string) to a reference object.
3385 Convert a unicode (or string) to a reference object.
3381 If unicode evaluates to False it returns None.
3386 If unicode evaluates to False it returns None.
3382 """
3387 """
3383 if raw:
3388 if raw:
3384 refs = raw.split(':')
3389 refs = raw.split(':')
3385 return Reference(*refs)
3390 return Reference(*refs)
3386 else:
3391 else:
3387 return None
3392 return None
3388
3393
3389 def reference_to_unicode(self, ref):
3394 def reference_to_unicode(self, ref):
3390 """
3395 """
3391 Convert a reference object to unicode.
3396 Convert a reference object to unicode.
3392 If reference is None it returns None.
3397 If reference is None it returns None.
3393 """
3398 """
3394 if ref:
3399 if ref:
3395 return u':'.join(ref)
3400 return u':'.join(ref)
3396 else:
3401 else:
3397 return None
3402 return None
3398
3403
3399 def get_api_data(self, with_merge_state=True):
3404 def get_api_data(self, with_merge_state=True):
3400 from rhodecode.model.pull_request import PullRequestModel
3405 from rhodecode.model.pull_request import PullRequestModel
3401
3406
3402 pull_request = self
3407 pull_request = self
3403 if with_merge_state:
3408 if with_merge_state:
3404 merge_status = PullRequestModel().merge_status(pull_request)
3409 merge_status = PullRequestModel().merge_status(pull_request)
3405 merge_state = {
3410 merge_state = {
3406 'status': merge_status[0],
3411 'status': merge_status[0],
3407 'message': safe_unicode(merge_status[1]),
3412 'message': safe_unicode(merge_status[1]),
3408 }
3413 }
3409 else:
3414 else:
3410 merge_state = {'status': 'not_available',
3415 merge_state = {'status': 'not_available',
3411 'message': 'not_available'}
3416 'message': 'not_available'}
3412
3417
3413 merge_data = {
3418 merge_data = {
3414 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3419 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3415 'reference': (
3420 'reference': (
3416 pull_request.shadow_merge_ref._asdict()
3421 pull_request.shadow_merge_ref._asdict()
3417 if pull_request.shadow_merge_ref else None),
3422 if pull_request.shadow_merge_ref else None),
3418 }
3423 }
3419
3424
3420 data = {
3425 data = {
3421 'pull_request_id': pull_request.pull_request_id,
3426 'pull_request_id': pull_request.pull_request_id,
3422 'url': PullRequestModel().get_url(pull_request),
3427 'url': PullRequestModel().get_url(pull_request),
3423 'title': pull_request.title,
3428 'title': pull_request.title,
3424 'description': pull_request.description,
3429 'description': pull_request.description,
3425 'status': pull_request.status,
3430 'status': pull_request.status,
3426 'created_on': pull_request.created_on,
3431 'created_on': pull_request.created_on,
3427 'updated_on': pull_request.updated_on,
3432 'updated_on': pull_request.updated_on,
3428 'commit_ids': pull_request.revisions,
3433 'commit_ids': pull_request.revisions,
3429 'review_status': pull_request.calculated_review_status(),
3434 'review_status': pull_request.calculated_review_status(),
3430 'mergeable': merge_state,
3435 'mergeable': merge_state,
3431 'source': {
3436 'source': {
3432 'clone_url': pull_request.source_repo.clone_url(),
3437 'clone_url': pull_request.source_repo.clone_url(),
3433 'repository': pull_request.source_repo.repo_name,
3438 'repository': pull_request.source_repo.repo_name,
3434 'reference': {
3439 'reference': {
3435 'name': pull_request.source_ref_parts.name,
3440 'name': pull_request.source_ref_parts.name,
3436 'type': pull_request.source_ref_parts.type,
3441 'type': pull_request.source_ref_parts.type,
3437 'commit_id': pull_request.source_ref_parts.commit_id,
3442 'commit_id': pull_request.source_ref_parts.commit_id,
3438 },
3443 },
3439 },
3444 },
3440 'target': {
3445 'target': {
3441 'clone_url': pull_request.target_repo.clone_url(),
3446 'clone_url': pull_request.target_repo.clone_url(),
3442 'repository': pull_request.target_repo.repo_name,
3447 'repository': pull_request.target_repo.repo_name,
3443 'reference': {
3448 'reference': {
3444 'name': pull_request.target_ref_parts.name,
3449 'name': pull_request.target_ref_parts.name,
3445 'type': pull_request.target_ref_parts.type,
3450 'type': pull_request.target_ref_parts.type,
3446 'commit_id': pull_request.target_ref_parts.commit_id,
3451 'commit_id': pull_request.target_ref_parts.commit_id,
3447 },
3452 },
3448 },
3453 },
3449 'merge': merge_data,
3454 'merge': merge_data,
3450 'author': pull_request.author.get_api_data(include_secrets=False,
3455 'author': pull_request.author.get_api_data(include_secrets=False,
3451 details='basic'),
3456 details='basic'),
3452 'reviewers': [
3457 'reviewers': [
3453 {
3458 {
3454 'user': reviewer.get_api_data(include_secrets=False,
3459 'user': reviewer.get_api_data(include_secrets=False,
3455 details='basic'),
3460 details='basic'),
3456 'reasons': reasons,
3461 'reasons': reasons,
3457 'review_status': st[0][1].status if st else 'not_reviewed',
3462 'review_status': st[0][1].status if st else 'not_reviewed',
3458 }
3463 }
3459 for reviewer, reasons, mandatory, st in
3464 for reviewer, reasons, mandatory, st in
3460 pull_request.reviewers_statuses()
3465 pull_request.reviewers_statuses()
3461 ]
3466 ]
3462 }
3467 }
3463
3468
3464 return data
3469 return data
3465
3470
3466
3471
3467 class PullRequest(Base, _PullRequestBase):
3472 class PullRequest(Base, _PullRequestBase):
3468 __tablename__ = 'pull_requests'
3473 __tablename__ = 'pull_requests'
3469 __table_args__ = (
3474 __table_args__ = (
3470 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3471 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3476 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3472 )
3477 )
3473
3478
3474 pull_request_id = Column(
3479 pull_request_id = Column(
3475 'pull_request_id', Integer(), nullable=False, primary_key=True)
3480 'pull_request_id', Integer(), nullable=False, primary_key=True)
3476
3481
3477 def __repr__(self):
3482 def __repr__(self):
3478 if self.pull_request_id:
3483 if self.pull_request_id:
3479 return '<DB:PullRequest #%s>' % self.pull_request_id
3484 return '<DB:PullRequest #%s>' % self.pull_request_id
3480 else:
3485 else:
3481 return '<DB:PullRequest at %#x>' % id(self)
3486 return '<DB:PullRequest at %#x>' % id(self)
3482
3487
3483 reviewers = relationship('PullRequestReviewers',
3488 reviewers = relationship('PullRequestReviewers',
3484 cascade="all, delete, delete-orphan")
3489 cascade="all, delete, delete-orphan")
3485 statuses = relationship('ChangesetStatus',
3490 statuses = relationship('ChangesetStatus',
3486 cascade="all, delete, delete-orphan")
3491 cascade="all, delete, delete-orphan")
3487 comments = relationship('ChangesetComment',
3492 comments = relationship('ChangesetComment',
3488 cascade="all, delete, delete-orphan")
3493 cascade="all, delete, delete-orphan")
3489 versions = relationship('PullRequestVersion',
3494 versions = relationship('PullRequestVersion',
3490 cascade="all, delete, delete-orphan",
3495 cascade="all, delete, delete-orphan",
3491 lazy='dynamic')
3496 lazy='dynamic')
3492
3497
3493 @classmethod
3498 @classmethod
3494 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3499 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3495 internal_methods=None):
3500 internal_methods=None):
3496
3501
3497 class PullRequestDisplay(object):
3502 class PullRequestDisplay(object):
3498 """
3503 """
3499 Special object wrapper for showing PullRequest data via Versions
3504 Special object wrapper for showing PullRequest data via Versions
3500 It mimics PR object as close as possible. This is read only object
3505 It mimics PR object as close as possible. This is read only object
3501 just for display
3506 just for display
3502 """
3507 """
3503
3508
3504 def __init__(self, attrs, internal=None):
3509 def __init__(self, attrs, internal=None):
3505 self.attrs = attrs
3510 self.attrs = attrs
3506 # internal have priority over the given ones via attrs
3511 # internal have priority over the given ones via attrs
3507 self.internal = internal or ['versions']
3512 self.internal = internal or ['versions']
3508
3513
3509 def __getattr__(self, item):
3514 def __getattr__(self, item):
3510 if item in self.internal:
3515 if item in self.internal:
3511 return getattr(self, item)
3516 return getattr(self, item)
3512 try:
3517 try:
3513 return self.attrs[item]
3518 return self.attrs[item]
3514 except KeyError:
3519 except KeyError:
3515 raise AttributeError(
3520 raise AttributeError(
3516 '%s object has no attribute %s' % (self, item))
3521 '%s object has no attribute %s' % (self, item))
3517
3522
3518 def __repr__(self):
3523 def __repr__(self):
3519 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3524 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3520
3525
3521 def versions(self):
3526 def versions(self):
3522 return pull_request_obj.versions.order_by(
3527 return pull_request_obj.versions.order_by(
3523 PullRequestVersion.pull_request_version_id).all()
3528 PullRequestVersion.pull_request_version_id).all()
3524
3529
3525 def is_closed(self):
3530 def is_closed(self):
3526 return pull_request_obj.is_closed()
3531 return pull_request_obj.is_closed()
3527
3532
3528 @property
3533 @property
3529 def pull_request_version_id(self):
3534 def pull_request_version_id(self):
3530 return getattr(pull_request_obj, 'pull_request_version_id', None)
3535 return getattr(pull_request_obj, 'pull_request_version_id', None)
3531
3536
3532 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3537 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3533
3538
3534 attrs.author = StrictAttributeDict(
3539 attrs.author = StrictAttributeDict(
3535 pull_request_obj.author.get_api_data())
3540 pull_request_obj.author.get_api_data())
3536 if pull_request_obj.target_repo:
3541 if pull_request_obj.target_repo:
3537 attrs.target_repo = StrictAttributeDict(
3542 attrs.target_repo = StrictAttributeDict(
3538 pull_request_obj.target_repo.get_api_data())
3543 pull_request_obj.target_repo.get_api_data())
3539 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3544 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3540
3545
3541 if pull_request_obj.source_repo:
3546 if pull_request_obj.source_repo:
3542 attrs.source_repo = StrictAttributeDict(
3547 attrs.source_repo = StrictAttributeDict(
3543 pull_request_obj.source_repo.get_api_data())
3548 pull_request_obj.source_repo.get_api_data())
3544 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3549 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3545
3550
3546 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3551 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3547 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3552 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3548 attrs.revisions = pull_request_obj.revisions
3553 attrs.revisions = pull_request_obj.revisions
3549
3554
3550 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3555 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3551 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3556 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3552 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3557 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3553
3558
3554 return PullRequestDisplay(attrs, internal=internal_methods)
3559 return PullRequestDisplay(attrs, internal=internal_methods)
3555
3560
3556 def is_closed(self):
3561 def is_closed(self):
3557 return self.status == self.STATUS_CLOSED
3562 return self.status == self.STATUS_CLOSED
3558
3563
3559 def __json__(self):
3564 def __json__(self):
3560 return {
3565 return {
3561 'revisions': self.revisions,
3566 'revisions': self.revisions,
3562 }
3567 }
3563
3568
3564 def calculated_review_status(self):
3569 def calculated_review_status(self):
3565 from rhodecode.model.changeset_status import ChangesetStatusModel
3570 from rhodecode.model.changeset_status import ChangesetStatusModel
3566 return ChangesetStatusModel().calculated_review_status(self)
3571 return ChangesetStatusModel().calculated_review_status(self)
3567
3572
3568 def reviewers_statuses(self):
3573 def reviewers_statuses(self):
3569 from rhodecode.model.changeset_status import ChangesetStatusModel
3574 from rhodecode.model.changeset_status import ChangesetStatusModel
3570 return ChangesetStatusModel().reviewers_statuses(self)
3575 return ChangesetStatusModel().reviewers_statuses(self)
3571
3576
3572 @property
3577 @property
3573 def workspace_id(self):
3578 def workspace_id(self):
3574 from rhodecode.model.pull_request import PullRequestModel
3579 from rhodecode.model.pull_request import PullRequestModel
3575 return PullRequestModel()._workspace_id(self)
3580 return PullRequestModel()._workspace_id(self)
3576
3581
3577 def get_shadow_repo(self):
3582 def get_shadow_repo(self):
3578 workspace_id = self.workspace_id
3583 workspace_id = self.workspace_id
3579 vcs_obj = self.target_repo.scm_instance()
3584 vcs_obj = self.target_repo.scm_instance()
3580 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3585 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3581 workspace_id)
3586 workspace_id)
3582 return vcs_obj._get_shadow_instance(shadow_repository_path)
3587 return vcs_obj._get_shadow_instance(shadow_repository_path)
3583
3588
3584
3589
3585 class PullRequestVersion(Base, _PullRequestBase):
3590 class PullRequestVersion(Base, _PullRequestBase):
3586 __tablename__ = 'pull_request_versions'
3591 __tablename__ = 'pull_request_versions'
3587 __table_args__ = (
3592 __table_args__ = (
3588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3589 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3590 )
3595 )
3591
3596
3592 pull_request_version_id = Column(
3597 pull_request_version_id = Column(
3593 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3598 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3594 pull_request_id = Column(
3599 pull_request_id = Column(
3595 'pull_request_id', Integer(),
3600 'pull_request_id', Integer(),
3596 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3601 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3597 pull_request = relationship('PullRequest')
3602 pull_request = relationship('PullRequest')
3598
3603
3599 def __repr__(self):
3604 def __repr__(self):
3600 if self.pull_request_version_id:
3605 if self.pull_request_version_id:
3601 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3606 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3602 else:
3607 else:
3603 return '<DB:PullRequestVersion at %#x>' % id(self)
3608 return '<DB:PullRequestVersion at %#x>' % id(self)
3604
3609
3605 @property
3610 @property
3606 def reviewers(self):
3611 def reviewers(self):
3607 return self.pull_request.reviewers
3612 return self.pull_request.reviewers
3608
3613
3609 @property
3614 @property
3610 def versions(self):
3615 def versions(self):
3611 return self.pull_request.versions
3616 return self.pull_request.versions
3612
3617
3613 def is_closed(self):
3618 def is_closed(self):
3614 # calculate from original
3619 # calculate from original
3615 return self.pull_request.status == self.STATUS_CLOSED
3620 return self.pull_request.status == self.STATUS_CLOSED
3616
3621
3617 def calculated_review_status(self):
3622 def calculated_review_status(self):
3618 return self.pull_request.calculated_review_status()
3623 return self.pull_request.calculated_review_status()
3619
3624
3620 def reviewers_statuses(self):
3625 def reviewers_statuses(self):
3621 return self.pull_request.reviewers_statuses()
3626 return self.pull_request.reviewers_statuses()
3622
3627
3623
3628
3624 class PullRequestReviewers(Base, BaseModel):
3629 class PullRequestReviewers(Base, BaseModel):
3625 __tablename__ = 'pull_request_reviewers'
3630 __tablename__ = 'pull_request_reviewers'
3626 __table_args__ = (
3631 __table_args__ = (
3627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3628 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3633 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3629 )
3634 )
3630
3635
3631 @hybrid_property
3636 @hybrid_property
3632 def reasons(self):
3637 def reasons(self):
3633 if not self._reasons:
3638 if not self._reasons:
3634 return []
3639 return []
3635 return self._reasons
3640 return self._reasons
3636
3641
3637 @reasons.setter
3642 @reasons.setter
3638 def reasons(self, val):
3643 def reasons(self, val):
3639 val = val or []
3644 val = val or []
3640 if any(not isinstance(x, basestring) for x in val):
3645 if any(not isinstance(x, basestring) for x in val):
3641 raise Exception('invalid reasons type, must be list of strings')
3646 raise Exception('invalid reasons type, must be list of strings')
3642 self._reasons = val
3647 self._reasons = val
3643
3648
3644 pull_requests_reviewers_id = Column(
3649 pull_requests_reviewers_id = Column(
3645 'pull_requests_reviewers_id', Integer(), nullable=False,
3650 'pull_requests_reviewers_id', Integer(), nullable=False,
3646 primary_key=True)
3651 primary_key=True)
3647 pull_request_id = Column(
3652 pull_request_id = Column(
3648 "pull_request_id", Integer(),
3653 "pull_request_id", Integer(),
3649 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3654 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3650 user_id = Column(
3655 user_id = Column(
3651 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3656 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3652 _reasons = Column(
3657 _reasons = Column(
3653 'reason', MutationList.as_mutable(
3658 'reason', MutationList.as_mutable(
3654 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3659 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3655 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3660 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3656 user = relationship('User')
3661 user = relationship('User')
3657 pull_request = relationship('PullRequest')
3662 pull_request = relationship('PullRequest')
3658
3663
3659
3664
3660 class Notification(Base, BaseModel):
3665 class Notification(Base, BaseModel):
3661 __tablename__ = 'notifications'
3666 __tablename__ = 'notifications'
3662 __table_args__ = (
3667 __table_args__ = (
3663 Index('notification_type_idx', 'type'),
3668 Index('notification_type_idx', 'type'),
3664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3665 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3670 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3666 )
3671 )
3667
3672
3668 TYPE_CHANGESET_COMMENT = u'cs_comment'
3673 TYPE_CHANGESET_COMMENT = u'cs_comment'
3669 TYPE_MESSAGE = u'message'
3674 TYPE_MESSAGE = u'message'
3670 TYPE_MENTION = u'mention'
3675 TYPE_MENTION = u'mention'
3671 TYPE_REGISTRATION = u'registration'
3676 TYPE_REGISTRATION = u'registration'
3672 TYPE_PULL_REQUEST = u'pull_request'
3677 TYPE_PULL_REQUEST = u'pull_request'
3673 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3678 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3674
3679
3675 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3680 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3676 subject = Column('subject', Unicode(512), nullable=True)
3681 subject = Column('subject', Unicode(512), nullable=True)
3677 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3682 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3678 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3683 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3679 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3684 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3680 type_ = Column('type', Unicode(255))
3685 type_ = Column('type', Unicode(255))
3681
3686
3682 created_by_user = relationship('User')
3687 created_by_user = relationship('User')
3683 notifications_to_users = relationship('UserNotification', lazy='joined',
3688 notifications_to_users = relationship('UserNotification', lazy='joined',
3684 cascade="all, delete, delete-orphan")
3689 cascade="all, delete, delete-orphan")
3685
3690
3686 @property
3691 @property
3687 def recipients(self):
3692 def recipients(self):
3688 return [x.user for x in UserNotification.query()\
3693 return [x.user for x in UserNotification.query()\
3689 .filter(UserNotification.notification == self)\
3694 .filter(UserNotification.notification == self)\
3690 .order_by(UserNotification.user_id.asc()).all()]
3695 .order_by(UserNotification.user_id.asc()).all()]
3691
3696
3692 @classmethod
3697 @classmethod
3693 def create(cls, created_by, subject, body, recipients, type_=None):
3698 def create(cls, created_by, subject, body, recipients, type_=None):
3694 if type_ is None:
3699 if type_ is None:
3695 type_ = Notification.TYPE_MESSAGE
3700 type_ = Notification.TYPE_MESSAGE
3696
3701
3697 notification = cls()
3702 notification = cls()
3698 notification.created_by_user = created_by
3703 notification.created_by_user = created_by
3699 notification.subject = subject
3704 notification.subject = subject
3700 notification.body = body
3705 notification.body = body
3701 notification.type_ = type_
3706 notification.type_ = type_
3702 notification.created_on = datetime.datetime.now()
3707 notification.created_on = datetime.datetime.now()
3703
3708
3704 for u in recipients:
3709 for u in recipients:
3705 assoc = UserNotification()
3710 assoc = UserNotification()
3706 assoc.notification = notification
3711 assoc.notification = notification
3707
3712
3708 # if created_by is inside recipients mark his notification
3713 # if created_by is inside recipients mark his notification
3709 # as read
3714 # as read
3710 if u.user_id == created_by.user_id:
3715 if u.user_id == created_by.user_id:
3711 assoc.read = True
3716 assoc.read = True
3712
3717
3713 u.notifications.append(assoc)
3718 u.notifications.append(assoc)
3714 Session().add(notification)
3719 Session().add(notification)
3715
3720
3716 return notification
3721 return notification
3717
3722
3718
3723
3719 class UserNotification(Base, BaseModel):
3724 class UserNotification(Base, BaseModel):
3720 __tablename__ = 'user_to_notification'
3725 __tablename__ = 'user_to_notification'
3721 __table_args__ = (
3726 __table_args__ = (
3722 UniqueConstraint('user_id', 'notification_id'),
3727 UniqueConstraint('user_id', 'notification_id'),
3723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3729 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3725 )
3730 )
3726 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3731 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3727 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3732 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3728 read = Column('read', Boolean, default=False)
3733 read = Column('read', Boolean, default=False)
3729 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3734 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3730
3735
3731 user = relationship('User', lazy="joined")
3736 user = relationship('User', lazy="joined")
3732 notification = relationship('Notification', lazy="joined",
3737 notification = relationship('Notification', lazy="joined",
3733 order_by=lambda: Notification.created_on.desc(),)
3738 order_by=lambda: Notification.created_on.desc(),)
3734
3739
3735 def mark_as_read(self):
3740 def mark_as_read(self):
3736 self.read = True
3741 self.read = True
3737 Session().add(self)
3742 Session().add(self)
3738
3743
3739
3744
3740 class Gist(Base, BaseModel):
3745 class Gist(Base, BaseModel):
3741 __tablename__ = 'gists'
3746 __tablename__ = 'gists'
3742 __table_args__ = (
3747 __table_args__ = (
3743 Index('g_gist_access_id_idx', 'gist_access_id'),
3748 Index('g_gist_access_id_idx', 'gist_access_id'),
3744 Index('g_created_on_idx', 'created_on'),
3749 Index('g_created_on_idx', 'created_on'),
3745 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3750 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3746 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3751 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3747 )
3752 )
3748 GIST_PUBLIC = u'public'
3753 GIST_PUBLIC = u'public'
3749 GIST_PRIVATE = u'private'
3754 GIST_PRIVATE = u'private'
3750 DEFAULT_FILENAME = u'gistfile1.txt'
3755 DEFAULT_FILENAME = u'gistfile1.txt'
3751
3756
3752 ACL_LEVEL_PUBLIC = u'acl_public'
3757 ACL_LEVEL_PUBLIC = u'acl_public'
3753 ACL_LEVEL_PRIVATE = u'acl_private'
3758 ACL_LEVEL_PRIVATE = u'acl_private'
3754
3759
3755 gist_id = Column('gist_id', Integer(), primary_key=True)
3760 gist_id = Column('gist_id', Integer(), primary_key=True)
3756 gist_access_id = Column('gist_access_id', Unicode(250))
3761 gist_access_id = Column('gist_access_id', Unicode(250))
3757 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3762 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3758 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3763 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3759 gist_expires = Column('gist_expires', Float(53), nullable=False)
3764 gist_expires = Column('gist_expires', Float(53), nullable=False)
3760 gist_type = Column('gist_type', Unicode(128), nullable=False)
3765 gist_type = Column('gist_type', Unicode(128), nullable=False)
3761 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3766 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3762 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3767 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3763 acl_level = Column('acl_level', Unicode(128), nullable=True)
3768 acl_level = Column('acl_level', Unicode(128), nullable=True)
3764
3769
3765 owner = relationship('User')
3770 owner = relationship('User')
3766
3771
3767 def __repr__(self):
3772 def __repr__(self):
3768 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3773 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3769
3774
3770 @hybrid_property
3775 @hybrid_property
3771 def description_safe(self):
3776 def description_safe(self):
3772 from rhodecode.lib import helpers as h
3777 from rhodecode.lib import helpers as h
3773 return h.escape(self.gist_description)
3778 return h.escape(self.gist_description)
3774
3779
3775 @classmethod
3780 @classmethod
3776 def get_or_404(cls, id_, pyramid_exc=False):
3781 def get_or_404(cls, id_, pyramid_exc=False):
3777
3782
3778 if pyramid_exc:
3783 if pyramid_exc:
3779 from pyramid.httpexceptions import HTTPNotFound
3784 from pyramid.httpexceptions import HTTPNotFound
3780 else:
3785 else:
3781 from webob.exc import HTTPNotFound
3786 from webob.exc import HTTPNotFound
3782
3787
3783 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3788 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3784 if not res:
3789 if not res:
3785 raise HTTPNotFound
3790 raise HTTPNotFound
3786 return res
3791 return res
3787
3792
3788 @classmethod
3793 @classmethod
3789 def get_by_access_id(cls, gist_access_id):
3794 def get_by_access_id(cls, gist_access_id):
3790 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3795 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3791
3796
3792 def gist_url(self):
3797 def gist_url(self):
3793 from rhodecode.model.gist import GistModel
3798 from rhodecode.model.gist import GistModel
3794 return GistModel().get_url(self)
3799 return GistModel().get_url(self)
3795
3800
3796 @classmethod
3801 @classmethod
3797 def base_path(cls):
3802 def base_path(cls):
3798 """
3803 """
3799 Returns base path when all gists are stored
3804 Returns base path when all gists are stored
3800
3805
3801 :param cls:
3806 :param cls:
3802 """
3807 """
3803 from rhodecode.model.gist import GIST_STORE_LOC
3808 from rhodecode.model.gist import GIST_STORE_LOC
3804 q = Session().query(RhodeCodeUi)\
3809 q = Session().query(RhodeCodeUi)\
3805 .filter(RhodeCodeUi.ui_key == URL_SEP)
3810 .filter(RhodeCodeUi.ui_key == URL_SEP)
3806 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3811 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3807 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3812 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3808
3813
3809 def get_api_data(self):
3814 def get_api_data(self):
3810 """
3815 """
3811 Common function for generating gist related data for API
3816 Common function for generating gist related data for API
3812 """
3817 """
3813 gist = self
3818 gist = self
3814 data = {
3819 data = {
3815 'gist_id': gist.gist_id,
3820 'gist_id': gist.gist_id,
3816 'type': gist.gist_type,
3821 'type': gist.gist_type,
3817 'access_id': gist.gist_access_id,
3822 'access_id': gist.gist_access_id,
3818 'description': gist.gist_description,
3823 'description': gist.gist_description,
3819 'url': gist.gist_url(),
3824 'url': gist.gist_url(),
3820 'expires': gist.gist_expires,
3825 'expires': gist.gist_expires,
3821 'created_on': gist.created_on,
3826 'created_on': gist.created_on,
3822 'modified_at': gist.modified_at,
3827 'modified_at': gist.modified_at,
3823 'content': None,
3828 'content': None,
3824 'acl_level': gist.acl_level,
3829 'acl_level': gist.acl_level,
3825 }
3830 }
3826 return data
3831 return data
3827
3832
3828 def __json__(self):
3833 def __json__(self):
3829 data = dict(
3834 data = dict(
3830 )
3835 )
3831 data.update(self.get_api_data())
3836 data.update(self.get_api_data())
3832 return data
3837 return data
3833 # SCM functions
3838 # SCM functions
3834
3839
3835 def scm_instance(self, **kwargs):
3840 def scm_instance(self, **kwargs):
3836 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3841 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3837 return get_vcs_instance(
3842 return get_vcs_instance(
3838 repo_path=safe_str(full_repo_path), create=False)
3843 repo_path=safe_str(full_repo_path), create=False)
3839
3844
3840
3845
3841 class ExternalIdentity(Base, BaseModel):
3846 class ExternalIdentity(Base, BaseModel):
3842 __tablename__ = 'external_identities'
3847 __tablename__ = 'external_identities'
3843 __table_args__ = (
3848 __table_args__ = (
3844 Index('local_user_id_idx', 'local_user_id'),
3849 Index('local_user_id_idx', 'local_user_id'),
3845 Index('external_id_idx', 'external_id'),
3850 Index('external_id_idx', 'external_id'),
3846 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3847 'mysql_charset': 'utf8'})
3852 'mysql_charset': 'utf8'})
3848
3853
3849 external_id = Column('external_id', Unicode(255), default=u'',
3854 external_id = Column('external_id', Unicode(255), default=u'',
3850 primary_key=True)
3855 primary_key=True)
3851 external_username = Column('external_username', Unicode(1024), default=u'')
3856 external_username = Column('external_username', Unicode(1024), default=u'')
3852 local_user_id = Column('local_user_id', Integer(),
3857 local_user_id = Column('local_user_id', Integer(),
3853 ForeignKey('users.user_id'), primary_key=True)
3858 ForeignKey('users.user_id'), primary_key=True)
3854 provider_name = Column('provider_name', Unicode(255), default=u'',
3859 provider_name = Column('provider_name', Unicode(255), default=u'',
3855 primary_key=True)
3860 primary_key=True)
3856 access_token = Column('access_token', String(1024), default=u'')
3861 access_token = Column('access_token', String(1024), default=u'')
3857 alt_token = Column('alt_token', String(1024), default=u'')
3862 alt_token = Column('alt_token', String(1024), default=u'')
3858 token_secret = Column('token_secret', String(1024), default=u'')
3863 token_secret = Column('token_secret', String(1024), default=u'')
3859
3864
3860 @classmethod
3865 @classmethod
3861 def by_external_id_and_provider(cls, external_id, provider_name,
3866 def by_external_id_and_provider(cls, external_id, provider_name,
3862 local_user_id=None):
3867 local_user_id=None):
3863 """
3868 """
3864 Returns ExternalIdentity instance based on search params
3869 Returns ExternalIdentity instance based on search params
3865
3870
3866 :param external_id:
3871 :param external_id:
3867 :param provider_name:
3872 :param provider_name:
3868 :return: ExternalIdentity
3873 :return: ExternalIdentity
3869 """
3874 """
3870 query = cls.query()
3875 query = cls.query()
3871 query = query.filter(cls.external_id == external_id)
3876 query = query.filter(cls.external_id == external_id)
3872 query = query.filter(cls.provider_name == provider_name)
3877 query = query.filter(cls.provider_name == provider_name)
3873 if local_user_id:
3878 if local_user_id:
3874 query = query.filter(cls.local_user_id == local_user_id)
3879 query = query.filter(cls.local_user_id == local_user_id)
3875 return query.first()
3880 return query.first()
3876
3881
3877 @classmethod
3882 @classmethod
3878 def user_by_external_id_and_provider(cls, external_id, provider_name):
3883 def user_by_external_id_and_provider(cls, external_id, provider_name):
3879 """
3884 """
3880 Returns User instance based on search params
3885 Returns User instance based on search params
3881
3886
3882 :param external_id:
3887 :param external_id:
3883 :param provider_name:
3888 :param provider_name:
3884 :return: User
3889 :return: User
3885 """
3890 """
3886 query = User.query()
3891 query = User.query()
3887 query = query.filter(cls.external_id == external_id)
3892 query = query.filter(cls.external_id == external_id)
3888 query = query.filter(cls.provider_name == provider_name)
3893 query = query.filter(cls.provider_name == provider_name)
3889 query = query.filter(User.user_id == cls.local_user_id)
3894 query = query.filter(User.user_id == cls.local_user_id)
3890 return query.first()
3895 return query.first()
3891
3896
3892 @classmethod
3897 @classmethod
3893 def by_local_user_id(cls, local_user_id):
3898 def by_local_user_id(cls, local_user_id):
3894 """
3899 """
3895 Returns all tokens for user
3900 Returns all tokens for user
3896
3901
3897 :param local_user_id:
3902 :param local_user_id:
3898 :return: ExternalIdentity
3903 :return: ExternalIdentity
3899 """
3904 """
3900 query = cls.query()
3905 query = cls.query()
3901 query = query.filter(cls.local_user_id == local_user_id)
3906 query = query.filter(cls.local_user_id == local_user_id)
3902 return query
3907 return query
3903
3908
3904
3909
3905 class Integration(Base, BaseModel):
3910 class Integration(Base, BaseModel):
3906 __tablename__ = 'integrations'
3911 __tablename__ = 'integrations'
3907 __table_args__ = (
3912 __table_args__ = (
3908 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3913 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3909 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3914 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3910 )
3915 )
3911
3916
3912 integration_id = Column('integration_id', Integer(), primary_key=True)
3917 integration_id = Column('integration_id', Integer(), primary_key=True)
3913 integration_type = Column('integration_type', String(255))
3918 integration_type = Column('integration_type', String(255))
3914 enabled = Column('enabled', Boolean(), nullable=False)
3919 enabled = Column('enabled', Boolean(), nullable=False)
3915 name = Column('name', String(255), nullable=False)
3920 name = Column('name', String(255), nullable=False)
3916 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3921 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3917 default=False)
3922 default=False)
3918
3923
3919 settings = Column(
3924 settings = Column(
3920 'settings_json', MutationObj.as_mutable(
3925 'settings_json', MutationObj.as_mutable(
3921 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3926 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3922 repo_id = Column(
3927 repo_id = Column(
3923 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3928 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3924 nullable=True, unique=None, default=None)
3929 nullable=True, unique=None, default=None)
3925 repo = relationship('Repository', lazy='joined')
3930 repo = relationship('Repository', lazy='joined')
3926
3931
3927 repo_group_id = Column(
3932 repo_group_id = Column(
3928 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3933 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3929 nullable=True, unique=None, default=None)
3934 nullable=True, unique=None, default=None)
3930 repo_group = relationship('RepoGroup', lazy='joined')
3935 repo_group = relationship('RepoGroup', lazy='joined')
3931
3936
3932 @property
3937 @property
3933 def scope(self):
3938 def scope(self):
3934 if self.repo:
3939 if self.repo:
3935 return repr(self.repo)
3940 return repr(self.repo)
3936 if self.repo_group:
3941 if self.repo_group:
3937 if self.child_repos_only:
3942 if self.child_repos_only:
3938 return repr(self.repo_group) + ' (child repos only)'
3943 return repr(self.repo_group) + ' (child repos only)'
3939 else:
3944 else:
3940 return repr(self.repo_group) + ' (recursive)'
3945 return repr(self.repo_group) + ' (recursive)'
3941 if self.child_repos_only:
3946 if self.child_repos_only:
3942 return 'root_repos'
3947 return 'root_repos'
3943 return 'global'
3948 return 'global'
3944
3949
3945 def __repr__(self):
3950 def __repr__(self):
3946 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3951 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3947
3952
3948
3953
3949 class RepoReviewRuleUser(Base, BaseModel):
3954 class RepoReviewRuleUser(Base, BaseModel):
3950 __tablename__ = 'repo_review_rules_users'
3955 __tablename__ = 'repo_review_rules_users'
3951 __table_args__ = (
3956 __table_args__ = (
3952 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3953 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3958 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3954 )
3959 )
3955 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3960 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
3956 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3961 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3957 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
3958 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3963 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3959 user = relationship('User')
3964 user = relationship('User')
3960
3965
3961 def rule_data(self):
3966 def rule_data(self):
3962 return {
3967 return {
3963 'mandatory': self.mandatory
3968 'mandatory': self.mandatory
3964 }
3969 }
3965
3970
3966
3971
3967 class RepoReviewRuleUserGroup(Base, BaseModel):
3972 class RepoReviewRuleUserGroup(Base, BaseModel):
3968 __tablename__ = 'repo_review_rules_users_groups'
3973 __tablename__ = 'repo_review_rules_users_groups'
3969 __table_args__ = (
3974 __table_args__ = (
3970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3975 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3971 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3976 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3972 )
3977 )
3973 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3978 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
3974 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3979 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3975 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3980 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
3976 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3981 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3977 users_group = relationship('UserGroup')
3982 users_group = relationship('UserGroup')
3978
3983
3979 def rule_data(self):
3984 def rule_data(self):
3980 return {
3985 return {
3981 'mandatory': self.mandatory
3986 'mandatory': self.mandatory
3982 }
3987 }
3983
3988
3984
3989
3985 class RepoReviewRule(Base, BaseModel):
3990 class RepoReviewRule(Base, BaseModel):
3986 __tablename__ = 'repo_review_rules'
3991 __tablename__ = 'repo_review_rules'
3987 __table_args__ = (
3992 __table_args__ = (
3988 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3989 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3990 )
3995 )
3991
3996
3992 repo_review_rule_id = Column(
3997 repo_review_rule_id = Column(
3993 'repo_review_rule_id', Integer(), primary_key=True)
3998 'repo_review_rule_id', Integer(), primary_key=True)
3994 repo_id = Column(
3999 repo_id = Column(
3995 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4000 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3996 repo = relationship('Repository', backref='review_rules')
4001 repo = relationship('Repository', backref='review_rules')
3997
4002
3998 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4003 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
3999 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4004 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4000
4005
4001 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4006 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4002 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4007 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4003 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4008 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4004 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4009 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4005
4010
4006 rule_users = relationship('RepoReviewRuleUser')
4011 rule_users = relationship('RepoReviewRuleUser')
4007 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4012 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4008
4013
4009 @hybrid_property
4014 @hybrid_property
4010 def branch_pattern(self):
4015 def branch_pattern(self):
4011 return self._branch_pattern or '*'
4016 return self._branch_pattern or '*'
4012
4017
4013 def _validate_glob(self, value):
4018 def _validate_glob(self, value):
4014 re.compile('^' + glob2re(value) + '$')
4019 re.compile('^' + glob2re(value) + '$')
4015
4020
4016 @branch_pattern.setter
4021 @branch_pattern.setter
4017 def branch_pattern(self, value):
4022 def branch_pattern(self, value):
4018 self._validate_glob(value)
4023 self._validate_glob(value)
4019 self._branch_pattern = value or '*'
4024 self._branch_pattern = value or '*'
4020
4025
4021 @hybrid_property
4026 @hybrid_property
4022 def file_pattern(self):
4027 def file_pattern(self):
4023 return self._file_pattern or '*'
4028 return self._file_pattern or '*'
4024
4029
4025 @file_pattern.setter
4030 @file_pattern.setter
4026 def file_pattern(self, value):
4031 def file_pattern(self, value):
4027 self._validate_glob(value)
4032 self._validate_glob(value)
4028 self._file_pattern = value or '*'
4033 self._file_pattern = value or '*'
4029
4034
4030 def matches(self, branch, files_changed):
4035 def matches(self, branch, files_changed):
4031 """
4036 """
4032 Check if this review rule matches a branch/files in a pull request
4037 Check if this review rule matches a branch/files in a pull request
4033
4038
4034 :param branch: branch name for the commit
4039 :param branch: branch name for the commit
4035 :param files_changed: list of file paths changed in the pull request
4040 :param files_changed: list of file paths changed in the pull request
4036 """
4041 """
4037
4042
4038 branch = branch or ''
4043 branch = branch or ''
4039 files_changed = files_changed or []
4044 files_changed = files_changed or []
4040
4045
4041 branch_matches = True
4046 branch_matches = True
4042 if branch:
4047 if branch:
4043 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4048 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4044 branch_matches = bool(branch_regex.search(branch))
4049 branch_matches = bool(branch_regex.search(branch))
4045
4050
4046 files_matches = True
4051 files_matches = True
4047 if self.file_pattern != '*':
4052 if self.file_pattern != '*':
4048 files_matches = False
4053 files_matches = False
4049 file_regex = re.compile(glob2re(self.file_pattern))
4054 file_regex = re.compile(glob2re(self.file_pattern))
4050 for filename in files_changed:
4055 for filename in files_changed:
4051 if file_regex.search(filename):
4056 if file_regex.search(filename):
4052 files_matches = True
4057 files_matches = True
4053 break
4058 break
4054
4059
4055 return branch_matches and files_matches
4060 return branch_matches and files_matches
4056
4061
4057 @property
4062 @property
4058 def review_users(self):
4063 def review_users(self):
4059 """ Returns the users which this rule applies to """
4064 """ Returns the users which this rule applies to """
4060
4065
4061 users = collections.OrderedDict()
4066 users = collections.OrderedDict()
4062
4067
4063 for rule_user in self.rule_users:
4068 for rule_user in self.rule_users:
4064 if rule_user.user.active:
4069 if rule_user.user.active:
4065 if rule_user.user not in users:
4070 if rule_user.user not in users:
4066 users[rule_user.user.username] = {
4071 users[rule_user.user.username] = {
4067 'user': rule_user.user,
4072 'user': rule_user.user,
4068 'source': 'user',
4073 'source': 'user',
4069 'source_data': {},
4074 'source_data': {},
4070 'data': rule_user.rule_data()
4075 'data': rule_user.rule_data()
4071 }
4076 }
4072
4077
4073 for rule_user_group in self.rule_user_groups:
4078 for rule_user_group in self.rule_user_groups:
4074 source_data = {
4079 source_data = {
4075 'name': rule_user_group.users_group.users_group_name,
4080 'name': rule_user_group.users_group.users_group_name,
4076 'members': len(rule_user_group.users_group.members)
4081 'members': len(rule_user_group.users_group.members)
4077 }
4082 }
4078 for member in rule_user_group.users_group.members:
4083 for member in rule_user_group.users_group.members:
4079 if member.user.active:
4084 if member.user.active:
4080 users[member.user.username] = {
4085 users[member.user.username] = {
4081 'user': member.user,
4086 'user': member.user,
4082 'source': 'user_group',
4087 'source': 'user_group',
4083 'source_data': source_data,
4088 'source_data': source_data,
4084 'data': rule_user_group.rule_data()
4089 'data': rule_user_group.rule_data()
4085 }
4090 }
4086
4091
4087 return users
4092 return users
4088
4093
4089 def __repr__(self):
4094 def __repr__(self):
4090 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4095 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4091 self.repo_review_rule_id, self.repo)
4096 self.repo_review_rule_id, self.repo)
4092
4097
4093
4098
4094 class DbMigrateVersion(Base, BaseModel):
4099 class DbMigrateVersion(Base, BaseModel):
4095 __tablename__ = 'db_migrate_version'
4100 __tablename__ = 'db_migrate_version'
4096 __table_args__ = (
4101 __table_args__ = (
4097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4098 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4099 )
4104 )
4100 repository_id = Column('repository_id', String(250), primary_key=True)
4105 repository_id = Column('repository_id', String(250), primary_key=True)
4101 repository_path = Column('repository_path', Text)
4106 repository_path = Column('repository_path', Text)
4102 version = Column('version', Integer)
4107 version = Column('version', Integer)
4103
4108
4104
4109
4105 class DbSession(Base, BaseModel):
4110 class DbSession(Base, BaseModel):
4106 __tablename__ = 'db_session'
4111 __tablename__ = 'db_session'
4107 __table_args__ = (
4112 __table_args__ = (
4108 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4113 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4109 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4114 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4110 )
4115 )
4111
4116
4112 def __repr__(self):
4117 def __repr__(self):
4113 return '<DB:DbSession({})>'.format(self.id)
4118 return '<DB:DbSession({})>'.format(self.id)
4114
4119
4115 id = Column('id', Integer())
4120 id = Column('id', Integer())
4116 namespace = Column('namespace', String(255), primary_key=True)
4121 namespace = Column('namespace', String(255), primary_key=True)
4117 accessed = Column('accessed', DateTime, nullable=False)
4122 accessed = Column('accessed', DateTime, nullable=False)
4118 created = Column('created', DateTime, nullable=False)
4123 created = Column('created', DateTime, nullable=False)
4119 data = Column('data', PickleType, nullable=False)
4124 data = Column('data', PickleType, nullable=False)
@@ -1,1028 +1,1029 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime, timedelta
31 import datetime
32
32
33 from pyramid.threadlocal import get_current_request
33 from pyramid.threadlocal import get_current_request
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
38 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.exceptions import AttachedForksError
40 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.hooks_base import log_delete_repository
41 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.utils import make_db_config
42 from rhodecode.lib.utils import make_db_config
43 from rhodecode.lib.utils2 import (
43 from rhodecode.lib.utils2 import (
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 from rhodecode.lib.vcs.backends import get_backend
46 from rhodecode.lib.vcs.backends import get_backend
47 from rhodecode.model import BaseModel
47 from rhodecode.model import BaseModel
48 from rhodecode.model.db import (_hash_key,
48 from rhodecode.model.db import (_hash_key,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 RepoGroup, RepositoryField)
51 RepoGroup, RepositoryField)
52
52
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class RepoModel(BaseModel):
59 class RepoModel(BaseModel):
60
60
61 cls = Repository
61 cls = Repository
62
62
63 def _get_user_group(self, users_group):
63 def _get_user_group(self, users_group):
64 return self._get_instance(UserGroup, users_group,
64 return self._get_instance(UserGroup, users_group,
65 callback=UserGroup.get_by_group_name)
65 callback=UserGroup.get_by_group_name)
66
66
67 def _get_repo_group(self, repo_group):
67 def _get_repo_group(self, repo_group):
68 return self._get_instance(RepoGroup, repo_group,
68 return self._get_instance(RepoGroup, repo_group,
69 callback=RepoGroup.get_by_group_name)
69 callback=RepoGroup.get_by_group_name)
70
70
71 def _create_default_perms(self, repository, private):
71 def _create_default_perms(self, repository, private):
72 # create default permission
72 # create default permission
73 default = 'repository.read'
73 default = 'repository.read'
74 def_user = User.get_default_user()
74 def_user = User.get_default_user()
75 for p in def_user.user_perms:
75 for p in def_user.user_perms:
76 if p.permission.permission_name.startswith('repository.'):
76 if p.permission.permission_name.startswith('repository.'):
77 default = p.permission.permission_name
77 default = p.permission.permission_name
78 break
78 break
79
79
80 default_perm = 'repository.none' if private else default
80 default_perm = 'repository.none' if private else default
81
81
82 repo_to_perm = UserRepoToPerm()
82 repo_to_perm = UserRepoToPerm()
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
83 repo_to_perm.permission = Permission.get_by_key(default_perm)
84
84
85 repo_to_perm.repository = repository
85 repo_to_perm.repository = repository
86 repo_to_perm.user_id = def_user.user_id
86 repo_to_perm.user_id = def_user.user_id
87
87
88 return repo_to_perm
88 return repo_to_perm
89
89
90 @LazyProperty
90 @LazyProperty
91 def repos_path(self):
91 def repos_path(self):
92 """
92 """
93 Gets the repositories root path from database
93 Gets the repositories root path from database
94 """
94 """
95 settings_model = VcsSettingsModel(sa=self.sa)
95 settings_model = VcsSettingsModel(sa=self.sa)
96 return settings_model.get_repos_location()
96 return settings_model.get_repos_location()
97
97
98 def get(self, repo_id, cache=False):
98 def get(self, repo_id, cache=False):
99 repo = self.sa.query(Repository) \
99 repo = self.sa.query(Repository) \
100 .filter(Repository.repo_id == repo_id)
100 .filter(Repository.repo_id == repo_id)
101
101
102 if cache:
102 if cache:
103 repo = repo.options(
103 repo = repo.options(
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
105 return repo.scalar()
105 return repo.scalar()
106
106
107 def get_repo(self, repository):
107 def get_repo(self, repository):
108 return self._get_repo(repository)
108 return self._get_repo(repository)
109
109
110 def get_by_repo_name(self, repo_name, cache=False):
110 def get_by_repo_name(self, repo_name, cache=False):
111 repo = self.sa.query(Repository) \
111 repo = self.sa.query(Repository) \
112 .filter(Repository.repo_name == repo_name)
112 .filter(Repository.repo_name == repo_name)
113
113
114 if cache:
114 if cache:
115 name_key = _hash_key(repo_name)
115 name_key = _hash_key(repo_name)
116 repo = repo.options(
116 repo = repo.options(
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
118 return repo.scalar()
118 return repo.scalar()
119
119
120 def _extract_id_from_repo_name(self, repo_name):
120 def _extract_id_from_repo_name(self, repo_name):
121 if repo_name.startswith('/'):
121 if repo_name.startswith('/'):
122 repo_name = repo_name.lstrip('/')
122 repo_name = repo_name.lstrip('/')
123 by_id_match = re.match(r'^_(\d{1,})', repo_name)
123 by_id_match = re.match(r'^_(\d{1,})', repo_name)
124 if by_id_match:
124 if by_id_match:
125 return by_id_match.groups()[0]
125 return by_id_match.groups()[0]
126
126
127 def get_repo_by_id(self, repo_name):
127 def get_repo_by_id(self, repo_name):
128 """
128 """
129 Extracts repo_name by id from special urls.
129 Extracts repo_name by id from special urls.
130 Example url is _11/repo_name
130 Example url is _11/repo_name
131
131
132 :param repo_name:
132 :param repo_name:
133 :return: repo object if matched else None
133 :return: repo object if matched else None
134 """
134 """
135
135
136 try:
136 try:
137 _repo_id = self._extract_id_from_repo_name(repo_name)
137 _repo_id = self._extract_id_from_repo_name(repo_name)
138 if _repo_id:
138 if _repo_id:
139 return self.get(_repo_id)
139 return self.get(_repo_id)
140 except Exception:
140 except Exception:
141 log.exception('Failed to extract repo_name from URL')
141 log.exception('Failed to extract repo_name from URL')
142
142
143 return None
143 return None
144
144
145 def get_repos_for_root(self, root, traverse=False):
145 def get_repos_for_root(self, root, traverse=False):
146 if traverse:
146 if traverse:
147 like_expression = u'{}%'.format(safe_unicode(root))
147 like_expression = u'{}%'.format(safe_unicode(root))
148 repos = Repository.query().filter(
148 repos = Repository.query().filter(
149 Repository.repo_name.like(like_expression)).all()
149 Repository.repo_name.like(like_expression)).all()
150 else:
150 else:
151 if root and not isinstance(root, RepoGroup):
151 if root and not isinstance(root, RepoGroup):
152 raise ValueError(
152 raise ValueError(
153 'Root must be an instance '
153 'Root must be an instance '
154 'of RepoGroup, got:{} instead'.format(type(root)))
154 'of RepoGroup, got:{} instead'.format(type(root)))
155 repos = Repository.query().filter(Repository.group == root).all()
155 repos = Repository.query().filter(Repository.group == root).all()
156 return repos
156 return repos
157
157
158 def get_url(self, repo, request=None, permalink=False):
158 def get_url(self, repo, request=None, permalink=False):
159 if not request:
159 if not request:
160 request = get_current_request()
160 request = get_current_request()
161
161
162 if not request:
162 if not request:
163 return
163 return
164
164
165 if permalink:
165 if permalink:
166 return request.route_url(
166 return request.route_url(
167 'repo_summary', repo_name=safe_str(repo.repo_id))
167 'repo_summary', repo_name=safe_str(repo.repo_id))
168 else:
168 else:
169 return request.route_url(
169 return request.route_url(
170 'repo_summary', repo_name=safe_str(repo.repo_name))
170 'repo_summary', repo_name=safe_str(repo.repo_name))
171
171
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
173 if not request:
173 if not request:
174 request = get_current_request()
174 request = get_current_request()
175
175
176 if not request:
176 if not request:
177 return
177 return
178
178
179 if permalink:
179 if permalink:
180 return request.route_url(
180 return request.route_url(
181 'repo_commit', repo_name=safe_str(repo.repo_id),
181 'repo_commit', repo_name=safe_str(repo.repo_id),
182 commit_id=commit_id)
182 commit_id=commit_id)
183
183
184 else:
184 else:
185 return request.route_url(
185 return request.route_url(
186 'repo_commit', repo_name=safe_str(repo.repo_name),
186 'repo_commit', repo_name=safe_str(repo.repo_name),
187 commit_id=commit_id)
187 commit_id=commit_id)
188
188
189 @classmethod
189 @classmethod
190 def update_repoinfo(cls, repositories=None):
190 def update_repoinfo(cls, repositories=None):
191 if not repositories:
191 if not repositories:
192 repositories = Repository.getAll()
192 repositories = Repository.getAll()
193 for repo in repositories:
193 for repo in repositories:
194 repo.update_commit_cache()
194 repo.update_commit_cache()
195
195
196 def get_repos_as_dict(self, repo_list=None, admin=False,
196 def get_repos_as_dict(self, repo_list=None, admin=False,
197 super_user_actions=False):
197 super_user_actions=False):
198 _render = get_current_request().get_partial_renderer(
198 _render = get_current_request().get_partial_renderer(
199 'data_table/_dt_elements.mako')
199 'data_table/_dt_elements.mako')
200 c = _render.get_call_context()
200 c = _render.get_call_context()
201
201
202 def quick_menu(repo_name):
202 def quick_menu(repo_name):
203 return _render('quick_menu', repo_name)
203 return _render('quick_menu', repo_name)
204
204
205 def repo_lnk(name, rtype, rstate, private, fork_of):
205 def repo_lnk(name, rtype, rstate, private, fork_of):
206 return _render('repo_name', name, rtype, rstate, private, fork_of,
206 return _render('repo_name', name, rtype, rstate, private, fork_of,
207 short_name=not admin, admin=False)
207 short_name=not admin, admin=False)
208
208
209 def last_change(last_change):
209 def last_change(last_change):
210 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
210 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
211 last_change = last_change + timedelta(seconds=
211 last_change = last_change + datetime.timedelta(seconds=
212 (datetime.now() - datetime.utcnow()).seconds)
212 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
213 return _render("last_change", last_change)
213 return _render("last_change", last_change)
214
214
215 def rss_lnk(repo_name):
215 def rss_lnk(repo_name):
216 return _render("rss", repo_name)
216 return _render("rss", repo_name)
217
217
218 def atom_lnk(repo_name):
218 def atom_lnk(repo_name):
219 return _render("atom", repo_name)
219 return _render("atom", repo_name)
220
220
221 def last_rev(repo_name, cs_cache):
221 def last_rev(repo_name, cs_cache):
222 return _render('revision', repo_name, cs_cache.get('revision'),
222 return _render('revision', repo_name, cs_cache.get('revision'),
223 cs_cache.get('raw_id'), cs_cache.get('author'),
223 cs_cache.get('raw_id'), cs_cache.get('author'),
224 cs_cache.get('message'))
224 cs_cache.get('message'))
225
225
226 def desc(desc):
226 def desc(desc):
227 if c.visual.stylify_metatags:
227 if c.visual.stylify_metatags:
228 desc = h.urlify_text(h.escaped_stylize(desc))
228 desc = h.urlify_text(h.escaped_stylize(desc))
229 else:
229 else:
230 desc = h.urlify_text(h.html_escape(desc))
230 desc = h.urlify_text(h.html_escape(desc))
231
231
232 return _render('repo_desc', desc)
232 return _render('repo_desc', desc)
233
233
234 def state(repo_state):
234 def state(repo_state):
235 return _render("repo_state", repo_state)
235 return _render("repo_state", repo_state)
236
236
237 def repo_actions(repo_name):
237 def repo_actions(repo_name):
238 return _render('repo_actions', repo_name, super_user_actions)
238 return _render('repo_actions', repo_name, super_user_actions)
239
239
240 def user_profile(username):
240 def user_profile(username):
241 return _render('user_profile', username)
241 return _render('user_profile', username)
242
242
243 repos_data = []
243 repos_data = []
244 for repo in repo_list:
244 for repo in repo_list:
245 cs_cache = repo.changeset_cache
245 cs_cache = repo.changeset_cache
246 row = {
246 row = {
247 "menu": quick_menu(repo.repo_name),
247 "menu": quick_menu(repo.repo_name),
248
248
249 "name": repo_lnk(repo.repo_name, repo.repo_type,
249 "name": repo_lnk(repo.repo_name, repo.repo_type,
250 repo.repo_state, repo.private, repo.fork),
250 repo.repo_state, repo.private, repo.fork),
251 "name_raw": repo.repo_name.lower(),
251 "name_raw": repo.repo_name.lower(),
252
252
253 "last_change": last_change(repo.last_db_change),
253 "last_change": last_change(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
254 "last_change_raw": datetime_to_time(repo.last_db_change),
255
255
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
256 "last_changeset": last_rev(repo.repo_name, cs_cache),
257 "last_changeset_raw": cs_cache.get('revision'),
257 "last_changeset_raw": cs_cache.get('revision'),
258
258
259 "desc": desc(repo.description_safe),
259 "desc": desc(repo.description_safe),
260 "owner": user_profile(repo.user.username),
260 "owner": user_profile(repo.user.username),
261
261
262 "state": state(repo.repo_state),
262 "state": state(repo.repo_state),
263 "rss": rss_lnk(repo.repo_name),
263 "rss": rss_lnk(repo.repo_name),
264
264
265 "atom": atom_lnk(repo.repo_name),
265 "atom": atom_lnk(repo.repo_name),
266 }
266 }
267 if admin:
267 if admin:
268 row.update({
268 row.update({
269 "action": repo_actions(repo.repo_name),
269 "action": repo_actions(repo.repo_name),
270 })
270 })
271 repos_data.append(row)
271 repos_data.append(row)
272
272
273 return repos_data
273 return repos_data
274
274
275 def _get_defaults(self, repo_name):
275 def _get_defaults(self, repo_name):
276 """
276 """
277 Gets information about repository, and returns a dict for
277 Gets information about repository, and returns a dict for
278 usage in forms
278 usage in forms
279
279
280 :param repo_name:
280 :param repo_name:
281 """
281 """
282
282
283 repo_info = Repository.get_by_repo_name(repo_name)
283 repo_info = Repository.get_by_repo_name(repo_name)
284
284
285 if repo_info is None:
285 if repo_info is None:
286 return None
286 return None
287
287
288 defaults = repo_info.get_dict()
288 defaults = repo_info.get_dict()
289 defaults['repo_name'] = repo_info.just_name
289 defaults['repo_name'] = repo_info.just_name
290
290
291 groups = repo_info.groups_with_parents
291 groups = repo_info.groups_with_parents
292 parent_group = groups[-1] if groups else None
292 parent_group = groups[-1] if groups else None
293
293
294 # we use -1 as this is how in HTML, we mark an empty group
294 # we use -1 as this is how in HTML, we mark an empty group
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
296
296
297 keys_to_process = (
297 keys_to_process = (
298 {'k': 'repo_type', 'strip': False},
298 {'k': 'repo_type', 'strip': False},
299 {'k': 'repo_enable_downloads', 'strip': True},
299 {'k': 'repo_enable_downloads', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
300 {'k': 'repo_description', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
301 {'k': 'repo_enable_locking', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
302 {'k': 'repo_landing_rev', 'strip': True},
303 {'k': 'clone_uri', 'strip': False},
303 {'k': 'clone_uri', 'strip': False},
304 {'k': 'repo_private', 'strip': True},
304 {'k': 'repo_private', 'strip': True},
305 {'k': 'repo_enable_statistics', 'strip': True}
305 {'k': 'repo_enable_statistics', 'strip': True}
306 )
306 )
307
307
308 for item in keys_to_process:
308 for item in keys_to_process:
309 attr = item['k']
309 attr = item['k']
310 if item['strip']:
310 if item['strip']:
311 attr = remove_prefix(item['k'], 'repo_')
311 attr = remove_prefix(item['k'], 'repo_')
312
312
313 val = defaults[attr]
313 val = defaults[attr]
314 if item['k'] == 'repo_landing_rev':
314 if item['k'] == 'repo_landing_rev':
315 val = ':'.join(defaults[attr])
315 val = ':'.join(defaults[attr])
316 defaults[item['k']] = val
316 defaults[item['k']] = val
317 if item['k'] == 'clone_uri':
317 if item['k'] == 'clone_uri':
318 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
318 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
319
319
320 # fill owner
320 # fill owner
321 if repo_info.user:
321 if repo_info.user:
322 defaults.update({'user': repo_info.user.username})
322 defaults.update({'user': repo_info.user.username})
323 else:
323 else:
324 replacement_user = User.get_first_super_admin().username
324 replacement_user = User.get_first_super_admin().username
325 defaults.update({'user': replacement_user})
325 defaults.update({'user': replacement_user})
326
326
327 return defaults
327 return defaults
328
328
329 def update(self, repo, **kwargs):
329 def update(self, repo, **kwargs):
330 try:
330 try:
331 cur_repo = self._get_repo(repo)
331 cur_repo = self._get_repo(repo)
332 source_repo_name = cur_repo.repo_name
332 source_repo_name = cur_repo.repo_name
333 if 'user' in kwargs:
333 if 'user' in kwargs:
334 cur_repo.user = User.get_by_username(kwargs['user'])
334 cur_repo.user = User.get_by_username(kwargs['user'])
335
335
336 if 'repo_group' in kwargs:
336 if 'repo_group' in kwargs:
337 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
337 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
338 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
338 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
339
339
340 update_keys = [
340 update_keys = [
341 (1, 'repo_description'),
341 (1, 'repo_description'),
342 (1, 'repo_landing_rev'),
342 (1, 'repo_landing_rev'),
343 (1, 'repo_private'),
343 (1, 'repo_private'),
344 (1, 'repo_enable_downloads'),
344 (1, 'repo_enable_downloads'),
345 (1, 'repo_enable_locking'),
345 (1, 'repo_enable_locking'),
346 (1, 'repo_enable_statistics'),
346 (1, 'repo_enable_statistics'),
347 (0, 'clone_uri'),
347 (0, 'clone_uri'),
348 (0, 'fork_id')
348 (0, 'fork_id')
349 ]
349 ]
350 for strip, k in update_keys:
350 for strip, k in update_keys:
351 if k in kwargs:
351 if k in kwargs:
352 val = kwargs[k]
352 val = kwargs[k]
353 if strip:
353 if strip:
354 k = remove_prefix(k, 'repo_')
354 k = remove_prefix(k, 'repo_')
355
355
356 setattr(cur_repo, k, val)
356 setattr(cur_repo, k, val)
357
357
358 new_name = cur_repo.get_new_name(kwargs['repo_name'])
358 new_name = cur_repo.get_new_name(kwargs['repo_name'])
359 cur_repo.repo_name = new_name
359 cur_repo.repo_name = new_name
360
360
361 # if private flag is set, reset default permission to NONE
361 # if private flag is set, reset default permission to NONE
362 if kwargs.get('repo_private'):
362 if kwargs.get('repo_private'):
363 EMPTY_PERM = 'repository.none'
363 EMPTY_PERM = 'repository.none'
364 RepoModel().grant_user_permission(
364 RepoModel().grant_user_permission(
365 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
365 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
366 )
366 )
367
367
368 # handle extra fields
368 # handle extra fields
369 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
369 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
370 kwargs):
370 kwargs):
371 k = RepositoryField.un_prefix_key(field)
371 k = RepositoryField.un_prefix_key(field)
372 ex_field = RepositoryField.get_by_key_name(
372 ex_field = RepositoryField.get_by_key_name(
373 key=k, repo=cur_repo)
373 key=k, repo=cur_repo)
374 if ex_field:
374 if ex_field:
375 ex_field.field_value = kwargs[field]
375 ex_field.field_value = kwargs[field]
376 self.sa.add(ex_field)
376 self.sa.add(ex_field)
377 cur_repo.updated_on = datetime.datetime.now()
377 self.sa.add(cur_repo)
378 self.sa.add(cur_repo)
378
379
379 if source_repo_name != new_name:
380 if source_repo_name != new_name:
380 # rename repository
381 # rename repository
381 self._rename_filesystem_repo(
382 self._rename_filesystem_repo(
382 old=source_repo_name, new=new_name)
383 old=source_repo_name, new=new_name)
383
384
384 return cur_repo
385 return cur_repo
385 except Exception:
386 except Exception:
386 log.error(traceback.format_exc())
387 log.error(traceback.format_exc())
387 raise
388 raise
388
389
389 def _create_repo(self, repo_name, repo_type, description, owner,
390 def _create_repo(self, repo_name, repo_type, description, owner,
390 private=False, clone_uri=None, repo_group=None,
391 private=False, clone_uri=None, repo_group=None,
391 landing_rev='rev:tip', fork_of=None,
392 landing_rev='rev:tip', fork_of=None,
392 copy_fork_permissions=False, enable_statistics=False,
393 copy_fork_permissions=False, enable_statistics=False,
393 enable_locking=False, enable_downloads=False,
394 enable_locking=False, enable_downloads=False,
394 copy_group_permissions=False,
395 copy_group_permissions=False,
395 state=Repository.STATE_PENDING):
396 state=Repository.STATE_PENDING):
396 """
397 """
397 Create repository inside database with PENDING state, this should be
398 Create repository inside database with PENDING state, this should be
398 only executed by create() repo. With exception of importing existing
399 only executed by create() repo. With exception of importing existing
399 repos
400 repos
400 """
401 """
401 from rhodecode.model.scm import ScmModel
402 from rhodecode.model.scm import ScmModel
402
403
403 owner = self._get_user(owner)
404 owner = self._get_user(owner)
404 fork_of = self._get_repo(fork_of)
405 fork_of = self._get_repo(fork_of)
405 repo_group = self._get_repo_group(safe_int(repo_group))
406 repo_group = self._get_repo_group(safe_int(repo_group))
406
407
407 try:
408 try:
408 repo_name = safe_unicode(repo_name)
409 repo_name = safe_unicode(repo_name)
409 description = safe_unicode(description)
410 description = safe_unicode(description)
410 # repo name is just a name of repository
411 # repo name is just a name of repository
411 # while repo_name_full is a full qualified name that is combined
412 # while repo_name_full is a full qualified name that is combined
412 # with name and path of group
413 # with name and path of group
413 repo_name_full = repo_name
414 repo_name_full = repo_name
414 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
415 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
415
416
416 new_repo = Repository()
417 new_repo = Repository()
417 new_repo.repo_state = state
418 new_repo.repo_state = state
418 new_repo.enable_statistics = False
419 new_repo.enable_statistics = False
419 new_repo.repo_name = repo_name_full
420 new_repo.repo_name = repo_name_full
420 new_repo.repo_type = repo_type
421 new_repo.repo_type = repo_type
421 new_repo.user = owner
422 new_repo.user = owner
422 new_repo.group = repo_group
423 new_repo.group = repo_group
423 new_repo.description = description or repo_name
424 new_repo.description = description or repo_name
424 new_repo.private = private
425 new_repo.private = private
425 new_repo.clone_uri = clone_uri
426 new_repo.clone_uri = clone_uri
426 new_repo.landing_rev = landing_rev
427 new_repo.landing_rev = landing_rev
427
428
428 new_repo.enable_statistics = enable_statistics
429 new_repo.enable_statistics = enable_statistics
429 new_repo.enable_locking = enable_locking
430 new_repo.enable_locking = enable_locking
430 new_repo.enable_downloads = enable_downloads
431 new_repo.enable_downloads = enable_downloads
431
432
432 if repo_group:
433 if repo_group:
433 new_repo.enable_locking = repo_group.enable_locking
434 new_repo.enable_locking = repo_group.enable_locking
434
435
435 if fork_of:
436 if fork_of:
436 parent_repo = fork_of
437 parent_repo = fork_of
437 new_repo.fork = parent_repo
438 new_repo.fork = parent_repo
438
439
439 events.trigger(events.RepoPreCreateEvent(new_repo))
440 events.trigger(events.RepoPreCreateEvent(new_repo))
440
441
441 self.sa.add(new_repo)
442 self.sa.add(new_repo)
442
443
443 EMPTY_PERM = 'repository.none'
444 EMPTY_PERM = 'repository.none'
444 if fork_of and copy_fork_permissions:
445 if fork_of and copy_fork_permissions:
445 repo = fork_of
446 repo = fork_of
446 user_perms = UserRepoToPerm.query() \
447 user_perms = UserRepoToPerm.query() \
447 .filter(UserRepoToPerm.repository == repo).all()
448 .filter(UserRepoToPerm.repository == repo).all()
448 group_perms = UserGroupRepoToPerm.query() \
449 group_perms = UserGroupRepoToPerm.query() \
449 .filter(UserGroupRepoToPerm.repository == repo).all()
450 .filter(UserGroupRepoToPerm.repository == repo).all()
450
451
451 for perm in user_perms:
452 for perm in user_perms:
452 UserRepoToPerm.create(
453 UserRepoToPerm.create(
453 perm.user, new_repo, perm.permission)
454 perm.user, new_repo, perm.permission)
454
455
455 for perm in group_perms:
456 for perm in group_perms:
456 UserGroupRepoToPerm.create(
457 UserGroupRepoToPerm.create(
457 perm.users_group, new_repo, perm.permission)
458 perm.users_group, new_repo, perm.permission)
458 # in case we copy permissions and also set this repo to private
459 # in case we copy permissions and also set this repo to private
459 # override the default user permission to make it a private
460 # override the default user permission to make it a private
460 # repo
461 # repo
461 if private:
462 if private:
462 RepoModel(self.sa).grant_user_permission(
463 RepoModel(self.sa).grant_user_permission(
463 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
464 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
464
465
465 elif repo_group and copy_group_permissions:
466 elif repo_group and copy_group_permissions:
466 user_perms = UserRepoGroupToPerm.query() \
467 user_perms = UserRepoGroupToPerm.query() \
467 .filter(UserRepoGroupToPerm.group == repo_group).all()
468 .filter(UserRepoGroupToPerm.group == repo_group).all()
468
469
469 group_perms = UserGroupRepoGroupToPerm.query() \
470 group_perms = UserGroupRepoGroupToPerm.query() \
470 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
471 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
471
472
472 for perm in user_perms:
473 for perm in user_perms:
473 perm_name = perm.permission.permission_name.replace(
474 perm_name = perm.permission.permission_name.replace(
474 'group.', 'repository.')
475 'group.', 'repository.')
475 perm_obj = Permission.get_by_key(perm_name)
476 perm_obj = Permission.get_by_key(perm_name)
476 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
477 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
477
478
478 for perm in group_perms:
479 for perm in group_perms:
479 perm_name = perm.permission.permission_name.replace(
480 perm_name = perm.permission.permission_name.replace(
480 'group.', 'repository.')
481 'group.', 'repository.')
481 perm_obj = Permission.get_by_key(perm_name)
482 perm_obj = Permission.get_by_key(perm_name)
482 UserGroupRepoToPerm.create(
483 UserGroupRepoToPerm.create(
483 perm.users_group, new_repo, perm_obj)
484 perm.users_group, new_repo, perm_obj)
484
485
485 if private:
486 if private:
486 RepoModel(self.sa).grant_user_permission(
487 RepoModel(self.sa).grant_user_permission(
487 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
488 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
488
489
489 else:
490 else:
490 perm_obj = self._create_default_perms(new_repo, private)
491 perm_obj = self._create_default_perms(new_repo, private)
491 self.sa.add(perm_obj)
492 self.sa.add(perm_obj)
492
493
493 # now automatically start following this repository as owner
494 # now automatically start following this repository as owner
494 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
495 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
495 owner.user_id)
496 owner.user_id)
496
497
497 # we need to flush here, in order to check if database won't
498 # we need to flush here, in order to check if database won't
498 # throw any exceptions, create filesystem dirs at the very end
499 # throw any exceptions, create filesystem dirs at the very end
499 self.sa.flush()
500 self.sa.flush()
500 events.trigger(events.RepoCreateEvent(new_repo))
501 events.trigger(events.RepoCreateEvent(new_repo))
501 return new_repo
502 return new_repo
502
503
503 except Exception:
504 except Exception:
504 log.error(traceback.format_exc())
505 log.error(traceback.format_exc())
505 raise
506 raise
506
507
507 def create(self, form_data, cur_user):
508 def create(self, form_data, cur_user):
508 """
509 """
509 Create repository using celery tasks
510 Create repository using celery tasks
510
511
511 :param form_data:
512 :param form_data:
512 :param cur_user:
513 :param cur_user:
513 """
514 """
514 from rhodecode.lib.celerylib import tasks, run_task
515 from rhodecode.lib.celerylib import tasks, run_task
515 return run_task(tasks.create_repo, form_data, cur_user)
516 return run_task(tasks.create_repo, form_data, cur_user)
516
517
517 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
518 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
518 perm_deletions=None, check_perms=True,
519 perm_deletions=None, check_perms=True,
519 cur_user=None):
520 cur_user=None):
520 if not perm_additions:
521 if not perm_additions:
521 perm_additions = []
522 perm_additions = []
522 if not perm_updates:
523 if not perm_updates:
523 perm_updates = []
524 perm_updates = []
524 if not perm_deletions:
525 if not perm_deletions:
525 perm_deletions = []
526 perm_deletions = []
526
527
527 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
528 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
528
529
529 changes = {
530 changes = {
530 'added': [],
531 'added': [],
531 'updated': [],
532 'updated': [],
532 'deleted': []
533 'deleted': []
533 }
534 }
534 # update permissions
535 # update permissions
535 for member_id, perm, member_type in perm_updates:
536 for member_id, perm, member_type in perm_updates:
536 member_id = int(member_id)
537 member_id = int(member_id)
537 if member_type == 'user':
538 if member_type == 'user':
538 member_name = User.get(member_id).username
539 member_name = User.get(member_id).username
539 # this updates also current one if found
540 # this updates also current one if found
540 self.grant_user_permission(
541 self.grant_user_permission(
541 repo=repo, user=member_id, perm=perm)
542 repo=repo, user=member_id, perm=perm)
542 else: # set for user group
543 else: # set for user group
543 # check if we have permissions to alter this usergroup
544 # check if we have permissions to alter this usergroup
544 member_name = UserGroup.get(member_id).users_group_name
545 member_name = UserGroup.get(member_id).users_group_name
545 if not check_perms or HasUserGroupPermissionAny(
546 if not check_perms or HasUserGroupPermissionAny(
546 *req_perms)(member_name, user=cur_user):
547 *req_perms)(member_name, user=cur_user):
547 self.grant_user_group_permission(
548 self.grant_user_group_permission(
548 repo=repo, group_name=member_id, perm=perm)
549 repo=repo, group_name=member_id, perm=perm)
549
550
550 changes['updated'].append({'type': member_type, 'id': member_id,
551 changes['updated'].append({'type': member_type, 'id': member_id,
551 'name': member_name, 'new_perm': perm})
552 'name': member_name, 'new_perm': perm})
552
553
553 # set new permissions
554 # set new permissions
554 for member_id, perm, member_type in perm_additions:
555 for member_id, perm, member_type in perm_additions:
555 member_id = int(member_id)
556 member_id = int(member_id)
556 if member_type == 'user':
557 if member_type == 'user':
557 member_name = User.get(member_id).username
558 member_name = User.get(member_id).username
558 self.grant_user_permission(
559 self.grant_user_permission(
559 repo=repo, user=member_id, perm=perm)
560 repo=repo, user=member_id, perm=perm)
560 else: # set for user group
561 else: # set for user group
561 # check if we have permissions to alter this usergroup
562 # check if we have permissions to alter this usergroup
562 member_name = UserGroup.get(member_id).users_group_name
563 member_name = UserGroup.get(member_id).users_group_name
563 if not check_perms or HasUserGroupPermissionAny(
564 if not check_perms or HasUserGroupPermissionAny(
564 *req_perms)(member_name, user=cur_user):
565 *req_perms)(member_name, user=cur_user):
565 self.grant_user_group_permission(
566 self.grant_user_group_permission(
566 repo=repo, group_name=member_id, perm=perm)
567 repo=repo, group_name=member_id, perm=perm)
567 changes['added'].append({'type': member_type, 'id': member_id,
568 changes['added'].append({'type': member_type, 'id': member_id,
568 'name': member_name, 'new_perm': perm})
569 'name': member_name, 'new_perm': perm})
569 # delete permissions
570 # delete permissions
570 for member_id, perm, member_type in perm_deletions:
571 for member_id, perm, member_type in perm_deletions:
571 member_id = int(member_id)
572 member_id = int(member_id)
572 if member_type == 'user':
573 if member_type == 'user':
573 member_name = User.get(member_id).username
574 member_name = User.get(member_id).username
574 self.revoke_user_permission(repo=repo, user=member_id)
575 self.revoke_user_permission(repo=repo, user=member_id)
575 else: # set for user group
576 else: # set for user group
576 # check if we have permissions to alter this usergroup
577 # check if we have permissions to alter this usergroup
577 member_name = UserGroup.get(member_id).users_group_name
578 member_name = UserGroup.get(member_id).users_group_name
578 if not check_perms or HasUserGroupPermissionAny(
579 if not check_perms or HasUserGroupPermissionAny(
579 *req_perms)(member_name, user=cur_user):
580 *req_perms)(member_name, user=cur_user):
580 self.revoke_user_group_permission(
581 self.revoke_user_group_permission(
581 repo=repo, group_name=member_id)
582 repo=repo, group_name=member_id)
582
583
583 changes['deleted'].append({'type': member_type, 'id': member_id,
584 changes['deleted'].append({'type': member_type, 'id': member_id,
584 'name': member_name, 'new_perm': perm})
585 'name': member_name, 'new_perm': perm})
585 return changes
586 return changes
586
587
587 def create_fork(self, form_data, cur_user):
588 def create_fork(self, form_data, cur_user):
588 """
589 """
589 Simple wrapper into executing celery task for fork creation
590 Simple wrapper into executing celery task for fork creation
590
591
591 :param form_data:
592 :param form_data:
592 :param cur_user:
593 :param cur_user:
593 """
594 """
594 from rhodecode.lib.celerylib import tasks, run_task
595 from rhodecode.lib.celerylib import tasks, run_task
595 return run_task(tasks.create_repo_fork, form_data, cur_user)
596 return run_task(tasks.create_repo_fork, form_data, cur_user)
596
597
597 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
598 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
598 """
599 """
599 Delete given repository, forks parameter defines what do do with
600 Delete given repository, forks parameter defines what do do with
600 attached forks. Throws AttachedForksError if deleted repo has attached
601 attached forks. Throws AttachedForksError if deleted repo has attached
601 forks
602 forks
602
603
603 :param repo:
604 :param repo:
604 :param forks: str 'delete' or 'detach'
605 :param forks: str 'delete' or 'detach'
605 :param fs_remove: remove(archive) repo from filesystem
606 :param fs_remove: remove(archive) repo from filesystem
606 """
607 """
607 if not cur_user:
608 if not cur_user:
608 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
609 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
609 repo = self._get_repo(repo)
610 repo = self._get_repo(repo)
610 if repo:
611 if repo:
611 if forks == 'detach':
612 if forks == 'detach':
612 for r in repo.forks:
613 for r in repo.forks:
613 r.fork = None
614 r.fork = None
614 self.sa.add(r)
615 self.sa.add(r)
615 elif forks == 'delete':
616 elif forks == 'delete':
616 for r in repo.forks:
617 for r in repo.forks:
617 self.delete(r, forks='delete')
618 self.delete(r, forks='delete')
618 elif [f for f in repo.forks]:
619 elif [f for f in repo.forks]:
619 raise AttachedForksError()
620 raise AttachedForksError()
620
621
621 old_repo_dict = repo.get_dict()
622 old_repo_dict = repo.get_dict()
622 events.trigger(events.RepoPreDeleteEvent(repo))
623 events.trigger(events.RepoPreDeleteEvent(repo))
623 try:
624 try:
624 self.sa.delete(repo)
625 self.sa.delete(repo)
625 if fs_remove:
626 if fs_remove:
626 self._delete_filesystem_repo(repo)
627 self._delete_filesystem_repo(repo)
627 else:
628 else:
628 log.debug('skipping removal from filesystem')
629 log.debug('skipping removal from filesystem')
629 old_repo_dict.update({
630 old_repo_dict.update({
630 'deleted_by': cur_user,
631 'deleted_by': cur_user,
631 'deleted_on': time.time(),
632 'deleted_on': time.time(),
632 })
633 })
633 log_delete_repository(**old_repo_dict)
634 log_delete_repository(**old_repo_dict)
634 events.trigger(events.RepoDeleteEvent(repo))
635 events.trigger(events.RepoDeleteEvent(repo))
635 except Exception:
636 except Exception:
636 log.error(traceback.format_exc())
637 log.error(traceback.format_exc())
637 raise
638 raise
638
639
639 def grant_user_permission(self, repo, user, perm):
640 def grant_user_permission(self, repo, user, perm):
640 """
641 """
641 Grant permission for user on given repository, or update existing one
642 Grant permission for user on given repository, or update existing one
642 if found
643 if found
643
644
644 :param repo: Instance of Repository, repository_id, or repository name
645 :param repo: Instance of Repository, repository_id, or repository name
645 :param user: Instance of User, user_id or username
646 :param user: Instance of User, user_id or username
646 :param perm: Instance of Permission, or permission_name
647 :param perm: Instance of Permission, or permission_name
647 """
648 """
648 user = self._get_user(user)
649 user = self._get_user(user)
649 repo = self._get_repo(repo)
650 repo = self._get_repo(repo)
650 permission = self._get_perm(perm)
651 permission = self._get_perm(perm)
651
652
652 # check if we have that permission already
653 # check if we have that permission already
653 obj = self.sa.query(UserRepoToPerm) \
654 obj = self.sa.query(UserRepoToPerm) \
654 .filter(UserRepoToPerm.user == user) \
655 .filter(UserRepoToPerm.user == user) \
655 .filter(UserRepoToPerm.repository == repo) \
656 .filter(UserRepoToPerm.repository == repo) \
656 .scalar()
657 .scalar()
657 if obj is None:
658 if obj is None:
658 # create new !
659 # create new !
659 obj = UserRepoToPerm()
660 obj = UserRepoToPerm()
660 obj.repository = repo
661 obj.repository = repo
661 obj.user = user
662 obj.user = user
662 obj.permission = permission
663 obj.permission = permission
663 self.sa.add(obj)
664 self.sa.add(obj)
664 log.debug('Granted perm %s to %s on %s', perm, user, repo)
665 log.debug('Granted perm %s to %s on %s', perm, user, repo)
665 action_logger_generic(
666 action_logger_generic(
666 'granted permission: {} to user: {} on repo: {}'.format(
667 'granted permission: {} to user: {} on repo: {}'.format(
667 perm, user, repo), namespace='security.repo')
668 perm, user, repo), namespace='security.repo')
668 return obj
669 return obj
669
670
670 def revoke_user_permission(self, repo, user):
671 def revoke_user_permission(self, repo, user):
671 """
672 """
672 Revoke permission for user on given repository
673 Revoke permission for user on given repository
673
674
674 :param repo: Instance of Repository, repository_id, or repository name
675 :param repo: Instance of Repository, repository_id, or repository name
675 :param user: Instance of User, user_id or username
676 :param user: Instance of User, user_id or username
676 """
677 """
677
678
678 user = self._get_user(user)
679 user = self._get_user(user)
679 repo = self._get_repo(repo)
680 repo = self._get_repo(repo)
680
681
681 obj = self.sa.query(UserRepoToPerm) \
682 obj = self.sa.query(UserRepoToPerm) \
682 .filter(UserRepoToPerm.repository == repo) \
683 .filter(UserRepoToPerm.repository == repo) \
683 .filter(UserRepoToPerm.user == user) \
684 .filter(UserRepoToPerm.user == user) \
684 .scalar()
685 .scalar()
685 if obj:
686 if obj:
686 self.sa.delete(obj)
687 self.sa.delete(obj)
687 log.debug('Revoked perm on %s on %s', repo, user)
688 log.debug('Revoked perm on %s on %s', repo, user)
688 action_logger_generic(
689 action_logger_generic(
689 'revoked permission from user: {} on repo: {}'.format(
690 'revoked permission from user: {} on repo: {}'.format(
690 user, repo), namespace='security.repo')
691 user, repo), namespace='security.repo')
691
692
692 def grant_user_group_permission(self, repo, group_name, perm):
693 def grant_user_group_permission(self, repo, group_name, perm):
693 """
694 """
694 Grant permission for user group on given repository, or update
695 Grant permission for user group on given repository, or update
695 existing one if found
696 existing one if found
696
697
697 :param repo: Instance of Repository, repository_id, or repository name
698 :param repo: Instance of Repository, repository_id, or repository name
698 :param group_name: Instance of UserGroup, users_group_id,
699 :param group_name: Instance of UserGroup, users_group_id,
699 or user group name
700 or user group name
700 :param perm: Instance of Permission, or permission_name
701 :param perm: Instance of Permission, or permission_name
701 """
702 """
702 repo = self._get_repo(repo)
703 repo = self._get_repo(repo)
703 group_name = self._get_user_group(group_name)
704 group_name = self._get_user_group(group_name)
704 permission = self._get_perm(perm)
705 permission = self._get_perm(perm)
705
706
706 # check if we have that permission already
707 # check if we have that permission already
707 obj = self.sa.query(UserGroupRepoToPerm) \
708 obj = self.sa.query(UserGroupRepoToPerm) \
708 .filter(UserGroupRepoToPerm.users_group == group_name) \
709 .filter(UserGroupRepoToPerm.users_group == group_name) \
709 .filter(UserGroupRepoToPerm.repository == repo) \
710 .filter(UserGroupRepoToPerm.repository == repo) \
710 .scalar()
711 .scalar()
711
712
712 if obj is None:
713 if obj is None:
713 # create new
714 # create new
714 obj = UserGroupRepoToPerm()
715 obj = UserGroupRepoToPerm()
715
716
716 obj.repository = repo
717 obj.repository = repo
717 obj.users_group = group_name
718 obj.users_group = group_name
718 obj.permission = permission
719 obj.permission = permission
719 self.sa.add(obj)
720 self.sa.add(obj)
720 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
721 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
721 action_logger_generic(
722 action_logger_generic(
722 'granted permission: {} to usergroup: {} on repo: {}'.format(
723 'granted permission: {} to usergroup: {} on repo: {}'.format(
723 perm, group_name, repo), namespace='security.repo')
724 perm, group_name, repo), namespace='security.repo')
724
725
725 return obj
726 return obj
726
727
727 def revoke_user_group_permission(self, repo, group_name):
728 def revoke_user_group_permission(self, repo, group_name):
728 """
729 """
729 Revoke permission for user group on given repository
730 Revoke permission for user group on given repository
730
731
731 :param repo: Instance of Repository, repository_id, or repository name
732 :param repo: Instance of Repository, repository_id, or repository name
732 :param group_name: Instance of UserGroup, users_group_id,
733 :param group_name: Instance of UserGroup, users_group_id,
733 or user group name
734 or user group name
734 """
735 """
735 repo = self._get_repo(repo)
736 repo = self._get_repo(repo)
736 group_name = self._get_user_group(group_name)
737 group_name = self._get_user_group(group_name)
737
738
738 obj = self.sa.query(UserGroupRepoToPerm) \
739 obj = self.sa.query(UserGroupRepoToPerm) \
739 .filter(UserGroupRepoToPerm.repository == repo) \
740 .filter(UserGroupRepoToPerm.repository == repo) \
740 .filter(UserGroupRepoToPerm.users_group == group_name) \
741 .filter(UserGroupRepoToPerm.users_group == group_name) \
741 .scalar()
742 .scalar()
742 if obj:
743 if obj:
743 self.sa.delete(obj)
744 self.sa.delete(obj)
744 log.debug('Revoked perm to %s on %s', repo, group_name)
745 log.debug('Revoked perm to %s on %s', repo, group_name)
745 action_logger_generic(
746 action_logger_generic(
746 'revoked permission from usergroup: {} on repo: {}'.format(
747 'revoked permission from usergroup: {} on repo: {}'.format(
747 group_name, repo), namespace='security.repo')
748 group_name, repo), namespace='security.repo')
748
749
749 def delete_stats(self, repo_name):
750 def delete_stats(self, repo_name):
750 """
751 """
751 removes stats for given repo
752 removes stats for given repo
752
753
753 :param repo_name:
754 :param repo_name:
754 """
755 """
755 repo = self._get_repo(repo_name)
756 repo = self._get_repo(repo_name)
756 try:
757 try:
757 obj = self.sa.query(Statistics) \
758 obj = self.sa.query(Statistics) \
758 .filter(Statistics.repository == repo).scalar()
759 .filter(Statistics.repository == repo).scalar()
759 if obj:
760 if obj:
760 self.sa.delete(obj)
761 self.sa.delete(obj)
761 except Exception:
762 except Exception:
762 log.error(traceback.format_exc())
763 log.error(traceback.format_exc())
763 raise
764 raise
764
765
765 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
766 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
766 field_type='str', field_desc=''):
767 field_type='str', field_desc=''):
767
768
768 repo = self._get_repo(repo_name)
769 repo = self._get_repo(repo_name)
769
770
770 new_field = RepositoryField()
771 new_field = RepositoryField()
771 new_field.repository = repo
772 new_field.repository = repo
772 new_field.field_key = field_key
773 new_field.field_key = field_key
773 new_field.field_type = field_type # python type
774 new_field.field_type = field_type # python type
774 new_field.field_value = field_value
775 new_field.field_value = field_value
775 new_field.field_desc = field_desc
776 new_field.field_desc = field_desc
776 new_field.field_label = field_label
777 new_field.field_label = field_label
777 self.sa.add(new_field)
778 self.sa.add(new_field)
778 return new_field
779 return new_field
779
780
780 def delete_repo_field(self, repo_name, field_key):
781 def delete_repo_field(self, repo_name, field_key):
781 repo = self._get_repo(repo_name)
782 repo = self._get_repo(repo_name)
782 field = RepositoryField.get_by_key_name(field_key, repo)
783 field = RepositoryField.get_by_key_name(field_key, repo)
783 if field:
784 if field:
784 self.sa.delete(field)
785 self.sa.delete(field)
785
786
786 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
787 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
787 clone_uri=None, repo_store_location=None,
788 clone_uri=None, repo_store_location=None,
788 use_global_config=False):
789 use_global_config=False):
789 """
790 """
790 makes repository on filesystem. It's group aware means it'll create
791 makes repository on filesystem. It's group aware means it'll create
791 a repository within a group, and alter the paths accordingly of
792 a repository within a group, and alter the paths accordingly of
792 group location
793 group location
793
794
794 :param repo_name:
795 :param repo_name:
795 :param alias:
796 :param alias:
796 :param parent:
797 :param parent:
797 :param clone_uri:
798 :param clone_uri:
798 :param repo_store_location:
799 :param repo_store_location:
799 """
800 """
800 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
801 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
801 from rhodecode.model.scm import ScmModel
802 from rhodecode.model.scm import ScmModel
802
803
803 if Repository.NAME_SEP in repo_name:
804 if Repository.NAME_SEP in repo_name:
804 raise ValueError(
805 raise ValueError(
805 'repo_name must not contain groups got `%s`' % repo_name)
806 'repo_name must not contain groups got `%s`' % repo_name)
806
807
807 if isinstance(repo_group, RepoGroup):
808 if isinstance(repo_group, RepoGroup):
808 new_parent_path = os.sep.join(repo_group.full_path_splitted)
809 new_parent_path = os.sep.join(repo_group.full_path_splitted)
809 else:
810 else:
810 new_parent_path = repo_group or ''
811 new_parent_path = repo_group or ''
811
812
812 if repo_store_location:
813 if repo_store_location:
813 _paths = [repo_store_location]
814 _paths = [repo_store_location]
814 else:
815 else:
815 _paths = [self.repos_path, new_parent_path, repo_name]
816 _paths = [self.repos_path, new_parent_path, repo_name]
816 # we need to make it str for mercurial
817 # we need to make it str for mercurial
817 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
818 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
818
819
819 # check if this path is not a repository
820 # check if this path is not a repository
820 if is_valid_repo(repo_path, self.repos_path):
821 if is_valid_repo(repo_path, self.repos_path):
821 raise Exception('This path %s is a valid repository' % repo_path)
822 raise Exception('This path %s is a valid repository' % repo_path)
822
823
823 # check if this path is a group
824 # check if this path is a group
824 if is_valid_repo_group(repo_path, self.repos_path):
825 if is_valid_repo_group(repo_path, self.repos_path):
825 raise Exception('This path %s is a valid group' % repo_path)
826 raise Exception('This path %s is a valid group' % repo_path)
826
827
827 log.info('creating repo %s in %s from url: `%s`',
828 log.info('creating repo %s in %s from url: `%s`',
828 repo_name, safe_unicode(repo_path),
829 repo_name, safe_unicode(repo_path),
829 obfuscate_url_pw(clone_uri))
830 obfuscate_url_pw(clone_uri))
830
831
831 backend = get_backend(repo_type)
832 backend = get_backend(repo_type)
832
833
833 config_repo = None if use_global_config else repo_name
834 config_repo = None if use_global_config else repo_name
834 if config_repo and new_parent_path:
835 if config_repo and new_parent_path:
835 config_repo = Repository.NAME_SEP.join(
836 config_repo = Repository.NAME_SEP.join(
836 (new_parent_path, config_repo))
837 (new_parent_path, config_repo))
837 config = make_db_config(clear_session=False, repo=config_repo)
838 config = make_db_config(clear_session=False, repo=config_repo)
838 config.set('extensions', 'largefiles', '')
839 config.set('extensions', 'largefiles', '')
839
840
840 # patch and reset hooks section of UI config to not run any
841 # patch and reset hooks section of UI config to not run any
841 # hooks on creating remote repo
842 # hooks on creating remote repo
842 config.clear_section('hooks')
843 config.clear_section('hooks')
843
844
844 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
845 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
845 if repo_type == 'git':
846 if repo_type == 'git':
846 repo = backend(
847 repo = backend(
847 repo_path, config=config, create=True, src_url=clone_uri,
848 repo_path, config=config, create=True, src_url=clone_uri,
848 bare=True)
849 bare=True)
849 else:
850 else:
850 repo = backend(
851 repo = backend(
851 repo_path, config=config, create=True, src_url=clone_uri)
852 repo_path, config=config, create=True, src_url=clone_uri)
852
853
853 ScmModel().install_hooks(repo, repo_type=repo_type)
854 ScmModel().install_hooks(repo, repo_type=repo_type)
854
855
855 log.debug('Created repo %s with %s backend',
856 log.debug('Created repo %s with %s backend',
856 safe_unicode(repo_name), safe_unicode(repo_type))
857 safe_unicode(repo_name), safe_unicode(repo_type))
857 return repo
858 return repo
858
859
859 def _rename_filesystem_repo(self, old, new):
860 def _rename_filesystem_repo(self, old, new):
860 """
861 """
861 renames repository on filesystem
862 renames repository on filesystem
862
863
863 :param old: old name
864 :param old: old name
864 :param new: new name
865 :param new: new name
865 """
866 """
866 log.info('renaming repo from %s to %s', old, new)
867 log.info('renaming repo from %s to %s', old, new)
867
868
868 old_path = os.path.join(self.repos_path, old)
869 old_path = os.path.join(self.repos_path, old)
869 new_path = os.path.join(self.repos_path, new)
870 new_path = os.path.join(self.repos_path, new)
870 if os.path.isdir(new_path):
871 if os.path.isdir(new_path):
871 raise Exception(
872 raise Exception(
872 'Was trying to rename to already existing dir %s' % new_path
873 'Was trying to rename to already existing dir %s' % new_path
873 )
874 )
874 shutil.move(old_path, new_path)
875 shutil.move(old_path, new_path)
875
876
876 def _delete_filesystem_repo(self, repo):
877 def _delete_filesystem_repo(self, repo):
877 """
878 """
878 removes repo from filesystem, the removal is acctually made by
879 removes repo from filesystem, the removal is acctually made by
879 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
880 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
880 repository is no longer valid for rhodecode, can be undeleted later on
881 repository is no longer valid for rhodecode, can be undeleted later on
881 by reverting the renames on this repository
882 by reverting the renames on this repository
882
883
883 :param repo: repo object
884 :param repo: repo object
884 """
885 """
885 rm_path = os.path.join(self.repos_path, repo.repo_name)
886 rm_path = os.path.join(self.repos_path, repo.repo_name)
886 repo_group = repo.group
887 repo_group = repo.group
887 log.info("Removing repository %s", rm_path)
888 log.info("Removing repository %s", rm_path)
888 # disable hg/git internal that it doesn't get detected as repo
889 # disable hg/git internal that it doesn't get detected as repo
889 alias = repo.repo_type
890 alias = repo.repo_type
890
891
891 config = make_db_config(clear_session=False)
892 config = make_db_config(clear_session=False)
892 config.set('extensions', 'largefiles', '')
893 config.set('extensions', 'largefiles', '')
893 bare = getattr(repo.scm_instance(config=config), 'bare', False)
894 bare = getattr(repo.scm_instance(config=config), 'bare', False)
894
895
895 # skip this for bare git repos
896 # skip this for bare git repos
896 if not bare:
897 if not bare:
897 # disable VCS repo
898 # disable VCS repo
898 vcs_path = os.path.join(rm_path, '.%s' % alias)
899 vcs_path = os.path.join(rm_path, '.%s' % alias)
899 if os.path.exists(vcs_path):
900 if os.path.exists(vcs_path):
900 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
901 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
901
902
902 _now = datetime.now()
903 _now = datetime.datetime.now()
903 _ms = str(_now.microsecond).rjust(6, '0')
904 _ms = str(_now.microsecond).rjust(6, '0')
904 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
905 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
905 repo.just_name)
906 repo.just_name)
906 if repo_group:
907 if repo_group:
907 # if repository is in group, prefix the removal path with the group
908 # if repository is in group, prefix the removal path with the group
908 args = repo_group.full_path_splitted + [_d]
909 args = repo_group.full_path_splitted + [_d]
909 _d = os.path.join(*args)
910 _d = os.path.join(*args)
910
911
911 if os.path.isdir(rm_path):
912 if os.path.isdir(rm_path):
912 shutil.move(rm_path, os.path.join(self.repos_path, _d))
913 shutil.move(rm_path, os.path.join(self.repos_path, _d))
913
914
914
915
915 class ReadmeFinder:
916 class ReadmeFinder:
916 """
917 """
917 Utility which knows how to find a readme for a specific commit.
918 Utility which knows how to find a readme for a specific commit.
918
919
919 The main idea is that this is a configurable algorithm. When creating an
920 The main idea is that this is a configurable algorithm. When creating an
920 instance you can define parameters, currently only the `default_renderer`.
921 instance you can define parameters, currently only the `default_renderer`.
921 Based on this configuration the method :meth:`search` behaves slightly
922 Based on this configuration the method :meth:`search` behaves slightly
922 different.
923 different.
923 """
924 """
924
925
925 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
926 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
926 path_re = re.compile(r'^docs?', re.IGNORECASE)
927 path_re = re.compile(r'^docs?', re.IGNORECASE)
927
928
928 default_priorities = {
929 default_priorities = {
929 None: 0,
930 None: 0,
930 '.text': 2,
931 '.text': 2,
931 '.txt': 3,
932 '.txt': 3,
932 '.rst': 1,
933 '.rst': 1,
933 '.rest': 2,
934 '.rest': 2,
934 '.md': 1,
935 '.md': 1,
935 '.mkdn': 2,
936 '.mkdn': 2,
936 '.mdown': 3,
937 '.mdown': 3,
937 '.markdown': 4,
938 '.markdown': 4,
938 }
939 }
939
940
940 path_priority = {
941 path_priority = {
941 'doc': 0,
942 'doc': 0,
942 'docs': 1,
943 'docs': 1,
943 }
944 }
944
945
945 FALLBACK_PRIORITY = 99
946 FALLBACK_PRIORITY = 99
946
947
947 RENDERER_TO_EXTENSION = {
948 RENDERER_TO_EXTENSION = {
948 'rst': ['.rst', '.rest'],
949 'rst': ['.rst', '.rest'],
949 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
950 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
950 }
951 }
951
952
952 def __init__(self, default_renderer=None):
953 def __init__(self, default_renderer=None):
953 self._default_renderer = default_renderer
954 self._default_renderer = default_renderer
954 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
955 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
955 default_renderer, [])
956 default_renderer, [])
956
957
957 def search(self, commit, path='/'):
958 def search(self, commit, path='/'):
958 """
959 """
959 Find a readme in the given `commit`.
960 Find a readme in the given `commit`.
960 """
961 """
961 nodes = commit.get_nodes(path)
962 nodes = commit.get_nodes(path)
962 matches = self._match_readmes(nodes)
963 matches = self._match_readmes(nodes)
963 matches = self._sort_according_to_priority(matches)
964 matches = self._sort_according_to_priority(matches)
964 if matches:
965 if matches:
965 return matches[0].node
966 return matches[0].node
966
967
967 paths = self._match_paths(nodes)
968 paths = self._match_paths(nodes)
968 paths = self._sort_paths_according_to_priority(paths)
969 paths = self._sort_paths_according_to_priority(paths)
969 for path in paths:
970 for path in paths:
970 match = self.search(commit, path=path)
971 match = self.search(commit, path=path)
971 if match:
972 if match:
972 return match
973 return match
973
974
974 return None
975 return None
975
976
976 def _match_readmes(self, nodes):
977 def _match_readmes(self, nodes):
977 for node in nodes:
978 for node in nodes:
978 if not node.is_file():
979 if not node.is_file():
979 continue
980 continue
980 path = node.path.rsplit('/', 1)[-1]
981 path = node.path.rsplit('/', 1)[-1]
981 match = self.readme_re.match(path)
982 match = self.readme_re.match(path)
982 if match:
983 if match:
983 extension = match.group(1)
984 extension = match.group(1)
984 yield ReadmeMatch(node, match, self._priority(extension))
985 yield ReadmeMatch(node, match, self._priority(extension))
985
986
986 def _match_paths(self, nodes):
987 def _match_paths(self, nodes):
987 for node in nodes:
988 for node in nodes:
988 if not node.is_dir():
989 if not node.is_dir():
989 continue
990 continue
990 match = self.path_re.match(node.path)
991 match = self.path_re.match(node.path)
991 if match:
992 if match:
992 yield node.path
993 yield node.path
993
994
994 def _priority(self, extension):
995 def _priority(self, extension):
995 renderer_priority = (
996 renderer_priority = (
996 0 if extension in self._renderer_extensions else 1)
997 0 if extension in self._renderer_extensions else 1)
997 extension_priority = self.default_priorities.get(
998 extension_priority = self.default_priorities.get(
998 extension, self.FALLBACK_PRIORITY)
999 extension, self.FALLBACK_PRIORITY)
999 return (renderer_priority, extension_priority)
1000 return (renderer_priority, extension_priority)
1000
1001
1001 def _sort_according_to_priority(self, matches):
1002 def _sort_according_to_priority(self, matches):
1002
1003
1003 def priority_and_path(match):
1004 def priority_and_path(match):
1004 return (match.priority, match.path)
1005 return (match.priority, match.path)
1005
1006
1006 return sorted(matches, key=priority_and_path)
1007 return sorted(matches, key=priority_and_path)
1007
1008
1008 def _sort_paths_according_to_priority(self, paths):
1009 def _sort_paths_according_to_priority(self, paths):
1009
1010
1010 def priority_and_path(path):
1011 def priority_and_path(path):
1011 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1012 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1012
1013
1013 return sorted(paths, key=priority_and_path)
1014 return sorted(paths, key=priority_and_path)
1014
1015
1015
1016
1016 class ReadmeMatch:
1017 class ReadmeMatch:
1017
1018
1018 def __init__(self, node, match, priority):
1019 def __init__(self, node, match, priority):
1019 self.node = node
1020 self.node = node
1020 self._match = match
1021 self._match = match
1021 self.priority = priority
1022 self.priority = priority
1022
1023
1023 @property
1024 @property
1024 def path(self):
1025 def path(self):
1025 return self.node.path
1026 return self.node.path
1026
1027
1027 def __repr__(self):
1028 def __repr__(self):
1028 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1029 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,734 +1,744 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import traceback
31 import traceback
32 import string
32 import string
33
33
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (_hash_key,
38 from rhodecode.model.db import (_hash_key,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.utils2 import action_logger_generic
43 from rhodecode.lib.utils2 import action_logger_generic, datetime_to_time
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoGroupModel(BaseModel):
48 class RepoGroupModel(BaseModel):
49
49
50 cls = RepoGroup
50 cls = RepoGroup
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_PATTERN = '${username}' # default
52 PERSONAL_GROUP_PATTERN = '${username}' # default
53
53
54 def _get_user_group(self, users_group):
54 def _get_user_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repo_group(self, repo_group):
58 def _get_repo_group(self, repo_group):
59 return self._get_instance(RepoGroup, repo_group,
59 return self._get_instance(RepoGroup, repo_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Gets the repositories root path from database
65 Gets the repositories root path from database
66 """
66 """
67
67
68 settings_model = VcsSettingsModel(sa=self.sa)
68 settings_model = VcsSettingsModel(sa=self.sa)
69 return settings_model.get_repos_location()
69 return settings_model.get_repos_location()
70
70
71 def get_by_group_name(self, repo_group_name, cache=None):
71 def get_by_group_name(self, repo_group_name, cache=None):
72 repo = self.sa.query(RepoGroup) \
72 repo = self.sa.query(RepoGroup) \
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 name_key = _hash_key(repo_group_name)
76 name_key = _hash_key(repo_group_name)
77 repo = repo.options(
77 repo = repo.options(
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 return repo.scalar()
79 return repo.scalar()
80
80
81 def get_default_create_personal_repo_group(self):
81 def get_default_create_personal_repo_group(self):
82 value = SettingsModel().get_setting_by_name(
82 value = SettingsModel().get_setting_by_name(
83 'create_personal_repo_group')
83 'create_personal_repo_group')
84 return value.app_settings_value if value else None or False
84 return value.app_settings_value if value else None or False
85
85
86 def get_personal_group_name_pattern(self):
86 def get_personal_group_name_pattern(self):
87 value = SettingsModel().get_setting_by_name(
87 value = SettingsModel().get_setting_by_name(
88 'personal_repo_group_pattern')
88 'personal_repo_group_pattern')
89 val = value.app_settings_value if value else None
89 val = value.app_settings_value if value else None
90 group_template = val or self.PERSONAL_GROUP_PATTERN
90 group_template = val or self.PERSONAL_GROUP_PATTERN
91
91
92 group_template = group_template.lstrip('/')
92 group_template = group_template.lstrip('/')
93 return group_template
93 return group_template
94
94
95 def get_personal_group_name(self, user):
95 def get_personal_group_name(self, user):
96 template = self.get_personal_group_name_pattern()
96 template = self.get_personal_group_name_pattern()
97 return string.Template(template).safe_substitute(
97 return string.Template(template).safe_substitute(
98 username=user.username,
98 username=user.username,
99 user_id=user.user_id,
99 user_id=user.user_id,
100 )
100 )
101
101
102 def create_personal_repo_group(self, user, commit_early=True):
102 def create_personal_repo_group(self, user, commit_early=True):
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 personal_repo_group_name = self.get_personal_group_name(user)
104 personal_repo_group_name = self.get_personal_group_name(user)
105
105
106 # create a new one
106 # create a new one
107 RepoGroupModel().create(
107 RepoGroupModel().create(
108 group_name=personal_repo_group_name,
108 group_name=personal_repo_group_name,
109 group_description=desc,
109 group_description=desc,
110 owner=user.username,
110 owner=user.username,
111 personal=True,
111 personal=True,
112 commit_early=commit_early)
112 commit_early=commit_early)
113
113
114 def _create_default_perms(self, new_group):
114 def _create_default_perms(self, new_group):
115 # create default permission
115 # create default permission
116 default_perm = 'group.read'
116 default_perm = 'group.read'
117 def_user = User.get_default_user()
117 def_user = User.get_default_user()
118 for p in def_user.user_perms:
118 for p in def_user.user_perms:
119 if p.permission.permission_name.startswith('group.'):
119 if p.permission.permission_name.startswith('group.'):
120 default_perm = p.permission.permission_name
120 default_perm = p.permission.permission_name
121 break
121 break
122
122
123 repo_group_to_perm = UserRepoGroupToPerm()
123 repo_group_to_perm = UserRepoGroupToPerm()
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125
125
126 repo_group_to_perm.group = new_group
126 repo_group_to_perm.group = new_group
127 repo_group_to_perm.user_id = def_user.user_id
127 repo_group_to_perm.user_id = def_user.user_id
128 return repo_group_to_perm
128 return repo_group_to_perm
129
129
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 get_object=False):
131 get_object=False):
132 """
132 """
133 Get's the group name and a parent group name from given group name.
133 Get's the group name and a parent group name from given group name.
134 If repo_in_path is set to truth, we asume the full path also includes
134 If repo_in_path is set to truth, we asume the full path also includes
135 repo name, in such case we clean the last element.
135 repo name, in such case we clean the last element.
136
136
137 :param group_name_full:
137 :param group_name_full:
138 """
138 """
139 split_paths = 1
139 split_paths = 1
140 if repo_in_path:
140 if repo_in_path:
141 split_paths = 2
141 split_paths = 2
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143
143
144 if repo_in_path and len(_parts) > 1:
144 if repo_in_path and len(_parts) > 1:
145 # such case last element is the repo_name
145 # such case last element is the repo_name
146 _parts.pop(-1)
146 _parts.pop(-1)
147 group_name_cleaned = _parts[-1] # just the group name
147 group_name_cleaned = _parts[-1] # just the group name
148 parent_repo_group_name = None
148 parent_repo_group_name = None
149
149
150 if len(_parts) > 1:
150 if len(_parts) > 1:
151 parent_repo_group_name = _parts[0]
151 parent_repo_group_name = _parts[0]
152
152
153 parent_group = None
153 parent_group = None
154 if parent_repo_group_name:
154 if parent_repo_group_name:
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156
156
157 if get_object:
157 if get_object:
158 return group_name_cleaned, parent_repo_group_name, parent_group
158 return group_name_cleaned, parent_repo_group_name, parent_group
159
159
160 return group_name_cleaned, parent_repo_group_name
160 return group_name_cleaned, parent_repo_group_name
161
161
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 create_path = os.path.join(self.repos_path, group_name)
163 create_path = os.path.join(self.repos_path, group_name)
164 log.debug('creating new group in %s', create_path)
164 log.debug('creating new group in %s', create_path)
165
165
166 if os.path.isdir(create_path):
166 if os.path.isdir(create_path):
167 if exc_on_failure:
167 if exc_on_failure:
168 abs_create_path = os.path.abspath(create_path)
168 abs_create_path = os.path.abspath(create_path)
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 return False
170 return False
171 return True
171 return True
172
172
173 def _create_group(self, group_name):
173 def _create_group(self, group_name):
174 """
174 """
175 makes repository group on filesystem
175 makes repository group on filesystem
176
176
177 :param repo_name:
177 :param repo_name:
178 :param parent_id:
178 :param parent_id:
179 """
179 """
180
180
181 self.check_exist_filesystem(group_name)
181 self.check_exist_filesystem(group_name)
182 create_path = os.path.join(self.repos_path, group_name)
182 create_path = os.path.join(self.repos_path, group_name)
183 log.debug('creating new group in %s', create_path)
183 log.debug('creating new group in %s', create_path)
184 os.makedirs(create_path, mode=0755)
184 os.makedirs(create_path, mode=0755)
185 log.debug('created group in %s', create_path)
185 log.debug('created group in %s', create_path)
186
186
187 def _rename_group(self, old, new):
187 def _rename_group(self, old, new):
188 """
188 """
189 Renames a group on filesystem
189 Renames a group on filesystem
190
190
191 :param group_name:
191 :param group_name:
192 """
192 """
193
193
194 if old == new:
194 if old == new:
195 log.debug('skipping group rename')
195 log.debug('skipping group rename')
196 return
196 return
197
197
198 log.debug('renaming repository group from %s to %s', old, new)
198 log.debug('renaming repository group from %s to %s', old, new)
199
199
200 old_path = os.path.join(self.repos_path, old)
200 old_path = os.path.join(self.repos_path, old)
201 new_path = os.path.join(self.repos_path, new)
201 new_path = os.path.join(self.repos_path, new)
202
202
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204
204
205 if os.path.isdir(new_path):
205 if os.path.isdir(new_path):
206 raise Exception('Was trying to rename to already '
206 raise Exception('Was trying to rename to already '
207 'existing dir %s' % new_path)
207 'existing dir %s' % new_path)
208 shutil.move(old_path, new_path)
208 shutil.move(old_path, new_path)
209
209
210 def _delete_filesystem_group(self, group, force_delete=False):
210 def _delete_filesystem_group(self, group, force_delete=False):
211 """
211 """
212 Deletes a group from a filesystem
212 Deletes a group from a filesystem
213
213
214 :param group: instance of group from database
214 :param group: instance of group from database
215 :param force_delete: use shutil rmtree to remove all objects
215 :param force_delete: use shutil rmtree to remove all objects
216 """
216 """
217 paths = group.full_path.split(RepoGroup.url_sep())
217 paths = group.full_path.split(RepoGroup.url_sep())
218 paths = os.sep.join(paths)
218 paths = os.sep.join(paths)
219
219
220 rm_path = os.path.join(self.repos_path, paths)
220 rm_path = os.path.join(self.repos_path, paths)
221 log.info("Removing group %s", rm_path)
221 log.info("Removing group %s", rm_path)
222 # delete only if that path really exists
222 # delete only if that path really exists
223 if os.path.isdir(rm_path):
223 if os.path.isdir(rm_path):
224 if force_delete:
224 if force_delete:
225 shutil.rmtree(rm_path)
225 shutil.rmtree(rm_path)
226 else:
226 else:
227 # archive that group`
227 # archive that group`
228 _now = datetime.datetime.now()
228 _now = datetime.datetime.now()
229 _ms = str(_now.microsecond).rjust(6, '0')
229 _ms = str(_now.microsecond).rjust(6, '0')
230 _d = 'rm__%s_GROUP_%s' % (
230 _d = 'rm__%s_GROUP_%s' % (
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233
233
234 def create(self, group_name, group_description, owner, just_db=False,
234 def create(self, group_name, group_description, owner, just_db=False,
235 copy_permissions=False, personal=None, commit_early=True):
235 copy_permissions=False, personal=None, commit_early=True):
236
236
237 (group_name_cleaned,
237 (group_name_cleaned,
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239
239
240 parent_group = None
240 parent_group = None
241 if parent_group_name:
241 if parent_group_name:
242 parent_group = self._get_repo_group(parent_group_name)
242 parent_group = self._get_repo_group(parent_group_name)
243 if not parent_group:
243 if not parent_group:
244 # we tried to create a nested group, but the parent is not
244 # we tried to create a nested group, but the parent is not
245 # existing
245 # existing
246 raise ValueError(
246 raise ValueError(
247 'Parent group `%s` given in `%s` group name '
247 'Parent group `%s` given in `%s` group name '
248 'is not yet existing.' % (parent_group_name, group_name))
248 'is not yet existing.' % (parent_group_name, group_name))
249
249
250 # because we are doing a cleanup, we need to check if such directory
250 # because we are doing a cleanup, we need to check if such directory
251 # already exists. If we don't do that we can accidentally delete
251 # already exists. If we don't do that we can accidentally delete
252 # existing directory via cleanup that can cause data issues, since
252 # existing directory via cleanup that can cause data issues, since
253 # delete does a folder rename to special syntax later cleanup
253 # delete does a folder rename to special syntax later cleanup
254 # functions can delete this
254 # functions can delete this
255 cleanup_group = self.check_exist_filesystem(group_name,
255 cleanup_group = self.check_exist_filesystem(group_name,
256 exc_on_failure=False)
256 exc_on_failure=False)
257 try:
257 try:
258 user = self._get_user(owner)
258 user = self._get_user(owner)
259 new_repo_group = RepoGroup()
259 new_repo_group = RepoGroup()
260 new_repo_group.user = user
260 new_repo_group.user = user
261 new_repo_group.group_description = group_description or group_name
261 new_repo_group.group_description = group_description or group_name
262 new_repo_group.parent_group = parent_group
262 new_repo_group.parent_group = parent_group
263 new_repo_group.group_name = group_name
263 new_repo_group.group_name = group_name
264 new_repo_group.personal = personal
264 new_repo_group.personal = personal
265
265
266 self.sa.add(new_repo_group)
266 self.sa.add(new_repo_group)
267
267
268 # create an ADMIN permission for owner except if we're super admin,
268 # create an ADMIN permission for owner except if we're super admin,
269 # later owner should go into the owner field of groups
269 # later owner should go into the owner field of groups
270 if not user.is_admin:
270 if not user.is_admin:
271 self.grant_user_permission(repo_group=new_repo_group,
271 self.grant_user_permission(repo_group=new_repo_group,
272 user=owner, perm='group.admin')
272 user=owner, perm='group.admin')
273
273
274 if parent_group and copy_permissions:
274 if parent_group and copy_permissions:
275 # copy permissions from parent
275 # copy permissions from parent
276 user_perms = UserRepoGroupToPerm.query() \
276 user_perms = UserRepoGroupToPerm.query() \
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
277 .filter(UserRepoGroupToPerm.group == parent_group).all()
278
278
279 group_perms = UserGroupRepoGroupToPerm.query() \
279 group_perms = UserGroupRepoGroupToPerm.query() \
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
281
281
282 for perm in user_perms:
282 for perm in user_perms:
283 # don't copy over the permission for user who is creating
283 # don't copy over the permission for user who is creating
284 # this group, if he is not super admin he get's admin
284 # this group, if he is not super admin he get's admin
285 # permission set above
285 # permission set above
286 if perm.user != user or user.is_admin:
286 if perm.user != user or user.is_admin:
287 UserRepoGroupToPerm.create(
287 UserRepoGroupToPerm.create(
288 perm.user, new_repo_group, perm.permission)
288 perm.user, new_repo_group, perm.permission)
289
289
290 for perm in group_perms:
290 for perm in group_perms:
291 UserGroupRepoGroupToPerm.create(
291 UserGroupRepoGroupToPerm.create(
292 perm.users_group, new_repo_group, perm.permission)
292 perm.users_group, new_repo_group, perm.permission)
293 else:
293 else:
294 perm_obj = self._create_default_perms(new_repo_group)
294 perm_obj = self._create_default_perms(new_repo_group)
295 self.sa.add(perm_obj)
295 self.sa.add(perm_obj)
296
296
297 # now commit the changes, earlier so we are sure everything is in
297 # now commit the changes, earlier so we are sure everything is in
298 # the database.
298 # the database.
299 if commit_early:
299 if commit_early:
300 self.sa.commit()
300 self.sa.commit()
301 if not just_db:
301 if not just_db:
302 self._create_group(new_repo_group.group_name)
302 self._create_group(new_repo_group.group_name)
303
303
304 # trigger the post hook
304 # trigger the post hook
305 from rhodecode.lib.hooks_base import log_create_repository_group
305 from rhodecode.lib.hooks_base import log_create_repository_group
306 repo_group = RepoGroup.get_by_group_name(group_name)
306 repo_group = RepoGroup.get_by_group_name(group_name)
307 log_create_repository_group(
307 log_create_repository_group(
308 created_by=user.username, **repo_group.get_dict())
308 created_by=user.username, **repo_group.get_dict())
309
309
310 # Trigger create event.
310 # Trigger create event.
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
311 events.trigger(events.RepoGroupCreateEvent(repo_group))
312
312
313 return new_repo_group
313 return new_repo_group
314 except Exception:
314 except Exception:
315 self.sa.rollback()
315 self.sa.rollback()
316 log.exception('Exception occurred when creating repository group, '
316 log.exception('Exception occurred when creating repository group, '
317 'doing cleanup...')
317 'doing cleanup...')
318 # rollback things manually !
318 # rollback things manually !
319 repo_group = RepoGroup.get_by_group_name(group_name)
319 repo_group = RepoGroup.get_by_group_name(group_name)
320 if repo_group:
320 if repo_group:
321 RepoGroup.delete(repo_group.group_id)
321 RepoGroup.delete(repo_group.group_id)
322 self.sa.commit()
322 self.sa.commit()
323 if cleanup_group:
323 if cleanup_group:
324 RepoGroupModel()._delete_filesystem_group(repo_group)
324 RepoGroupModel()._delete_filesystem_group(repo_group)
325 raise
325 raise
326
326
327 def update_permissions(
327 def update_permissions(
328 self, repo_group, perm_additions=None, perm_updates=None,
328 self, repo_group, perm_additions=None, perm_updates=None,
329 perm_deletions=None, recursive=None, check_perms=True,
329 perm_deletions=None, recursive=None, check_perms=True,
330 cur_user=None):
330 cur_user=None):
331 from rhodecode.model.repo import RepoModel
331 from rhodecode.model.repo import RepoModel
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
332 from rhodecode.lib.auth import HasUserGroupPermissionAny
333
333
334 if not perm_additions:
334 if not perm_additions:
335 perm_additions = []
335 perm_additions = []
336 if not perm_updates:
336 if not perm_updates:
337 perm_updates = []
337 perm_updates = []
338 if not perm_deletions:
338 if not perm_deletions:
339 perm_deletions = []
339 perm_deletions = []
340
340
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
342
342
343 changes = {
343 changes = {
344 'added': [],
344 'added': [],
345 'updated': [],
345 'updated': [],
346 'deleted': []
346 'deleted': []
347 }
347 }
348
348
349 def _set_perm_user(obj, user, perm):
349 def _set_perm_user(obj, user, perm):
350 if isinstance(obj, RepoGroup):
350 if isinstance(obj, RepoGroup):
351 self.grant_user_permission(
351 self.grant_user_permission(
352 repo_group=obj, user=user, perm=perm)
352 repo_group=obj, user=user, perm=perm)
353 elif isinstance(obj, Repository):
353 elif isinstance(obj, Repository):
354 # private repos will not allow to change the default
354 # private repos will not allow to change the default
355 # permissions using recursive mode
355 # permissions using recursive mode
356 if obj.private and user == User.DEFAULT_USER:
356 if obj.private and user == User.DEFAULT_USER:
357 return
357 return
358
358
359 # we set group permission but we have to switch to repo
359 # we set group permission but we have to switch to repo
360 # permission
360 # permission
361 perm = perm.replace('group.', 'repository.')
361 perm = perm.replace('group.', 'repository.')
362 RepoModel().grant_user_permission(
362 RepoModel().grant_user_permission(
363 repo=obj, user=user, perm=perm)
363 repo=obj, user=user, perm=perm)
364
364
365 def _set_perm_group(obj, users_group, perm):
365 def _set_perm_group(obj, users_group, perm):
366 if isinstance(obj, RepoGroup):
366 if isinstance(obj, RepoGroup):
367 self.grant_user_group_permission(
367 self.grant_user_group_permission(
368 repo_group=obj, group_name=users_group, perm=perm)
368 repo_group=obj, group_name=users_group, perm=perm)
369 elif isinstance(obj, Repository):
369 elif isinstance(obj, Repository):
370 # we set group permission but we have to switch to repo
370 # we set group permission but we have to switch to repo
371 # permission
371 # permission
372 perm = perm.replace('group.', 'repository.')
372 perm = perm.replace('group.', 'repository.')
373 RepoModel().grant_user_group_permission(
373 RepoModel().grant_user_group_permission(
374 repo=obj, group_name=users_group, perm=perm)
374 repo=obj, group_name=users_group, perm=perm)
375
375
376 def _revoke_perm_user(obj, user):
376 def _revoke_perm_user(obj, user):
377 if isinstance(obj, RepoGroup):
377 if isinstance(obj, RepoGroup):
378 self.revoke_user_permission(repo_group=obj, user=user)
378 self.revoke_user_permission(repo_group=obj, user=user)
379 elif isinstance(obj, Repository):
379 elif isinstance(obj, Repository):
380 RepoModel().revoke_user_permission(repo=obj, user=user)
380 RepoModel().revoke_user_permission(repo=obj, user=user)
381
381
382 def _revoke_perm_group(obj, user_group):
382 def _revoke_perm_group(obj, user_group):
383 if isinstance(obj, RepoGroup):
383 if isinstance(obj, RepoGroup):
384 self.revoke_user_group_permission(
384 self.revoke_user_group_permission(
385 repo_group=obj, group_name=user_group)
385 repo_group=obj, group_name=user_group)
386 elif isinstance(obj, Repository):
386 elif isinstance(obj, Repository):
387 RepoModel().revoke_user_group_permission(
387 RepoModel().revoke_user_group_permission(
388 repo=obj, group_name=user_group)
388 repo=obj, group_name=user_group)
389
389
390 # start updates
390 # start updates
391 log.debug('Now updating permissions for %s in recursive mode:%s',
391 log.debug('Now updating permissions for %s in recursive mode:%s',
392 repo_group, recursive)
392 repo_group, recursive)
393
393
394 # initialize check function, we'll call that multiple times
394 # initialize check function, we'll call that multiple times
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
396
396
397 for obj in repo_group.recursive_groups_and_repos():
397 for obj in repo_group.recursive_groups_and_repos():
398 # iterated obj is an instance of a repos group or repository in
398 # iterated obj is an instance of a repos group or repository in
399 # that group, recursive option can be: none, repos, groups, all
399 # that group, recursive option can be: none, repos, groups, all
400 if recursive == 'all':
400 if recursive == 'all':
401 obj = obj
401 obj = obj
402 elif recursive == 'repos':
402 elif recursive == 'repos':
403 # skip groups, other than this one
403 # skip groups, other than this one
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
404 if isinstance(obj, RepoGroup) and not obj == repo_group:
405 continue
405 continue
406 elif recursive == 'groups':
406 elif recursive == 'groups':
407 # skip repos
407 # skip repos
408 if isinstance(obj, Repository):
408 if isinstance(obj, Repository):
409 continue
409 continue
410 else: # recursive == 'none':
410 else: # recursive == 'none':
411 # DEFAULT option - don't apply to iterated objects
411 # DEFAULT option - don't apply to iterated objects
412 # also we do a break at the end of this loop. if we are not
412 # also we do a break at the end of this loop. if we are not
413 # in recursive mode
413 # in recursive mode
414 obj = repo_group
414 obj = repo_group
415
415
416 change_obj = obj.get_api_data()
416 change_obj = obj.get_api_data()
417
417
418 # update permissions
418 # update permissions
419 for member_id, perm, member_type in perm_updates:
419 for member_id, perm, member_type in perm_updates:
420 member_id = int(member_id)
420 member_id = int(member_id)
421 if member_type == 'user':
421 if member_type == 'user':
422 member_name = User.get(member_id).username
422 member_name = User.get(member_id).username
423 # this updates also current one if found
423 # this updates also current one if found
424 _set_perm_user(obj, user=member_id, perm=perm)
424 _set_perm_user(obj, user=member_id, perm=perm)
425 else: # set for user group
425 else: # set for user group
426 member_name = UserGroup.get(member_id).users_group_name
426 member_name = UserGroup.get(member_id).users_group_name
427 if not check_perms or has_group_perm(member_name,
427 if not check_perms or has_group_perm(member_name,
428 user=cur_user):
428 user=cur_user):
429 _set_perm_group(obj, users_group=member_id, perm=perm)
429 _set_perm_group(obj, users_group=member_id, perm=perm)
430
430
431 changes['updated'].append(
431 changes['updated'].append(
432 {'change_obj': change_obj, 'type': member_type,
432 {'change_obj': change_obj, 'type': member_type,
433 'id': member_id, 'name': member_name, 'new_perm': perm})
433 'id': member_id, 'name': member_name, 'new_perm': perm})
434
434
435 # set new permissions
435 # set new permissions
436 for member_id, perm, member_type in perm_additions:
436 for member_id, perm, member_type in perm_additions:
437 member_id = int(member_id)
437 member_id = int(member_id)
438 if member_type == 'user':
438 if member_type == 'user':
439 member_name = User.get(member_id).username
439 member_name = User.get(member_id).username
440 _set_perm_user(obj, user=member_id, perm=perm)
440 _set_perm_user(obj, user=member_id, perm=perm)
441 else: # set for user group
441 else: # set for user group
442 # check if we have permissions to alter this usergroup
442 # check if we have permissions to alter this usergroup
443 member_name = UserGroup.get(member_id).users_group_name
443 member_name = UserGroup.get(member_id).users_group_name
444 if not check_perms or has_group_perm(member_name,
444 if not check_perms or has_group_perm(member_name,
445 user=cur_user):
445 user=cur_user):
446 _set_perm_group(obj, users_group=member_id, perm=perm)
446 _set_perm_group(obj, users_group=member_id, perm=perm)
447
447
448 changes['added'].append(
448 changes['added'].append(
449 {'change_obj': change_obj, 'type': member_type,
449 {'change_obj': change_obj, 'type': member_type,
450 'id': member_id, 'name': member_name, 'new_perm': perm})
450 'id': member_id, 'name': member_name, 'new_perm': perm})
451
451
452 # delete permissions
452 # delete permissions
453 for member_id, perm, member_type in perm_deletions:
453 for member_id, perm, member_type in perm_deletions:
454 member_id = int(member_id)
454 member_id = int(member_id)
455 if member_type == 'user':
455 if member_type == 'user':
456 member_name = User.get(member_id).username
456 member_name = User.get(member_id).username
457 _revoke_perm_user(obj, user=member_id)
457 _revoke_perm_user(obj, user=member_id)
458 else: # set for user group
458 else: # set for user group
459 # check if we have permissions to alter this usergroup
459 # check if we have permissions to alter this usergroup
460 member_name = UserGroup.get(member_id).users_group_name
460 member_name = UserGroup.get(member_id).users_group_name
461 if not check_perms or has_group_perm(member_name,
461 if not check_perms or has_group_perm(member_name,
462 user=cur_user):
462 user=cur_user):
463 _revoke_perm_group(obj, user_group=member_id)
463 _revoke_perm_group(obj, user_group=member_id)
464
464
465 changes['deleted'].append(
465 changes['deleted'].append(
466 {'change_obj': change_obj, 'type': member_type,
466 {'change_obj': change_obj, 'type': member_type,
467 'id': member_id, 'name': member_name, 'new_perm': perm})
467 'id': member_id, 'name': member_name, 'new_perm': perm})
468
468
469 # if it's not recursive call for all,repos,groups
469 # if it's not recursive call for all,repos,groups
470 # break the loop and don't proceed with other changes
470 # break the loop and don't proceed with other changes
471 if recursive not in ['all', 'repos', 'groups']:
471 if recursive not in ['all', 'repos', 'groups']:
472 break
472 break
473
473
474 return changes
474 return changes
475
475
476 def update(self, repo_group, form_data):
476 def update(self, repo_group, form_data):
477 try:
477 try:
478 repo_group = self._get_repo_group(repo_group)
478 repo_group = self._get_repo_group(repo_group)
479 old_path = repo_group.full_path
479 old_path = repo_group.full_path
480
480
481 # change properties
481 # change properties
482 if 'group_description' in form_data:
482 if 'group_description' in form_data:
483 repo_group.group_description = form_data['group_description']
483 repo_group.group_description = form_data['group_description']
484
484
485 if 'enable_locking' in form_data:
485 if 'enable_locking' in form_data:
486 repo_group.enable_locking = form_data['enable_locking']
486 repo_group.enable_locking = form_data['enable_locking']
487
487
488 if 'group_parent_id' in form_data:
488 if 'group_parent_id' in form_data:
489 parent_group = (
489 parent_group = (
490 self._get_repo_group(form_data['group_parent_id']))
490 self._get_repo_group(form_data['group_parent_id']))
491 repo_group.group_parent_id = (
491 repo_group.group_parent_id = (
492 parent_group.group_id if parent_group else None)
492 parent_group.group_id if parent_group else None)
493 repo_group.parent_group = parent_group
493 repo_group.parent_group = parent_group
494
494
495 # mikhail: to update the full_path, we have to explicitly
495 # mikhail: to update the full_path, we have to explicitly
496 # update group_name
496 # update group_name
497 group_name = form_data.get('group_name', repo_group.name)
497 group_name = form_data.get('group_name', repo_group.name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
498 repo_group.group_name = repo_group.get_new_name(group_name)
499
499
500 new_path = repo_group.full_path
500 new_path = repo_group.full_path
501
501
502 if 'user' in form_data:
502 if 'user' in form_data:
503 repo_group.user = User.get_by_username(form_data['user'])
503 repo_group.user = User.get_by_username(form_data['user'])
504
504 repo_group.updated_on = datetime.datetime.now()
505 self.sa.add(repo_group)
505 self.sa.add(repo_group)
506
506
507 # iterate over all members of this groups and do fixes
507 # iterate over all members of this groups and do fixes
508 # set locking if given
508 # set locking if given
509 # if obj is a repoGroup also fix the name of the group according
509 # if obj is a repoGroup also fix the name of the group according
510 # to the parent
510 # to the parent
511 # if obj is a Repo fix it's name
511 # if obj is a Repo fix it's name
512 # this can be potentially heavy operation
512 # this can be potentially heavy operation
513 for obj in repo_group.recursive_groups_and_repos():
513 for obj in repo_group.recursive_groups_and_repos():
514 # set the value from it's parent
514 # set the value from it's parent
515 obj.enable_locking = repo_group.enable_locking
515 obj.enable_locking = repo_group.enable_locking
516 if isinstance(obj, RepoGroup):
516 if isinstance(obj, RepoGroup):
517 new_name = obj.get_new_name(obj.name)
517 new_name = obj.get_new_name(obj.name)
518 log.debug('Fixing group %s to new name %s',
518 log.debug('Fixing group %s to new name %s',
519 obj.group_name, new_name)
519 obj.group_name, new_name)
520 obj.group_name = new_name
520 obj.group_name = new_name
521 obj.updated_on = datetime.datetime.now()
521 elif isinstance(obj, Repository):
522 elif isinstance(obj, Repository):
522 # we need to get all repositories from this new group and
523 # we need to get all repositories from this new group and
523 # rename them accordingly to new group path
524 # rename them accordingly to new group path
524 new_name = obj.get_new_name(obj.just_name)
525 new_name = obj.get_new_name(obj.just_name)
525 log.debug('Fixing repo %s to new name %s',
526 log.debug('Fixing repo %s to new name %s',
526 obj.repo_name, new_name)
527 obj.repo_name, new_name)
527 obj.repo_name = new_name
528 obj.repo_name = new_name
529 obj.updated_on = datetime.datetime.now()
528 self.sa.add(obj)
530 self.sa.add(obj)
529
531
530 self._rename_group(old_path, new_path)
532 self._rename_group(old_path, new_path)
531
533
532 # Trigger update event.
534 # Trigger update event.
533 events.trigger(events.RepoGroupUpdateEvent(repo_group))
535 events.trigger(events.RepoGroupUpdateEvent(repo_group))
534
536
535 return repo_group
537 return repo_group
536 except Exception:
538 except Exception:
537 log.error(traceback.format_exc())
539 log.error(traceback.format_exc())
538 raise
540 raise
539
541
540 def delete(self, repo_group, force_delete=False, fs_remove=True):
542 def delete(self, repo_group, force_delete=False, fs_remove=True):
541 repo_group = self._get_repo_group(repo_group)
543 repo_group = self._get_repo_group(repo_group)
542 if not repo_group:
544 if not repo_group:
543 return False
545 return False
544 try:
546 try:
545 self.sa.delete(repo_group)
547 self.sa.delete(repo_group)
546 if fs_remove:
548 if fs_remove:
547 self._delete_filesystem_group(repo_group, force_delete)
549 self._delete_filesystem_group(repo_group, force_delete)
548 else:
550 else:
549 log.debug('skipping removal from filesystem')
551 log.debug('skipping removal from filesystem')
550
552
551 # Trigger delete event.
553 # Trigger delete event.
552 events.trigger(events.RepoGroupDeleteEvent(repo_group))
554 events.trigger(events.RepoGroupDeleteEvent(repo_group))
553 return True
555 return True
554
556
555 except Exception:
557 except Exception:
556 log.error('Error removing repo_group %s', repo_group)
558 log.error('Error removing repo_group %s', repo_group)
557 raise
559 raise
558
560
559 def grant_user_permission(self, repo_group, user, perm):
561 def grant_user_permission(self, repo_group, user, perm):
560 """
562 """
561 Grant permission for user on given repository group, or update
563 Grant permission for user on given repository group, or update
562 existing one if found
564 existing one if found
563
565
564 :param repo_group: Instance of RepoGroup, repositories_group_id,
566 :param repo_group: Instance of RepoGroup, repositories_group_id,
565 or repositories_group name
567 or repositories_group name
566 :param user: Instance of User, user_id or username
568 :param user: Instance of User, user_id or username
567 :param perm: Instance of Permission, or permission_name
569 :param perm: Instance of Permission, or permission_name
568 """
570 """
569
571
570 repo_group = self._get_repo_group(repo_group)
572 repo_group = self._get_repo_group(repo_group)
571 user = self._get_user(user)
573 user = self._get_user(user)
572 permission = self._get_perm(perm)
574 permission = self._get_perm(perm)
573
575
574 # check if we have that permission already
576 # check if we have that permission already
575 obj = self.sa.query(UserRepoGroupToPerm)\
577 obj = self.sa.query(UserRepoGroupToPerm)\
576 .filter(UserRepoGroupToPerm.user == user)\
578 .filter(UserRepoGroupToPerm.user == user)\
577 .filter(UserRepoGroupToPerm.group == repo_group)\
579 .filter(UserRepoGroupToPerm.group == repo_group)\
578 .scalar()
580 .scalar()
579 if obj is None:
581 if obj is None:
580 # create new !
582 # create new !
581 obj = UserRepoGroupToPerm()
583 obj = UserRepoGroupToPerm()
582 obj.group = repo_group
584 obj.group = repo_group
583 obj.user = user
585 obj.user = user
584 obj.permission = permission
586 obj.permission = permission
585 self.sa.add(obj)
587 self.sa.add(obj)
586 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
588 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
587 action_logger_generic(
589 action_logger_generic(
588 'granted permission: {} to user: {} on repogroup: {}'.format(
590 'granted permission: {} to user: {} on repogroup: {}'.format(
589 perm, user, repo_group), namespace='security.repogroup')
591 perm, user, repo_group), namespace='security.repogroup')
590 return obj
592 return obj
591
593
592 def revoke_user_permission(self, repo_group, user):
594 def revoke_user_permission(self, repo_group, user):
593 """
595 """
594 Revoke permission for user on given repository group
596 Revoke permission for user on given repository group
595
597
596 :param repo_group: Instance of RepoGroup, repositories_group_id,
598 :param repo_group: Instance of RepoGroup, repositories_group_id,
597 or repositories_group name
599 or repositories_group name
598 :param user: Instance of User, user_id or username
600 :param user: Instance of User, user_id or username
599 """
601 """
600
602
601 repo_group = self._get_repo_group(repo_group)
603 repo_group = self._get_repo_group(repo_group)
602 user = self._get_user(user)
604 user = self._get_user(user)
603
605
604 obj = self.sa.query(UserRepoGroupToPerm)\
606 obj = self.sa.query(UserRepoGroupToPerm)\
605 .filter(UserRepoGroupToPerm.user == user)\
607 .filter(UserRepoGroupToPerm.user == user)\
606 .filter(UserRepoGroupToPerm.group == repo_group)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
607 .scalar()
609 .scalar()
608 if obj:
610 if obj:
609 self.sa.delete(obj)
611 self.sa.delete(obj)
610 log.debug('Revoked perm on %s on %s', repo_group, user)
612 log.debug('Revoked perm on %s on %s', repo_group, user)
611 action_logger_generic(
613 action_logger_generic(
612 'revoked permission from user: {} on repogroup: {}'.format(
614 'revoked permission from user: {} on repogroup: {}'.format(
613 user, repo_group), namespace='security.repogroup')
615 user, repo_group), namespace='security.repogroup')
614
616
615 def grant_user_group_permission(self, repo_group, group_name, perm):
617 def grant_user_group_permission(self, repo_group, group_name, perm):
616 """
618 """
617 Grant permission for user group on given repository group, or update
619 Grant permission for user group on given repository group, or update
618 existing one if found
620 existing one if found
619
621
620 :param repo_group: Instance of RepoGroup, repositories_group_id,
622 :param repo_group: Instance of RepoGroup, repositories_group_id,
621 or repositories_group name
623 or repositories_group name
622 :param group_name: Instance of UserGroup, users_group_id,
624 :param group_name: Instance of UserGroup, users_group_id,
623 or user group name
625 or user group name
624 :param perm: Instance of Permission, or permission_name
626 :param perm: Instance of Permission, or permission_name
625 """
627 """
626 repo_group = self._get_repo_group(repo_group)
628 repo_group = self._get_repo_group(repo_group)
627 group_name = self._get_user_group(group_name)
629 group_name = self._get_user_group(group_name)
628 permission = self._get_perm(perm)
630 permission = self._get_perm(perm)
629
631
630 # check if we have that permission already
632 # check if we have that permission already
631 obj = self.sa.query(UserGroupRepoGroupToPerm)\
633 obj = self.sa.query(UserGroupRepoGroupToPerm)\
632 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
634 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
633 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
635 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
634 .scalar()
636 .scalar()
635
637
636 if obj is None:
638 if obj is None:
637 # create new
639 # create new
638 obj = UserGroupRepoGroupToPerm()
640 obj = UserGroupRepoGroupToPerm()
639
641
640 obj.group = repo_group
642 obj.group = repo_group
641 obj.users_group = group_name
643 obj.users_group = group_name
642 obj.permission = permission
644 obj.permission = permission
643 self.sa.add(obj)
645 self.sa.add(obj)
644 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
646 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
645 action_logger_generic(
647 action_logger_generic(
646 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
648 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
647 perm, group_name, repo_group), namespace='security.repogroup')
649 perm, group_name, repo_group), namespace='security.repogroup')
648 return obj
650 return obj
649
651
650 def revoke_user_group_permission(self, repo_group, group_name):
652 def revoke_user_group_permission(self, repo_group, group_name):
651 """
653 """
652 Revoke permission for user group on given repository group
654 Revoke permission for user group on given repository group
653
655
654 :param repo_group: Instance of RepoGroup, repositories_group_id,
656 :param repo_group: Instance of RepoGroup, repositories_group_id,
655 or repositories_group name
657 or repositories_group name
656 :param group_name: Instance of UserGroup, users_group_id,
658 :param group_name: Instance of UserGroup, users_group_id,
657 or user group name
659 or user group name
658 """
660 """
659 repo_group = self._get_repo_group(repo_group)
661 repo_group = self._get_repo_group(repo_group)
660 group_name = self._get_user_group(group_name)
662 group_name = self._get_user_group(group_name)
661
663
662 obj = self.sa.query(UserGroupRepoGroupToPerm)\
664 obj = self.sa.query(UserGroupRepoGroupToPerm)\
663 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
665 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
664 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
666 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
665 .scalar()
667 .scalar()
666 if obj:
668 if obj:
667 self.sa.delete(obj)
669 self.sa.delete(obj)
668 log.debug('Revoked perm to %s on %s', repo_group, group_name)
670 log.debug('Revoked perm to %s on %s', repo_group, group_name)
669 action_logger_generic(
671 action_logger_generic(
670 'revoked permission from usergroup: {} on repogroup: {}'.format(
672 'revoked permission from usergroup: {} on repogroup: {}'.format(
671 group_name, repo_group), namespace='security.repogroup')
673 group_name, repo_group), namespace='security.repogroup')
672
674
673 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
675 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
674 super_user_actions=False):
676 super_user_actions=False):
675
677
676 from pyramid.threadlocal import get_current_request
678 from pyramid.threadlocal import get_current_request
677 _render = get_current_request().get_partial_renderer(
679 _render = get_current_request().get_partial_renderer(
678 'data_table/_dt_elements.mako')
680 'data_table/_dt_elements.mako')
679 c = _render.get_call_context()
681 c = _render.get_call_context()
680 h = _render.get_helpers()
682 h = _render.get_helpers()
681
683
682 def quick_menu(repo_group_name):
684 def quick_menu(repo_group_name):
683 return _render('quick_repo_group_menu', repo_group_name)
685 return _render('quick_repo_group_menu', repo_group_name)
684
686
685 def repo_group_lnk(repo_group_name):
687 def repo_group_lnk(repo_group_name):
686 return _render('repo_group_name', repo_group_name)
688 return _render('repo_group_name', repo_group_name)
687
689
690 def last_change(last_change):
691 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
692 last_change = last_change + datetime.timedelta(seconds=
693 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
694 return _render("last_change", last_change)
695
688 def desc(desc, personal):
696 def desc(desc, personal):
689 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
697 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
690
698
691 if c.visual.stylify_metatags:
699 if c.visual.stylify_metatags:
692 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
700 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
693 else:
701 else:
694 desc = h.urlify_text(prefix + h.html_escape(desc))
702 desc = h.urlify_text(prefix + h.html_escape(desc))
695
703
696 return _render('repo_group_desc', desc)
704 return _render('repo_group_desc', desc)
697
705
698 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
706 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
699 return _render(
707 return _render(
700 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
708 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
701
709
702 def repo_group_name(repo_group_name, children_groups):
710 def repo_group_name(repo_group_name, children_groups):
703 return _render("repo_group_name", repo_group_name, children_groups)
711 return _render("repo_group_name", repo_group_name, children_groups)
704
712
705 def user_profile(username):
713 def user_profile(username):
706 return _render('user_profile', username)
714 return _render('user_profile', username)
707
715
708 repo_group_data = []
716 repo_group_data = []
709 for group in repo_group_list:
717 for group in repo_group_list:
710
718
711 row = {
719 row = {
712 "menu": quick_menu(group.group_name),
720 "menu": quick_menu(group.group_name),
713 "name": repo_group_lnk(group.group_name),
721 "name": repo_group_lnk(group.group_name),
714 "name_raw": group.group_name,
722 "name_raw": group.group_name,
723 "last_change": last_change(group.last_db_change),
724 "last_change_raw": datetime_to_time(group.last_db_change),
715 "desc": desc(group.description_safe, group.personal),
725 "desc": desc(group.description_safe, group.personal),
716 "top_level_repos": 0,
726 "top_level_repos": 0,
717 "owner": user_profile(group.user.username)
727 "owner": user_profile(group.user.username)
718 }
728 }
719 if admin:
729 if admin:
720 repo_count = group.repositories.count()
730 repo_count = group.repositories.count()
721 children_groups = map(
731 children_groups = map(
722 h.safe_unicode,
732 h.safe_unicode,
723 itertools.chain((g.name for g in group.parents),
733 itertools.chain((g.name for g in group.parents),
724 (x.name for x in [group])))
734 (x.name for x in [group])))
725 row.update({
735 row.update({
726 "action": repo_group_actions(
736 "action": repo_group_actions(
727 group.group_id, group.group_name, repo_count),
737 group.group_id, group.group_name, repo_count),
728 "top_level_repos": repo_count,
738 "top_level_repos": repo_count,
729 "name": repo_group_name(group.group_name, children_groups),
739 "name": repo_group_name(group.group_name, children_groups),
730
740
731 })
741 })
732 repo_group_data.append(row)
742 repo_group_data.append(row)
733
743
734 return repo_group_data
744 return repo_group_data
@@ -1,94 +1,97 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repository groups administration')}
5 ${_('Repository groups administration')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
17 ${self.menu_items(active='admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 <ul class="links">
24 <ul class="links">
25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
26 <li>
26 <li>
27 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
27 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
28 </li>
28 </li>
29 %endif
29 %endif
30 </ul>
30 </ul>
31 </div>
31 </div>
32 <div id="repos_list_wrap">
32 <div id="repos_list_wrap">
33 <table id="group_list_table" class="display"></table>
33 <table id="group_list_table" class="display"></table>
34 </div>
34 </div>
35 </div>
35 </div>
36
36
37 <script>
37 <script>
38 $(document).ready(function() {
38 $(document).ready(function() {
39
39
40 var get_datatable_count = function(){
40 var get_datatable_count = function(){
41 var api = $('#group_list_table').dataTable().api();
41 var api = $('#group_list_table').dataTable().api();
42 $('#repo_group_count').text(api.page.info().recordsDisplay);
42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 };
43 };
44
44
45 // repo group list
45 // repo group list
46 $('#group_list_table').DataTable({
46 $('#group_list_table').DataTable({
47 data: ${c.data|n},
47 data: ${c.data|n},
48 dom: 'rtp',
48 dom: 'rtp',
49 pageLength: ${c.visual.admin_grid_items},
49 pageLength: ${c.visual.admin_grid_items},
50 order: [[ 0, "asc" ]],
50 order: [[ 0, "asc" ]],
51 columns: [
51 columns: [
52 { data: {"_": "name",
52 { data: {"_": "name",
53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 { data: {"_": "desc",
55 { data: {"_": "desc",
56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 { data: {"_": "last_change",
58 "sort": "last_change_raw",
59 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
57 { data: {"_": "top_level_repos",
60 { data: {"_": "top_level_repos",
58 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
61 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
59 { data: {"_": "owner",
62 { data: {"_": "owner",
60 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
61 { data: {"_": "action",
64 { data: {"_": "action",
62 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
63 ],
66 ],
64 language: {
67 language: {
65 paginate: DEFAULT_GRID_PAGINATION,
68 paginate: DEFAULT_GRID_PAGINATION,
66 emptyTable: _gettext("No repository groups available yet.")
69 emptyTable: _gettext("No repository groups available yet.")
67 },
70 },
68 "initComplete": function( settings, json ) {
71 "initComplete": function( settings, json ) {
69 get_datatable_count();
72 get_datatable_count();
70 quick_repo_menu();
73 quick_repo_menu();
71 }
74 }
72 });
75 });
73
76
74 // update the counter when doing search
77 // update the counter when doing search
75 $('#group_list_table').on( 'search.dt', function (e,settings) {
78 $('#group_list_table').on( 'search.dt', function (e,settings) {
76 get_datatable_count();
79 get_datatable_count();
77 });
80 });
78
81
79 // filter, filter both grids
82 // filter, filter both grids
80 $('#q_filter').on( 'keyup', function () {
83 $('#q_filter').on( 'keyup', function () {
81
84
82 var repo_group_api = $('#group_list_table').dataTable().api();
85 var repo_group_api = $('#group_list_table').dataTable().api();
83 repo_group_api
86 repo_group_api
84 .columns(0)
87 .columns(0)
85 .search(this.value)
88 .search(this.value)
86 .draw();
89 .draw();
87 });
90 });
88
91
89 // refilter table if page load via back button
92 // refilter table if page load via back button
90 $("#q_filter").trigger('keyup');
93 $("#q_filter").trigger('keyup');
91 });
94 });
92 </script>
95 </script>
93 </%def>
96 </%def>
94
97
@@ -1,170 +1,173 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="main()">
3 <%def name="main()">
4 <div class="box">
4 <div class="box">
5 <!-- box / title -->
5 <!-- box / title -->
6 <div class="title">
6 <div class="title">
7 <div class="block-left breadcrumbs">
7 <div class="block-left breadcrumbs">
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 ${self.breadcrumbs()}
9 ${self.breadcrumbs()}
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
11 </div>
11 </div>
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
12 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 <div class="block-right">
13 <div class="block-right">
14 <%
14 <%
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
19
19
20 gr_name = c.repo_group.group_name if c.repo_group else None
20 gr_name = c.repo_group.group_name if c.repo_group else None
21 # create repositories with write permission on group is set to true
21 # create repositories with write permission on group is set to true
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
25 %>
25 %>
26
26
27 %if not c.repo_group:
27 %if not c.repo_group:
28 ## no repository group context here
28 ## no repository group context here
29 %if is_admin or create_repo:
29 %if is_admin or create_repo:
30 <a href="${h.url('new_repo')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
30 <a href="${h.url('new_repo')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
31 %endif
31 %endif
32
32
33 %if is_admin or create_repo_group:
33 %if is_admin or create_repo_group:
34 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
34 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
35 %endif
35 %endif
36 %else:
36 %else:
37 ##we're inside other repository group other terms apply
37 ##we're inside other repository group other terms apply
38 %if is_admin or group_admin or (group_write and create_on_write):
38 %if is_admin or group_admin or (group_write and create_on_write):
39 <a href="${h.url('new_repo',parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
39 <a href="${h.url('new_repo',parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
40 %endif
40 %endif
41 %if is_admin or group_admin:
41 %if is_admin or group_admin:
42 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
42 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
43 %endif
43 %endif
44 %if is_admin or group_admin:
44 %if is_admin or group_admin:
45 <a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
45 <a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
46 %endif
46 %endif
47 %endif
47 %endif
48 </div>
48 </div>
49 %endif
49 %endif
50 </div>
50 </div>
51 <!-- end box / title -->
51 <!-- end box / title -->
52 <div class="table">
52 <div class="table">
53 <div id="groups_list_wrap">
53 <div id="groups_list_wrap">
54 <table id="group_list_table" class="display"></table>
54 <table id="group_list_table" class="display"></table>
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="table">
58 <div class="table">
59 <div id="repos_list_wrap">
59 <div id="repos_list_wrap">
60 <table id="repo_list_table" class="display"></table>
60 <table id="repo_list_table" class="display"></table>
61 </div>
61 </div>
62 </div>
62 </div>
63 </div>
63 </div>
64 <script>
64 <script>
65 $(document).ready(function() {
65 $(document).ready(function() {
66
66
67 var get_datatable_count = function() {
67 var get_datatable_count = function() {
68 var api = $('#repo_list_table').dataTable().api();
68 var api = $('#repo_list_table').dataTable().api();
69 var pageInfo = api.page.info();
69 var pageInfo = api.page.info();
70 var repos = pageInfo.recordsDisplay;
70 var repos = pageInfo.recordsDisplay;
71 var reposTotal = pageInfo.recordsTotal;
71 var reposTotal = pageInfo.recordsTotal;
72
72
73 api = $('#group_list_table').dataTable().api();
73 api = $('#group_list_table').dataTable().api();
74 pageInfo = api.page.info();
74 pageInfo = api.page.info();
75 var repoGroups = pageInfo.recordsDisplay;
75 var repoGroups = pageInfo.recordsDisplay;
76 var repoGroupsTotal = pageInfo.recordsTotal;
76 var repoGroupsTotal = pageInfo.recordsTotal;
77
77
78 if (repoGroups !== repoGroupsTotal) {
78 if (repoGroups !== repoGroupsTotal) {
79 $('#match_count').text(repos+repoGroups);
79 $('#match_count').text(repos+repoGroups);
80 }
80 }
81 if (repos !== reposTotal) {
81 if (repos !== reposTotal) {
82 $('#match_container').show();
82 $('#match_container').show();
83 }
83 }
84 if ($('#q_filter').val() === '') {
84 if ($('#q_filter').val() === '') {
85 $('#match_container').hide();
85 $('#match_container').hide();
86 }
86 }
87 };
87 };
88
88
89 // repo group list
89 // repo group list
90 $('#group_list_table').DataTable({
90 $('#group_list_table').DataTable({
91 data: ${c.repo_groups_data|n},
91 data: ${c.repo_groups_data|n},
92 dom: 'rtp',
92 dom: 'rtp',
93 pageLength: ${c.visual.dashboard_items},
93 pageLength: ${c.visual.dashboard_items},
94 order: [[ 0, "asc" ]],
94 order: [[ 0, "asc" ]],
95 columns: [
95 columns: [
96 { data: {"_": "name",
96 { data: {"_": "name",
97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
99 { data: {"_": "desc",
99 { data: {"_": "desc",
100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
101 { data: {"_": "last_change",
102 "sort": "last_change_raw",
103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
101 { data: {"_": "owner",
104 { data: {"_": "owner",
102 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
103 ],
106 ],
104 language: {
107 language: {
105 paginate: DEFAULT_GRID_PAGINATION,
108 paginate: DEFAULT_GRID_PAGINATION,
106 emptyTable: _gettext("No repository groups available yet.")
109 emptyTable: _gettext("No repository groups available yet.")
107 },
110 },
108 "drawCallback": function( settings, json ) {
111 "drawCallback": function( settings, json ) {
109 timeagoActivate();
112 timeagoActivate();
110 quick_repo_menu();
113 quick_repo_menu();
111 }
114 }
112 });
115 });
113
116
114 // repo list
117 // repo list
115 $('#repo_list_table').DataTable({
118 $('#repo_list_table').DataTable({
116 data: ${c.repos_data|n},
119 data: ${c.repos_data|n},
117 dom: 'rtp',
120 dom: 'rtp',
118 order: [[ 0, "asc" ]],
121 order: [[ 0, "asc" ]],
119 pageLength: ${c.visual.dashboard_items},
122 pageLength: ${c.visual.dashboard_items},
120 columns: [
123 columns: [
121 { data: {"_": "name",
124 { data: {"_": "name",
122 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
123 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
124 { data: {"_": "desc",
127 { data: {"_": "desc",
125 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
126 { data: {"_": "last_change",
129 { data: {"_": "last_change",
127 "sort": "last_change_raw",
130 "sort": "last_change_raw",
128 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
129 { data: {"_": "last_changeset",
132 { data: {"_": "last_changeset",
130 "sort": "last_changeset_raw",
133 "sort": "last_changeset_raw",
131 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
132 { data: {"_": "owner",
135 { data: {"_": "owner",
133 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
134 ],
137 ],
135 language: {
138 language: {
136 paginate: DEFAULT_GRID_PAGINATION,
139 paginate: DEFAULT_GRID_PAGINATION,
137 emptyTable: _gettext("No repositories available yet.")
140 emptyTable: _gettext("No repositories available yet.")
138 },
141 },
139 "drawCallback": function( settings, json ) {
142 "drawCallback": function( settings, json ) {
140 timeagoActivate();
143 timeagoActivate();
141 quick_repo_menu();
144 quick_repo_menu();
142 }
145 }
143 });
146 });
144
147
145 // update the counter when doing search
148 // update the counter when doing search
146 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
147 get_datatable_count();
150 get_datatable_count();
148 });
151 });
149
152
150 // filter, filter both grids
153 // filter, filter both grids
151 $('#q_filter').on( 'keyup', function () {
154 $('#q_filter').on( 'keyup', function () {
152 var repo_api = $('#repo_list_table').dataTable().api();
155 var repo_api = $('#repo_list_table').dataTable().api();
153 repo_api
156 repo_api
154 .columns( 0 )
157 .columns( 0 )
155 .search( this.value )
158 .search( this.value )
156 .draw();
159 .draw();
157
160
158 var repo_group_api = $('#group_list_table').dataTable().api();
161 var repo_group_api = $('#group_list_table').dataTable().api();
159 repo_group_api
162 repo_group_api
160 .columns( 0 )
163 .columns( 0 )
161 .search( this.value )
164 .search( this.value )
162 .draw();
165 .draw();
163 });
166 });
164
167
165 // refilter table if page load via back button
168 // refilter table if page load via back button
166 $("#q_filter").trigger('keyup');
169 $("#q_filter").trigger('keyup');
167
170
168 });
171 });
169 </script>
172 </script>
170 </%def>
173 </%def>
General Comments 0
You need to be logged in to leave comments. Login now