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