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