##// END OF EJS Templates
artifacts: refactor metadata code...
marcink -
r3997:823cbf31 default
parent child Browse files
Show More
@@ -1,159 +1,171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Set of custom exceptions used in RhodeCode
22 Set of custom exceptions used in RhodeCode
23 """
23 """
24
24
25 from webob.exc import HTTPClientError
25 from webob.exc import HTTPClientError
26 from pyramid.httpexceptions import HTTPBadGateway
26 from pyramid.httpexceptions import HTTPBadGateway
27
27
28
28
29 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
30 pass
30 pass
31
31
32
32
33 class LdapPasswordError(Exception):
33 class LdapPasswordError(Exception):
34 pass
34 pass
35
35
36
36
37 class LdapConnectionError(Exception):
37 class LdapConnectionError(Exception):
38 pass
38 pass
39
39
40
40
41 class LdapImportError(Exception):
41 class LdapImportError(Exception):
42 pass
42 pass
43
43
44
44
45 class DefaultUserException(Exception):
45 class DefaultUserException(Exception):
46 pass
46 pass
47
47
48
48
49 class UserOwnsReposException(Exception):
49 class UserOwnsReposException(Exception):
50 pass
50 pass
51
51
52
52
53 class UserOwnsRepoGroupsException(Exception):
53 class UserOwnsRepoGroupsException(Exception):
54 pass
54 pass
55
55
56
56
57 class UserOwnsUserGroupsException(Exception):
57 class UserOwnsUserGroupsException(Exception):
58 pass
58 pass
59
59
60
60
61 class UserGroupAssignedException(Exception):
61 class UserGroupAssignedException(Exception):
62 pass
62 pass
63
63
64
64
65 class StatusChangeOnClosedPullRequestError(Exception):
65 class StatusChangeOnClosedPullRequestError(Exception):
66 pass
66 pass
67
67
68
68
69 class AttachedForksError(Exception):
69 class AttachedForksError(Exception):
70 pass
70 pass
71
71
72
72
73 class AttachedPullRequestsError(Exception):
73 class AttachedPullRequestsError(Exception):
74 pass
74 pass
75
75
76
76
77 class RepoGroupAssignmentError(Exception):
77 class RepoGroupAssignmentError(Exception):
78 pass
78 pass
79
79
80
80
81 class NonRelativePathError(Exception):
81 class NonRelativePathError(Exception):
82 pass
82 pass
83
83
84
84
85 class HTTPRequirementError(HTTPClientError):
85 class HTTPRequirementError(HTTPClientError):
86 title = explanation = 'Repository Requirement Missing'
86 title = explanation = 'Repository Requirement Missing'
87 reason = None
87 reason = None
88
88
89 def __init__(self, message, *args, **kwargs):
89 def __init__(self, message, *args, **kwargs):
90 self.title = self.explanation = message
90 self.title = self.explanation = message
91 super(HTTPRequirementError, self).__init__(*args, **kwargs)
91 super(HTTPRequirementError, self).__init__(*args, **kwargs)
92 self.args = (message, )
92 self.args = (message, )
93
93
94
94
95 class HTTPLockedRC(HTTPClientError):
95 class HTTPLockedRC(HTTPClientError):
96 """
96 """
97 Special Exception For locked Repos in RhodeCode, the return code can
97 Special Exception For locked Repos in RhodeCode, the return code can
98 be overwritten by _code keyword argument passed into constructors
98 be overwritten by _code keyword argument passed into constructors
99 """
99 """
100 code = 423
100 code = 423
101 title = explanation = 'Repository Locked'
101 title = explanation = 'Repository Locked'
102 reason = None
102 reason = None
103
103
104 def __init__(self, message, *args, **kwargs):
104 def __init__(self, message, *args, **kwargs):
105 from rhodecode import CONFIG
105 from rhodecode import CONFIG
106 from rhodecode.lib.utils2 import safe_int
106 from rhodecode.lib.utils2 import safe_int
107 _code = CONFIG.get('lock_ret_code')
107 _code = CONFIG.get('lock_ret_code')
108 self.code = safe_int(_code, self.code)
108 self.code = safe_int(_code, self.code)
109 self.title = self.explanation = message
109 self.title = self.explanation = message
110 super(HTTPLockedRC, self).__init__(*args, **kwargs)
110 super(HTTPLockedRC, self).__init__(*args, **kwargs)
111 self.args = (message, )
111 self.args = (message, )
112
112
113
113
114 class HTTPBranchProtected(HTTPClientError):
114 class HTTPBranchProtected(HTTPClientError):
115 """
115 """
116 Special Exception For Indicating that branch is protected in RhodeCode, the
116 Special Exception For Indicating that branch is protected in RhodeCode, the
117 return code can be overwritten by _code keyword argument passed into constructors
117 return code can be overwritten by _code keyword argument passed into constructors
118 """
118 """
119 code = 403
119 code = 403
120 title = explanation = 'Branch Protected'
120 title = explanation = 'Branch Protected'
121 reason = None
121 reason = None
122
122
123 def __init__(self, message, *args, **kwargs):
123 def __init__(self, message, *args, **kwargs):
124 self.title = self.explanation = message
124 self.title = self.explanation = message
125 super(HTTPBranchProtected, self).__init__(*args, **kwargs)
125 super(HTTPBranchProtected, self).__init__(*args, **kwargs)
126 self.args = (message, )
126 self.args = (message, )
127
127
128
128
129 class IMCCommitError(Exception):
129 class IMCCommitError(Exception):
130 pass
130 pass
131
131
132
132
133 class UserCreationError(Exception):
133 class UserCreationError(Exception):
134 pass
134 pass
135
135
136
136
137 class NotAllowedToCreateUserError(Exception):
137 class NotAllowedToCreateUserError(Exception):
138 pass
138 pass
139
139
140
140
141 class RepositoryCreationError(Exception):
141 class RepositoryCreationError(Exception):
142 pass
142 pass
143
143
144
144
145 class VCSServerUnavailable(HTTPBadGateway):
145 class VCSServerUnavailable(HTTPBadGateway):
146 """ HTTP Exception class for VCS Server errors """
146 """ HTTP Exception class for VCS Server errors """
147 code = 502
147 code = 502
148 title = 'VCS Server Error'
148 title = 'VCS Server Error'
149 causes = [
149 causes = [
150 'VCS Server is not running',
150 'VCS Server is not running',
151 'Incorrect vcs.server=host:port',
151 'Incorrect vcs.server=host:port',
152 'Incorrect vcs.server.protocol',
152 'Incorrect vcs.server.protocol',
153 ]
153 ]
154
154
155 def __init__(self, message=''):
155 def __init__(self, message=''):
156 self.explanation = 'Could not connect to VCS Server'
156 self.explanation = 'Could not connect to VCS Server'
157 if message:
157 if message:
158 self.explanation += ': ' + message
158 self.explanation += ': ' + message
159 super(VCSServerUnavailable, self).__init__()
159 super(VCSServerUnavailable, self).__init__()
160
161
162 class ArtifactMetadataDuplicate(ValueError):
163
164 def __init__(self, *args, **kwargs):
165 self.err_section = kwargs.pop('err_section', None)
166 self.err_key = kwargs.pop('err_key', None)
167 super(ArtifactMetadataDuplicate, self).__init__(*args, **kwargs)
168
169
170 class ArtifactMetadataBadValueType(ValueError):
171 pass
@@ -1,5316 +1,5355 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, TypeDecorator, event,
40 or_, and_, not_, func, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType)
43 Text, Float, PickleType)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers.text import collapse, remove_formatting
55 from webhelpers.text import collapse, remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
70 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
71
73
72 URL_SEP = '/'
74 URL_SEP = '/'
73 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
74
76
75 # =============================================================================
77 # =============================================================================
76 # BASE CLASSES
78 # BASE CLASSES
77 # =============================================================================
79 # =============================================================================
78
80
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
80 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
81 # and initialized at environment.py
83 # and initialized at environment.py
82 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
83
85
84 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
85 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
86 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
87 'admin': '####',
89 'admin': '####',
88 'write': '###',
90 'write': '###',
89 'read': '##',
91 'read': '##',
90 'none': '#',
92 'none': '#',
91 }
93 }
92
94
93
95
94 def display_user_sort(obj):
96 def display_user_sort(obj):
95 """
97 """
96 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
98 of all other resources
100 of all other resources
99 """
101 """
100
102
101 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
102 return '#####'
104 return '#####'
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
104 return prefix + obj.username
106 return prefix + obj.username
105
107
106
108
107 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
108 """
110 """
109 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
111 of all other resources
113 of all other resources
112 """
114 """
113
115
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
115 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
116
118
117
119
118 def _hash_key(k):
120 def _hash_key(k):
119 return sha1_safe(k)
121 return sha1_safe(k)
120
122
121
123
122 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
123 """
125 """
124 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
125 e.g.::
127 e.g.::
126 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
127 or_(
129 or_(
128 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
129 )).count()
131 )).count()
130 """
132 """
131 if not items:
133 if not items:
132 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
133 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
134 items = [-1]
136 items = [-1]
135
137
136 parts = []
138 parts = []
137 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
138 parts.append(
140 parts.append(
139 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
140 )
142 )
141
143
142 return parts
144 return parts
143
145
144
146
145 base_table_args = {
147 base_table_args = {
146 'extend_existing': True,
148 'extend_existing': True,
147 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
148 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
149 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
150 }
152 }
151
153
152
154
153 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
154 """
156 """
155 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
156
158
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
158
160
159 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
160 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
161 """
163 """
162 impl = Text
164 impl = Text
163
165
164 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
165 """
167 """
166 Setter for storing value
168 Setter for storing value
167 """
169 """
168 import rhodecode
170 import rhodecode
169 if not value:
171 if not value:
170 return value
172 return value
171
173
172 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
173 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
174 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
175 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
176 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
177 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
178
180
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
180 if algo == 'aes':
182 if algo == 'aes':
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
182 elif algo == 'fernet':
184 elif algo == 'fernet':
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
184 else:
186 else:
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
186
188
187 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
188 """
190 """
189 Getter for retrieving value
191 Getter for retrieving value
190 """
192 """
191
193
192 import rhodecode
194 import rhodecode
193 if not value:
195 if not value:
194 return value
196 return value
195
197
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
198 if algo == 'aes':
200 if algo == 'aes':
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
200 elif algo == 'fernet':
202 elif algo == 'fernet':
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
202 else:
204 else:
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
204 return decrypted_data
206 return decrypted_data
205
207
206
208
207 class BaseModel(object):
209 class BaseModel(object):
208 """
210 """
209 Base Model for all classes
211 Base Model for all classes
210 """
212 """
211
213
212 @classmethod
214 @classmethod
213 def _get_keys(cls):
215 def _get_keys(cls):
214 """return column names for this model """
216 """return column names for this model """
215 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
216
218
217 def get_dict(self):
219 def get_dict(self):
218 """
220 """
219 return dict with keys and values corresponding
221 return dict with keys and values corresponding
220 to this model data """
222 to this model data """
221
223
222 d = {}
224 d = {}
223 for k in self._get_keys():
225 for k in self._get_keys():
224 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
225
227
226 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
227 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
228 if _json_attr:
230 if _json_attr:
229 # update with attributes from __json__
231 # update with attributes from __json__
230 if callable(_json_attr):
232 if callable(_json_attr):
231 _json_attr = _json_attr()
233 _json_attr = _json_attr()
232 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
233 d[k] = val
235 d[k] = val
234 return d
236 return d
235
237
236 def get_appstruct(self):
238 def get_appstruct(self):
237 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
238 to this model data """
240 to this model data """
239
241
240 lst = []
242 lst = []
241 for k in self._get_keys():
243 for k in self._get_keys():
242 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
243 return lst
245 return lst
244
246
245 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
246 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
247
249
248 for k in self._get_keys():
250 for k in self._get_keys():
249 if k in populate_dict:
251 if k in populate_dict:
250 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
251
253
252 @classmethod
254 @classmethod
253 def query(cls):
255 def query(cls):
254 return Session().query(cls)
256 return Session().query(cls)
255
257
256 @classmethod
258 @classmethod
257 def get(cls, id_):
259 def get(cls, id_):
258 if id_:
260 if id_:
259 return cls.query().get(id_)
261 return cls.query().get(id_)
260
262
261 @classmethod
263 @classmethod
262 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
263 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
264
266
265 try:
267 try:
266 id_ = int(id_)
268 id_ = int(id_)
267 except (TypeError, ValueError):
269 except (TypeError, ValueError):
268 raise HTTPNotFound()
270 raise HTTPNotFound()
269
271
270 res = cls.query().get(id_)
272 res = cls.query().get(id_)
271 if not res:
273 if not res:
272 raise HTTPNotFound()
274 raise HTTPNotFound()
273 return res
275 return res
274
276
275 @classmethod
277 @classmethod
276 def getAll(cls):
278 def getAll(cls):
277 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
278 return cls.get_all()
280 return cls.get_all()
279
281
280 @classmethod
282 @classmethod
281 def get_all(cls):
283 def get_all(cls):
282 return cls.query().all()
284 return cls.query().all()
283
285
284 @classmethod
286 @classmethod
285 def delete(cls, id_):
287 def delete(cls, id_):
286 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
287 Session().delete(obj)
289 Session().delete(obj)
288
290
289 @classmethod
291 @classmethod
290 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
291 exist_in_session = []
293 exist_in_session = []
292 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
293 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
294 exist_in_session.append(instance)
296 exist_in_session.append(instance)
295 if exist_in_session:
297 if exist_in_session:
296 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
297 return exist_in_session[0]
299 return exist_in_session[0]
298 log.exception(
300 log.exception(
299 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
300 'value %s found with same name: %r',
302 'value %s found with same name: %r',
301 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
302
304
303 def __repr__(self):
305 def __repr__(self):
304 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
305 # python repr needs to return str
307 # python repr needs to return str
306 try:
308 try:
307 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
308 except UnicodeDecodeError:
310 except UnicodeDecodeError:
309 pass
311 pass
310 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
311
313
312
314
313 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
314 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
315 __table_args__ = (
317 __table_args__ = (
316 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
317 base_table_args
319 base_table_args
318 )
320 )
319
321
320 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
321 'str': safe_str,
323 'str': safe_str,
322 'int': safe_int,
324 'int': safe_int,
323 'unicode': safe_unicode,
325 'unicode': safe_unicode,
324 'bool': str2bool,
326 'bool': str2bool,
325 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
326 }
328 }
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
328 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
329
331
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
334
336
335 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
336 self.app_settings_name = key
338 self.app_settings_name = key
337 self.app_settings_type = type
339 self.app_settings_type = type
338 self.app_settings_value = val
340 self.app_settings_value = val
339
341
340 @validates('_app_settings_value')
342 @validates('_app_settings_value')
341 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
342 assert type(val) == unicode
344 assert type(val) == unicode
343 return val
345 return val
344
346
345 @hybrid_property
347 @hybrid_property
346 def app_settings_value(self):
348 def app_settings_value(self):
347 v = self._app_settings_value
349 v = self._app_settings_value
348 _type = self.app_settings_type
350 _type = self.app_settings_type
349 if _type:
351 if _type:
350 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
351 # decode the encrypted value
353 # decode the encrypted value
352 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
353 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
354 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
355
357
356 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
357 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
358 return converter(v)
360 return converter(v)
359
361
360 @app_settings_value.setter
362 @app_settings_value.setter
361 def app_settings_value(self, val):
363 def app_settings_value(self, val):
362 """
364 """
363 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
364
366
365 :param val:
367 :param val:
366 """
368 """
367 val = safe_unicode(val)
369 val = safe_unicode(val)
368 # encode the encrypted value
370 # encode the encrypted value
369 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
370 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
371 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
372 self._app_settings_value = val
374 self._app_settings_value = val
373
375
374 @hybrid_property
376 @hybrid_property
375 def app_settings_type(self):
377 def app_settings_type(self):
376 return self._app_settings_type
378 return self._app_settings_type
377
379
378 @app_settings_type.setter
380 @app_settings_type.setter
379 def app_settings_type(self, val):
381 def app_settings_type(self, val):
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
381 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
382 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
383 self._app_settings_type = val
385 self._app_settings_type = val
384
386
385 @classmethod
387 @classmethod
386 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
387 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
389 .all()
391 .all()
390
392
391 def __unicode__(self):
393 def __unicode__(self):
392 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
393 self.__class__.__name__,
395 self.__class__.__name__,
394 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
395 self.app_settings_type
397 self.app_settings_type
396 )
398 )
397
399
398
400
399 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
400 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
401 __table_args__ = (
403 __table_args__ = (
402 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
403 base_table_args
405 base_table_args
404 )
406 )
405
407
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
407 # HG
409 # HG
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
409 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
412 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
414
416
415 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
416 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
417 HOOK_PULL,
419 HOOK_PULL,
418 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
419 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
420 HOOK_PUSH,
422 HOOK_PUSH,
421 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
422 ]
424 ]
423
425
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
425 # git part is currently hardcoded.
427 # git part is currently hardcoded.
426
428
427 # SVN PATTERNS
429 # SVN PATTERNS
428 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
429 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
430
432
431 ui_id = Column(
433 ui_id = Column(
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
433 primary_key=True)
435 primary_key=True)
434 ui_section = Column(
436 ui_section = Column(
435 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
436 ui_key = Column(
438 ui_key = Column(
437 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
438 ui_value = Column(
440 ui_value = Column(
439 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
440 ui_active = Column(
442 ui_active = Column(
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
442
444
443 def __repr__(self):
445 def __repr__(self):
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
445 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
446
448
447
449
448 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
449 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
450 __table_args__ = (
452 __table_args__ = (
451 UniqueConstraint(
453 UniqueConstraint(
452 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
453 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
454 base_table_args
456 base_table_args
455 )
457 )
456
458
457 repository_id = Column(
459 repository_id = Column(
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
459 nullable=False)
461 nullable=False)
460 app_settings_id = Column(
462 app_settings_id = Column(
461 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
462 default=None, primary_key=True)
464 default=None, primary_key=True)
463 app_settings_name = Column(
465 app_settings_name = Column(
464 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
465 default=None)
467 default=None)
466 _app_settings_value = Column(
468 _app_settings_value = Column(
467 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
468 default=None)
470 default=None)
469 _app_settings_type = Column(
471 _app_settings_type = Column(
470 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
471 default=None)
473 default=None)
472
474
473 repository = relationship('Repository')
475 repository = relationship('Repository')
474
476
475 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
476 self.repository_id = repository_id
478 self.repository_id = repository_id
477 self.app_settings_name = key
479 self.app_settings_name = key
478 self.app_settings_type = type
480 self.app_settings_type = type
479 self.app_settings_value = val
481 self.app_settings_value = val
480
482
481 @validates('_app_settings_value')
483 @validates('_app_settings_value')
482 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
483 assert type(val) == unicode
485 assert type(val) == unicode
484 return val
486 return val
485
487
486 @hybrid_property
488 @hybrid_property
487 def app_settings_value(self):
489 def app_settings_value(self):
488 v = self._app_settings_value
490 v = self._app_settings_value
489 type_ = self.app_settings_type
491 type_ = self.app_settings_type
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
492 return converter(v)
494 return converter(v)
493
495
494 @app_settings_value.setter
496 @app_settings_value.setter
495 def app_settings_value(self, val):
497 def app_settings_value(self, val):
496 """
498 """
497 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
498
500
499 :param val:
501 :param val:
500 """
502 """
501 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
502
504
503 @hybrid_property
505 @hybrid_property
504 def app_settings_type(self):
506 def app_settings_type(self):
505 return self._app_settings_type
507 return self._app_settings_type
506
508
507 @app_settings_type.setter
509 @app_settings_type.setter
508 def app_settings_type(self, val):
510 def app_settings_type(self, val):
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
510 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
511 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
512 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
513 self._app_settings_type = val
515 self._app_settings_type = val
514
516
515 def __unicode__(self):
517 def __unicode__(self):
516 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
517 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
518 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
519 self.app_settings_type
521 self.app_settings_type
520 )
522 )
521
523
522
524
523 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
524 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
525 __table_args__ = (
527 __table_args__ = (
526 UniqueConstraint(
528 UniqueConstraint(
527 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
529 base_table_args
531 base_table_args
530 )
532 )
531
533
532 repository_id = Column(
534 repository_id = Column(
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
534 nullable=False)
536 nullable=False)
535 ui_id = Column(
537 ui_id = Column(
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
537 primary_key=True)
539 primary_key=True)
538 ui_section = Column(
540 ui_section = Column(
539 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
540 ui_key = Column(
542 ui_key = Column(
541 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
542 ui_value = Column(
544 ui_value = Column(
543 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
544 ui_active = Column(
546 ui_active = Column(
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
546
548
547 repository = relationship('Repository')
549 repository = relationship('Repository')
548
550
549 def __repr__(self):
551 def __repr__(self):
550 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
551 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
552 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
553
555
554
556
555 class User(Base, BaseModel):
557 class User(Base, BaseModel):
556 __tablename__ = 'users'
558 __tablename__ = 'users'
557 __table_args__ = (
559 __table_args__ = (
558 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
559 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
560 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
561 base_table_args
563 base_table_args
562 )
564 )
563
565
564 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
567
569
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
578
580
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
581 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
582 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
583 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
584 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
585 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
586 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
585
587
586 user_log = relationship('UserLog')
588 user_log = relationship('UserLog')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
589 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
588
590
589 repositories = relationship('Repository')
591 repositories = relationship('Repository')
590 repository_groups = relationship('RepoGroup')
592 repository_groups = relationship('RepoGroup')
591 user_groups = relationship('UserGroup')
593 user_groups = relationship('UserGroup')
592
594
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
595 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
596 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
595
597
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
598 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599
601
600 group_member = relationship('UserGroupMember', cascade='all')
602 group_member = relationship('UserGroupMember', cascade='all')
601
603
602 notifications = relationship('UserNotification', cascade='all')
604 notifications = relationship('UserNotification', cascade='all')
603 # notifications assigned to this user
605 # notifications assigned to this user
604 user_created_notifications = relationship('Notification', cascade='all')
606 user_created_notifications = relationship('Notification', cascade='all')
605 # comments created by this user
607 # comments created by this user
606 user_comments = relationship('ChangesetComment', cascade='all')
608 user_comments = relationship('ChangesetComment', cascade='all')
607 # user profile extra info
609 # user profile extra info
608 user_emails = relationship('UserEmailMap', cascade='all')
610 user_emails = relationship('UserEmailMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
611 user_ip_map = relationship('UserIpMap', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
612 user_auth_tokens = relationship('UserApiKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
613 user_ssh_keys = relationship('UserSshKeys', cascade='all')
612
614
613 # gists
615 # gists
614 user_gists = relationship('Gist', cascade='all')
616 user_gists = relationship('Gist', cascade='all')
615 # user pull requests
617 # user pull requests
616 user_pull_requests = relationship('PullRequest', cascade='all')
618 user_pull_requests = relationship('PullRequest', cascade='all')
617 # external identities
619 # external identities
618 extenal_identities = relationship(
620 extenal_identities = relationship(
619 'ExternalIdentity',
621 'ExternalIdentity',
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
622 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
621 cascade='all')
623 cascade='all')
622 # review rules
624 # review rules
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
625 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
624
626
625 def __unicode__(self):
627 def __unicode__(self):
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
628 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
627 self.user_id, self.username)
629 self.user_id, self.username)
628
630
629 @hybrid_property
631 @hybrid_property
630 def email(self):
632 def email(self):
631 return self._email
633 return self._email
632
634
633 @email.setter
635 @email.setter
634 def email(self, val):
636 def email(self, val):
635 self._email = val.lower() if val else None
637 self._email = val.lower() if val else None
636
638
637 @hybrid_property
639 @hybrid_property
638 def first_name(self):
640 def first_name(self):
639 from rhodecode.lib import helpers as h
641 from rhodecode.lib import helpers as h
640 if self.name:
642 if self.name:
641 return h.escape(self.name)
643 return h.escape(self.name)
642 return self.name
644 return self.name
643
645
644 @hybrid_property
646 @hybrid_property
645 def last_name(self):
647 def last_name(self):
646 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
647 if self.lastname:
649 if self.lastname:
648 return h.escape(self.lastname)
650 return h.escape(self.lastname)
649 return self.lastname
651 return self.lastname
650
652
651 @hybrid_property
653 @hybrid_property
652 def api_key(self):
654 def api_key(self):
653 """
655 """
654 Fetch if exist an auth-token with role ALL connected to this user
656 Fetch if exist an auth-token with role ALL connected to this user
655 """
657 """
656 user_auth_token = UserApiKeys.query()\
658 user_auth_token = UserApiKeys.query()\
657 .filter(UserApiKeys.user_id == self.user_id)\
659 .filter(UserApiKeys.user_id == self.user_id)\
658 .filter(or_(UserApiKeys.expires == -1,
660 .filter(or_(UserApiKeys.expires == -1,
659 UserApiKeys.expires >= time.time()))\
661 UserApiKeys.expires >= time.time()))\
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
662 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
661 if user_auth_token:
663 if user_auth_token:
662 user_auth_token = user_auth_token.api_key
664 user_auth_token = user_auth_token.api_key
663
665
664 return user_auth_token
666 return user_auth_token
665
667
666 @api_key.setter
668 @api_key.setter
667 def api_key(self, val):
669 def api_key(self, val):
668 # don't allow to set API key this is deprecated for now
670 # don't allow to set API key this is deprecated for now
669 self._api_key = None
671 self._api_key = None
670
672
671 @property
673 @property
672 def reviewer_pull_requests(self):
674 def reviewer_pull_requests(self):
673 return PullRequestReviewers.query() \
675 return PullRequestReviewers.query() \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
676 .options(joinedload(PullRequestReviewers.pull_request)) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
677 .filter(PullRequestReviewers.user_id == self.user_id) \
676 .all()
678 .all()
677
679
678 @property
680 @property
679 def firstname(self):
681 def firstname(self):
680 # alias for future
682 # alias for future
681 return self.name
683 return self.name
682
684
683 @property
685 @property
684 def emails(self):
686 def emails(self):
685 other = UserEmailMap.query()\
687 other = UserEmailMap.query()\
686 .filter(UserEmailMap.user == self) \
688 .filter(UserEmailMap.user == self) \
687 .order_by(UserEmailMap.email_id.asc()) \
689 .order_by(UserEmailMap.email_id.asc()) \
688 .all()
690 .all()
689 return [self.email] + [x.email for x in other]
691 return [self.email] + [x.email for x in other]
690
692
691 @property
693 @property
692 def auth_tokens(self):
694 def auth_tokens(self):
693 auth_tokens = self.get_auth_tokens()
695 auth_tokens = self.get_auth_tokens()
694 return [x.api_key for x in auth_tokens]
696 return [x.api_key for x in auth_tokens]
695
697
696 def get_auth_tokens(self):
698 def get_auth_tokens(self):
697 return UserApiKeys.query()\
699 return UserApiKeys.query()\
698 .filter(UserApiKeys.user == self)\
700 .filter(UserApiKeys.user == self)\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
701 .order_by(UserApiKeys.user_api_key_id.asc())\
700 .all()
702 .all()
701
703
702 @LazyProperty
704 @LazyProperty
703 def feed_token(self):
705 def feed_token(self):
704 return self.get_feed_token()
706 return self.get_feed_token()
705
707
706 def get_feed_token(self, cache=True):
708 def get_feed_token(self, cache=True):
707 feed_tokens = UserApiKeys.query()\
709 feed_tokens = UserApiKeys.query()\
708 .filter(UserApiKeys.user == self)\
710 .filter(UserApiKeys.user == self)\
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
711 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
710 if cache:
712 if cache:
711 feed_tokens = feed_tokens.options(
713 feed_tokens = feed_tokens.options(
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
714 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
713
715
714 feed_tokens = feed_tokens.all()
716 feed_tokens = feed_tokens.all()
715 if feed_tokens:
717 if feed_tokens:
716 return feed_tokens[0].api_key
718 return feed_tokens[0].api_key
717 return 'NO_FEED_TOKEN_AVAILABLE'
719 return 'NO_FEED_TOKEN_AVAILABLE'
718
720
719 @classmethod
721 @classmethod
720 def get(cls, user_id, cache=False):
722 def get(cls, user_id, cache=False):
721 if not user_id:
723 if not user_id:
722 return
724 return
723
725
724 user = cls.query()
726 user = cls.query()
725 if cache:
727 if cache:
726 user = user.options(
728 user = user.options(
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
729 FromCache("sql_cache_short", "get_users_%s" % user_id))
728 return user.get(user_id)
730 return user.get(user_id)
729
731
730 @classmethod
732 @classmethod
731 def extra_valid_auth_tokens(cls, user, role=None):
733 def extra_valid_auth_tokens(cls, user, role=None):
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
734 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
733 .filter(or_(UserApiKeys.expires == -1,
735 .filter(or_(UserApiKeys.expires == -1,
734 UserApiKeys.expires >= time.time()))
736 UserApiKeys.expires >= time.time()))
735 if role:
737 if role:
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
738 tokens = tokens.filter(or_(UserApiKeys.role == role,
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
739 UserApiKeys.role == UserApiKeys.ROLE_ALL))
738 return tokens.all()
740 return tokens.all()
739
741
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
742 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
741 from rhodecode.lib import auth
743 from rhodecode.lib import auth
742
744
743 log.debug('Trying to authenticate user: %s via auth-token, '
745 log.debug('Trying to authenticate user: %s via auth-token, '
744 'and roles: %s', self, roles)
746 'and roles: %s', self, roles)
745
747
746 if not auth_token:
748 if not auth_token:
747 return False
749 return False
748
750
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
751 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
750 tokens_q = UserApiKeys.query()\
752 tokens_q = UserApiKeys.query()\
751 .filter(UserApiKeys.user_id == self.user_id)\
753 .filter(UserApiKeys.user_id == self.user_id)\
752 .filter(or_(UserApiKeys.expires == -1,
754 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))
755 UserApiKeys.expires >= time.time()))
754
756
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
757 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
756
758
757 crypto_backend = auth.crypto_backend()
759 crypto_backend = auth.crypto_backend()
758 enc_token_map = {}
760 enc_token_map = {}
759 plain_token_map = {}
761 plain_token_map = {}
760 for token in tokens_q:
762 for token in tokens_q:
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
763 if token.api_key.startswith(crypto_backend.ENC_PREF):
762 enc_token_map[token.api_key] = token
764 enc_token_map[token.api_key] = token
763 else:
765 else:
764 plain_token_map[token.api_key] = token
766 plain_token_map[token.api_key] = token
765 log.debug(
767 log.debug(
766 'Found %s plain and %s encrypted user tokens to check for authentication',
768 'Found %s plain and %s encrypted user tokens to check for authentication',
767 len(plain_token_map), len(enc_token_map))
769 len(plain_token_map), len(enc_token_map))
768
770
769 # plain token match comes first
771 # plain token match comes first
770 match = plain_token_map.get(auth_token)
772 match = plain_token_map.get(auth_token)
771
773
772 # check encrypted tokens now
774 # check encrypted tokens now
773 if not match:
775 if not match:
774 for token_hash, token in enc_token_map.items():
776 for token_hash, token in enc_token_map.items():
775 # NOTE(marcink): this is expensive to calculate, but most secure
777 # NOTE(marcink): this is expensive to calculate, but most secure
776 if crypto_backend.hash_check(auth_token, token_hash):
778 if crypto_backend.hash_check(auth_token, token_hash):
777 match = token
779 match = token
778 break
780 break
779
781
780 if match:
782 if match:
781 log.debug('Found matching token %s', match)
783 log.debug('Found matching token %s', match)
782 if match.repo_id:
784 if match.repo_id:
783 log.debug('Found scope, checking for scope match of token %s', match)
785 log.debug('Found scope, checking for scope match of token %s', match)
784 if match.repo_id == scope_repo_id:
786 if match.repo_id == scope_repo_id:
785 return True
787 return True
786 else:
788 else:
787 log.debug(
789 log.debug(
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
790 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
789 'and calling scope is:%s, skipping further checks',
791 'and calling scope is:%s, skipping further checks',
790 match.repo, scope_repo_id)
792 match.repo, scope_repo_id)
791 return False
793 return False
792 else:
794 else:
793 return True
795 return True
794
796
795 return False
797 return False
796
798
797 @property
799 @property
798 def ip_addresses(self):
800 def ip_addresses(self):
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
801 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
800 return [x.ip_addr for x in ret]
802 return [x.ip_addr for x in ret]
801
803
802 @property
804 @property
803 def username_and_name(self):
805 def username_and_name(self):
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
806 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
805
807
806 @property
808 @property
807 def username_or_name_or_email(self):
809 def username_or_name_or_email(self):
808 full_name = self.full_name if self.full_name is not ' ' else None
810 full_name = self.full_name if self.full_name is not ' ' else None
809 return self.username or full_name or self.email
811 return self.username or full_name or self.email
810
812
811 @property
813 @property
812 def full_name(self):
814 def full_name(self):
813 return '%s %s' % (self.first_name, self.last_name)
815 return '%s %s' % (self.first_name, self.last_name)
814
816
815 @property
817 @property
816 def full_name_or_username(self):
818 def full_name_or_username(self):
817 return ('%s %s' % (self.first_name, self.last_name)
819 return ('%s %s' % (self.first_name, self.last_name)
818 if (self.first_name and self.last_name) else self.username)
820 if (self.first_name and self.last_name) else self.username)
819
821
820 @property
822 @property
821 def full_contact(self):
823 def full_contact(self):
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
824 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
823
825
824 @property
826 @property
825 def short_contact(self):
827 def short_contact(self):
826 return '%s %s' % (self.first_name, self.last_name)
828 return '%s %s' % (self.first_name, self.last_name)
827
829
828 @property
830 @property
829 def is_admin(self):
831 def is_admin(self):
830 return self.admin
832 return self.admin
831
833
832 def AuthUser(self, **kwargs):
834 def AuthUser(self, **kwargs):
833 """
835 """
834 Returns instance of AuthUser for this user
836 Returns instance of AuthUser for this user
835 """
837 """
836 from rhodecode.lib.auth import AuthUser
838 from rhodecode.lib.auth import AuthUser
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
839 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
838
840
839 @hybrid_property
841 @hybrid_property
840 def user_data(self):
842 def user_data(self):
841 if not self._user_data:
843 if not self._user_data:
842 return {}
844 return {}
843
845
844 try:
846 try:
845 return json.loads(self._user_data)
847 return json.loads(self._user_data)
846 except TypeError:
848 except TypeError:
847 return {}
849 return {}
848
850
849 @user_data.setter
851 @user_data.setter
850 def user_data(self, val):
852 def user_data(self, val):
851 if not isinstance(val, dict):
853 if not isinstance(val, dict):
852 raise Exception('user_data must be dict, got %s' % type(val))
854 raise Exception('user_data must be dict, got %s' % type(val))
853 try:
855 try:
854 self._user_data = json.dumps(val)
856 self._user_data = json.dumps(val)
855 except Exception:
857 except Exception:
856 log.error(traceback.format_exc())
858 log.error(traceback.format_exc())
857
859
858 @classmethod
860 @classmethod
859 def get_by_username(cls, username, case_insensitive=False,
861 def get_by_username(cls, username, case_insensitive=False,
860 cache=False, identity_cache=False):
862 cache=False, identity_cache=False):
861 session = Session()
863 session = Session()
862
864
863 if case_insensitive:
865 if case_insensitive:
864 q = cls.query().filter(
866 q = cls.query().filter(
865 func.lower(cls.username) == func.lower(username))
867 func.lower(cls.username) == func.lower(username))
866 else:
868 else:
867 q = cls.query().filter(cls.username == username)
869 q = cls.query().filter(cls.username == username)
868
870
869 if cache:
871 if cache:
870 if identity_cache:
872 if identity_cache:
871 val = cls.identity_cache(session, 'username', username)
873 val = cls.identity_cache(session, 'username', username)
872 if val:
874 if val:
873 return val
875 return val
874 else:
876 else:
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
877 cache_key = "get_user_by_name_%s" % _hash_key(username)
876 q = q.options(
878 q = q.options(
877 FromCache("sql_cache_short", cache_key))
879 FromCache("sql_cache_short", cache_key))
878
880
879 return q.scalar()
881 return q.scalar()
880
882
881 @classmethod
883 @classmethod
882 def get_by_auth_token(cls, auth_token, cache=False):
884 def get_by_auth_token(cls, auth_token, cache=False):
883 q = UserApiKeys.query()\
885 q = UserApiKeys.query()\
884 .filter(UserApiKeys.api_key == auth_token)\
886 .filter(UserApiKeys.api_key == auth_token)\
885 .filter(or_(UserApiKeys.expires == -1,
887 .filter(or_(UserApiKeys.expires == -1,
886 UserApiKeys.expires >= time.time()))
888 UserApiKeys.expires >= time.time()))
887 if cache:
889 if cache:
888 q = q.options(
890 q = q.options(
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
891 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
890
892
891 match = q.first()
893 match = q.first()
892 if match:
894 if match:
893 return match.user
895 return match.user
894
896
895 @classmethod
897 @classmethod
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
898 def get_by_email(cls, email, case_insensitive=False, cache=False):
897
899
898 if case_insensitive:
900 if case_insensitive:
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
901 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
900
902
901 else:
903 else:
902 q = cls.query().filter(cls.email == email)
904 q = cls.query().filter(cls.email == email)
903
905
904 email_key = _hash_key(email)
906 email_key = _hash_key(email)
905 if cache:
907 if cache:
906 q = q.options(
908 q = q.options(
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
909 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
908
910
909 ret = q.scalar()
911 ret = q.scalar()
910 if ret is None:
912 if ret is None:
911 q = UserEmailMap.query()
913 q = UserEmailMap.query()
912 # try fetching in alternate email map
914 # try fetching in alternate email map
913 if case_insensitive:
915 if case_insensitive:
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
916 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
915 else:
917 else:
916 q = q.filter(UserEmailMap.email == email)
918 q = q.filter(UserEmailMap.email == email)
917 q = q.options(joinedload(UserEmailMap.user))
919 q = q.options(joinedload(UserEmailMap.user))
918 if cache:
920 if cache:
919 q = q.options(
921 q = q.options(
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
922 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
921 ret = getattr(q.scalar(), 'user', None)
923 ret = getattr(q.scalar(), 'user', None)
922
924
923 return ret
925 return ret
924
926
925 @classmethod
927 @classmethod
926 def get_from_cs_author(cls, author):
928 def get_from_cs_author(cls, author):
927 """
929 """
928 Tries to get User objects out of commit author string
930 Tries to get User objects out of commit author string
929
931
930 :param author:
932 :param author:
931 """
933 """
932 from rhodecode.lib.helpers import email, author_name
934 from rhodecode.lib.helpers import email, author_name
933 # Valid email in the attribute passed, see if they're in the system
935 # Valid email in the attribute passed, see if they're in the system
934 _email = email(author)
936 _email = email(author)
935 if _email:
937 if _email:
936 user = cls.get_by_email(_email, case_insensitive=True)
938 user = cls.get_by_email(_email, case_insensitive=True)
937 if user:
939 if user:
938 return user
940 return user
939 # Maybe we can match by username?
941 # Maybe we can match by username?
940 _author = author_name(author)
942 _author = author_name(author)
941 user = cls.get_by_username(_author, case_insensitive=True)
943 user = cls.get_by_username(_author, case_insensitive=True)
942 if user:
944 if user:
943 return user
945 return user
944
946
945 def update_userdata(self, **kwargs):
947 def update_userdata(self, **kwargs):
946 usr = self
948 usr = self
947 old = usr.user_data
949 old = usr.user_data
948 old.update(**kwargs)
950 old.update(**kwargs)
949 usr.user_data = old
951 usr.user_data = old
950 Session().add(usr)
952 Session().add(usr)
951 log.debug('updated userdata with %s', kwargs)
953 log.debug('updated userdata with %s', kwargs)
952
954
953 def update_lastlogin(self):
955 def update_lastlogin(self):
954 """Update user lastlogin"""
956 """Update user lastlogin"""
955 self.last_login = datetime.datetime.now()
957 self.last_login = datetime.datetime.now()
956 Session().add(self)
958 Session().add(self)
957 log.debug('updated user %s lastlogin', self.username)
959 log.debug('updated user %s lastlogin', self.username)
958
960
959 def update_password(self, new_password):
961 def update_password(self, new_password):
960 from rhodecode.lib.auth import get_crypt_password
962 from rhodecode.lib.auth import get_crypt_password
961
963
962 self.password = get_crypt_password(new_password)
964 self.password = get_crypt_password(new_password)
963 Session().add(self)
965 Session().add(self)
964
966
965 @classmethod
967 @classmethod
966 def get_first_super_admin(cls):
968 def get_first_super_admin(cls):
967 user = User.query()\
969 user = User.query()\
968 .filter(User.admin == true()) \
970 .filter(User.admin == true()) \
969 .order_by(User.user_id.asc()) \
971 .order_by(User.user_id.asc()) \
970 .first()
972 .first()
971
973
972 if user is None:
974 if user is None:
973 raise Exception('FATAL: Missing administrative account!')
975 raise Exception('FATAL: Missing administrative account!')
974 return user
976 return user
975
977
976 @classmethod
978 @classmethod
977 def get_all_super_admins(cls, only_active=False):
979 def get_all_super_admins(cls, only_active=False):
978 """
980 """
979 Returns all admin accounts sorted by username
981 Returns all admin accounts sorted by username
980 """
982 """
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
983 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
982 if only_active:
984 if only_active:
983 qry = qry.filter(User.active == true())
985 qry = qry.filter(User.active == true())
984 return qry.all()
986 return qry.all()
985
987
986 @classmethod
988 @classmethod
987 def get_default_user(cls, cache=False, refresh=False):
989 def get_default_user(cls, cache=False, refresh=False):
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
990 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
989 if user is None:
991 if user is None:
990 raise Exception('FATAL: Missing default account!')
992 raise Exception('FATAL: Missing default account!')
991 if refresh:
993 if refresh:
992 # The default user might be based on outdated state which
994 # The default user might be based on outdated state which
993 # has been loaded from the cache.
995 # has been loaded from the cache.
994 # A call to refresh() ensures that the
996 # A call to refresh() ensures that the
995 # latest state from the database is used.
997 # latest state from the database is used.
996 Session().refresh(user)
998 Session().refresh(user)
997 return user
999 return user
998
1000
999 def _get_default_perms(self, user, suffix=''):
1001 def _get_default_perms(self, user, suffix=''):
1000 from rhodecode.model.permission import PermissionModel
1002 from rhodecode.model.permission import PermissionModel
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1003 return PermissionModel().get_default_perms(user.user_perms, suffix)
1002
1004
1003 def get_default_perms(self, suffix=''):
1005 def get_default_perms(self, suffix=''):
1004 return self._get_default_perms(self, suffix)
1006 return self._get_default_perms(self, suffix)
1005
1007
1006 def get_api_data(self, include_secrets=False, details='full'):
1008 def get_api_data(self, include_secrets=False, details='full'):
1007 """
1009 """
1008 Common function for generating user related data for API
1010 Common function for generating user related data for API
1009
1011
1010 :param include_secrets: By default secrets in the API data will be replaced
1012 :param include_secrets: By default secrets in the API data will be replaced
1011 by a placeholder value to prevent exposing this data by accident. In case
1013 by a placeholder value to prevent exposing this data by accident. In case
1012 this data shall be exposed, set this flag to ``True``.
1014 this data shall be exposed, set this flag to ``True``.
1013
1015
1014 :param details: details can be 'basic|full' basic gives only a subset of
1016 :param details: details can be 'basic|full' basic gives only a subset of
1015 the available user information that includes user_id, name and emails.
1017 the available user information that includes user_id, name and emails.
1016 """
1018 """
1017 user = self
1019 user = self
1018 user_data = self.user_data
1020 user_data = self.user_data
1019 data = {
1021 data = {
1020 'user_id': user.user_id,
1022 'user_id': user.user_id,
1021 'username': user.username,
1023 'username': user.username,
1022 'firstname': user.name,
1024 'firstname': user.name,
1023 'lastname': user.lastname,
1025 'lastname': user.lastname,
1024 'email': user.email,
1026 'email': user.email,
1025 'emails': user.emails,
1027 'emails': user.emails,
1026 }
1028 }
1027 if details == 'basic':
1029 if details == 'basic':
1028 return data
1030 return data
1029
1031
1030 auth_token_length = 40
1032 auth_token_length = 40
1031 auth_token_replacement = '*' * auth_token_length
1033 auth_token_replacement = '*' * auth_token_length
1032
1034
1033 extras = {
1035 extras = {
1034 'auth_tokens': [auth_token_replacement],
1036 'auth_tokens': [auth_token_replacement],
1035 'active': user.active,
1037 'active': user.active,
1036 'admin': user.admin,
1038 'admin': user.admin,
1037 'extern_type': user.extern_type,
1039 'extern_type': user.extern_type,
1038 'extern_name': user.extern_name,
1040 'extern_name': user.extern_name,
1039 'last_login': user.last_login,
1041 'last_login': user.last_login,
1040 'last_activity': user.last_activity,
1042 'last_activity': user.last_activity,
1041 'ip_addresses': user.ip_addresses,
1043 'ip_addresses': user.ip_addresses,
1042 'language': user_data.get('language')
1044 'language': user_data.get('language')
1043 }
1045 }
1044 data.update(extras)
1046 data.update(extras)
1045
1047
1046 if include_secrets:
1048 if include_secrets:
1047 data['auth_tokens'] = user.auth_tokens
1049 data['auth_tokens'] = user.auth_tokens
1048 return data
1050 return data
1049
1051
1050 def __json__(self):
1052 def __json__(self):
1051 data = {
1053 data = {
1052 'full_name': self.full_name,
1054 'full_name': self.full_name,
1053 'full_name_or_username': self.full_name_or_username,
1055 'full_name_or_username': self.full_name_or_username,
1054 'short_contact': self.short_contact,
1056 'short_contact': self.short_contact,
1055 'full_contact': self.full_contact,
1057 'full_contact': self.full_contact,
1056 }
1058 }
1057 data.update(self.get_api_data())
1059 data.update(self.get_api_data())
1058 return data
1060 return data
1059
1061
1060
1062
1061 class UserApiKeys(Base, BaseModel):
1063 class UserApiKeys(Base, BaseModel):
1062 __tablename__ = 'user_api_keys'
1064 __tablename__ = 'user_api_keys'
1063 __table_args__ = (
1065 __table_args__ = (
1064 Index('uak_api_key_idx', 'api_key'),
1066 Index('uak_api_key_idx', 'api_key'),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1067 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1066 base_table_args
1068 base_table_args
1067 )
1069 )
1068 __mapper_args__ = {}
1070 __mapper_args__ = {}
1069
1071
1070 # ApiKey role
1072 # ApiKey role
1071 ROLE_ALL = 'token_role_all'
1073 ROLE_ALL = 'token_role_all'
1072 ROLE_HTTP = 'token_role_http'
1074 ROLE_HTTP = 'token_role_http'
1073 ROLE_VCS = 'token_role_vcs'
1075 ROLE_VCS = 'token_role_vcs'
1074 ROLE_API = 'token_role_api'
1076 ROLE_API = 'token_role_api'
1075 ROLE_FEED = 'token_role_feed'
1077 ROLE_FEED = 'token_role_feed'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1078 ROLE_PASSWORD_RESET = 'token_password_reset'
1077
1079
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1080 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1079
1081
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1082 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1083 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1084 api_key = Column("api_key", String(255), nullable=False, unique=True)
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1085 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1084 expires = Column('expires', Float(53), nullable=False)
1086 expires = Column('expires', Float(53), nullable=False)
1085 role = Column('role', String(255), nullable=True)
1087 role = Column('role', String(255), nullable=True)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1088 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1087
1089
1088 # scope columns
1090 # scope columns
1089 repo_id = Column(
1091 repo_id = Column(
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1092 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1091 nullable=True, unique=None, default=None)
1093 nullable=True, unique=None, default=None)
1092 repo = relationship('Repository', lazy='joined')
1094 repo = relationship('Repository', lazy='joined')
1093
1095
1094 repo_group_id = Column(
1096 repo_group_id = Column(
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1097 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1096 nullable=True, unique=None, default=None)
1098 nullable=True, unique=None, default=None)
1097 repo_group = relationship('RepoGroup', lazy='joined')
1099 repo_group = relationship('RepoGroup', lazy='joined')
1098
1100
1099 user = relationship('User', lazy='joined')
1101 user = relationship('User', lazy='joined')
1100
1102
1101 def __unicode__(self):
1103 def __unicode__(self):
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1104 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1103
1105
1104 def __json__(self):
1106 def __json__(self):
1105 data = {
1107 data = {
1106 'auth_token': self.api_key,
1108 'auth_token': self.api_key,
1107 'role': self.role,
1109 'role': self.role,
1108 'scope': self.scope_humanized,
1110 'scope': self.scope_humanized,
1109 'expired': self.expired
1111 'expired': self.expired
1110 }
1112 }
1111 return data
1113 return data
1112
1114
1113 def get_api_data(self, include_secrets=False):
1115 def get_api_data(self, include_secrets=False):
1114 data = self.__json__()
1116 data = self.__json__()
1115 if include_secrets:
1117 if include_secrets:
1116 return data
1118 return data
1117 else:
1119 else:
1118 data['auth_token'] = self.token_obfuscated
1120 data['auth_token'] = self.token_obfuscated
1119 return data
1121 return data
1120
1122
1121 @hybrid_property
1123 @hybrid_property
1122 def description_safe(self):
1124 def description_safe(self):
1123 from rhodecode.lib import helpers as h
1125 from rhodecode.lib import helpers as h
1124 return h.escape(self.description)
1126 return h.escape(self.description)
1125
1127
1126 @property
1128 @property
1127 def expired(self):
1129 def expired(self):
1128 if self.expires == -1:
1130 if self.expires == -1:
1129 return False
1131 return False
1130 return time.time() > self.expires
1132 return time.time() > self.expires
1131
1133
1132 @classmethod
1134 @classmethod
1133 def _get_role_name(cls, role):
1135 def _get_role_name(cls, role):
1134 return {
1136 return {
1135 cls.ROLE_ALL: _('all'),
1137 cls.ROLE_ALL: _('all'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1138 cls.ROLE_HTTP: _('http/web interface'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1139 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1138 cls.ROLE_API: _('api calls'),
1140 cls.ROLE_API: _('api calls'),
1139 cls.ROLE_FEED: _('feed access'),
1141 cls.ROLE_FEED: _('feed access'),
1140 }.get(role, role)
1142 }.get(role, role)
1141
1143
1142 @property
1144 @property
1143 def role_humanized(self):
1145 def role_humanized(self):
1144 return self._get_role_name(self.role)
1146 return self._get_role_name(self.role)
1145
1147
1146 def _get_scope(self):
1148 def _get_scope(self):
1147 if self.repo:
1149 if self.repo:
1148 return 'Repository: {}'.format(self.repo.repo_name)
1150 return 'Repository: {}'.format(self.repo.repo_name)
1149 if self.repo_group:
1151 if self.repo_group:
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1152 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1151 return 'Global'
1153 return 'Global'
1152
1154
1153 @property
1155 @property
1154 def scope_humanized(self):
1156 def scope_humanized(self):
1155 return self._get_scope()
1157 return self._get_scope()
1156
1158
1157 @property
1159 @property
1158 def token_obfuscated(self):
1160 def token_obfuscated(self):
1159 if self.api_key:
1161 if self.api_key:
1160 return self.api_key[:4] + "****"
1162 return self.api_key[:4] + "****"
1161
1163
1162
1164
1163 class UserEmailMap(Base, BaseModel):
1165 class UserEmailMap(Base, BaseModel):
1164 __tablename__ = 'user_email_map'
1166 __tablename__ = 'user_email_map'
1165 __table_args__ = (
1167 __table_args__ = (
1166 Index('uem_email_idx', 'email'),
1168 Index('uem_email_idx', 'email'),
1167 UniqueConstraint('email'),
1169 UniqueConstraint('email'),
1168 base_table_args
1170 base_table_args
1169 )
1171 )
1170 __mapper_args__ = {}
1172 __mapper_args__ = {}
1171
1173
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1174 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1175 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1176 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1175 user = relationship('User', lazy='joined')
1177 user = relationship('User', lazy='joined')
1176
1178
1177 @validates('_email')
1179 @validates('_email')
1178 def validate_email(self, key, email):
1180 def validate_email(self, key, email):
1179 # check if this email is not main one
1181 # check if this email is not main one
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1182 main_email = Session().query(User).filter(User.email == email).scalar()
1181 if main_email is not None:
1183 if main_email is not None:
1182 raise AttributeError('email %s is present is user table' % email)
1184 raise AttributeError('email %s is present is user table' % email)
1183 return email
1185 return email
1184
1186
1185 @hybrid_property
1187 @hybrid_property
1186 def email(self):
1188 def email(self):
1187 return self._email
1189 return self._email
1188
1190
1189 @email.setter
1191 @email.setter
1190 def email(self, val):
1192 def email(self, val):
1191 self._email = val.lower() if val else None
1193 self._email = val.lower() if val else None
1192
1194
1193
1195
1194 class UserIpMap(Base, BaseModel):
1196 class UserIpMap(Base, BaseModel):
1195 __tablename__ = 'user_ip_map'
1197 __tablename__ = 'user_ip_map'
1196 __table_args__ = (
1198 __table_args__ = (
1197 UniqueConstraint('user_id', 'ip_addr'),
1199 UniqueConstraint('user_id', 'ip_addr'),
1198 base_table_args
1200 base_table_args
1199 )
1201 )
1200 __mapper_args__ = {}
1202 __mapper_args__ = {}
1201
1203
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1204 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1205 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1206 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1207 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1208 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1207 user = relationship('User', lazy='joined')
1209 user = relationship('User', lazy='joined')
1208
1210
1209 @hybrid_property
1211 @hybrid_property
1210 def description_safe(self):
1212 def description_safe(self):
1211 from rhodecode.lib import helpers as h
1213 from rhodecode.lib import helpers as h
1212 return h.escape(self.description)
1214 return h.escape(self.description)
1213
1215
1214 @classmethod
1216 @classmethod
1215 def _get_ip_range(cls, ip_addr):
1217 def _get_ip_range(cls, ip_addr):
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1218 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1217 return [str(net.network_address), str(net.broadcast_address)]
1219 return [str(net.network_address), str(net.broadcast_address)]
1218
1220
1219 def __json__(self):
1221 def __json__(self):
1220 return {
1222 return {
1221 'ip_addr': self.ip_addr,
1223 'ip_addr': self.ip_addr,
1222 'ip_range': self._get_ip_range(self.ip_addr),
1224 'ip_range': self._get_ip_range(self.ip_addr),
1223 }
1225 }
1224
1226
1225 def __unicode__(self):
1227 def __unicode__(self):
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1228 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1227 self.user_id, self.ip_addr)
1229 self.user_id, self.ip_addr)
1228
1230
1229
1231
1230 class UserSshKeys(Base, BaseModel):
1232 class UserSshKeys(Base, BaseModel):
1231 __tablename__ = 'user_ssh_keys'
1233 __tablename__ = 'user_ssh_keys'
1232 __table_args__ = (
1234 __table_args__ = (
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1235 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1234
1236
1235 UniqueConstraint('ssh_key_fingerprint'),
1237 UniqueConstraint('ssh_key_fingerprint'),
1236
1238
1237 base_table_args
1239 base_table_args
1238 )
1240 )
1239 __mapper_args__ = {}
1241 __mapper_args__ = {}
1240
1242
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1243 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1244 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1245 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1244
1246
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1247 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1246
1248
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1249 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1250 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1251 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1250
1252
1251 user = relationship('User', lazy='joined')
1253 user = relationship('User', lazy='joined')
1252
1254
1253 def __json__(self):
1255 def __json__(self):
1254 data = {
1256 data = {
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1257 'ssh_fingerprint': self.ssh_key_fingerprint,
1256 'description': self.description,
1258 'description': self.description,
1257 'created_on': self.created_on
1259 'created_on': self.created_on
1258 }
1260 }
1259 return data
1261 return data
1260
1262
1261 def get_api_data(self):
1263 def get_api_data(self):
1262 data = self.__json__()
1264 data = self.__json__()
1263 return data
1265 return data
1264
1266
1265
1267
1266 class UserLog(Base, BaseModel):
1268 class UserLog(Base, BaseModel):
1267 __tablename__ = 'user_logs'
1269 __tablename__ = 'user_logs'
1268 __table_args__ = (
1270 __table_args__ = (
1269 base_table_args,
1271 base_table_args,
1270 )
1272 )
1271
1273
1272 VERSION_1 = 'v1'
1274 VERSION_1 = 'v1'
1273 VERSION_2 = 'v2'
1275 VERSION_2 = 'v2'
1274 VERSIONS = [VERSION_1, VERSION_2]
1276 VERSIONS = [VERSION_1, VERSION_2]
1275
1277
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1278 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1279 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1280 username = Column("username", String(255), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1281 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1282 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1283 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1284 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1285 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1284
1286
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1287 version = Column("version", String(255), nullable=True, default=VERSION_1)
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1288 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1289 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1288
1290
1289 def __unicode__(self):
1291 def __unicode__(self):
1290 return u"<%s('id:%s:%s')>" % (
1292 return u"<%s('id:%s:%s')>" % (
1291 self.__class__.__name__, self.repository_name, self.action)
1293 self.__class__.__name__, self.repository_name, self.action)
1292
1294
1293 def __json__(self):
1295 def __json__(self):
1294 return {
1296 return {
1295 'user_id': self.user_id,
1297 'user_id': self.user_id,
1296 'username': self.username,
1298 'username': self.username,
1297 'repository_id': self.repository_id,
1299 'repository_id': self.repository_id,
1298 'repository_name': self.repository_name,
1300 'repository_name': self.repository_name,
1299 'user_ip': self.user_ip,
1301 'user_ip': self.user_ip,
1300 'action_date': self.action_date,
1302 'action_date': self.action_date,
1301 'action': self.action,
1303 'action': self.action,
1302 }
1304 }
1303
1305
1304 @hybrid_property
1306 @hybrid_property
1305 def entry_id(self):
1307 def entry_id(self):
1306 return self.user_log_id
1308 return self.user_log_id
1307
1309
1308 @property
1310 @property
1309 def action_as_day(self):
1311 def action_as_day(self):
1310 return datetime.date(*self.action_date.timetuple()[:3])
1312 return datetime.date(*self.action_date.timetuple()[:3])
1311
1313
1312 user = relationship('User')
1314 user = relationship('User')
1313 repository = relationship('Repository', cascade='')
1315 repository = relationship('Repository', cascade='')
1314
1316
1315
1317
1316 class UserGroup(Base, BaseModel):
1318 class UserGroup(Base, BaseModel):
1317 __tablename__ = 'users_groups'
1319 __tablename__ = 'users_groups'
1318 __table_args__ = (
1320 __table_args__ = (
1319 base_table_args,
1321 base_table_args,
1320 )
1322 )
1321
1323
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1324 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1325 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1326 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1327 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1328 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1329 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1330 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1331 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1330
1332
1331 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1333 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1334 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1335 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1336 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1337 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1338 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1337
1339
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1340 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1341 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1340
1342
1341 @classmethod
1343 @classmethod
1342 def _load_group_data(cls, column):
1344 def _load_group_data(cls, column):
1343 if not column:
1345 if not column:
1344 return {}
1346 return {}
1345
1347
1346 try:
1348 try:
1347 return json.loads(column) or {}
1349 return json.loads(column) or {}
1348 except TypeError:
1350 except TypeError:
1349 return {}
1351 return {}
1350
1352
1351 @hybrid_property
1353 @hybrid_property
1352 def description_safe(self):
1354 def description_safe(self):
1353 from rhodecode.lib import helpers as h
1355 from rhodecode.lib import helpers as h
1354 return h.escape(self.user_group_description)
1356 return h.escape(self.user_group_description)
1355
1357
1356 @hybrid_property
1358 @hybrid_property
1357 def group_data(self):
1359 def group_data(self):
1358 return self._load_group_data(self._group_data)
1360 return self._load_group_data(self._group_data)
1359
1361
1360 @group_data.expression
1362 @group_data.expression
1361 def group_data(self, **kwargs):
1363 def group_data(self, **kwargs):
1362 return self._group_data
1364 return self._group_data
1363
1365
1364 @group_data.setter
1366 @group_data.setter
1365 def group_data(self, val):
1367 def group_data(self, val):
1366 try:
1368 try:
1367 self._group_data = json.dumps(val)
1369 self._group_data = json.dumps(val)
1368 except Exception:
1370 except Exception:
1369 log.error(traceback.format_exc())
1371 log.error(traceback.format_exc())
1370
1372
1371 @classmethod
1373 @classmethod
1372 def _load_sync(cls, group_data):
1374 def _load_sync(cls, group_data):
1373 if group_data:
1375 if group_data:
1374 return group_data.get('extern_type')
1376 return group_data.get('extern_type')
1375
1377
1376 @property
1378 @property
1377 def sync(self):
1379 def sync(self):
1378 return self._load_sync(self.group_data)
1380 return self._load_sync(self.group_data)
1379
1381
1380 def __unicode__(self):
1382 def __unicode__(self):
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1383 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1382 self.users_group_id,
1384 self.users_group_id,
1383 self.users_group_name)
1385 self.users_group_name)
1384
1386
1385 @classmethod
1387 @classmethod
1386 def get_by_group_name(cls, group_name, cache=False,
1388 def get_by_group_name(cls, group_name, cache=False,
1387 case_insensitive=False):
1389 case_insensitive=False):
1388 if case_insensitive:
1390 if case_insensitive:
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1391 q = cls.query().filter(func.lower(cls.users_group_name) ==
1390 func.lower(group_name))
1392 func.lower(group_name))
1391
1393
1392 else:
1394 else:
1393 q = cls.query().filter(cls.users_group_name == group_name)
1395 q = cls.query().filter(cls.users_group_name == group_name)
1394 if cache:
1396 if cache:
1395 q = q.options(
1397 q = q.options(
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1398 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1397 return q.scalar()
1399 return q.scalar()
1398
1400
1399 @classmethod
1401 @classmethod
1400 def get(cls, user_group_id, cache=False):
1402 def get(cls, user_group_id, cache=False):
1401 if not user_group_id:
1403 if not user_group_id:
1402 return
1404 return
1403
1405
1404 user_group = cls.query()
1406 user_group = cls.query()
1405 if cache:
1407 if cache:
1406 user_group = user_group.options(
1408 user_group = user_group.options(
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1409 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1408 return user_group.get(user_group_id)
1410 return user_group.get(user_group_id)
1409
1411
1410 def permissions(self, with_admins=True, with_owner=True,
1412 def permissions(self, with_admins=True, with_owner=True,
1411 expand_from_user_groups=False):
1413 expand_from_user_groups=False):
1412 """
1414 """
1413 Permissions for user groups
1415 Permissions for user groups
1414 """
1416 """
1415 _admin_perm = 'usergroup.admin'
1417 _admin_perm = 'usergroup.admin'
1416
1418
1417 owner_row = []
1419 owner_row = []
1418 if with_owner:
1420 if with_owner:
1419 usr = AttributeDict(self.user.get_dict())
1421 usr = AttributeDict(self.user.get_dict())
1420 usr.owner_row = True
1422 usr.owner_row = True
1421 usr.permission = _admin_perm
1423 usr.permission = _admin_perm
1422 owner_row.append(usr)
1424 owner_row.append(usr)
1423
1425
1424 super_admin_ids = []
1426 super_admin_ids = []
1425 super_admin_rows = []
1427 super_admin_rows = []
1426 if with_admins:
1428 if with_admins:
1427 for usr in User.get_all_super_admins():
1429 for usr in User.get_all_super_admins():
1428 super_admin_ids.append(usr.user_id)
1430 super_admin_ids.append(usr.user_id)
1429 # if this admin is also owner, don't double the record
1431 # if this admin is also owner, don't double the record
1430 if usr.user_id == owner_row[0].user_id:
1432 if usr.user_id == owner_row[0].user_id:
1431 owner_row[0].admin_row = True
1433 owner_row[0].admin_row = True
1432 else:
1434 else:
1433 usr = AttributeDict(usr.get_dict())
1435 usr = AttributeDict(usr.get_dict())
1434 usr.admin_row = True
1436 usr.admin_row = True
1435 usr.permission = _admin_perm
1437 usr.permission = _admin_perm
1436 super_admin_rows.append(usr)
1438 super_admin_rows.append(usr)
1437
1439
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1440 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1441 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1440 joinedload(UserUserGroupToPerm.user),
1442 joinedload(UserUserGroupToPerm.user),
1441 joinedload(UserUserGroupToPerm.permission),)
1443 joinedload(UserUserGroupToPerm.permission),)
1442
1444
1443 # get owners and admins and permissions. We do a trick of re-writing
1445 # get owners and admins and permissions. We do a trick of re-writing
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1446 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1445 # has a global reference and changing one object propagates to all
1447 # has a global reference and changing one object propagates to all
1446 # others. This means if admin is also an owner admin_row that change
1448 # others. This means if admin is also an owner admin_row that change
1447 # would propagate to both objects
1449 # would propagate to both objects
1448 perm_rows = []
1450 perm_rows = []
1449 for _usr in q.all():
1451 for _usr in q.all():
1450 usr = AttributeDict(_usr.user.get_dict())
1452 usr = AttributeDict(_usr.user.get_dict())
1451 # if this user is also owner/admin, mark as duplicate record
1453 # if this user is also owner/admin, mark as duplicate record
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1454 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1453 usr.duplicate_perm = True
1455 usr.duplicate_perm = True
1454 usr.permission = _usr.permission.permission_name
1456 usr.permission = _usr.permission.permission_name
1455 perm_rows.append(usr)
1457 perm_rows.append(usr)
1456
1458
1457 # filter the perm rows by 'default' first and then sort them by
1459 # filter the perm rows by 'default' first and then sort them by
1458 # admin,write,read,none permissions sorted again alphabetically in
1460 # admin,write,read,none permissions sorted again alphabetically in
1459 # each group
1461 # each group
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1462 perm_rows = sorted(perm_rows, key=display_user_sort)
1461
1463
1462 user_groups_rows = []
1464 user_groups_rows = []
1463 if expand_from_user_groups:
1465 if expand_from_user_groups:
1464 for ug in self.permission_user_groups(with_members=True):
1466 for ug in self.permission_user_groups(with_members=True):
1465 for user_data in ug.members:
1467 for user_data in ug.members:
1466 user_groups_rows.append(user_data)
1468 user_groups_rows.append(user_data)
1467
1469
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1470 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1469
1471
1470 def permission_user_groups(self, with_members=False):
1472 def permission_user_groups(self, with_members=False):
1471 q = UserGroupUserGroupToPerm.query()\
1473 q = UserGroupUserGroupToPerm.query()\
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1474 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1475 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1476 joinedload(UserGroupUserGroupToPerm.target_user_group),
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1477 joinedload(UserGroupUserGroupToPerm.permission),)
1476
1478
1477 perm_rows = []
1479 perm_rows = []
1478 for _user_group in q.all():
1480 for _user_group in q.all():
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1481 entry = AttributeDict(_user_group.user_group.get_dict())
1480 entry.permission = _user_group.permission.permission_name
1482 entry.permission = _user_group.permission.permission_name
1481 if with_members:
1483 if with_members:
1482 entry.members = [x.user.get_dict()
1484 entry.members = [x.user.get_dict()
1483 for x in _user_group.user_group.members]
1485 for x in _user_group.user_group.members]
1484 perm_rows.append(entry)
1486 perm_rows.append(entry)
1485
1487
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1488 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1487 return perm_rows
1489 return perm_rows
1488
1490
1489 def _get_default_perms(self, user_group, suffix=''):
1491 def _get_default_perms(self, user_group, suffix=''):
1490 from rhodecode.model.permission import PermissionModel
1492 from rhodecode.model.permission import PermissionModel
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1493 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1492
1494
1493 def get_default_perms(self, suffix=''):
1495 def get_default_perms(self, suffix=''):
1494 return self._get_default_perms(self, suffix)
1496 return self._get_default_perms(self, suffix)
1495
1497
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1498 def get_api_data(self, with_group_members=True, include_secrets=False):
1497 """
1499 """
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1500 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1499 basically forwarded.
1501 basically forwarded.
1500
1502
1501 """
1503 """
1502 user_group = self
1504 user_group = self
1503 data = {
1505 data = {
1504 'users_group_id': user_group.users_group_id,
1506 'users_group_id': user_group.users_group_id,
1505 'group_name': user_group.users_group_name,
1507 'group_name': user_group.users_group_name,
1506 'group_description': user_group.user_group_description,
1508 'group_description': user_group.user_group_description,
1507 'active': user_group.users_group_active,
1509 'active': user_group.users_group_active,
1508 'owner': user_group.user.username,
1510 'owner': user_group.user.username,
1509 'sync': user_group.sync,
1511 'sync': user_group.sync,
1510 'owner_email': user_group.user.email,
1512 'owner_email': user_group.user.email,
1511 }
1513 }
1512
1514
1513 if with_group_members:
1515 if with_group_members:
1514 users = []
1516 users = []
1515 for user in user_group.members:
1517 for user in user_group.members:
1516 user = user.user
1518 user = user.user
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1519 users.append(user.get_api_data(include_secrets=include_secrets))
1518 data['users'] = users
1520 data['users'] = users
1519
1521
1520 return data
1522 return data
1521
1523
1522
1524
1523 class UserGroupMember(Base, BaseModel):
1525 class UserGroupMember(Base, BaseModel):
1524 __tablename__ = 'users_groups_members'
1526 __tablename__ = 'users_groups_members'
1525 __table_args__ = (
1527 __table_args__ = (
1526 base_table_args,
1528 base_table_args,
1527 )
1529 )
1528
1530
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1531 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1532
1534
1533 user = relationship('User', lazy='joined')
1535 user = relationship('User', lazy='joined')
1534 users_group = relationship('UserGroup')
1536 users_group = relationship('UserGroup')
1535
1537
1536 def __init__(self, gr_id='', u_id=''):
1538 def __init__(self, gr_id='', u_id=''):
1537 self.users_group_id = gr_id
1539 self.users_group_id = gr_id
1538 self.user_id = u_id
1540 self.user_id = u_id
1539
1541
1540
1542
1541 class RepositoryField(Base, BaseModel):
1543 class RepositoryField(Base, BaseModel):
1542 __tablename__ = 'repositories_fields'
1544 __tablename__ = 'repositories_fields'
1543 __table_args__ = (
1545 __table_args__ = (
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1546 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1545 base_table_args,
1547 base_table_args,
1546 )
1548 )
1547
1549
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1550 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1549
1551
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1552 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1553 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1552 field_key = Column("field_key", String(250))
1554 field_key = Column("field_key", String(250))
1553 field_label = Column("field_label", String(1024), nullable=False)
1555 field_label = Column("field_label", String(1024), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1556 field_value = Column("field_value", String(10000), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1557 field_desc = Column("field_desc", String(1024), nullable=False)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1558 field_type = Column("field_type", String(255), nullable=False, unique=None)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1559 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1558
1560
1559 repository = relationship('Repository')
1561 repository = relationship('Repository')
1560
1562
1561 @property
1563 @property
1562 def field_key_prefixed(self):
1564 def field_key_prefixed(self):
1563 return 'ex_%s' % self.field_key
1565 return 'ex_%s' % self.field_key
1564
1566
1565 @classmethod
1567 @classmethod
1566 def un_prefix_key(cls, key):
1568 def un_prefix_key(cls, key):
1567 if key.startswith(cls.PREFIX):
1569 if key.startswith(cls.PREFIX):
1568 return key[len(cls.PREFIX):]
1570 return key[len(cls.PREFIX):]
1569 return key
1571 return key
1570
1572
1571 @classmethod
1573 @classmethod
1572 def get_by_key_name(cls, key, repo):
1574 def get_by_key_name(cls, key, repo):
1573 row = cls.query()\
1575 row = cls.query()\
1574 .filter(cls.repository == repo)\
1576 .filter(cls.repository == repo)\
1575 .filter(cls.field_key == key).scalar()
1577 .filter(cls.field_key == key).scalar()
1576 return row
1578 return row
1577
1579
1578
1580
1579 class Repository(Base, BaseModel):
1581 class Repository(Base, BaseModel):
1580 __tablename__ = 'repositories'
1582 __tablename__ = 'repositories'
1581 __table_args__ = (
1583 __table_args__ = (
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1584 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1583 base_table_args,
1585 base_table_args,
1584 )
1586 )
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1587 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1588 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1589 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1588
1590
1589 STATE_CREATED = 'repo_state_created'
1591 STATE_CREATED = 'repo_state_created'
1590 STATE_PENDING = 'repo_state_pending'
1592 STATE_PENDING = 'repo_state_pending'
1591 STATE_ERROR = 'repo_state_error'
1593 STATE_ERROR = 'repo_state_error'
1592
1594
1593 LOCK_AUTOMATIC = 'lock_auto'
1595 LOCK_AUTOMATIC = 'lock_auto'
1594 LOCK_API = 'lock_api'
1596 LOCK_API = 'lock_api'
1595 LOCK_WEB = 'lock_web'
1597 LOCK_WEB = 'lock_web'
1596 LOCK_PULL = 'lock_pull'
1598 LOCK_PULL = 'lock_pull'
1597
1599
1598 NAME_SEP = URL_SEP
1600 NAME_SEP = URL_SEP
1599
1601
1600 repo_id = Column(
1602 repo_id = Column(
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1603 "repo_id", Integer(), nullable=False, unique=True, default=None,
1602 primary_key=True)
1604 primary_key=True)
1603 _repo_name = Column(
1605 _repo_name = Column(
1604 "repo_name", Text(), nullable=False, default=None)
1606 "repo_name", Text(), nullable=False, default=None)
1605 _repo_name_hash = Column(
1607 _repo_name_hash = Column(
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1608 "repo_name_hash", String(255), nullable=False, unique=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1609 repo_state = Column("repo_state", String(255), nullable=True)
1608
1610
1609 clone_uri = Column(
1611 clone_uri = Column(
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1612 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1611 default=None)
1613 default=None)
1612 push_uri = Column(
1614 push_uri = Column(
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1615 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1614 default=None)
1616 default=None)
1615 repo_type = Column(
1617 repo_type = Column(
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1618 "repo_type", String(255), nullable=False, unique=False, default=None)
1617 user_id = Column(
1619 user_id = Column(
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1620 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1619 unique=False, default=None)
1621 unique=False, default=None)
1620 private = Column(
1622 private = Column(
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1623 "private", Boolean(), nullable=True, unique=None, default=None)
1622 archived = Column(
1624 archived = Column(
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1625 "archived", Boolean(), nullable=True, unique=None, default=None)
1624 enable_statistics = Column(
1626 enable_statistics = Column(
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1627 "statistics", Boolean(), nullable=True, unique=None, default=True)
1626 enable_downloads = Column(
1628 enable_downloads = Column(
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1629 "downloads", Boolean(), nullable=True, unique=None, default=True)
1628 description = Column(
1630 description = Column(
1629 "description", String(10000), nullable=True, unique=None, default=None)
1631 "description", String(10000), nullable=True, unique=None, default=None)
1630 created_on = Column(
1632 created_on = Column(
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1633 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1632 default=datetime.datetime.now)
1634 default=datetime.datetime.now)
1633 updated_on = Column(
1635 updated_on = Column(
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1636 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1635 default=datetime.datetime.now)
1637 default=datetime.datetime.now)
1636 _landing_revision = Column(
1638 _landing_revision = Column(
1637 "landing_revision", String(255), nullable=False, unique=False,
1639 "landing_revision", String(255), nullable=False, unique=False,
1638 default=None)
1640 default=None)
1639 enable_locking = Column(
1641 enable_locking = Column(
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1642 "enable_locking", Boolean(), nullable=False, unique=None,
1641 default=False)
1643 default=False)
1642 _locked = Column(
1644 _locked = Column(
1643 "locked", String(255), nullable=True, unique=False, default=None)
1645 "locked", String(255), nullable=True, unique=False, default=None)
1644 _changeset_cache = Column(
1646 _changeset_cache = Column(
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1647 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1646
1648
1647 fork_id = Column(
1649 fork_id = Column(
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1650 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1649 nullable=True, unique=False, default=None)
1651 nullable=True, unique=False, default=None)
1650 group_id = Column(
1652 group_id = Column(
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1653 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1652 unique=False, default=None)
1654 unique=False, default=None)
1653
1655
1654 user = relationship('User', lazy='joined')
1656 user = relationship('User', lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1657 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1658 group = relationship('RepoGroup', lazy='joined')
1657 repo_to_perm = relationship(
1659 repo_to_perm = relationship(
1658 'UserRepoToPerm', cascade='all',
1660 'UserRepoToPerm', cascade='all',
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1661 order_by='UserRepoToPerm.repo_to_perm_id')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1662 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1663 stats = relationship('Statistics', cascade='all', uselist=False)
1662
1664
1663 followers = relationship(
1665 followers = relationship(
1664 'UserFollowing',
1666 'UserFollowing',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1667 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1666 cascade='all')
1668 cascade='all')
1667 extra_fields = relationship(
1669 extra_fields = relationship(
1668 'RepositoryField', cascade="all, delete-orphan")
1670 'RepositoryField', cascade="all, delete-orphan")
1669 logs = relationship('UserLog')
1671 logs = relationship('UserLog')
1670 comments = relationship(
1672 comments = relationship(
1671 'ChangesetComment', cascade="all, delete-orphan")
1673 'ChangesetComment', cascade="all, delete-orphan")
1672 pull_requests_source = relationship(
1674 pull_requests_source = relationship(
1673 'PullRequest',
1675 'PullRequest',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1676 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1675 cascade="all, delete-orphan")
1677 cascade="all, delete-orphan")
1676 pull_requests_target = relationship(
1678 pull_requests_target = relationship(
1677 'PullRequest',
1679 'PullRequest',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1680 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1679 cascade="all, delete-orphan")
1681 cascade="all, delete-orphan")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1682 ui = relationship('RepoRhodeCodeUi', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1683 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1682 integrations = relationship('Integration', cascade="all, delete-orphan")
1684 integrations = relationship('Integration', cascade="all, delete-orphan")
1683
1685
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1686 scoped_tokens = relationship('UserApiKeys', cascade="all")
1685
1687
1686 artifacts = relationship('FileStore', cascade="all")
1688 artifacts = relationship('FileStore', cascade="all")
1687
1689
1688 def __unicode__(self):
1690 def __unicode__(self):
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1691 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1690 safe_unicode(self.repo_name))
1692 safe_unicode(self.repo_name))
1691
1693
1692 @hybrid_property
1694 @hybrid_property
1693 def description_safe(self):
1695 def description_safe(self):
1694 from rhodecode.lib import helpers as h
1696 from rhodecode.lib import helpers as h
1695 return h.escape(self.description)
1697 return h.escape(self.description)
1696
1698
1697 @hybrid_property
1699 @hybrid_property
1698 def landing_rev(self):
1700 def landing_rev(self):
1699 # always should return [rev_type, rev]
1701 # always should return [rev_type, rev]
1700 if self._landing_revision:
1702 if self._landing_revision:
1701 _rev_info = self._landing_revision.split(':')
1703 _rev_info = self._landing_revision.split(':')
1702 if len(_rev_info) < 2:
1704 if len(_rev_info) < 2:
1703 _rev_info.insert(0, 'rev')
1705 _rev_info.insert(0, 'rev')
1704 return [_rev_info[0], _rev_info[1]]
1706 return [_rev_info[0], _rev_info[1]]
1705 return [None, None]
1707 return [None, None]
1706
1708
1707 @landing_rev.setter
1709 @landing_rev.setter
1708 def landing_rev(self, val):
1710 def landing_rev(self, val):
1709 if ':' not in val:
1711 if ':' not in val:
1710 raise ValueError('value must be delimited with `:` and consist '
1712 raise ValueError('value must be delimited with `:` and consist '
1711 'of <rev_type>:<rev>, got %s instead' % val)
1713 'of <rev_type>:<rev>, got %s instead' % val)
1712 self._landing_revision = val
1714 self._landing_revision = val
1713
1715
1714 @hybrid_property
1716 @hybrid_property
1715 def locked(self):
1717 def locked(self):
1716 if self._locked:
1718 if self._locked:
1717 user_id, timelocked, reason = self._locked.split(':')
1719 user_id, timelocked, reason = self._locked.split(':')
1718 lock_values = int(user_id), timelocked, reason
1720 lock_values = int(user_id), timelocked, reason
1719 else:
1721 else:
1720 lock_values = [None, None, None]
1722 lock_values = [None, None, None]
1721 return lock_values
1723 return lock_values
1722
1724
1723 @locked.setter
1725 @locked.setter
1724 def locked(self, val):
1726 def locked(self, val):
1725 if val and isinstance(val, (list, tuple)):
1727 if val and isinstance(val, (list, tuple)):
1726 self._locked = ':'.join(map(str, val))
1728 self._locked = ':'.join(map(str, val))
1727 else:
1729 else:
1728 self._locked = None
1730 self._locked = None
1729
1731
1730 @hybrid_property
1732 @hybrid_property
1731 def changeset_cache(self):
1733 def changeset_cache(self):
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1734 from rhodecode.lib.vcs.backends.base import EmptyCommit
1733 dummy = EmptyCommit().__json__()
1735 dummy = EmptyCommit().__json__()
1734 if not self._changeset_cache:
1736 if not self._changeset_cache:
1735 dummy['source_repo_id'] = self.repo_id
1737 dummy['source_repo_id'] = self.repo_id
1736 return json.loads(json.dumps(dummy))
1738 return json.loads(json.dumps(dummy))
1737
1739
1738 try:
1740 try:
1739 return json.loads(self._changeset_cache)
1741 return json.loads(self._changeset_cache)
1740 except TypeError:
1742 except TypeError:
1741 return dummy
1743 return dummy
1742 except Exception:
1744 except Exception:
1743 log.error(traceback.format_exc())
1745 log.error(traceback.format_exc())
1744 return dummy
1746 return dummy
1745
1747
1746 @changeset_cache.setter
1748 @changeset_cache.setter
1747 def changeset_cache(self, val):
1749 def changeset_cache(self, val):
1748 try:
1750 try:
1749 self._changeset_cache = json.dumps(val)
1751 self._changeset_cache = json.dumps(val)
1750 except Exception:
1752 except Exception:
1751 log.error(traceback.format_exc())
1753 log.error(traceback.format_exc())
1752
1754
1753 @hybrid_property
1755 @hybrid_property
1754 def repo_name(self):
1756 def repo_name(self):
1755 return self._repo_name
1757 return self._repo_name
1756
1758
1757 @repo_name.setter
1759 @repo_name.setter
1758 def repo_name(self, value):
1760 def repo_name(self, value):
1759 self._repo_name = value
1761 self._repo_name = value
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1762 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1761
1763
1762 @classmethod
1764 @classmethod
1763 def normalize_repo_name(cls, repo_name):
1765 def normalize_repo_name(cls, repo_name):
1764 """
1766 """
1765 Normalizes os specific repo_name to the format internally stored inside
1767 Normalizes os specific repo_name to the format internally stored inside
1766 database using URL_SEP
1768 database using URL_SEP
1767
1769
1768 :param cls:
1770 :param cls:
1769 :param repo_name:
1771 :param repo_name:
1770 """
1772 """
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1773 return cls.NAME_SEP.join(repo_name.split(os.sep))
1772
1774
1773 @classmethod
1775 @classmethod
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1776 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1775 session = Session()
1777 session = Session()
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1778 q = session.query(cls).filter(cls.repo_name == repo_name)
1777
1779
1778 if cache:
1780 if cache:
1779 if identity_cache:
1781 if identity_cache:
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1782 val = cls.identity_cache(session, 'repo_name', repo_name)
1781 if val:
1783 if val:
1782 return val
1784 return val
1783 else:
1785 else:
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1786 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1785 q = q.options(
1787 q = q.options(
1786 FromCache("sql_cache_short", cache_key))
1788 FromCache("sql_cache_short", cache_key))
1787
1789
1788 return q.scalar()
1790 return q.scalar()
1789
1791
1790 @classmethod
1792 @classmethod
1791 def get_by_id_or_repo_name(cls, repoid):
1793 def get_by_id_or_repo_name(cls, repoid):
1792 if isinstance(repoid, (int, long)):
1794 if isinstance(repoid, (int, long)):
1793 try:
1795 try:
1794 repo = cls.get(repoid)
1796 repo = cls.get(repoid)
1795 except ValueError:
1797 except ValueError:
1796 repo = None
1798 repo = None
1797 else:
1799 else:
1798 repo = cls.get_by_repo_name(repoid)
1800 repo = cls.get_by_repo_name(repoid)
1799 return repo
1801 return repo
1800
1802
1801 @classmethod
1803 @classmethod
1802 def get_by_full_path(cls, repo_full_path):
1804 def get_by_full_path(cls, repo_full_path):
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1805 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1804 repo_name = cls.normalize_repo_name(repo_name)
1806 repo_name = cls.normalize_repo_name(repo_name)
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1807 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1806
1808
1807 @classmethod
1809 @classmethod
1808 def get_repo_forks(cls, repo_id):
1810 def get_repo_forks(cls, repo_id):
1809 return cls.query().filter(Repository.fork_id == repo_id)
1811 return cls.query().filter(Repository.fork_id == repo_id)
1810
1812
1811 @classmethod
1813 @classmethod
1812 def base_path(cls):
1814 def base_path(cls):
1813 """
1815 """
1814 Returns base path when all repos are stored
1816 Returns base path when all repos are stored
1815
1817
1816 :param cls:
1818 :param cls:
1817 """
1819 """
1818 q = Session().query(RhodeCodeUi)\
1820 q = Session().query(RhodeCodeUi)\
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1821 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1822 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 return q.one().ui_value
1823 return q.one().ui_value
1822
1824
1823 @classmethod
1825 @classmethod
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1826 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1825 case_insensitive=True, archived=False):
1827 case_insensitive=True, archived=False):
1826 q = Repository.query()
1828 q = Repository.query()
1827
1829
1828 if not archived:
1830 if not archived:
1829 q = q.filter(Repository.archived.isnot(true()))
1831 q = q.filter(Repository.archived.isnot(true()))
1830
1832
1831 if not isinstance(user_id, Optional):
1833 if not isinstance(user_id, Optional):
1832 q = q.filter(Repository.user_id == user_id)
1834 q = q.filter(Repository.user_id == user_id)
1833
1835
1834 if not isinstance(group_id, Optional):
1836 if not isinstance(group_id, Optional):
1835 q = q.filter(Repository.group_id == group_id)
1837 q = q.filter(Repository.group_id == group_id)
1836
1838
1837 if case_insensitive:
1839 if case_insensitive:
1838 q = q.order_by(func.lower(Repository.repo_name))
1840 q = q.order_by(func.lower(Repository.repo_name))
1839 else:
1841 else:
1840 q = q.order_by(Repository.repo_name)
1842 q = q.order_by(Repository.repo_name)
1841
1843
1842 return q.all()
1844 return q.all()
1843
1845
1844 @property
1846 @property
1845 def repo_uid(self):
1847 def repo_uid(self):
1846 return '_{}'.format(self.repo_id)
1848 return '_{}'.format(self.repo_id)
1847
1849
1848 @property
1850 @property
1849 def forks(self):
1851 def forks(self):
1850 """
1852 """
1851 Return forks of this repo
1853 Return forks of this repo
1852 """
1854 """
1853 return Repository.get_repo_forks(self.repo_id)
1855 return Repository.get_repo_forks(self.repo_id)
1854
1856
1855 @property
1857 @property
1856 def parent(self):
1858 def parent(self):
1857 """
1859 """
1858 Returns fork parent
1860 Returns fork parent
1859 """
1861 """
1860 return self.fork
1862 return self.fork
1861
1863
1862 @property
1864 @property
1863 def just_name(self):
1865 def just_name(self):
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1866 return self.repo_name.split(self.NAME_SEP)[-1]
1865
1867
1866 @property
1868 @property
1867 def groups_with_parents(self):
1869 def groups_with_parents(self):
1868 groups = []
1870 groups = []
1869 if self.group is None:
1871 if self.group is None:
1870 return groups
1872 return groups
1871
1873
1872 cur_gr = self.group
1874 cur_gr = self.group
1873 groups.insert(0, cur_gr)
1875 groups.insert(0, cur_gr)
1874 while 1:
1876 while 1:
1875 gr = getattr(cur_gr, 'parent_group', None)
1877 gr = getattr(cur_gr, 'parent_group', None)
1876 cur_gr = cur_gr.parent_group
1878 cur_gr = cur_gr.parent_group
1877 if gr is None:
1879 if gr is None:
1878 break
1880 break
1879 groups.insert(0, gr)
1881 groups.insert(0, gr)
1880
1882
1881 return groups
1883 return groups
1882
1884
1883 @property
1885 @property
1884 def groups_and_repo(self):
1886 def groups_and_repo(self):
1885 return self.groups_with_parents, self
1887 return self.groups_with_parents, self
1886
1888
1887 @LazyProperty
1889 @LazyProperty
1888 def repo_path(self):
1890 def repo_path(self):
1889 """
1891 """
1890 Returns base full path for that repository means where it actually
1892 Returns base full path for that repository means where it actually
1891 exists on a filesystem
1893 exists on a filesystem
1892 """
1894 """
1893 q = Session().query(RhodeCodeUi).filter(
1895 q = Session().query(RhodeCodeUi).filter(
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1896 RhodeCodeUi.ui_key == self.NAME_SEP)
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1897 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1896 return q.one().ui_value
1898 return q.one().ui_value
1897
1899
1898 @property
1900 @property
1899 def repo_full_path(self):
1901 def repo_full_path(self):
1900 p = [self.repo_path]
1902 p = [self.repo_path]
1901 # we need to split the name by / since this is how we store the
1903 # we need to split the name by / since this is how we store the
1902 # names in the database, but that eventually needs to be converted
1904 # names in the database, but that eventually needs to be converted
1903 # into a valid system path
1905 # into a valid system path
1904 p += self.repo_name.split(self.NAME_SEP)
1906 p += self.repo_name.split(self.NAME_SEP)
1905 return os.path.join(*map(safe_unicode, p))
1907 return os.path.join(*map(safe_unicode, p))
1906
1908
1907 @property
1909 @property
1908 def cache_keys(self):
1910 def cache_keys(self):
1909 """
1911 """
1910 Returns associated cache keys for that repo
1912 Returns associated cache keys for that repo
1911 """
1913 """
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1914 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1913 repo_id=self.repo_id)
1915 repo_id=self.repo_id)
1914 return CacheKey.query()\
1916 return CacheKey.query()\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1917 .filter(CacheKey.cache_args == invalidation_namespace)\
1916 .order_by(CacheKey.cache_key)\
1918 .order_by(CacheKey.cache_key)\
1917 .all()
1919 .all()
1918
1920
1919 @property
1921 @property
1920 def cached_diffs_relative_dir(self):
1922 def cached_diffs_relative_dir(self):
1921 """
1923 """
1922 Return a relative to the repository store path of cached diffs
1924 Return a relative to the repository store path of cached diffs
1923 used for safe display for users, who shouldn't know the absolute store
1925 used for safe display for users, who shouldn't know the absolute store
1924 path
1926 path
1925 """
1927 """
1926 return os.path.join(
1928 return os.path.join(
1927 os.path.dirname(self.repo_name),
1929 os.path.dirname(self.repo_name),
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1930 self.cached_diffs_dir.split(os.path.sep)[-1])
1929
1931
1930 @property
1932 @property
1931 def cached_diffs_dir(self):
1933 def cached_diffs_dir(self):
1932 path = self.repo_full_path
1934 path = self.repo_full_path
1933 return os.path.join(
1935 return os.path.join(
1934 os.path.dirname(path),
1936 os.path.dirname(path),
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1937 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1936
1938
1937 def cached_diffs(self):
1939 def cached_diffs(self):
1938 diff_cache_dir = self.cached_diffs_dir
1940 diff_cache_dir = self.cached_diffs_dir
1939 if os.path.isdir(diff_cache_dir):
1941 if os.path.isdir(diff_cache_dir):
1940 return os.listdir(diff_cache_dir)
1942 return os.listdir(diff_cache_dir)
1941 return []
1943 return []
1942
1944
1943 def shadow_repos(self):
1945 def shadow_repos(self):
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1946 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1945 return [
1947 return [
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1948 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1947 if x.startswith(shadow_repos_pattern)]
1949 if x.startswith(shadow_repos_pattern)]
1948
1950
1949 def get_new_name(self, repo_name):
1951 def get_new_name(self, repo_name):
1950 """
1952 """
1951 returns new full repository name based on assigned group and new new
1953 returns new full repository name based on assigned group and new new
1952
1954
1953 :param group_name:
1955 :param group_name:
1954 """
1956 """
1955 path_prefix = self.group.full_path_splitted if self.group else []
1957 path_prefix = self.group.full_path_splitted if self.group else []
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1958 return self.NAME_SEP.join(path_prefix + [repo_name])
1957
1959
1958 @property
1960 @property
1959 def _config(self):
1961 def _config(self):
1960 """
1962 """
1961 Returns db based config object.
1963 Returns db based config object.
1962 """
1964 """
1963 from rhodecode.lib.utils import make_db_config
1965 from rhodecode.lib.utils import make_db_config
1964 return make_db_config(clear_session=False, repo=self)
1966 return make_db_config(clear_session=False, repo=self)
1965
1967
1966 def permissions(self, with_admins=True, with_owner=True,
1968 def permissions(self, with_admins=True, with_owner=True,
1967 expand_from_user_groups=False):
1969 expand_from_user_groups=False):
1968 """
1970 """
1969 Permissions for repositories
1971 Permissions for repositories
1970 """
1972 """
1971 _admin_perm = 'repository.admin'
1973 _admin_perm = 'repository.admin'
1972
1974
1973 owner_row = []
1975 owner_row = []
1974 if with_owner:
1976 if with_owner:
1975 usr = AttributeDict(self.user.get_dict())
1977 usr = AttributeDict(self.user.get_dict())
1976 usr.owner_row = True
1978 usr.owner_row = True
1977 usr.permission = _admin_perm
1979 usr.permission = _admin_perm
1978 usr.permission_id = None
1980 usr.permission_id = None
1979 owner_row.append(usr)
1981 owner_row.append(usr)
1980
1982
1981 super_admin_ids = []
1983 super_admin_ids = []
1982 super_admin_rows = []
1984 super_admin_rows = []
1983 if with_admins:
1985 if with_admins:
1984 for usr in User.get_all_super_admins():
1986 for usr in User.get_all_super_admins():
1985 super_admin_ids.append(usr.user_id)
1987 super_admin_ids.append(usr.user_id)
1986 # if this admin is also owner, don't double the record
1988 # if this admin is also owner, don't double the record
1987 if usr.user_id == owner_row[0].user_id:
1989 if usr.user_id == owner_row[0].user_id:
1988 owner_row[0].admin_row = True
1990 owner_row[0].admin_row = True
1989 else:
1991 else:
1990 usr = AttributeDict(usr.get_dict())
1992 usr = AttributeDict(usr.get_dict())
1991 usr.admin_row = True
1993 usr.admin_row = True
1992 usr.permission = _admin_perm
1994 usr.permission = _admin_perm
1993 usr.permission_id = None
1995 usr.permission_id = None
1994 super_admin_rows.append(usr)
1996 super_admin_rows.append(usr)
1995
1997
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1998 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1999 q = q.options(joinedload(UserRepoToPerm.repository),
1998 joinedload(UserRepoToPerm.user),
2000 joinedload(UserRepoToPerm.user),
1999 joinedload(UserRepoToPerm.permission),)
2001 joinedload(UserRepoToPerm.permission),)
2000
2002
2001 # get owners and admins and permissions. We do a trick of re-writing
2003 # get owners and admins and permissions. We do a trick of re-writing
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2004 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2003 # has a global reference and changing one object propagates to all
2005 # has a global reference and changing one object propagates to all
2004 # others. This means if admin is also an owner admin_row that change
2006 # others. This means if admin is also an owner admin_row that change
2005 # would propagate to both objects
2007 # would propagate to both objects
2006 perm_rows = []
2008 perm_rows = []
2007 for _usr in q.all():
2009 for _usr in q.all():
2008 usr = AttributeDict(_usr.user.get_dict())
2010 usr = AttributeDict(_usr.user.get_dict())
2009 # if this user is also owner/admin, mark as duplicate record
2011 # if this user is also owner/admin, mark as duplicate record
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2012 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2011 usr.duplicate_perm = True
2013 usr.duplicate_perm = True
2012 # also check if this permission is maybe used by branch_permissions
2014 # also check if this permission is maybe used by branch_permissions
2013 if _usr.branch_perm_entry:
2015 if _usr.branch_perm_entry:
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2016 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2015
2017
2016 usr.permission = _usr.permission.permission_name
2018 usr.permission = _usr.permission.permission_name
2017 usr.permission_id = _usr.repo_to_perm_id
2019 usr.permission_id = _usr.repo_to_perm_id
2018 perm_rows.append(usr)
2020 perm_rows.append(usr)
2019
2021
2020 # filter the perm rows by 'default' first and then sort them by
2022 # filter the perm rows by 'default' first and then sort them by
2021 # admin,write,read,none permissions sorted again alphabetically in
2023 # admin,write,read,none permissions sorted again alphabetically in
2022 # each group
2024 # each group
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2025 perm_rows = sorted(perm_rows, key=display_user_sort)
2024
2026
2025 user_groups_rows = []
2027 user_groups_rows = []
2026 if expand_from_user_groups:
2028 if expand_from_user_groups:
2027 for ug in self.permission_user_groups(with_members=True):
2029 for ug in self.permission_user_groups(with_members=True):
2028 for user_data in ug.members:
2030 for user_data in ug.members:
2029 user_groups_rows.append(user_data)
2031 user_groups_rows.append(user_data)
2030
2032
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2033 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2032
2034
2033 def permission_user_groups(self, with_members=True):
2035 def permission_user_groups(self, with_members=True):
2034 q = UserGroupRepoToPerm.query()\
2036 q = UserGroupRepoToPerm.query()\
2035 .filter(UserGroupRepoToPerm.repository == self)
2037 .filter(UserGroupRepoToPerm.repository == self)
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2038 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2037 joinedload(UserGroupRepoToPerm.users_group),
2039 joinedload(UserGroupRepoToPerm.users_group),
2038 joinedload(UserGroupRepoToPerm.permission),)
2040 joinedload(UserGroupRepoToPerm.permission),)
2039
2041
2040 perm_rows = []
2042 perm_rows = []
2041 for _user_group in q.all():
2043 for _user_group in q.all():
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2044 entry = AttributeDict(_user_group.users_group.get_dict())
2043 entry.permission = _user_group.permission.permission_name
2045 entry.permission = _user_group.permission.permission_name
2044 if with_members:
2046 if with_members:
2045 entry.members = [x.user.get_dict()
2047 entry.members = [x.user.get_dict()
2046 for x in _user_group.users_group.members]
2048 for x in _user_group.users_group.members]
2047 perm_rows.append(entry)
2049 perm_rows.append(entry)
2048
2050
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2051 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2050 return perm_rows
2052 return perm_rows
2051
2053
2052 def get_api_data(self, include_secrets=False):
2054 def get_api_data(self, include_secrets=False):
2053 """
2055 """
2054 Common function for generating repo api data
2056 Common function for generating repo api data
2055
2057
2056 :param include_secrets: See :meth:`User.get_api_data`.
2058 :param include_secrets: See :meth:`User.get_api_data`.
2057
2059
2058 """
2060 """
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2061 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2060 # move this methods on models level.
2062 # move this methods on models level.
2061 from rhodecode.model.settings import SettingsModel
2063 from rhodecode.model.settings import SettingsModel
2062 from rhodecode.model.repo import RepoModel
2064 from rhodecode.model.repo import RepoModel
2063
2065
2064 repo = self
2066 repo = self
2065 _user_id, _time, _reason = self.locked
2067 _user_id, _time, _reason = self.locked
2066
2068
2067 data = {
2069 data = {
2068 'repo_id': repo.repo_id,
2070 'repo_id': repo.repo_id,
2069 'repo_name': repo.repo_name,
2071 'repo_name': repo.repo_name,
2070 'repo_type': repo.repo_type,
2072 'repo_type': repo.repo_type,
2071 'clone_uri': repo.clone_uri or '',
2073 'clone_uri': repo.clone_uri or '',
2072 'push_uri': repo.push_uri or '',
2074 'push_uri': repo.push_uri or '',
2073 'url': RepoModel().get_url(self),
2075 'url': RepoModel().get_url(self),
2074 'private': repo.private,
2076 'private': repo.private,
2075 'created_on': repo.created_on,
2077 'created_on': repo.created_on,
2076 'description': repo.description_safe,
2078 'description': repo.description_safe,
2077 'landing_rev': repo.landing_rev,
2079 'landing_rev': repo.landing_rev,
2078 'owner': repo.user.username,
2080 'owner': repo.user.username,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2081 'fork_of': repo.fork.repo_name if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2082 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2081 'enable_statistics': repo.enable_statistics,
2083 'enable_statistics': repo.enable_statistics,
2082 'enable_locking': repo.enable_locking,
2084 'enable_locking': repo.enable_locking,
2083 'enable_downloads': repo.enable_downloads,
2085 'enable_downloads': repo.enable_downloads,
2084 'last_changeset': repo.changeset_cache,
2086 'last_changeset': repo.changeset_cache,
2085 'locked_by': User.get(_user_id).get_api_data(
2087 'locked_by': User.get(_user_id).get_api_data(
2086 include_secrets=include_secrets) if _user_id else None,
2088 include_secrets=include_secrets) if _user_id else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2089 'locked_date': time_to_datetime(_time) if _time else None,
2088 'lock_reason': _reason if _reason else None,
2090 'lock_reason': _reason if _reason else None,
2089 }
2091 }
2090
2092
2091 # TODO: mikhail: should be per-repo settings here
2093 # TODO: mikhail: should be per-repo settings here
2092 rc_config = SettingsModel().get_all_settings()
2094 rc_config = SettingsModel().get_all_settings()
2093 repository_fields = str2bool(
2095 repository_fields = str2bool(
2094 rc_config.get('rhodecode_repository_fields'))
2096 rc_config.get('rhodecode_repository_fields'))
2095 if repository_fields:
2097 if repository_fields:
2096 for f in self.extra_fields:
2098 for f in self.extra_fields:
2097 data[f.field_key_prefixed] = f.field_value
2099 data[f.field_key_prefixed] = f.field_value
2098
2100
2099 return data
2101 return data
2100
2102
2101 @classmethod
2103 @classmethod
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2104 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2103 if not lock_time:
2105 if not lock_time:
2104 lock_time = time.time()
2106 lock_time = time.time()
2105 if not lock_reason:
2107 if not lock_reason:
2106 lock_reason = cls.LOCK_AUTOMATIC
2108 lock_reason = cls.LOCK_AUTOMATIC
2107 repo.locked = [user_id, lock_time, lock_reason]
2109 repo.locked = [user_id, lock_time, lock_reason]
2108 Session().add(repo)
2110 Session().add(repo)
2109 Session().commit()
2111 Session().commit()
2110
2112
2111 @classmethod
2113 @classmethod
2112 def unlock(cls, repo):
2114 def unlock(cls, repo):
2113 repo.locked = None
2115 repo.locked = None
2114 Session().add(repo)
2116 Session().add(repo)
2115 Session().commit()
2117 Session().commit()
2116
2118
2117 @classmethod
2119 @classmethod
2118 def getlock(cls, repo):
2120 def getlock(cls, repo):
2119 return repo.locked
2121 return repo.locked
2120
2122
2121 def is_user_lock(self, user_id):
2123 def is_user_lock(self, user_id):
2122 if self.lock[0]:
2124 if self.lock[0]:
2123 lock_user_id = safe_int(self.lock[0])
2125 lock_user_id = safe_int(self.lock[0])
2124 user_id = safe_int(user_id)
2126 user_id = safe_int(user_id)
2125 # both are ints, and they are equal
2127 # both are ints, and they are equal
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2128 return all([lock_user_id, user_id]) and lock_user_id == user_id
2127
2129
2128 return False
2130 return False
2129
2131
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2132 def get_locking_state(self, action, user_id, only_when_enabled=True):
2131 """
2133 """
2132 Checks locking on this repository, if locking is enabled and lock is
2134 Checks locking on this repository, if locking is enabled and lock is
2133 present returns a tuple of make_lock, locked, locked_by.
2135 present returns a tuple of make_lock, locked, locked_by.
2134 make_lock can have 3 states None (do nothing) True, make lock
2136 make_lock can have 3 states None (do nothing) True, make lock
2135 False release lock, This value is later propagated to hooks, which
2137 False release lock, This value is later propagated to hooks, which
2136 do the locking. Think about this as signals passed to hooks what to do.
2138 do the locking. Think about this as signals passed to hooks what to do.
2137
2139
2138 """
2140 """
2139 # TODO: johbo: This is part of the business logic and should be moved
2141 # TODO: johbo: This is part of the business logic and should be moved
2140 # into the RepositoryModel.
2142 # into the RepositoryModel.
2141
2143
2142 if action not in ('push', 'pull'):
2144 if action not in ('push', 'pull'):
2143 raise ValueError("Invalid action value: %s" % repr(action))
2145 raise ValueError("Invalid action value: %s" % repr(action))
2144
2146
2145 # defines if locked error should be thrown to user
2147 # defines if locked error should be thrown to user
2146 currently_locked = False
2148 currently_locked = False
2147 # defines if new lock should be made, tri-state
2149 # defines if new lock should be made, tri-state
2148 make_lock = None
2150 make_lock = None
2149 repo = self
2151 repo = self
2150 user = User.get(user_id)
2152 user = User.get(user_id)
2151
2153
2152 lock_info = repo.locked
2154 lock_info = repo.locked
2153
2155
2154 if repo and (repo.enable_locking or not only_when_enabled):
2156 if repo and (repo.enable_locking or not only_when_enabled):
2155 if action == 'push':
2157 if action == 'push':
2156 # check if it's already locked !, if it is compare users
2158 # check if it's already locked !, if it is compare users
2157 locked_by_user_id = lock_info[0]
2159 locked_by_user_id = lock_info[0]
2158 if user.user_id == locked_by_user_id:
2160 if user.user_id == locked_by_user_id:
2159 log.debug(
2161 log.debug(
2160 'Got `push` action from user %s, now unlocking', user)
2162 'Got `push` action from user %s, now unlocking', user)
2161 # unlock if we have push from user who locked
2163 # unlock if we have push from user who locked
2162 make_lock = False
2164 make_lock = False
2163 else:
2165 else:
2164 # we're not the same user who locked, ban with
2166 # we're not the same user who locked, ban with
2165 # code defined in settings (default is 423 HTTP Locked) !
2167 # code defined in settings (default is 423 HTTP Locked) !
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2168 log.debug('Repo %s is currently locked by %s', repo, user)
2167 currently_locked = True
2169 currently_locked = True
2168 elif action == 'pull':
2170 elif action == 'pull':
2169 # [0] user [1] date
2171 # [0] user [1] date
2170 if lock_info[0] and lock_info[1]:
2172 if lock_info[0] and lock_info[1]:
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2173 log.debug('Repo %s is currently locked by %s', repo, user)
2172 currently_locked = True
2174 currently_locked = True
2173 else:
2175 else:
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2176 log.debug('Setting lock on repo %s by %s', repo, user)
2175 make_lock = True
2177 make_lock = True
2176
2178
2177 else:
2179 else:
2178 log.debug('Repository %s do not have locking enabled', repo)
2180 log.debug('Repository %s do not have locking enabled', repo)
2179
2181
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2182 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2181 make_lock, currently_locked, lock_info)
2183 make_lock, currently_locked, lock_info)
2182
2184
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2185 from rhodecode.lib.auth import HasRepoPermissionAny
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2186 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2187 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2186 # if we don't have at least write permission we cannot make a lock
2188 # if we don't have at least write permission we cannot make a lock
2187 log.debug('lock state reset back to FALSE due to lack '
2189 log.debug('lock state reset back to FALSE due to lack '
2188 'of at least read permission')
2190 'of at least read permission')
2189 make_lock = False
2191 make_lock = False
2190
2192
2191 return make_lock, currently_locked, lock_info
2193 return make_lock, currently_locked, lock_info
2192
2194
2193 @property
2195 @property
2194 def last_commit_cache_update_diff(self):
2196 def last_commit_cache_update_diff(self):
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2197 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2196
2198
2197 @property
2199 @property
2198 def last_commit_change(self):
2200 def last_commit_change(self):
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2201 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2200 empty_date = datetime.datetime.fromtimestamp(0)
2202 empty_date = datetime.datetime.fromtimestamp(0)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2203 date_latest = self.changeset_cache.get('date', empty_date)
2202 try:
2204 try:
2203 return parse_datetime(date_latest)
2205 return parse_datetime(date_latest)
2204 except Exception:
2206 except Exception:
2205 return empty_date
2207 return empty_date
2206
2208
2207 @property
2209 @property
2208 def last_db_change(self):
2210 def last_db_change(self):
2209 return self.updated_on
2211 return self.updated_on
2210
2212
2211 @property
2213 @property
2212 def clone_uri_hidden(self):
2214 def clone_uri_hidden(self):
2213 clone_uri = self.clone_uri
2215 clone_uri = self.clone_uri
2214 if clone_uri:
2216 if clone_uri:
2215 import urlobject
2217 import urlobject
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2218 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2217 if url_obj.password:
2219 if url_obj.password:
2218 clone_uri = url_obj.with_password('*****')
2220 clone_uri = url_obj.with_password('*****')
2219 return clone_uri
2221 return clone_uri
2220
2222
2221 @property
2223 @property
2222 def push_uri_hidden(self):
2224 def push_uri_hidden(self):
2223 push_uri = self.push_uri
2225 push_uri = self.push_uri
2224 if push_uri:
2226 if push_uri:
2225 import urlobject
2227 import urlobject
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2228 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2227 if url_obj.password:
2229 if url_obj.password:
2228 push_uri = url_obj.with_password('*****')
2230 push_uri = url_obj.with_password('*****')
2229 return push_uri
2231 return push_uri
2230
2232
2231 def clone_url(self, **override):
2233 def clone_url(self, **override):
2232 from rhodecode.model.settings import SettingsModel
2234 from rhodecode.model.settings import SettingsModel
2233
2235
2234 uri_tmpl = None
2236 uri_tmpl = None
2235 if 'with_id' in override:
2237 if 'with_id' in override:
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2238 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2237 del override['with_id']
2239 del override['with_id']
2238
2240
2239 if 'uri_tmpl' in override:
2241 if 'uri_tmpl' in override:
2240 uri_tmpl = override['uri_tmpl']
2242 uri_tmpl = override['uri_tmpl']
2241 del override['uri_tmpl']
2243 del override['uri_tmpl']
2242
2244
2243 ssh = False
2245 ssh = False
2244 if 'ssh' in override:
2246 if 'ssh' in override:
2245 ssh = True
2247 ssh = True
2246 del override['ssh']
2248 del override['ssh']
2247
2249
2248 # we didn't override our tmpl from **overrides
2250 # we didn't override our tmpl from **overrides
2249 request = get_current_request()
2251 request = get_current_request()
2250 if not uri_tmpl:
2252 if not uri_tmpl:
2251 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2253 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2252 rc_config = request.call_context.rc_config
2254 rc_config = request.call_context.rc_config
2253 else:
2255 else:
2254 rc_config = SettingsModel().get_all_settings(cache=True)
2256 rc_config = SettingsModel().get_all_settings(cache=True)
2255 if ssh:
2257 if ssh:
2256 uri_tmpl = rc_config.get(
2258 uri_tmpl = rc_config.get(
2257 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2259 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2258 else:
2260 else:
2259 uri_tmpl = rc_config.get(
2261 uri_tmpl = rc_config.get(
2260 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2262 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2261
2263
2262 return get_clone_url(request=request,
2264 return get_clone_url(request=request,
2263 uri_tmpl=uri_tmpl,
2265 uri_tmpl=uri_tmpl,
2264 repo_name=self.repo_name,
2266 repo_name=self.repo_name,
2265 repo_id=self.repo_id, **override)
2267 repo_id=self.repo_id, **override)
2266
2268
2267 def set_state(self, state):
2269 def set_state(self, state):
2268 self.repo_state = state
2270 self.repo_state = state
2269 Session().add(self)
2271 Session().add(self)
2270 #==========================================================================
2272 #==========================================================================
2271 # SCM PROPERTIES
2273 # SCM PROPERTIES
2272 #==========================================================================
2274 #==========================================================================
2273
2275
2274 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2276 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2275 return get_commit_safe(
2277 return get_commit_safe(
2276 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2278 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2277
2279
2278 def get_changeset(self, rev=None, pre_load=None):
2280 def get_changeset(self, rev=None, pre_load=None):
2279 warnings.warn("Use get_commit", DeprecationWarning)
2281 warnings.warn("Use get_commit", DeprecationWarning)
2280 commit_id = None
2282 commit_id = None
2281 commit_idx = None
2283 commit_idx = None
2282 if isinstance(rev, compat.string_types):
2284 if isinstance(rev, compat.string_types):
2283 commit_id = rev
2285 commit_id = rev
2284 else:
2286 else:
2285 commit_idx = rev
2287 commit_idx = rev
2286 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2288 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2287 pre_load=pre_load)
2289 pre_load=pre_load)
2288
2290
2289 def get_landing_commit(self):
2291 def get_landing_commit(self):
2290 """
2292 """
2291 Returns landing commit, or if that doesn't exist returns the tip
2293 Returns landing commit, or if that doesn't exist returns the tip
2292 """
2294 """
2293 _rev_type, _rev = self.landing_rev
2295 _rev_type, _rev = self.landing_rev
2294 commit = self.get_commit(_rev)
2296 commit = self.get_commit(_rev)
2295 if isinstance(commit, EmptyCommit):
2297 if isinstance(commit, EmptyCommit):
2296 return self.get_commit()
2298 return self.get_commit()
2297 return commit
2299 return commit
2298
2300
2299 def update_commit_cache(self, cs_cache=None, config=None):
2301 def update_commit_cache(self, cs_cache=None, config=None):
2300 """
2302 """
2301 Update cache of last commit for repository, keys should be::
2303 Update cache of last commit for repository, keys should be::
2302
2304
2303 source_repo_id
2305 source_repo_id
2304 short_id
2306 short_id
2305 raw_id
2307 raw_id
2306 revision
2308 revision
2307 parents
2309 parents
2308 message
2310 message
2309 date
2311 date
2310 author
2312 author
2311 updated_on
2313 updated_on
2312
2314
2313 """
2315 """
2314 from rhodecode.lib.vcs.backends.base import BaseChangeset
2316 from rhodecode.lib.vcs.backends.base import BaseChangeset
2315 if cs_cache is None:
2317 if cs_cache is None:
2316 # use no-cache version here
2318 # use no-cache version here
2317 scm_repo = self.scm_instance(cache=False, config=config)
2319 scm_repo = self.scm_instance(cache=False, config=config)
2318
2320
2319 empty = scm_repo is None or scm_repo.is_empty()
2321 empty = scm_repo is None or scm_repo.is_empty()
2320 if not empty:
2322 if not empty:
2321 cs_cache = scm_repo.get_commit(
2323 cs_cache = scm_repo.get_commit(
2322 pre_load=["author", "date", "message", "parents", "branch"])
2324 pre_load=["author", "date", "message", "parents", "branch"])
2323 else:
2325 else:
2324 cs_cache = EmptyCommit()
2326 cs_cache = EmptyCommit()
2325
2327
2326 if isinstance(cs_cache, BaseChangeset):
2328 if isinstance(cs_cache, BaseChangeset):
2327 cs_cache = cs_cache.__json__()
2329 cs_cache = cs_cache.__json__()
2328
2330
2329 def is_outdated(new_cs_cache):
2331 def is_outdated(new_cs_cache):
2330 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2332 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2331 new_cs_cache['revision'] != self.changeset_cache['revision']):
2333 new_cs_cache['revision'] != self.changeset_cache['revision']):
2332 return True
2334 return True
2333 return False
2335 return False
2334
2336
2335 # check if we have maybe already latest cached revision
2337 # check if we have maybe already latest cached revision
2336 if is_outdated(cs_cache) or not self.changeset_cache:
2338 if is_outdated(cs_cache) or not self.changeset_cache:
2337 _default = datetime.datetime.utcnow()
2339 _default = datetime.datetime.utcnow()
2338 last_change = cs_cache.get('date') or _default
2340 last_change = cs_cache.get('date') or _default
2339 # we check if last update is newer than the new value
2341 # we check if last update is newer than the new value
2340 # if yes, we use the current timestamp instead. Imagine you get
2342 # if yes, we use the current timestamp instead. Imagine you get
2341 # old commit pushed 1y ago, we'd set last update 1y to ago.
2343 # old commit pushed 1y ago, we'd set last update 1y to ago.
2342 last_change_timestamp = datetime_to_time(last_change)
2344 last_change_timestamp = datetime_to_time(last_change)
2343 current_timestamp = datetime_to_time(last_change)
2345 current_timestamp = datetime_to_time(last_change)
2344 if last_change_timestamp > current_timestamp:
2346 if last_change_timestamp > current_timestamp:
2345 cs_cache['date'] = _default
2347 cs_cache['date'] = _default
2346
2348
2347 cs_cache['updated_on'] = time.time()
2349 cs_cache['updated_on'] = time.time()
2348 self.changeset_cache = cs_cache
2350 self.changeset_cache = cs_cache
2349 Session().add(self)
2351 Session().add(self)
2350 Session().commit()
2352 Session().commit()
2351
2353
2352 log.debug('updated repo %s with new commit cache %s',
2354 log.debug('updated repo %s with new commit cache %s',
2353 self.repo_name, cs_cache)
2355 self.repo_name, cs_cache)
2354 else:
2356 else:
2355 cs_cache = self.changeset_cache
2357 cs_cache = self.changeset_cache
2356 cs_cache['updated_on'] = time.time()
2358 cs_cache['updated_on'] = time.time()
2357 self.changeset_cache = cs_cache
2359 self.changeset_cache = cs_cache
2358 Session().add(self)
2360 Session().add(self)
2359 Session().commit()
2361 Session().commit()
2360
2362
2361 log.debug('Skipping update_commit_cache for repo:`%s` '
2363 log.debug('Skipping update_commit_cache for repo:`%s` '
2362 'commit already with latest changes', self.repo_name)
2364 'commit already with latest changes', self.repo_name)
2363
2365
2364 @property
2366 @property
2365 def tip(self):
2367 def tip(self):
2366 return self.get_commit('tip')
2368 return self.get_commit('tip')
2367
2369
2368 @property
2370 @property
2369 def author(self):
2371 def author(self):
2370 return self.tip.author
2372 return self.tip.author
2371
2373
2372 @property
2374 @property
2373 def last_change(self):
2375 def last_change(self):
2374 return self.scm_instance().last_change
2376 return self.scm_instance().last_change
2375
2377
2376 def get_comments(self, revisions=None):
2378 def get_comments(self, revisions=None):
2377 """
2379 """
2378 Returns comments for this repository grouped by revisions
2380 Returns comments for this repository grouped by revisions
2379
2381
2380 :param revisions: filter query by revisions only
2382 :param revisions: filter query by revisions only
2381 """
2383 """
2382 cmts = ChangesetComment.query()\
2384 cmts = ChangesetComment.query()\
2383 .filter(ChangesetComment.repo == self)
2385 .filter(ChangesetComment.repo == self)
2384 if revisions:
2386 if revisions:
2385 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2387 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2386 grouped = collections.defaultdict(list)
2388 grouped = collections.defaultdict(list)
2387 for cmt in cmts.all():
2389 for cmt in cmts.all():
2388 grouped[cmt.revision].append(cmt)
2390 grouped[cmt.revision].append(cmt)
2389 return grouped
2391 return grouped
2390
2392
2391 def statuses(self, revisions=None):
2393 def statuses(self, revisions=None):
2392 """
2394 """
2393 Returns statuses for this repository
2395 Returns statuses for this repository
2394
2396
2395 :param revisions: list of revisions to get statuses for
2397 :param revisions: list of revisions to get statuses for
2396 """
2398 """
2397 statuses = ChangesetStatus.query()\
2399 statuses = ChangesetStatus.query()\
2398 .filter(ChangesetStatus.repo == self)\
2400 .filter(ChangesetStatus.repo == self)\
2399 .filter(ChangesetStatus.version == 0)
2401 .filter(ChangesetStatus.version == 0)
2400
2402
2401 if revisions:
2403 if revisions:
2402 # Try doing the filtering in chunks to avoid hitting limits
2404 # Try doing the filtering in chunks to avoid hitting limits
2403 size = 500
2405 size = 500
2404 status_results = []
2406 status_results = []
2405 for chunk in xrange(0, len(revisions), size):
2407 for chunk in xrange(0, len(revisions), size):
2406 status_results += statuses.filter(
2408 status_results += statuses.filter(
2407 ChangesetStatus.revision.in_(
2409 ChangesetStatus.revision.in_(
2408 revisions[chunk: chunk+size])
2410 revisions[chunk: chunk+size])
2409 ).all()
2411 ).all()
2410 else:
2412 else:
2411 status_results = statuses.all()
2413 status_results = statuses.all()
2412
2414
2413 grouped = {}
2415 grouped = {}
2414
2416
2415 # maybe we have open new pullrequest without a status?
2417 # maybe we have open new pullrequest without a status?
2416 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2418 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2417 status_lbl = ChangesetStatus.get_status_lbl(stat)
2419 status_lbl = ChangesetStatus.get_status_lbl(stat)
2418 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2420 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2419 for rev in pr.revisions:
2421 for rev in pr.revisions:
2420 pr_id = pr.pull_request_id
2422 pr_id = pr.pull_request_id
2421 pr_repo = pr.target_repo.repo_name
2423 pr_repo = pr.target_repo.repo_name
2422 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2424 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2423
2425
2424 for stat in status_results:
2426 for stat in status_results:
2425 pr_id = pr_repo = None
2427 pr_id = pr_repo = None
2426 if stat.pull_request:
2428 if stat.pull_request:
2427 pr_id = stat.pull_request.pull_request_id
2429 pr_id = stat.pull_request.pull_request_id
2428 pr_repo = stat.pull_request.target_repo.repo_name
2430 pr_repo = stat.pull_request.target_repo.repo_name
2429 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2431 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2430 pr_id, pr_repo]
2432 pr_id, pr_repo]
2431 return grouped
2433 return grouped
2432
2434
2433 # ==========================================================================
2435 # ==========================================================================
2434 # SCM CACHE INSTANCE
2436 # SCM CACHE INSTANCE
2435 # ==========================================================================
2437 # ==========================================================================
2436
2438
2437 def scm_instance(self, **kwargs):
2439 def scm_instance(self, **kwargs):
2438 import rhodecode
2440 import rhodecode
2439
2441
2440 # Passing a config will not hit the cache currently only used
2442 # Passing a config will not hit the cache currently only used
2441 # for repo2dbmapper
2443 # for repo2dbmapper
2442 config = kwargs.pop('config', None)
2444 config = kwargs.pop('config', None)
2443 cache = kwargs.pop('cache', None)
2445 cache = kwargs.pop('cache', None)
2444 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2446 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2445 if vcs_full_cache is not None:
2447 if vcs_full_cache is not None:
2446 # allows override global config
2448 # allows override global config
2447 full_cache = vcs_full_cache
2449 full_cache = vcs_full_cache
2448 else:
2450 else:
2449 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2451 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2450 # if cache is NOT defined use default global, else we have a full
2452 # if cache is NOT defined use default global, else we have a full
2451 # control over cache behaviour
2453 # control over cache behaviour
2452 if cache is None and full_cache and not config:
2454 if cache is None and full_cache and not config:
2453 log.debug('Initializing pure cached instance for %s', self.repo_path)
2455 log.debug('Initializing pure cached instance for %s', self.repo_path)
2454 return self._get_instance_cached()
2456 return self._get_instance_cached()
2455
2457
2456 # cache here is sent to the "vcs server"
2458 # cache here is sent to the "vcs server"
2457 return self._get_instance(cache=bool(cache), config=config)
2459 return self._get_instance(cache=bool(cache), config=config)
2458
2460
2459 def _get_instance_cached(self):
2461 def _get_instance_cached(self):
2460 from rhodecode.lib import rc_cache
2462 from rhodecode.lib import rc_cache
2461
2463
2462 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2464 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2463 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2465 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2464 repo_id=self.repo_id)
2466 repo_id=self.repo_id)
2465 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2467 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2466
2468
2467 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2469 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2468 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2470 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2469 return self._get_instance(repo_state_uid=_cache_state_uid)
2471 return self._get_instance(repo_state_uid=_cache_state_uid)
2470
2472
2471 # we must use thread scoped cache here,
2473 # we must use thread scoped cache here,
2472 # because each thread of gevent needs it's own not shared connection and cache
2474 # because each thread of gevent needs it's own not shared connection and cache
2473 # we also alter `args` so the cache key is individual for every green thread.
2475 # we also alter `args` so the cache key is individual for every green thread.
2474 inv_context_manager = rc_cache.InvalidationContext(
2476 inv_context_manager = rc_cache.InvalidationContext(
2475 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2477 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2476 thread_scoped=True)
2478 thread_scoped=True)
2477 with inv_context_manager as invalidation_context:
2479 with inv_context_manager as invalidation_context:
2478 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2480 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2479 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2481 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2480
2482
2481 # re-compute and store cache if we get invalidate signal
2483 # re-compute and store cache if we get invalidate signal
2482 if invalidation_context.should_invalidate():
2484 if invalidation_context.should_invalidate():
2483 instance = get_instance_cached.refresh(*args)
2485 instance = get_instance_cached.refresh(*args)
2484 else:
2486 else:
2485 instance = get_instance_cached(*args)
2487 instance = get_instance_cached(*args)
2486
2488
2487 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2489 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2488 return instance
2490 return instance
2489
2491
2490 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2492 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2491 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2493 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2492 self.repo_type, self.repo_path, cache)
2494 self.repo_type, self.repo_path, cache)
2493 config = config or self._config
2495 config = config or self._config
2494 custom_wire = {
2496 custom_wire = {
2495 'cache': cache, # controls the vcs.remote cache
2497 'cache': cache, # controls the vcs.remote cache
2496 'repo_state_uid': repo_state_uid
2498 'repo_state_uid': repo_state_uid
2497 }
2499 }
2498 repo = get_vcs_instance(
2500 repo = get_vcs_instance(
2499 repo_path=safe_str(self.repo_full_path),
2501 repo_path=safe_str(self.repo_full_path),
2500 config=config,
2502 config=config,
2501 with_wire=custom_wire,
2503 with_wire=custom_wire,
2502 create=False,
2504 create=False,
2503 _vcs_alias=self.repo_type)
2505 _vcs_alias=self.repo_type)
2504 if repo is not None:
2506 if repo is not None:
2505 repo.count() # cache rebuild
2507 repo.count() # cache rebuild
2506 return repo
2508 return repo
2507
2509
2508 def get_shadow_repository_path(self, workspace_id):
2510 def get_shadow_repository_path(self, workspace_id):
2509 from rhodecode.lib.vcs.backends.base import BaseRepository
2511 from rhodecode.lib.vcs.backends.base import BaseRepository
2510 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2512 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2511 self.repo_full_path, self.repo_id, workspace_id)
2513 self.repo_full_path, self.repo_id, workspace_id)
2512 return shadow_repo_path
2514 return shadow_repo_path
2513
2515
2514 def __json__(self):
2516 def __json__(self):
2515 return {'landing_rev': self.landing_rev}
2517 return {'landing_rev': self.landing_rev}
2516
2518
2517 def get_dict(self):
2519 def get_dict(self):
2518
2520
2519 # Since we transformed `repo_name` to a hybrid property, we need to
2521 # Since we transformed `repo_name` to a hybrid property, we need to
2520 # keep compatibility with the code which uses `repo_name` field.
2522 # keep compatibility with the code which uses `repo_name` field.
2521
2523
2522 result = super(Repository, self).get_dict()
2524 result = super(Repository, self).get_dict()
2523 result['repo_name'] = result.pop('_repo_name', None)
2525 result['repo_name'] = result.pop('_repo_name', None)
2524 return result
2526 return result
2525
2527
2526
2528
2527 class RepoGroup(Base, BaseModel):
2529 class RepoGroup(Base, BaseModel):
2528 __tablename__ = 'groups'
2530 __tablename__ = 'groups'
2529 __table_args__ = (
2531 __table_args__ = (
2530 UniqueConstraint('group_name', 'group_parent_id'),
2532 UniqueConstraint('group_name', 'group_parent_id'),
2531 base_table_args,
2533 base_table_args,
2532 )
2534 )
2533 __mapper_args__ = {'order_by': 'group_name'}
2535 __mapper_args__ = {'order_by': 'group_name'}
2534
2536
2535 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2537 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2536
2538
2537 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2539 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2538 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2540 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2539 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2541 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2540 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2542 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2541 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2543 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2542 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2544 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2544 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2546 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2545 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2547 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2546 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2548 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2547 _changeset_cache = Column(
2549 _changeset_cache = Column(
2548 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2550 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2549
2551
2550 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2552 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2551 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2553 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2552 parent_group = relationship('RepoGroup', remote_side=group_id)
2554 parent_group = relationship('RepoGroup', remote_side=group_id)
2553 user = relationship('User')
2555 user = relationship('User')
2554 integrations = relationship('Integration', cascade="all, delete-orphan")
2556 integrations = relationship('Integration', cascade="all, delete-orphan")
2555
2557
2556 def __init__(self, group_name='', parent_group=None):
2558 def __init__(self, group_name='', parent_group=None):
2557 self.group_name = group_name
2559 self.group_name = group_name
2558 self.parent_group = parent_group
2560 self.parent_group = parent_group
2559
2561
2560 def __unicode__(self):
2562 def __unicode__(self):
2561 return u"<%s('id:%s:%s')>" % (
2563 return u"<%s('id:%s:%s')>" % (
2562 self.__class__.__name__, self.group_id, self.group_name)
2564 self.__class__.__name__, self.group_id, self.group_name)
2563
2565
2564 @hybrid_property
2566 @hybrid_property
2565 def group_name(self):
2567 def group_name(self):
2566 return self._group_name
2568 return self._group_name
2567
2569
2568 @group_name.setter
2570 @group_name.setter
2569 def group_name(self, value):
2571 def group_name(self, value):
2570 self._group_name = value
2572 self._group_name = value
2571 self.group_name_hash = self.hash_repo_group_name(value)
2573 self.group_name_hash = self.hash_repo_group_name(value)
2572
2574
2573 @hybrid_property
2575 @hybrid_property
2574 def changeset_cache(self):
2576 def changeset_cache(self):
2575 from rhodecode.lib.vcs.backends.base import EmptyCommit
2577 from rhodecode.lib.vcs.backends.base import EmptyCommit
2576 dummy = EmptyCommit().__json__()
2578 dummy = EmptyCommit().__json__()
2577 if not self._changeset_cache:
2579 if not self._changeset_cache:
2578 dummy['source_repo_id'] = ''
2580 dummy['source_repo_id'] = ''
2579 return json.loads(json.dumps(dummy))
2581 return json.loads(json.dumps(dummy))
2580
2582
2581 try:
2583 try:
2582 return json.loads(self._changeset_cache)
2584 return json.loads(self._changeset_cache)
2583 except TypeError:
2585 except TypeError:
2584 return dummy
2586 return dummy
2585 except Exception:
2587 except Exception:
2586 log.error(traceback.format_exc())
2588 log.error(traceback.format_exc())
2587 return dummy
2589 return dummy
2588
2590
2589 @changeset_cache.setter
2591 @changeset_cache.setter
2590 def changeset_cache(self, val):
2592 def changeset_cache(self, val):
2591 try:
2593 try:
2592 self._changeset_cache = json.dumps(val)
2594 self._changeset_cache = json.dumps(val)
2593 except Exception:
2595 except Exception:
2594 log.error(traceback.format_exc())
2596 log.error(traceback.format_exc())
2595
2597
2596 @validates('group_parent_id')
2598 @validates('group_parent_id')
2597 def validate_group_parent_id(self, key, val):
2599 def validate_group_parent_id(self, key, val):
2598 """
2600 """
2599 Check cycle references for a parent group to self
2601 Check cycle references for a parent group to self
2600 """
2602 """
2601 if self.group_id and val:
2603 if self.group_id and val:
2602 assert val != self.group_id
2604 assert val != self.group_id
2603
2605
2604 return val
2606 return val
2605
2607
2606 @hybrid_property
2608 @hybrid_property
2607 def description_safe(self):
2609 def description_safe(self):
2608 from rhodecode.lib import helpers as h
2610 from rhodecode.lib import helpers as h
2609 return h.escape(self.group_description)
2611 return h.escape(self.group_description)
2610
2612
2611 @classmethod
2613 @classmethod
2612 def hash_repo_group_name(cls, repo_group_name):
2614 def hash_repo_group_name(cls, repo_group_name):
2613 val = remove_formatting(repo_group_name)
2615 val = remove_formatting(repo_group_name)
2614 val = safe_str(val).lower()
2616 val = safe_str(val).lower()
2615 chars = []
2617 chars = []
2616 for c in val:
2618 for c in val:
2617 if c not in string.ascii_letters:
2619 if c not in string.ascii_letters:
2618 c = str(ord(c))
2620 c = str(ord(c))
2619 chars.append(c)
2621 chars.append(c)
2620
2622
2621 return ''.join(chars)
2623 return ''.join(chars)
2622
2624
2623 @classmethod
2625 @classmethod
2624 def _generate_choice(cls, repo_group):
2626 def _generate_choice(cls, repo_group):
2625 from webhelpers.html import literal as _literal
2627 from webhelpers.html import literal as _literal
2626 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2628 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2627 return repo_group.group_id, _name(repo_group.full_path_splitted)
2629 return repo_group.group_id, _name(repo_group.full_path_splitted)
2628
2630
2629 @classmethod
2631 @classmethod
2630 def groups_choices(cls, groups=None, show_empty_group=True):
2632 def groups_choices(cls, groups=None, show_empty_group=True):
2631 if not groups:
2633 if not groups:
2632 groups = cls.query().all()
2634 groups = cls.query().all()
2633
2635
2634 repo_groups = []
2636 repo_groups = []
2635 if show_empty_group:
2637 if show_empty_group:
2636 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2638 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2637
2639
2638 repo_groups.extend([cls._generate_choice(x) for x in groups])
2640 repo_groups.extend([cls._generate_choice(x) for x in groups])
2639
2641
2640 repo_groups = sorted(
2642 repo_groups = sorted(
2641 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2643 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2642 return repo_groups
2644 return repo_groups
2643
2645
2644 @classmethod
2646 @classmethod
2645 def url_sep(cls):
2647 def url_sep(cls):
2646 return URL_SEP
2648 return URL_SEP
2647
2649
2648 @classmethod
2650 @classmethod
2649 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2651 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2650 if case_insensitive:
2652 if case_insensitive:
2651 gr = cls.query().filter(func.lower(cls.group_name)
2653 gr = cls.query().filter(func.lower(cls.group_name)
2652 == func.lower(group_name))
2654 == func.lower(group_name))
2653 else:
2655 else:
2654 gr = cls.query().filter(cls.group_name == group_name)
2656 gr = cls.query().filter(cls.group_name == group_name)
2655 if cache:
2657 if cache:
2656 name_key = _hash_key(group_name)
2658 name_key = _hash_key(group_name)
2657 gr = gr.options(
2659 gr = gr.options(
2658 FromCache("sql_cache_short", "get_group_%s" % name_key))
2660 FromCache("sql_cache_short", "get_group_%s" % name_key))
2659 return gr.scalar()
2661 return gr.scalar()
2660
2662
2661 @classmethod
2663 @classmethod
2662 def get_user_personal_repo_group(cls, user_id):
2664 def get_user_personal_repo_group(cls, user_id):
2663 user = User.get(user_id)
2665 user = User.get(user_id)
2664 if user.username == User.DEFAULT_USER:
2666 if user.username == User.DEFAULT_USER:
2665 return None
2667 return None
2666
2668
2667 return cls.query()\
2669 return cls.query()\
2668 .filter(cls.personal == true()) \
2670 .filter(cls.personal == true()) \
2669 .filter(cls.user == user) \
2671 .filter(cls.user == user) \
2670 .order_by(cls.group_id.asc()) \
2672 .order_by(cls.group_id.asc()) \
2671 .first()
2673 .first()
2672
2674
2673 @classmethod
2675 @classmethod
2674 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2676 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2675 case_insensitive=True):
2677 case_insensitive=True):
2676 q = RepoGroup.query()
2678 q = RepoGroup.query()
2677
2679
2678 if not isinstance(user_id, Optional):
2680 if not isinstance(user_id, Optional):
2679 q = q.filter(RepoGroup.user_id == user_id)
2681 q = q.filter(RepoGroup.user_id == user_id)
2680
2682
2681 if not isinstance(group_id, Optional):
2683 if not isinstance(group_id, Optional):
2682 q = q.filter(RepoGroup.group_parent_id == group_id)
2684 q = q.filter(RepoGroup.group_parent_id == group_id)
2683
2685
2684 if case_insensitive:
2686 if case_insensitive:
2685 q = q.order_by(func.lower(RepoGroup.group_name))
2687 q = q.order_by(func.lower(RepoGroup.group_name))
2686 else:
2688 else:
2687 q = q.order_by(RepoGroup.group_name)
2689 q = q.order_by(RepoGroup.group_name)
2688 return q.all()
2690 return q.all()
2689
2691
2690 @property
2692 @property
2691 def parents(self, parents_recursion_limit = 10):
2693 def parents(self, parents_recursion_limit = 10):
2692 groups = []
2694 groups = []
2693 if self.parent_group is None:
2695 if self.parent_group is None:
2694 return groups
2696 return groups
2695 cur_gr = self.parent_group
2697 cur_gr = self.parent_group
2696 groups.insert(0, cur_gr)
2698 groups.insert(0, cur_gr)
2697 cnt = 0
2699 cnt = 0
2698 while 1:
2700 while 1:
2699 cnt += 1
2701 cnt += 1
2700 gr = getattr(cur_gr, 'parent_group', None)
2702 gr = getattr(cur_gr, 'parent_group', None)
2701 cur_gr = cur_gr.parent_group
2703 cur_gr = cur_gr.parent_group
2702 if gr is None:
2704 if gr is None:
2703 break
2705 break
2704 if cnt == parents_recursion_limit:
2706 if cnt == parents_recursion_limit:
2705 # this will prevent accidental infinit loops
2707 # this will prevent accidental infinit loops
2706 log.error('more than %s parents found for group %s, stopping '
2708 log.error('more than %s parents found for group %s, stopping '
2707 'recursive parent fetching', parents_recursion_limit, self)
2709 'recursive parent fetching', parents_recursion_limit, self)
2708 break
2710 break
2709
2711
2710 groups.insert(0, gr)
2712 groups.insert(0, gr)
2711 return groups
2713 return groups
2712
2714
2713 @property
2715 @property
2714 def last_commit_cache_update_diff(self):
2716 def last_commit_cache_update_diff(self):
2715 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2717 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2716
2718
2717 @property
2719 @property
2718 def last_commit_change(self):
2720 def last_commit_change(self):
2719 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2721 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2720 empty_date = datetime.datetime.fromtimestamp(0)
2722 empty_date = datetime.datetime.fromtimestamp(0)
2721 date_latest = self.changeset_cache.get('date', empty_date)
2723 date_latest = self.changeset_cache.get('date', empty_date)
2722 try:
2724 try:
2723 return parse_datetime(date_latest)
2725 return parse_datetime(date_latest)
2724 except Exception:
2726 except Exception:
2725 return empty_date
2727 return empty_date
2726
2728
2727 @property
2729 @property
2728 def last_db_change(self):
2730 def last_db_change(self):
2729 return self.updated_on
2731 return self.updated_on
2730
2732
2731 @property
2733 @property
2732 def children(self):
2734 def children(self):
2733 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2735 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2734
2736
2735 @property
2737 @property
2736 def name(self):
2738 def name(self):
2737 return self.group_name.split(RepoGroup.url_sep())[-1]
2739 return self.group_name.split(RepoGroup.url_sep())[-1]
2738
2740
2739 @property
2741 @property
2740 def full_path(self):
2742 def full_path(self):
2741 return self.group_name
2743 return self.group_name
2742
2744
2743 @property
2745 @property
2744 def full_path_splitted(self):
2746 def full_path_splitted(self):
2745 return self.group_name.split(RepoGroup.url_sep())
2747 return self.group_name.split(RepoGroup.url_sep())
2746
2748
2747 @property
2749 @property
2748 def repositories(self):
2750 def repositories(self):
2749 return Repository.query()\
2751 return Repository.query()\
2750 .filter(Repository.group == self)\
2752 .filter(Repository.group == self)\
2751 .order_by(Repository.repo_name)
2753 .order_by(Repository.repo_name)
2752
2754
2753 @property
2755 @property
2754 def repositories_recursive_count(self):
2756 def repositories_recursive_count(self):
2755 cnt = self.repositories.count()
2757 cnt = self.repositories.count()
2756
2758
2757 def children_count(group):
2759 def children_count(group):
2758 cnt = 0
2760 cnt = 0
2759 for child in group.children:
2761 for child in group.children:
2760 cnt += child.repositories.count()
2762 cnt += child.repositories.count()
2761 cnt += children_count(child)
2763 cnt += children_count(child)
2762 return cnt
2764 return cnt
2763
2765
2764 return cnt + children_count(self)
2766 return cnt + children_count(self)
2765
2767
2766 def _recursive_objects(self, include_repos=True, include_groups=True):
2768 def _recursive_objects(self, include_repos=True, include_groups=True):
2767 all_ = []
2769 all_ = []
2768
2770
2769 def _get_members(root_gr):
2771 def _get_members(root_gr):
2770 if include_repos:
2772 if include_repos:
2771 for r in root_gr.repositories:
2773 for r in root_gr.repositories:
2772 all_.append(r)
2774 all_.append(r)
2773 childs = root_gr.children.all()
2775 childs = root_gr.children.all()
2774 if childs:
2776 if childs:
2775 for gr in childs:
2777 for gr in childs:
2776 if include_groups:
2778 if include_groups:
2777 all_.append(gr)
2779 all_.append(gr)
2778 _get_members(gr)
2780 _get_members(gr)
2779
2781
2780 root_group = []
2782 root_group = []
2781 if include_groups:
2783 if include_groups:
2782 root_group = [self]
2784 root_group = [self]
2783
2785
2784 _get_members(self)
2786 _get_members(self)
2785 return root_group + all_
2787 return root_group + all_
2786
2788
2787 def recursive_groups_and_repos(self):
2789 def recursive_groups_and_repos(self):
2788 """
2790 """
2789 Recursive return all groups, with repositories in those groups
2791 Recursive return all groups, with repositories in those groups
2790 """
2792 """
2791 return self._recursive_objects()
2793 return self._recursive_objects()
2792
2794
2793 def recursive_groups(self):
2795 def recursive_groups(self):
2794 """
2796 """
2795 Returns all children groups for this group including children of children
2797 Returns all children groups for this group including children of children
2796 """
2798 """
2797 return self._recursive_objects(include_repos=False)
2799 return self._recursive_objects(include_repos=False)
2798
2800
2799 def recursive_repos(self):
2801 def recursive_repos(self):
2800 """
2802 """
2801 Returns all children repositories for this group
2803 Returns all children repositories for this group
2802 """
2804 """
2803 return self._recursive_objects(include_groups=False)
2805 return self._recursive_objects(include_groups=False)
2804
2806
2805 def get_new_name(self, group_name):
2807 def get_new_name(self, group_name):
2806 """
2808 """
2807 returns new full group name based on parent and new name
2809 returns new full group name based on parent and new name
2808
2810
2809 :param group_name:
2811 :param group_name:
2810 """
2812 """
2811 path_prefix = (self.parent_group.full_path_splitted if
2813 path_prefix = (self.parent_group.full_path_splitted if
2812 self.parent_group else [])
2814 self.parent_group else [])
2813 return RepoGroup.url_sep().join(path_prefix + [group_name])
2815 return RepoGroup.url_sep().join(path_prefix + [group_name])
2814
2816
2815 def update_commit_cache(self, config=None):
2817 def update_commit_cache(self, config=None):
2816 """
2818 """
2817 Update cache of last changeset for newest repository inside this group, keys should be::
2819 Update cache of last changeset for newest repository inside this group, keys should be::
2818
2820
2819 source_repo_id
2821 source_repo_id
2820 short_id
2822 short_id
2821 raw_id
2823 raw_id
2822 revision
2824 revision
2823 parents
2825 parents
2824 message
2826 message
2825 date
2827 date
2826 author
2828 author
2827
2829
2828 """
2830 """
2829 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2831 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2830
2832
2831 def repo_groups_and_repos():
2833 def repo_groups_and_repos():
2832 all_entries = OrderedDefaultDict(list)
2834 all_entries = OrderedDefaultDict(list)
2833
2835
2834 def _get_members(root_gr, pos=0):
2836 def _get_members(root_gr, pos=0):
2835
2837
2836 for repo in root_gr.repositories:
2838 for repo in root_gr.repositories:
2837 all_entries[root_gr].append(repo)
2839 all_entries[root_gr].append(repo)
2838
2840
2839 # fill in all parent positions
2841 # fill in all parent positions
2840 for parent_group in root_gr.parents:
2842 for parent_group in root_gr.parents:
2841 all_entries[parent_group].extend(all_entries[root_gr])
2843 all_entries[parent_group].extend(all_entries[root_gr])
2842
2844
2843 children_groups = root_gr.children.all()
2845 children_groups = root_gr.children.all()
2844 if children_groups:
2846 if children_groups:
2845 for cnt, gr in enumerate(children_groups, 1):
2847 for cnt, gr in enumerate(children_groups, 1):
2846 _get_members(gr, pos=pos+cnt)
2848 _get_members(gr, pos=pos+cnt)
2847
2849
2848 _get_members(root_gr=self)
2850 _get_members(root_gr=self)
2849 return all_entries
2851 return all_entries
2850
2852
2851 empty_date = datetime.datetime.fromtimestamp(0)
2853 empty_date = datetime.datetime.fromtimestamp(0)
2852 for repo_group, repos in repo_groups_and_repos().items():
2854 for repo_group, repos in repo_groups_and_repos().items():
2853
2855
2854 latest_repo_cs_cache = {}
2856 latest_repo_cs_cache = {}
2855 for repo in repos:
2857 for repo in repos:
2856 repo_cs_cache = repo.changeset_cache
2858 repo_cs_cache = repo.changeset_cache
2857 date_latest = latest_repo_cs_cache.get('date', empty_date)
2859 date_latest = latest_repo_cs_cache.get('date', empty_date)
2858 date_current = repo_cs_cache.get('date', empty_date)
2860 date_current = repo_cs_cache.get('date', empty_date)
2859 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2861 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2860 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2862 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2861 latest_repo_cs_cache = repo_cs_cache
2863 latest_repo_cs_cache = repo_cs_cache
2862 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2864 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2863
2865
2864 latest_repo_cs_cache['updated_on'] = time.time()
2866 latest_repo_cs_cache['updated_on'] = time.time()
2865 repo_group.changeset_cache = latest_repo_cs_cache
2867 repo_group.changeset_cache = latest_repo_cs_cache
2866 Session().add(repo_group)
2868 Session().add(repo_group)
2867 Session().commit()
2869 Session().commit()
2868
2870
2869 log.debug('updated repo group %s with new commit cache %s',
2871 log.debug('updated repo group %s with new commit cache %s',
2870 repo_group.group_name, latest_repo_cs_cache)
2872 repo_group.group_name, latest_repo_cs_cache)
2871
2873
2872 def permissions(self, with_admins=True, with_owner=True,
2874 def permissions(self, with_admins=True, with_owner=True,
2873 expand_from_user_groups=False):
2875 expand_from_user_groups=False):
2874 """
2876 """
2875 Permissions for repository groups
2877 Permissions for repository groups
2876 """
2878 """
2877 _admin_perm = 'group.admin'
2879 _admin_perm = 'group.admin'
2878
2880
2879 owner_row = []
2881 owner_row = []
2880 if with_owner:
2882 if with_owner:
2881 usr = AttributeDict(self.user.get_dict())
2883 usr = AttributeDict(self.user.get_dict())
2882 usr.owner_row = True
2884 usr.owner_row = True
2883 usr.permission = _admin_perm
2885 usr.permission = _admin_perm
2884 owner_row.append(usr)
2886 owner_row.append(usr)
2885
2887
2886 super_admin_ids = []
2888 super_admin_ids = []
2887 super_admin_rows = []
2889 super_admin_rows = []
2888 if with_admins:
2890 if with_admins:
2889 for usr in User.get_all_super_admins():
2891 for usr in User.get_all_super_admins():
2890 super_admin_ids.append(usr.user_id)
2892 super_admin_ids.append(usr.user_id)
2891 # if this admin is also owner, don't double the record
2893 # if this admin is also owner, don't double the record
2892 if usr.user_id == owner_row[0].user_id:
2894 if usr.user_id == owner_row[0].user_id:
2893 owner_row[0].admin_row = True
2895 owner_row[0].admin_row = True
2894 else:
2896 else:
2895 usr = AttributeDict(usr.get_dict())
2897 usr = AttributeDict(usr.get_dict())
2896 usr.admin_row = True
2898 usr.admin_row = True
2897 usr.permission = _admin_perm
2899 usr.permission = _admin_perm
2898 super_admin_rows.append(usr)
2900 super_admin_rows.append(usr)
2899
2901
2900 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2902 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2901 q = q.options(joinedload(UserRepoGroupToPerm.group),
2903 q = q.options(joinedload(UserRepoGroupToPerm.group),
2902 joinedload(UserRepoGroupToPerm.user),
2904 joinedload(UserRepoGroupToPerm.user),
2903 joinedload(UserRepoGroupToPerm.permission),)
2905 joinedload(UserRepoGroupToPerm.permission),)
2904
2906
2905 # get owners and admins and permissions. We do a trick of re-writing
2907 # get owners and admins and permissions. We do a trick of re-writing
2906 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2908 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2907 # has a global reference and changing one object propagates to all
2909 # has a global reference and changing one object propagates to all
2908 # others. This means if admin is also an owner admin_row that change
2910 # others. This means if admin is also an owner admin_row that change
2909 # would propagate to both objects
2911 # would propagate to both objects
2910 perm_rows = []
2912 perm_rows = []
2911 for _usr in q.all():
2913 for _usr in q.all():
2912 usr = AttributeDict(_usr.user.get_dict())
2914 usr = AttributeDict(_usr.user.get_dict())
2913 # if this user is also owner/admin, mark as duplicate record
2915 # if this user is also owner/admin, mark as duplicate record
2914 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2916 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2915 usr.duplicate_perm = True
2917 usr.duplicate_perm = True
2916 usr.permission = _usr.permission.permission_name
2918 usr.permission = _usr.permission.permission_name
2917 perm_rows.append(usr)
2919 perm_rows.append(usr)
2918
2920
2919 # filter the perm rows by 'default' first and then sort them by
2921 # filter the perm rows by 'default' first and then sort them by
2920 # admin,write,read,none permissions sorted again alphabetically in
2922 # admin,write,read,none permissions sorted again alphabetically in
2921 # each group
2923 # each group
2922 perm_rows = sorted(perm_rows, key=display_user_sort)
2924 perm_rows = sorted(perm_rows, key=display_user_sort)
2923
2925
2924 user_groups_rows = []
2926 user_groups_rows = []
2925 if expand_from_user_groups:
2927 if expand_from_user_groups:
2926 for ug in self.permission_user_groups(with_members=True):
2928 for ug in self.permission_user_groups(with_members=True):
2927 for user_data in ug.members:
2929 for user_data in ug.members:
2928 user_groups_rows.append(user_data)
2930 user_groups_rows.append(user_data)
2929
2931
2930 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2932 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2931
2933
2932 def permission_user_groups(self, with_members=False):
2934 def permission_user_groups(self, with_members=False):
2933 q = UserGroupRepoGroupToPerm.query()\
2935 q = UserGroupRepoGroupToPerm.query()\
2934 .filter(UserGroupRepoGroupToPerm.group == self)
2936 .filter(UserGroupRepoGroupToPerm.group == self)
2935 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2937 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2936 joinedload(UserGroupRepoGroupToPerm.users_group),
2938 joinedload(UserGroupRepoGroupToPerm.users_group),
2937 joinedload(UserGroupRepoGroupToPerm.permission),)
2939 joinedload(UserGroupRepoGroupToPerm.permission),)
2938
2940
2939 perm_rows = []
2941 perm_rows = []
2940 for _user_group in q.all():
2942 for _user_group in q.all():
2941 entry = AttributeDict(_user_group.users_group.get_dict())
2943 entry = AttributeDict(_user_group.users_group.get_dict())
2942 entry.permission = _user_group.permission.permission_name
2944 entry.permission = _user_group.permission.permission_name
2943 if with_members:
2945 if with_members:
2944 entry.members = [x.user.get_dict()
2946 entry.members = [x.user.get_dict()
2945 for x in _user_group.users_group.members]
2947 for x in _user_group.users_group.members]
2946 perm_rows.append(entry)
2948 perm_rows.append(entry)
2947
2949
2948 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2950 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2949 return perm_rows
2951 return perm_rows
2950
2952
2951 def get_api_data(self):
2953 def get_api_data(self):
2952 """
2954 """
2953 Common function for generating api data
2955 Common function for generating api data
2954
2956
2955 """
2957 """
2956 group = self
2958 group = self
2957 data = {
2959 data = {
2958 'group_id': group.group_id,
2960 'group_id': group.group_id,
2959 'group_name': group.group_name,
2961 'group_name': group.group_name,
2960 'group_description': group.description_safe,
2962 'group_description': group.description_safe,
2961 'parent_group': group.parent_group.group_name if group.parent_group else None,
2963 'parent_group': group.parent_group.group_name if group.parent_group else None,
2962 'repositories': [x.repo_name for x in group.repositories],
2964 'repositories': [x.repo_name for x in group.repositories],
2963 'owner': group.user.username,
2965 'owner': group.user.username,
2964 }
2966 }
2965 return data
2967 return data
2966
2968
2967 def get_dict(self):
2969 def get_dict(self):
2968 # Since we transformed `group_name` to a hybrid property, we need to
2970 # Since we transformed `group_name` to a hybrid property, we need to
2969 # keep compatibility with the code which uses `group_name` field.
2971 # keep compatibility with the code which uses `group_name` field.
2970 result = super(RepoGroup, self).get_dict()
2972 result = super(RepoGroup, self).get_dict()
2971 result['group_name'] = result.pop('_group_name', None)
2973 result['group_name'] = result.pop('_group_name', None)
2972 return result
2974 return result
2973
2975
2974
2976
2975 class Permission(Base, BaseModel):
2977 class Permission(Base, BaseModel):
2976 __tablename__ = 'permissions'
2978 __tablename__ = 'permissions'
2977 __table_args__ = (
2979 __table_args__ = (
2978 Index('p_perm_name_idx', 'permission_name'),
2980 Index('p_perm_name_idx', 'permission_name'),
2979 base_table_args,
2981 base_table_args,
2980 )
2982 )
2981
2983
2982 PERMS = [
2984 PERMS = [
2983 ('hg.admin', _('RhodeCode Super Administrator')),
2985 ('hg.admin', _('RhodeCode Super Administrator')),
2984
2986
2985 ('repository.none', _('Repository no access')),
2987 ('repository.none', _('Repository no access')),
2986 ('repository.read', _('Repository read access')),
2988 ('repository.read', _('Repository read access')),
2987 ('repository.write', _('Repository write access')),
2989 ('repository.write', _('Repository write access')),
2988 ('repository.admin', _('Repository admin access')),
2990 ('repository.admin', _('Repository admin access')),
2989
2991
2990 ('group.none', _('Repository group no access')),
2992 ('group.none', _('Repository group no access')),
2991 ('group.read', _('Repository group read access')),
2993 ('group.read', _('Repository group read access')),
2992 ('group.write', _('Repository group write access')),
2994 ('group.write', _('Repository group write access')),
2993 ('group.admin', _('Repository group admin access')),
2995 ('group.admin', _('Repository group admin access')),
2994
2996
2995 ('usergroup.none', _('User group no access')),
2997 ('usergroup.none', _('User group no access')),
2996 ('usergroup.read', _('User group read access')),
2998 ('usergroup.read', _('User group read access')),
2997 ('usergroup.write', _('User group write access')),
2999 ('usergroup.write', _('User group write access')),
2998 ('usergroup.admin', _('User group admin access')),
3000 ('usergroup.admin', _('User group admin access')),
2999
3001
3000 ('branch.none', _('Branch no permissions')),
3002 ('branch.none', _('Branch no permissions')),
3001 ('branch.merge', _('Branch access by web merge')),
3003 ('branch.merge', _('Branch access by web merge')),
3002 ('branch.push', _('Branch access by push')),
3004 ('branch.push', _('Branch access by push')),
3003 ('branch.push_force', _('Branch access by push with force')),
3005 ('branch.push_force', _('Branch access by push with force')),
3004
3006
3005 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3007 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3006 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3008 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3007
3009
3008 ('hg.usergroup.create.false', _('User Group creation disabled')),
3010 ('hg.usergroup.create.false', _('User Group creation disabled')),
3009 ('hg.usergroup.create.true', _('User Group creation enabled')),
3011 ('hg.usergroup.create.true', _('User Group creation enabled')),
3010
3012
3011 ('hg.create.none', _('Repository creation disabled')),
3013 ('hg.create.none', _('Repository creation disabled')),
3012 ('hg.create.repository', _('Repository creation enabled')),
3014 ('hg.create.repository', _('Repository creation enabled')),
3013 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3015 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3014 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3016 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3015
3017
3016 ('hg.fork.none', _('Repository forking disabled')),
3018 ('hg.fork.none', _('Repository forking disabled')),
3017 ('hg.fork.repository', _('Repository forking enabled')),
3019 ('hg.fork.repository', _('Repository forking enabled')),
3018
3020
3019 ('hg.register.none', _('Registration disabled')),
3021 ('hg.register.none', _('Registration disabled')),
3020 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3022 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3021 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3023 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3022
3024
3023 ('hg.password_reset.enabled', _('Password reset enabled')),
3025 ('hg.password_reset.enabled', _('Password reset enabled')),
3024 ('hg.password_reset.hidden', _('Password reset hidden')),
3026 ('hg.password_reset.hidden', _('Password reset hidden')),
3025 ('hg.password_reset.disabled', _('Password reset disabled')),
3027 ('hg.password_reset.disabled', _('Password reset disabled')),
3026
3028
3027 ('hg.extern_activate.manual', _('Manual activation of external account')),
3029 ('hg.extern_activate.manual', _('Manual activation of external account')),
3028 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3030 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3029
3031
3030 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3032 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3031 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3033 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3032 ]
3034 ]
3033
3035
3034 # definition of system default permissions for DEFAULT user, created on
3036 # definition of system default permissions for DEFAULT user, created on
3035 # system setup
3037 # system setup
3036 DEFAULT_USER_PERMISSIONS = [
3038 DEFAULT_USER_PERMISSIONS = [
3037 # object perms
3039 # object perms
3038 'repository.read',
3040 'repository.read',
3039 'group.read',
3041 'group.read',
3040 'usergroup.read',
3042 'usergroup.read',
3041 # branch, for backward compat we need same value as before so forced pushed
3043 # branch, for backward compat we need same value as before so forced pushed
3042 'branch.push_force',
3044 'branch.push_force',
3043 # global
3045 # global
3044 'hg.create.repository',
3046 'hg.create.repository',
3045 'hg.repogroup.create.false',
3047 'hg.repogroup.create.false',
3046 'hg.usergroup.create.false',
3048 'hg.usergroup.create.false',
3047 'hg.create.write_on_repogroup.true',
3049 'hg.create.write_on_repogroup.true',
3048 'hg.fork.repository',
3050 'hg.fork.repository',
3049 'hg.register.manual_activate',
3051 'hg.register.manual_activate',
3050 'hg.password_reset.enabled',
3052 'hg.password_reset.enabled',
3051 'hg.extern_activate.auto',
3053 'hg.extern_activate.auto',
3052 'hg.inherit_default_perms.true',
3054 'hg.inherit_default_perms.true',
3053 ]
3055 ]
3054
3056
3055 # defines which permissions are more important higher the more important
3057 # defines which permissions are more important higher the more important
3056 # Weight defines which permissions are more important.
3058 # Weight defines which permissions are more important.
3057 # The higher number the more important.
3059 # The higher number the more important.
3058 PERM_WEIGHTS = {
3060 PERM_WEIGHTS = {
3059 'repository.none': 0,
3061 'repository.none': 0,
3060 'repository.read': 1,
3062 'repository.read': 1,
3061 'repository.write': 3,
3063 'repository.write': 3,
3062 'repository.admin': 4,
3064 'repository.admin': 4,
3063
3065
3064 'group.none': 0,
3066 'group.none': 0,
3065 'group.read': 1,
3067 'group.read': 1,
3066 'group.write': 3,
3068 'group.write': 3,
3067 'group.admin': 4,
3069 'group.admin': 4,
3068
3070
3069 'usergroup.none': 0,
3071 'usergroup.none': 0,
3070 'usergroup.read': 1,
3072 'usergroup.read': 1,
3071 'usergroup.write': 3,
3073 'usergroup.write': 3,
3072 'usergroup.admin': 4,
3074 'usergroup.admin': 4,
3073
3075
3074 'branch.none': 0,
3076 'branch.none': 0,
3075 'branch.merge': 1,
3077 'branch.merge': 1,
3076 'branch.push': 3,
3078 'branch.push': 3,
3077 'branch.push_force': 4,
3079 'branch.push_force': 4,
3078
3080
3079 'hg.repogroup.create.false': 0,
3081 'hg.repogroup.create.false': 0,
3080 'hg.repogroup.create.true': 1,
3082 'hg.repogroup.create.true': 1,
3081
3083
3082 'hg.usergroup.create.false': 0,
3084 'hg.usergroup.create.false': 0,
3083 'hg.usergroup.create.true': 1,
3085 'hg.usergroup.create.true': 1,
3084
3086
3085 'hg.fork.none': 0,
3087 'hg.fork.none': 0,
3086 'hg.fork.repository': 1,
3088 'hg.fork.repository': 1,
3087 'hg.create.none': 0,
3089 'hg.create.none': 0,
3088 'hg.create.repository': 1
3090 'hg.create.repository': 1
3089 }
3091 }
3090
3092
3091 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3093 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3092 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3094 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3093 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3095 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3094
3096
3095 def __unicode__(self):
3097 def __unicode__(self):
3096 return u"<%s('%s:%s')>" % (
3098 return u"<%s('%s:%s')>" % (
3097 self.__class__.__name__, self.permission_id, self.permission_name
3099 self.__class__.__name__, self.permission_id, self.permission_name
3098 )
3100 )
3099
3101
3100 @classmethod
3102 @classmethod
3101 def get_by_key(cls, key):
3103 def get_by_key(cls, key):
3102 return cls.query().filter(cls.permission_name == key).scalar()
3104 return cls.query().filter(cls.permission_name == key).scalar()
3103
3105
3104 @classmethod
3106 @classmethod
3105 def get_default_repo_perms(cls, user_id, repo_id=None):
3107 def get_default_repo_perms(cls, user_id, repo_id=None):
3106 q = Session().query(UserRepoToPerm, Repository, Permission)\
3108 q = Session().query(UserRepoToPerm, Repository, Permission)\
3107 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3109 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3108 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3110 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3109 .filter(UserRepoToPerm.user_id == user_id)
3111 .filter(UserRepoToPerm.user_id == user_id)
3110 if repo_id:
3112 if repo_id:
3111 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3113 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3112 return q.all()
3114 return q.all()
3113
3115
3114 @classmethod
3116 @classmethod
3115 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3117 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3116 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3118 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3117 .join(
3119 .join(
3118 Permission,
3120 Permission,
3119 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3121 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3120 .join(
3122 .join(
3121 UserRepoToPerm,
3123 UserRepoToPerm,
3122 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3124 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3123 .filter(UserRepoToPerm.user_id == user_id)
3125 .filter(UserRepoToPerm.user_id == user_id)
3124
3126
3125 if repo_id:
3127 if repo_id:
3126 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3128 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3127 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3129 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3128
3130
3129 @classmethod
3131 @classmethod
3130 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3132 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3131 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3133 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3132 .join(
3134 .join(
3133 Permission,
3135 Permission,
3134 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3136 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3135 .join(
3137 .join(
3136 Repository,
3138 Repository,
3137 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3139 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3138 .join(
3140 .join(
3139 UserGroup,
3141 UserGroup,
3140 UserGroupRepoToPerm.users_group_id ==
3142 UserGroupRepoToPerm.users_group_id ==
3141 UserGroup.users_group_id)\
3143 UserGroup.users_group_id)\
3142 .join(
3144 .join(
3143 UserGroupMember,
3145 UserGroupMember,
3144 UserGroupRepoToPerm.users_group_id ==
3146 UserGroupRepoToPerm.users_group_id ==
3145 UserGroupMember.users_group_id)\
3147 UserGroupMember.users_group_id)\
3146 .filter(
3148 .filter(
3147 UserGroupMember.user_id == user_id,
3149 UserGroupMember.user_id == user_id,
3148 UserGroup.users_group_active == true())
3150 UserGroup.users_group_active == true())
3149 if repo_id:
3151 if repo_id:
3150 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3152 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3151 return q.all()
3153 return q.all()
3152
3154
3153 @classmethod
3155 @classmethod
3154 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3156 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3155 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3157 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3156 .join(
3158 .join(
3157 Permission,
3159 Permission,
3158 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3160 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3159 .join(
3161 .join(
3160 UserGroupRepoToPerm,
3162 UserGroupRepoToPerm,
3161 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3163 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3162 .join(
3164 .join(
3163 UserGroup,
3165 UserGroup,
3164 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3166 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3165 .join(
3167 .join(
3166 UserGroupMember,
3168 UserGroupMember,
3167 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3169 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3168 .filter(
3170 .filter(
3169 UserGroupMember.user_id == user_id,
3171 UserGroupMember.user_id == user_id,
3170 UserGroup.users_group_active == true())
3172 UserGroup.users_group_active == true())
3171
3173
3172 if repo_id:
3174 if repo_id:
3173 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3175 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3174 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3176 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3175
3177
3176 @classmethod
3178 @classmethod
3177 def get_default_group_perms(cls, user_id, repo_group_id=None):
3179 def get_default_group_perms(cls, user_id, repo_group_id=None):
3178 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3180 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3179 .join(
3181 .join(
3180 Permission,
3182 Permission,
3181 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3183 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3182 .join(
3184 .join(
3183 RepoGroup,
3185 RepoGroup,
3184 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3186 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3185 .filter(UserRepoGroupToPerm.user_id == user_id)
3187 .filter(UserRepoGroupToPerm.user_id == user_id)
3186 if repo_group_id:
3188 if repo_group_id:
3187 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3189 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3188 return q.all()
3190 return q.all()
3189
3191
3190 @classmethod
3192 @classmethod
3191 def get_default_group_perms_from_user_group(
3193 def get_default_group_perms_from_user_group(
3192 cls, user_id, repo_group_id=None):
3194 cls, user_id, repo_group_id=None):
3193 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3195 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3194 .join(
3196 .join(
3195 Permission,
3197 Permission,
3196 UserGroupRepoGroupToPerm.permission_id ==
3198 UserGroupRepoGroupToPerm.permission_id ==
3197 Permission.permission_id)\
3199 Permission.permission_id)\
3198 .join(
3200 .join(
3199 RepoGroup,
3201 RepoGroup,
3200 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3202 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3201 .join(
3203 .join(
3202 UserGroup,
3204 UserGroup,
3203 UserGroupRepoGroupToPerm.users_group_id ==
3205 UserGroupRepoGroupToPerm.users_group_id ==
3204 UserGroup.users_group_id)\
3206 UserGroup.users_group_id)\
3205 .join(
3207 .join(
3206 UserGroupMember,
3208 UserGroupMember,
3207 UserGroupRepoGroupToPerm.users_group_id ==
3209 UserGroupRepoGroupToPerm.users_group_id ==
3208 UserGroupMember.users_group_id)\
3210 UserGroupMember.users_group_id)\
3209 .filter(
3211 .filter(
3210 UserGroupMember.user_id == user_id,
3212 UserGroupMember.user_id == user_id,
3211 UserGroup.users_group_active == true())
3213 UserGroup.users_group_active == true())
3212 if repo_group_id:
3214 if repo_group_id:
3213 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3215 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3214 return q.all()
3216 return q.all()
3215
3217
3216 @classmethod
3218 @classmethod
3217 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3219 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3218 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3220 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3219 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3221 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3220 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3222 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3221 .filter(UserUserGroupToPerm.user_id == user_id)
3223 .filter(UserUserGroupToPerm.user_id == user_id)
3222 if user_group_id:
3224 if user_group_id:
3223 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3225 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3224 return q.all()
3226 return q.all()
3225
3227
3226 @classmethod
3228 @classmethod
3227 def get_default_user_group_perms_from_user_group(
3229 def get_default_user_group_perms_from_user_group(
3228 cls, user_id, user_group_id=None):
3230 cls, user_id, user_group_id=None):
3229 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3231 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3230 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3232 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3231 .join(
3233 .join(
3232 Permission,
3234 Permission,
3233 UserGroupUserGroupToPerm.permission_id ==
3235 UserGroupUserGroupToPerm.permission_id ==
3234 Permission.permission_id)\
3236 Permission.permission_id)\
3235 .join(
3237 .join(
3236 TargetUserGroup,
3238 TargetUserGroup,
3237 UserGroupUserGroupToPerm.target_user_group_id ==
3239 UserGroupUserGroupToPerm.target_user_group_id ==
3238 TargetUserGroup.users_group_id)\
3240 TargetUserGroup.users_group_id)\
3239 .join(
3241 .join(
3240 UserGroup,
3242 UserGroup,
3241 UserGroupUserGroupToPerm.user_group_id ==
3243 UserGroupUserGroupToPerm.user_group_id ==
3242 UserGroup.users_group_id)\
3244 UserGroup.users_group_id)\
3243 .join(
3245 .join(
3244 UserGroupMember,
3246 UserGroupMember,
3245 UserGroupUserGroupToPerm.user_group_id ==
3247 UserGroupUserGroupToPerm.user_group_id ==
3246 UserGroupMember.users_group_id)\
3248 UserGroupMember.users_group_id)\
3247 .filter(
3249 .filter(
3248 UserGroupMember.user_id == user_id,
3250 UserGroupMember.user_id == user_id,
3249 UserGroup.users_group_active == true())
3251 UserGroup.users_group_active == true())
3250 if user_group_id:
3252 if user_group_id:
3251 q = q.filter(
3253 q = q.filter(
3252 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3254 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3253
3255
3254 return q.all()
3256 return q.all()
3255
3257
3256
3258
3257 class UserRepoToPerm(Base, BaseModel):
3259 class UserRepoToPerm(Base, BaseModel):
3258 __tablename__ = 'repo_to_perm'
3260 __tablename__ = 'repo_to_perm'
3259 __table_args__ = (
3261 __table_args__ = (
3260 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3262 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3261 base_table_args
3263 base_table_args
3262 )
3264 )
3263
3265
3264 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3266 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3267 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3266 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3268 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3269 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3268
3270
3269 user = relationship('User')
3271 user = relationship('User')
3270 repository = relationship('Repository')
3272 repository = relationship('Repository')
3271 permission = relationship('Permission')
3273 permission = relationship('Permission')
3272
3274
3273 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3275 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3274
3276
3275 @classmethod
3277 @classmethod
3276 def create(cls, user, repository, permission):
3278 def create(cls, user, repository, permission):
3277 n = cls()
3279 n = cls()
3278 n.user = user
3280 n.user = user
3279 n.repository = repository
3281 n.repository = repository
3280 n.permission = permission
3282 n.permission = permission
3281 Session().add(n)
3283 Session().add(n)
3282 return n
3284 return n
3283
3285
3284 def __unicode__(self):
3286 def __unicode__(self):
3285 return u'<%s => %s >' % (self.user, self.repository)
3287 return u'<%s => %s >' % (self.user, self.repository)
3286
3288
3287
3289
3288 class UserUserGroupToPerm(Base, BaseModel):
3290 class UserUserGroupToPerm(Base, BaseModel):
3289 __tablename__ = 'user_user_group_to_perm'
3291 __tablename__ = 'user_user_group_to_perm'
3290 __table_args__ = (
3292 __table_args__ = (
3291 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3293 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3292 base_table_args
3294 base_table_args
3293 )
3295 )
3294
3296
3295 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3297 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3296 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3298 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3297 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3299 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3298 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3300 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3299
3301
3300 user = relationship('User')
3302 user = relationship('User')
3301 user_group = relationship('UserGroup')
3303 user_group = relationship('UserGroup')
3302 permission = relationship('Permission')
3304 permission = relationship('Permission')
3303
3305
3304 @classmethod
3306 @classmethod
3305 def create(cls, user, user_group, permission):
3307 def create(cls, user, user_group, permission):
3306 n = cls()
3308 n = cls()
3307 n.user = user
3309 n.user = user
3308 n.user_group = user_group
3310 n.user_group = user_group
3309 n.permission = permission
3311 n.permission = permission
3310 Session().add(n)
3312 Session().add(n)
3311 return n
3313 return n
3312
3314
3313 def __unicode__(self):
3315 def __unicode__(self):
3314 return u'<%s => %s >' % (self.user, self.user_group)
3316 return u'<%s => %s >' % (self.user, self.user_group)
3315
3317
3316
3318
3317 class UserToPerm(Base, BaseModel):
3319 class UserToPerm(Base, BaseModel):
3318 __tablename__ = 'user_to_perm'
3320 __tablename__ = 'user_to_perm'
3319 __table_args__ = (
3321 __table_args__ = (
3320 UniqueConstraint('user_id', 'permission_id'),
3322 UniqueConstraint('user_id', 'permission_id'),
3321 base_table_args
3323 base_table_args
3322 )
3324 )
3323
3325
3324 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3326 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3328 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3327
3329
3328 user = relationship('User')
3330 user = relationship('User')
3329 permission = relationship('Permission', lazy='joined')
3331 permission = relationship('Permission', lazy='joined')
3330
3332
3331 def __unicode__(self):
3333 def __unicode__(self):
3332 return u'<%s => %s >' % (self.user, self.permission)
3334 return u'<%s => %s >' % (self.user, self.permission)
3333
3335
3334
3336
3335 class UserGroupRepoToPerm(Base, BaseModel):
3337 class UserGroupRepoToPerm(Base, BaseModel):
3336 __tablename__ = 'users_group_repo_to_perm'
3338 __tablename__ = 'users_group_repo_to_perm'
3337 __table_args__ = (
3339 __table_args__ = (
3338 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3340 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3339 base_table_args
3341 base_table_args
3340 )
3342 )
3341
3343
3342 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3344 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3343 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3345 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3344 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3346 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3345 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3347 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3346
3348
3347 users_group = relationship('UserGroup')
3349 users_group = relationship('UserGroup')
3348 permission = relationship('Permission')
3350 permission = relationship('Permission')
3349 repository = relationship('Repository')
3351 repository = relationship('Repository')
3350 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3352 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3351
3353
3352 @classmethod
3354 @classmethod
3353 def create(cls, users_group, repository, permission):
3355 def create(cls, users_group, repository, permission):
3354 n = cls()
3356 n = cls()
3355 n.users_group = users_group
3357 n.users_group = users_group
3356 n.repository = repository
3358 n.repository = repository
3357 n.permission = permission
3359 n.permission = permission
3358 Session().add(n)
3360 Session().add(n)
3359 return n
3361 return n
3360
3362
3361 def __unicode__(self):
3363 def __unicode__(self):
3362 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3364 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3363
3365
3364
3366
3365 class UserGroupUserGroupToPerm(Base, BaseModel):
3367 class UserGroupUserGroupToPerm(Base, BaseModel):
3366 __tablename__ = 'user_group_user_group_to_perm'
3368 __tablename__ = 'user_group_user_group_to_perm'
3367 __table_args__ = (
3369 __table_args__ = (
3368 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3370 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3369 CheckConstraint('target_user_group_id != user_group_id'),
3371 CheckConstraint('target_user_group_id != user_group_id'),
3370 base_table_args
3372 base_table_args
3371 )
3373 )
3372
3374
3373 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)
3375 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)
3374 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3376 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3375 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3377 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3376 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3378 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3377
3379
3378 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3380 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3379 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3381 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3380 permission = relationship('Permission')
3382 permission = relationship('Permission')
3381
3383
3382 @classmethod
3384 @classmethod
3383 def create(cls, target_user_group, user_group, permission):
3385 def create(cls, target_user_group, user_group, permission):
3384 n = cls()
3386 n = cls()
3385 n.target_user_group = target_user_group
3387 n.target_user_group = target_user_group
3386 n.user_group = user_group
3388 n.user_group = user_group
3387 n.permission = permission
3389 n.permission = permission
3388 Session().add(n)
3390 Session().add(n)
3389 return n
3391 return n
3390
3392
3391 def __unicode__(self):
3393 def __unicode__(self):
3392 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3394 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3393
3395
3394
3396
3395 class UserGroupToPerm(Base, BaseModel):
3397 class UserGroupToPerm(Base, BaseModel):
3396 __tablename__ = 'users_group_to_perm'
3398 __tablename__ = 'users_group_to_perm'
3397 __table_args__ = (
3399 __table_args__ = (
3398 UniqueConstraint('users_group_id', 'permission_id',),
3400 UniqueConstraint('users_group_id', 'permission_id',),
3399 base_table_args
3401 base_table_args
3400 )
3402 )
3401
3403
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3404 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3405 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3406 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405
3407
3406 users_group = relationship('UserGroup')
3408 users_group = relationship('UserGroup')
3407 permission = relationship('Permission')
3409 permission = relationship('Permission')
3408
3410
3409
3411
3410 class UserRepoGroupToPerm(Base, BaseModel):
3412 class UserRepoGroupToPerm(Base, BaseModel):
3411 __tablename__ = 'user_repo_group_to_perm'
3413 __tablename__ = 'user_repo_group_to_perm'
3412 __table_args__ = (
3414 __table_args__ = (
3413 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3415 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3414 base_table_args
3416 base_table_args
3415 )
3417 )
3416
3418
3417 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3419 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3420 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3419 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3421 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3420 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3422 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3421
3423
3422 user = relationship('User')
3424 user = relationship('User')
3423 group = relationship('RepoGroup')
3425 group = relationship('RepoGroup')
3424 permission = relationship('Permission')
3426 permission = relationship('Permission')
3425
3427
3426 @classmethod
3428 @classmethod
3427 def create(cls, user, repository_group, permission):
3429 def create(cls, user, repository_group, permission):
3428 n = cls()
3430 n = cls()
3429 n.user = user
3431 n.user = user
3430 n.group = repository_group
3432 n.group = repository_group
3431 n.permission = permission
3433 n.permission = permission
3432 Session().add(n)
3434 Session().add(n)
3433 return n
3435 return n
3434
3436
3435
3437
3436 class UserGroupRepoGroupToPerm(Base, BaseModel):
3438 class UserGroupRepoGroupToPerm(Base, BaseModel):
3437 __tablename__ = 'users_group_repo_group_to_perm'
3439 __tablename__ = 'users_group_repo_group_to_perm'
3438 __table_args__ = (
3440 __table_args__ = (
3439 UniqueConstraint('users_group_id', 'group_id'),
3441 UniqueConstraint('users_group_id', 'group_id'),
3440 base_table_args
3442 base_table_args
3441 )
3443 )
3442
3444
3443 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)
3445 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)
3444 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3446 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3445 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3447 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3448 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3447
3449
3448 users_group = relationship('UserGroup')
3450 users_group = relationship('UserGroup')
3449 permission = relationship('Permission')
3451 permission = relationship('Permission')
3450 group = relationship('RepoGroup')
3452 group = relationship('RepoGroup')
3451
3453
3452 @classmethod
3454 @classmethod
3453 def create(cls, user_group, repository_group, permission):
3455 def create(cls, user_group, repository_group, permission):
3454 n = cls()
3456 n = cls()
3455 n.users_group = user_group
3457 n.users_group = user_group
3456 n.group = repository_group
3458 n.group = repository_group
3457 n.permission = permission
3459 n.permission = permission
3458 Session().add(n)
3460 Session().add(n)
3459 return n
3461 return n
3460
3462
3461 def __unicode__(self):
3463 def __unicode__(self):
3462 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3464 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3463
3465
3464
3466
3465 class Statistics(Base, BaseModel):
3467 class Statistics(Base, BaseModel):
3466 __tablename__ = 'statistics'
3468 __tablename__ = 'statistics'
3467 __table_args__ = (
3469 __table_args__ = (
3468 base_table_args
3470 base_table_args
3469 )
3471 )
3470
3472
3471 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3473 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3472 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3474 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3473 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3475 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3474 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3476 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3475 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3477 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3476 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3478 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3477
3479
3478 repository = relationship('Repository', single_parent=True)
3480 repository = relationship('Repository', single_parent=True)
3479
3481
3480
3482
3481 class UserFollowing(Base, BaseModel):
3483 class UserFollowing(Base, BaseModel):
3482 __tablename__ = 'user_followings'
3484 __tablename__ = 'user_followings'
3483 __table_args__ = (
3485 __table_args__ = (
3484 UniqueConstraint('user_id', 'follows_repository_id'),
3486 UniqueConstraint('user_id', 'follows_repository_id'),
3485 UniqueConstraint('user_id', 'follows_user_id'),
3487 UniqueConstraint('user_id', 'follows_user_id'),
3486 base_table_args
3488 base_table_args
3487 )
3489 )
3488
3490
3489 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3491 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3490 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3491 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3493 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3492 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3494 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3493 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3495 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3494
3496
3495 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3497 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3496
3498
3497 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3499 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3498 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3500 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3499
3501
3500 @classmethod
3502 @classmethod
3501 def get_repo_followers(cls, repo_id):
3503 def get_repo_followers(cls, repo_id):
3502 return cls.query().filter(cls.follows_repo_id == repo_id)
3504 return cls.query().filter(cls.follows_repo_id == repo_id)
3503
3505
3504
3506
3505 class CacheKey(Base, BaseModel):
3507 class CacheKey(Base, BaseModel):
3506 __tablename__ = 'cache_invalidation'
3508 __tablename__ = 'cache_invalidation'
3507 __table_args__ = (
3509 __table_args__ = (
3508 UniqueConstraint('cache_key'),
3510 UniqueConstraint('cache_key'),
3509 Index('key_idx', 'cache_key'),
3511 Index('key_idx', 'cache_key'),
3510 base_table_args,
3512 base_table_args,
3511 )
3513 )
3512
3514
3513 CACHE_TYPE_FEED = 'FEED'
3515 CACHE_TYPE_FEED = 'FEED'
3514
3516
3515 # namespaces used to register process/thread aware caches
3517 # namespaces used to register process/thread aware caches
3516 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3518 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3517 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3519 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3518
3520
3519 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3521 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3520 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3522 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3521 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3523 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3522 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3524 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3523 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3525 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3524
3526
3525 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3527 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3526 self.cache_key = cache_key
3528 self.cache_key = cache_key
3527 self.cache_args = cache_args
3529 self.cache_args = cache_args
3528 self.cache_active = False
3530 self.cache_active = False
3529 # first key should be same for all entries, since all workers should share it
3531 # first key should be same for all entries, since all workers should share it
3530 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3532 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3531
3533
3532 def __unicode__(self):
3534 def __unicode__(self):
3533 return u"<%s('%s:%s[%s]')>" % (
3535 return u"<%s('%s:%s[%s]')>" % (
3534 self.__class__.__name__,
3536 self.__class__.__name__,
3535 self.cache_id, self.cache_key, self.cache_active)
3537 self.cache_id, self.cache_key, self.cache_active)
3536
3538
3537 def _cache_key_partition(self):
3539 def _cache_key_partition(self):
3538 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3540 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3539 return prefix, repo_name, suffix
3541 return prefix, repo_name, suffix
3540
3542
3541 def get_prefix(self):
3543 def get_prefix(self):
3542 """
3544 """
3543 Try to extract prefix from existing cache key. The key could consist
3545 Try to extract prefix from existing cache key. The key could consist
3544 of prefix, repo_name, suffix
3546 of prefix, repo_name, suffix
3545 """
3547 """
3546 # this returns prefix, repo_name, suffix
3548 # this returns prefix, repo_name, suffix
3547 return self._cache_key_partition()[0]
3549 return self._cache_key_partition()[0]
3548
3550
3549 def get_suffix(self):
3551 def get_suffix(self):
3550 """
3552 """
3551 get suffix that might have been used in _get_cache_key to
3553 get suffix that might have been used in _get_cache_key to
3552 generate self.cache_key. Only used for informational purposes
3554 generate self.cache_key. Only used for informational purposes
3553 in repo_edit.mako.
3555 in repo_edit.mako.
3554 """
3556 """
3555 # prefix, repo_name, suffix
3557 # prefix, repo_name, suffix
3556 return self._cache_key_partition()[2]
3558 return self._cache_key_partition()[2]
3557
3559
3558 @classmethod
3560 @classmethod
3559 def generate_new_state_uid(cls, based_on=None):
3561 def generate_new_state_uid(cls, based_on=None):
3560 if based_on:
3562 if based_on:
3561 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3563 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3562 else:
3564 else:
3563 return str(uuid.uuid4())
3565 return str(uuid.uuid4())
3564
3566
3565 @classmethod
3567 @classmethod
3566 def delete_all_cache(cls):
3568 def delete_all_cache(cls):
3567 """
3569 """
3568 Delete all cache keys from database.
3570 Delete all cache keys from database.
3569 Should only be run when all instances are down and all entries
3571 Should only be run when all instances are down and all entries
3570 thus stale.
3572 thus stale.
3571 """
3573 """
3572 cls.query().delete()
3574 cls.query().delete()
3573 Session().commit()
3575 Session().commit()
3574
3576
3575 @classmethod
3577 @classmethod
3576 def set_invalidate(cls, cache_uid, delete=False):
3578 def set_invalidate(cls, cache_uid, delete=False):
3577 """
3579 """
3578 Mark all caches of a repo as invalid in the database.
3580 Mark all caches of a repo as invalid in the database.
3579 """
3581 """
3580
3582
3581 try:
3583 try:
3582 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3584 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3583 if delete:
3585 if delete:
3584 qry.delete()
3586 qry.delete()
3585 log.debug('cache objects deleted for cache args %s',
3587 log.debug('cache objects deleted for cache args %s',
3586 safe_str(cache_uid))
3588 safe_str(cache_uid))
3587 else:
3589 else:
3588 qry.update({"cache_active": False,
3590 qry.update({"cache_active": False,
3589 "cache_state_uid": cls.generate_new_state_uid()})
3591 "cache_state_uid": cls.generate_new_state_uid()})
3590 log.debug('cache objects marked as invalid for cache args %s',
3592 log.debug('cache objects marked as invalid for cache args %s',
3591 safe_str(cache_uid))
3593 safe_str(cache_uid))
3592
3594
3593 Session().commit()
3595 Session().commit()
3594 except Exception:
3596 except Exception:
3595 log.exception(
3597 log.exception(
3596 'Cache key invalidation failed for cache args %s',
3598 'Cache key invalidation failed for cache args %s',
3597 safe_str(cache_uid))
3599 safe_str(cache_uid))
3598 Session().rollback()
3600 Session().rollback()
3599
3601
3600 @classmethod
3602 @classmethod
3601 def get_active_cache(cls, cache_key):
3603 def get_active_cache(cls, cache_key):
3602 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3604 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3603 if inv_obj:
3605 if inv_obj:
3604 return inv_obj
3606 return inv_obj
3605 return None
3607 return None
3606
3608
3607 @classmethod
3609 @classmethod
3608 def get_namespace_map(cls, namespace):
3610 def get_namespace_map(cls, namespace):
3609 return {
3611 return {
3610 x.cache_key: x
3612 x.cache_key: x
3611 for x in cls.query().filter(cls.cache_args == namespace)}
3613 for x in cls.query().filter(cls.cache_args == namespace)}
3612
3614
3613
3615
3614 class ChangesetComment(Base, BaseModel):
3616 class ChangesetComment(Base, BaseModel):
3615 __tablename__ = 'changeset_comments'
3617 __tablename__ = 'changeset_comments'
3616 __table_args__ = (
3618 __table_args__ = (
3617 Index('cc_revision_idx', 'revision'),
3619 Index('cc_revision_idx', 'revision'),
3618 base_table_args,
3620 base_table_args,
3619 )
3621 )
3620
3622
3621 COMMENT_OUTDATED = u'comment_outdated'
3623 COMMENT_OUTDATED = u'comment_outdated'
3622 COMMENT_TYPE_NOTE = u'note'
3624 COMMENT_TYPE_NOTE = u'note'
3623 COMMENT_TYPE_TODO = u'todo'
3625 COMMENT_TYPE_TODO = u'todo'
3624 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3626 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3625
3627
3626 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3628 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3627 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3629 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3628 revision = Column('revision', String(40), nullable=True)
3630 revision = Column('revision', String(40), nullable=True)
3629 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3631 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3630 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3632 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3631 line_no = Column('line_no', Unicode(10), nullable=True)
3633 line_no = Column('line_no', Unicode(10), nullable=True)
3632 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3634 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3633 f_path = Column('f_path', Unicode(1000), nullable=True)
3635 f_path = Column('f_path', Unicode(1000), nullable=True)
3634 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3636 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3635 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3637 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3636 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3638 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3637 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3639 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3638 renderer = Column('renderer', Unicode(64), nullable=True)
3640 renderer = Column('renderer', Unicode(64), nullable=True)
3639 display_state = Column('display_state', Unicode(128), nullable=True)
3641 display_state = Column('display_state', Unicode(128), nullable=True)
3640
3642
3641 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3643 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3642 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3644 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3643
3645
3644 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3646 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3645 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3647 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3646
3648
3647 author = relationship('User', lazy='joined')
3649 author = relationship('User', lazy='joined')
3648 repo = relationship('Repository')
3650 repo = relationship('Repository')
3649 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3651 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3650 pull_request = relationship('PullRequest', lazy='joined')
3652 pull_request = relationship('PullRequest', lazy='joined')
3651 pull_request_version = relationship('PullRequestVersion')
3653 pull_request_version = relationship('PullRequestVersion')
3652
3654
3653 @classmethod
3655 @classmethod
3654 def get_users(cls, revision=None, pull_request_id=None):
3656 def get_users(cls, revision=None, pull_request_id=None):
3655 """
3657 """
3656 Returns user associated with this ChangesetComment. ie those
3658 Returns user associated with this ChangesetComment. ie those
3657 who actually commented
3659 who actually commented
3658
3660
3659 :param cls:
3661 :param cls:
3660 :param revision:
3662 :param revision:
3661 """
3663 """
3662 q = Session().query(User)\
3664 q = Session().query(User)\
3663 .join(ChangesetComment.author)
3665 .join(ChangesetComment.author)
3664 if revision:
3666 if revision:
3665 q = q.filter(cls.revision == revision)
3667 q = q.filter(cls.revision == revision)
3666 elif pull_request_id:
3668 elif pull_request_id:
3667 q = q.filter(cls.pull_request_id == pull_request_id)
3669 q = q.filter(cls.pull_request_id == pull_request_id)
3668 return q.all()
3670 return q.all()
3669
3671
3670 @classmethod
3672 @classmethod
3671 def get_index_from_version(cls, pr_version, versions):
3673 def get_index_from_version(cls, pr_version, versions):
3672 num_versions = [x.pull_request_version_id for x in versions]
3674 num_versions = [x.pull_request_version_id for x in versions]
3673 try:
3675 try:
3674 return num_versions.index(pr_version) +1
3676 return num_versions.index(pr_version) +1
3675 except (IndexError, ValueError):
3677 except (IndexError, ValueError):
3676 return
3678 return
3677
3679
3678 @property
3680 @property
3679 def outdated(self):
3681 def outdated(self):
3680 return self.display_state == self.COMMENT_OUTDATED
3682 return self.display_state == self.COMMENT_OUTDATED
3681
3683
3682 def outdated_at_version(self, version):
3684 def outdated_at_version(self, version):
3683 """
3685 """
3684 Checks if comment is outdated for given pull request version
3686 Checks if comment is outdated for given pull request version
3685 """
3687 """
3686 return self.outdated and self.pull_request_version_id != version
3688 return self.outdated and self.pull_request_version_id != version
3687
3689
3688 def older_than_version(self, version):
3690 def older_than_version(self, version):
3689 """
3691 """
3690 Checks if comment is made from previous version than given
3692 Checks if comment is made from previous version than given
3691 """
3693 """
3692 if version is None:
3694 if version is None:
3693 return self.pull_request_version_id is not None
3695 return self.pull_request_version_id is not None
3694
3696
3695 return self.pull_request_version_id < version
3697 return self.pull_request_version_id < version
3696
3698
3697 @property
3699 @property
3698 def resolved(self):
3700 def resolved(self):
3699 return self.resolved_by[0] if self.resolved_by else None
3701 return self.resolved_by[0] if self.resolved_by else None
3700
3702
3701 @property
3703 @property
3702 def is_todo(self):
3704 def is_todo(self):
3703 return self.comment_type == self.COMMENT_TYPE_TODO
3705 return self.comment_type == self.COMMENT_TYPE_TODO
3704
3706
3705 @property
3707 @property
3706 def is_inline(self):
3708 def is_inline(self):
3707 return self.line_no and self.f_path
3709 return self.line_no and self.f_path
3708
3710
3709 def get_index_version(self, versions):
3711 def get_index_version(self, versions):
3710 return self.get_index_from_version(
3712 return self.get_index_from_version(
3711 self.pull_request_version_id, versions)
3713 self.pull_request_version_id, versions)
3712
3714
3713 def __repr__(self):
3715 def __repr__(self):
3714 if self.comment_id:
3716 if self.comment_id:
3715 return '<DB:Comment #%s>' % self.comment_id
3717 return '<DB:Comment #%s>' % self.comment_id
3716 else:
3718 else:
3717 return '<DB:Comment at %#x>' % id(self)
3719 return '<DB:Comment at %#x>' % id(self)
3718
3720
3719 def get_api_data(self):
3721 def get_api_data(self):
3720 comment = self
3722 comment = self
3721 data = {
3723 data = {
3722 'comment_id': comment.comment_id,
3724 'comment_id': comment.comment_id,
3723 'comment_type': comment.comment_type,
3725 'comment_type': comment.comment_type,
3724 'comment_text': comment.text,
3726 'comment_text': comment.text,
3725 'comment_status': comment.status_change,
3727 'comment_status': comment.status_change,
3726 'comment_f_path': comment.f_path,
3728 'comment_f_path': comment.f_path,
3727 'comment_lineno': comment.line_no,
3729 'comment_lineno': comment.line_no,
3728 'comment_author': comment.author,
3730 'comment_author': comment.author,
3729 'comment_created_on': comment.created_on,
3731 'comment_created_on': comment.created_on,
3730 'comment_resolved_by': self.resolved
3732 'comment_resolved_by': self.resolved
3731 }
3733 }
3732 return data
3734 return data
3733
3735
3734 def __json__(self):
3736 def __json__(self):
3735 data = dict()
3737 data = dict()
3736 data.update(self.get_api_data())
3738 data.update(self.get_api_data())
3737 return data
3739 return data
3738
3740
3739
3741
3740 class ChangesetStatus(Base, BaseModel):
3742 class ChangesetStatus(Base, BaseModel):
3741 __tablename__ = 'changeset_statuses'
3743 __tablename__ = 'changeset_statuses'
3742 __table_args__ = (
3744 __table_args__ = (
3743 Index('cs_revision_idx', 'revision'),
3745 Index('cs_revision_idx', 'revision'),
3744 Index('cs_version_idx', 'version'),
3746 Index('cs_version_idx', 'version'),
3745 UniqueConstraint('repo_id', 'revision', 'version'),
3747 UniqueConstraint('repo_id', 'revision', 'version'),
3746 base_table_args
3748 base_table_args
3747 )
3749 )
3748
3750
3749 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3751 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3750 STATUS_APPROVED = 'approved'
3752 STATUS_APPROVED = 'approved'
3751 STATUS_REJECTED = 'rejected'
3753 STATUS_REJECTED = 'rejected'
3752 STATUS_UNDER_REVIEW = 'under_review'
3754 STATUS_UNDER_REVIEW = 'under_review'
3753
3755
3754 STATUSES = [
3756 STATUSES = [
3755 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3757 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3756 (STATUS_APPROVED, _("Approved")),
3758 (STATUS_APPROVED, _("Approved")),
3757 (STATUS_REJECTED, _("Rejected")),
3759 (STATUS_REJECTED, _("Rejected")),
3758 (STATUS_UNDER_REVIEW, _("Under Review")),
3760 (STATUS_UNDER_REVIEW, _("Under Review")),
3759 ]
3761 ]
3760
3762
3761 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3763 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3762 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3764 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3763 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3765 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3764 revision = Column('revision', String(40), nullable=False)
3766 revision = Column('revision', String(40), nullable=False)
3765 status = Column('status', String(128), nullable=False, default=DEFAULT)
3767 status = Column('status', String(128), nullable=False, default=DEFAULT)
3766 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3768 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3767 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3769 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3768 version = Column('version', Integer(), nullable=False, default=0)
3770 version = Column('version', Integer(), nullable=False, default=0)
3769 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3771 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3770
3772
3771 author = relationship('User', lazy='joined')
3773 author = relationship('User', lazy='joined')
3772 repo = relationship('Repository')
3774 repo = relationship('Repository')
3773 comment = relationship('ChangesetComment', lazy='joined')
3775 comment = relationship('ChangesetComment', lazy='joined')
3774 pull_request = relationship('PullRequest', lazy='joined')
3776 pull_request = relationship('PullRequest', lazy='joined')
3775
3777
3776 def __unicode__(self):
3778 def __unicode__(self):
3777 return u"<%s('%s[v%s]:%s')>" % (
3779 return u"<%s('%s[v%s]:%s')>" % (
3778 self.__class__.__name__,
3780 self.__class__.__name__,
3779 self.status, self.version, self.author
3781 self.status, self.version, self.author
3780 )
3782 )
3781
3783
3782 @classmethod
3784 @classmethod
3783 def get_status_lbl(cls, value):
3785 def get_status_lbl(cls, value):
3784 return dict(cls.STATUSES).get(value)
3786 return dict(cls.STATUSES).get(value)
3785
3787
3786 @property
3788 @property
3787 def status_lbl(self):
3789 def status_lbl(self):
3788 return ChangesetStatus.get_status_lbl(self.status)
3790 return ChangesetStatus.get_status_lbl(self.status)
3789
3791
3790 def get_api_data(self):
3792 def get_api_data(self):
3791 status = self
3793 status = self
3792 data = {
3794 data = {
3793 'status_id': status.changeset_status_id,
3795 'status_id': status.changeset_status_id,
3794 'status': status.status,
3796 'status': status.status,
3795 }
3797 }
3796 return data
3798 return data
3797
3799
3798 def __json__(self):
3800 def __json__(self):
3799 data = dict()
3801 data = dict()
3800 data.update(self.get_api_data())
3802 data.update(self.get_api_data())
3801 return data
3803 return data
3802
3804
3803
3805
3804 class _SetState(object):
3806 class _SetState(object):
3805 """
3807 """
3806 Context processor allowing changing state for sensitive operation such as
3808 Context processor allowing changing state for sensitive operation such as
3807 pull request update or merge
3809 pull request update or merge
3808 """
3810 """
3809
3811
3810 def __init__(self, pull_request, pr_state, back_state=None):
3812 def __init__(self, pull_request, pr_state, back_state=None):
3811 self._pr = pull_request
3813 self._pr = pull_request
3812 self._org_state = back_state or pull_request.pull_request_state
3814 self._org_state = back_state or pull_request.pull_request_state
3813 self._pr_state = pr_state
3815 self._pr_state = pr_state
3814 self._current_state = None
3816 self._current_state = None
3815
3817
3816 def __enter__(self):
3818 def __enter__(self):
3817 log.debug('StateLock: entering set state context, setting state to: `%s`',
3819 log.debug('StateLock: entering set state context, setting state to: `%s`',
3818 self._pr_state)
3820 self._pr_state)
3819 self.set_pr_state(self._pr_state)
3821 self.set_pr_state(self._pr_state)
3820 return self
3822 return self
3821
3823
3822 def __exit__(self, exc_type, exc_val, exc_tb):
3824 def __exit__(self, exc_type, exc_val, exc_tb):
3823 if exc_val is not None:
3825 if exc_val is not None:
3824 log.error(traceback.format_exc(exc_tb))
3826 log.error(traceback.format_exc(exc_tb))
3825 return None
3827 return None
3826
3828
3827 self.set_pr_state(self._org_state)
3829 self.set_pr_state(self._org_state)
3828 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3830 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3829 self._org_state)
3831 self._org_state)
3830 @property
3832 @property
3831 def state(self):
3833 def state(self):
3832 return self._current_state
3834 return self._current_state
3833
3835
3834 def set_pr_state(self, pr_state):
3836 def set_pr_state(self, pr_state):
3835 try:
3837 try:
3836 self._pr.pull_request_state = pr_state
3838 self._pr.pull_request_state = pr_state
3837 Session().add(self._pr)
3839 Session().add(self._pr)
3838 Session().commit()
3840 Session().commit()
3839 self._current_state = pr_state
3841 self._current_state = pr_state
3840 except Exception:
3842 except Exception:
3841 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3843 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3842 raise
3844 raise
3843
3845
3844 class _PullRequestBase(BaseModel):
3846 class _PullRequestBase(BaseModel):
3845 """
3847 """
3846 Common attributes of pull request and version entries.
3848 Common attributes of pull request and version entries.
3847 """
3849 """
3848
3850
3849 # .status values
3851 # .status values
3850 STATUS_NEW = u'new'
3852 STATUS_NEW = u'new'
3851 STATUS_OPEN = u'open'
3853 STATUS_OPEN = u'open'
3852 STATUS_CLOSED = u'closed'
3854 STATUS_CLOSED = u'closed'
3853
3855
3854 # available states
3856 # available states
3855 STATE_CREATING = u'creating'
3857 STATE_CREATING = u'creating'
3856 STATE_UPDATING = u'updating'
3858 STATE_UPDATING = u'updating'
3857 STATE_MERGING = u'merging'
3859 STATE_MERGING = u'merging'
3858 STATE_CREATED = u'created'
3860 STATE_CREATED = u'created'
3859
3861
3860 title = Column('title', Unicode(255), nullable=True)
3862 title = Column('title', Unicode(255), nullable=True)
3861 description = Column(
3863 description = Column(
3862 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3864 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3863 nullable=True)
3865 nullable=True)
3864 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3866 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3865
3867
3866 # new/open/closed status of pull request (not approve/reject/etc)
3868 # new/open/closed status of pull request (not approve/reject/etc)
3867 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3869 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3868 created_on = Column(
3870 created_on = Column(
3869 'created_on', DateTime(timezone=False), nullable=False,
3871 'created_on', DateTime(timezone=False), nullable=False,
3870 default=datetime.datetime.now)
3872 default=datetime.datetime.now)
3871 updated_on = Column(
3873 updated_on = Column(
3872 'updated_on', DateTime(timezone=False), nullable=False,
3874 'updated_on', DateTime(timezone=False), nullable=False,
3873 default=datetime.datetime.now)
3875 default=datetime.datetime.now)
3874
3876
3875 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3877 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3876
3878
3877 @declared_attr
3879 @declared_attr
3878 def user_id(cls):
3880 def user_id(cls):
3879 return Column(
3881 return Column(
3880 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3882 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3881 unique=None)
3883 unique=None)
3882
3884
3883 # 500 revisions max
3885 # 500 revisions max
3884 _revisions = Column(
3886 _revisions = Column(
3885 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3887 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3886
3888
3887 @declared_attr
3889 @declared_attr
3888 def source_repo_id(cls):
3890 def source_repo_id(cls):
3889 # TODO: dan: rename column to source_repo_id
3891 # TODO: dan: rename column to source_repo_id
3890 return Column(
3892 return Column(
3891 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3893 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3892 nullable=False)
3894 nullable=False)
3893
3895
3894 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3896 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3895
3897
3896 @hybrid_property
3898 @hybrid_property
3897 def source_ref(self):
3899 def source_ref(self):
3898 return self._source_ref
3900 return self._source_ref
3899
3901
3900 @source_ref.setter
3902 @source_ref.setter
3901 def source_ref(self, val):
3903 def source_ref(self, val):
3902 parts = (val or '').split(':')
3904 parts = (val or '').split(':')
3903 if len(parts) != 3:
3905 if len(parts) != 3:
3904 raise ValueError(
3906 raise ValueError(
3905 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3907 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3906 self._source_ref = safe_unicode(val)
3908 self._source_ref = safe_unicode(val)
3907
3909
3908 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3910 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3909
3911
3910 @hybrid_property
3912 @hybrid_property
3911 def target_ref(self):
3913 def target_ref(self):
3912 return self._target_ref
3914 return self._target_ref
3913
3915
3914 @target_ref.setter
3916 @target_ref.setter
3915 def target_ref(self, val):
3917 def target_ref(self, val):
3916 parts = (val or '').split(':')
3918 parts = (val or '').split(':')
3917 if len(parts) != 3:
3919 if len(parts) != 3:
3918 raise ValueError(
3920 raise ValueError(
3919 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3921 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3920 self._target_ref = safe_unicode(val)
3922 self._target_ref = safe_unicode(val)
3921
3923
3922 @declared_attr
3924 @declared_attr
3923 def target_repo_id(cls):
3925 def target_repo_id(cls):
3924 # TODO: dan: rename column to target_repo_id
3926 # TODO: dan: rename column to target_repo_id
3925 return Column(
3927 return Column(
3926 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3928 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3927 nullable=False)
3929 nullable=False)
3928
3930
3929 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3931 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3930
3932
3931 # TODO: dan: rename column to last_merge_source_rev
3933 # TODO: dan: rename column to last_merge_source_rev
3932 _last_merge_source_rev = Column(
3934 _last_merge_source_rev = Column(
3933 'last_merge_org_rev', String(40), nullable=True)
3935 'last_merge_org_rev', String(40), nullable=True)
3934 # TODO: dan: rename column to last_merge_target_rev
3936 # TODO: dan: rename column to last_merge_target_rev
3935 _last_merge_target_rev = Column(
3937 _last_merge_target_rev = Column(
3936 'last_merge_other_rev', String(40), nullable=True)
3938 'last_merge_other_rev', String(40), nullable=True)
3937 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3939 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3938 merge_rev = Column('merge_rev', String(40), nullable=True)
3940 merge_rev = Column('merge_rev', String(40), nullable=True)
3939
3941
3940 reviewer_data = Column(
3942 reviewer_data = Column(
3941 'reviewer_data_json', MutationObj.as_mutable(
3943 'reviewer_data_json', MutationObj.as_mutable(
3942 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3944 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3943
3945
3944 @property
3946 @property
3945 def reviewer_data_json(self):
3947 def reviewer_data_json(self):
3946 return json.dumps(self.reviewer_data)
3948 return json.dumps(self.reviewer_data)
3947
3949
3948 @hybrid_property
3950 @hybrid_property
3949 def description_safe(self):
3951 def description_safe(self):
3950 from rhodecode.lib import helpers as h
3952 from rhodecode.lib import helpers as h
3951 return h.escape(self.description)
3953 return h.escape(self.description)
3952
3954
3953 @hybrid_property
3955 @hybrid_property
3954 def revisions(self):
3956 def revisions(self):
3955 return self._revisions.split(':') if self._revisions else []
3957 return self._revisions.split(':') if self._revisions else []
3956
3958
3957 @revisions.setter
3959 @revisions.setter
3958 def revisions(self, val):
3960 def revisions(self, val):
3959 self._revisions = u':'.join(val)
3961 self._revisions = u':'.join(val)
3960
3962
3961 @hybrid_property
3963 @hybrid_property
3962 def last_merge_status(self):
3964 def last_merge_status(self):
3963 return safe_int(self._last_merge_status)
3965 return safe_int(self._last_merge_status)
3964
3966
3965 @last_merge_status.setter
3967 @last_merge_status.setter
3966 def last_merge_status(self, val):
3968 def last_merge_status(self, val):
3967 self._last_merge_status = val
3969 self._last_merge_status = val
3968
3970
3969 @declared_attr
3971 @declared_attr
3970 def author(cls):
3972 def author(cls):
3971 return relationship('User', lazy='joined')
3973 return relationship('User', lazy='joined')
3972
3974
3973 @declared_attr
3975 @declared_attr
3974 def source_repo(cls):
3976 def source_repo(cls):
3975 return relationship(
3977 return relationship(
3976 'Repository',
3978 'Repository',
3977 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3979 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3978
3980
3979 @property
3981 @property
3980 def source_ref_parts(self):
3982 def source_ref_parts(self):
3981 return self.unicode_to_reference(self.source_ref)
3983 return self.unicode_to_reference(self.source_ref)
3982
3984
3983 @declared_attr
3985 @declared_attr
3984 def target_repo(cls):
3986 def target_repo(cls):
3985 return relationship(
3987 return relationship(
3986 'Repository',
3988 'Repository',
3987 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3989 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3988
3990
3989 @property
3991 @property
3990 def target_ref_parts(self):
3992 def target_ref_parts(self):
3991 return self.unicode_to_reference(self.target_ref)
3993 return self.unicode_to_reference(self.target_ref)
3992
3994
3993 @property
3995 @property
3994 def shadow_merge_ref(self):
3996 def shadow_merge_ref(self):
3995 return self.unicode_to_reference(self._shadow_merge_ref)
3997 return self.unicode_to_reference(self._shadow_merge_ref)
3996
3998
3997 @shadow_merge_ref.setter
3999 @shadow_merge_ref.setter
3998 def shadow_merge_ref(self, ref):
4000 def shadow_merge_ref(self, ref):
3999 self._shadow_merge_ref = self.reference_to_unicode(ref)
4001 self._shadow_merge_ref = self.reference_to_unicode(ref)
4000
4002
4001 @staticmethod
4003 @staticmethod
4002 def unicode_to_reference(raw):
4004 def unicode_to_reference(raw):
4003 """
4005 """
4004 Convert a unicode (or string) to a reference object.
4006 Convert a unicode (or string) to a reference object.
4005 If unicode evaluates to False it returns None.
4007 If unicode evaluates to False it returns None.
4006 """
4008 """
4007 if raw:
4009 if raw:
4008 refs = raw.split(':')
4010 refs = raw.split(':')
4009 return Reference(*refs)
4011 return Reference(*refs)
4010 else:
4012 else:
4011 return None
4013 return None
4012
4014
4013 @staticmethod
4015 @staticmethod
4014 def reference_to_unicode(ref):
4016 def reference_to_unicode(ref):
4015 """
4017 """
4016 Convert a reference object to unicode.
4018 Convert a reference object to unicode.
4017 If reference is None it returns None.
4019 If reference is None it returns None.
4018 """
4020 """
4019 if ref:
4021 if ref:
4020 return u':'.join(ref)
4022 return u':'.join(ref)
4021 else:
4023 else:
4022 return None
4024 return None
4023
4025
4024 def get_api_data(self, with_merge_state=True):
4026 def get_api_data(self, with_merge_state=True):
4025 from rhodecode.model.pull_request import PullRequestModel
4027 from rhodecode.model.pull_request import PullRequestModel
4026
4028
4027 pull_request = self
4029 pull_request = self
4028 if with_merge_state:
4030 if with_merge_state:
4029 merge_status = PullRequestModel().merge_status(pull_request)
4031 merge_status = PullRequestModel().merge_status(pull_request)
4030 merge_state = {
4032 merge_state = {
4031 'status': merge_status[0],
4033 'status': merge_status[0],
4032 'message': safe_unicode(merge_status[1]),
4034 'message': safe_unicode(merge_status[1]),
4033 }
4035 }
4034 else:
4036 else:
4035 merge_state = {'status': 'not_available',
4037 merge_state = {'status': 'not_available',
4036 'message': 'not_available'}
4038 'message': 'not_available'}
4037
4039
4038 merge_data = {
4040 merge_data = {
4039 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4041 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4040 'reference': (
4042 'reference': (
4041 pull_request.shadow_merge_ref._asdict()
4043 pull_request.shadow_merge_ref._asdict()
4042 if pull_request.shadow_merge_ref else None),
4044 if pull_request.shadow_merge_ref else None),
4043 }
4045 }
4044
4046
4045 data = {
4047 data = {
4046 'pull_request_id': pull_request.pull_request_id,
4048 'pull_request_id': pull_request.pull_request_id,
4047 'url': PullRequestModel().get_url(pull_request),
4049 'url': PullRequestModel().get_url(pull_request),
4048 'title': pull_request.title,
4050 'title': pull_request.title,
4049 'description': pull_request.description,
4051 'description': pull_request.description,
4050 'status': pull_request.status,
4052 'status': pull_request.status,
4051 'state': pull_request.pull_request_state,
4053 'state': pull_request.pull_request_state,
4052 'created_on': pull_request.created_on,
4054 'created_on': pull_request.created_on,
4053 'updated_on': pull_request.updated_on,
4055 'updated_on': pull_request.updated_on,
4054 'commit_ids': pull_request.revisions,
4056 'commit_ids': pull_request.revisions,
4055 'review_status': pull_request.calculated_review_status(),
4057 'review_status': pull_request.calculated_review_status(),
4056 'mergeable': merge_state,
4058 'mergeable': merge_state,
4057 'source': {
4059 'source': {
4058 'clone_url': pull_request.source_repo.clone_url(),
4060 'clone_url': pull_request.source_repo.clone_url(),
4059 'repository': pull_request.source_repo.repo_name,
4061 'repository': pull_request.source_repo.repo_name,
4060 'reference': {
4062 'reference': {
4061 'name': pull_request.source_ref_parts.name,
4063 'name': pull_request.source_ref_parts.name,
4062 'type': pull_request.source_ref_parts.type,
4064 'type': pull_request.source_ref_parts.type,
4063 'commit_id': pull_request.source_ref_parts.commit_id,
4065 'commit_id': pull_request.source_ref_parts.commit_id,
4064 },
4066 },
4065 },
4067 },
4066 'target': {
4068 'target': {
4067 'clone_url': pull_request.target_repo.clone_url(),
4069 'clone_url': pull_request.target_repo.clone_url(),
4068 'repository': pull_request.target_repo.repo_name,
4070 'repository': pull_request.target_repo.repo_name,
4069 'reference': {
4071 'reference': {
4070 'name': pull_request.target_ref_parts.name,
4072 'name': pull_request.target_ref_parts.name,
4071 'type': pull_request.target_ref_parts.type,
4073 'type': pull_request.target_ref_parts.type,
4072 'commit_id': pull_request.target_ref_parts.commit_id,
4074 'commit_id': pull_request.target_ref_parts.commit_id,
4073 },
4075 },
4074 },
4076 },
4075 'merge': merge_data,
4077 'merge': merge_data,
4076 'author': pull_request.author.get_api_data(include_secrets=False,
4078 'author': pull_request.author.get_api_data(include_secrets=False,
4077 details='basic'),
4079 details='basic'),
4078 'reviewers': [
4080 'reviewers': [
4079 {
4081 {
4080 'user': reviewer.get_api_data(include_secrets=False,
4082 'user': reviewer.get_api_data(include_secrets=False,
4081 details='basic'),
4083 details='basic'),
4082 'reasons': reasons,
4084 'reasons': reasons,
4083 'review_status': st[0][1].status if st else 'not_reviewed',
4085 'review_status': st[0][1].status if st else 'not_reviewed',
4084 }
4086 }
4085 for obj, reviewer, reasons, mandatory, st in
4087 for obj, reviewer, reasons, mandatory, st in
4086 pull_request.reviewers_statuses()
4088 pull_request.reviewers_statuses()
4087 ]
4089 ]
4088 }
4090 }
4089
4091
4090 return data
4092 return data
4091
4093
4092 def set_state(self, pull_request_state, final_state=None):
4094 def set_state(self, pull_request_state, final_state=None):
4093 """
4095 """
4094 # goes from initial state to updating to initial state.
4096 # goes from initial state to updating to initial state.
4095 # initial state can be changed by specifying back_state=
4097 # initial state can be changed by specifying back_state=
4096 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4098 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4097 pull_request.merge()
4099 pull_request.merge()
4098
4100
4099 :param pull_request_state:
4101 :param pull_request_state:
4100 :param final_state:
4102 :param final_state:
4101
4103
4102 """
4104 """
4103
4105
4104 return _SetState(self, pull_request_state, back_state=final_state)
4106 return _SetState(self, pull_request_state, back_state=final_state)
4105
4107
4106
4108
4107 class PullRequest(Base, _PullRequestBase):
4109 class PullRequest(Base, _PullRequestBase):
4108 __tablename__ = 'pull_requests'
4110 __tablename__ = 'pull_requests'
4109 __table_args__ = (
4111 __table_args__ = (
4110 base_table_args,
4112 base_table_args,
4111 )
4113 )
4112
4114
4113 pull_request_id = Column(
4115 pull_request_id = Column(
4114 'pull_request_id', Integer(), nullable=False, primary_key=True)
4116 'pull_request_id', Integer(), nullable=False, primary_key=True)
4115
4117
4116 def __repr__(self):
4118 def __repr__(self):
4117 if self.pull_request_id:
4119 if self.pull_request_id:
4118 return '<DB:PullRequest #%s>' % self.pull_request_id
4120 return '<DB:PullRequest #%s>' % self.pull_request_id
4119 else:
4121 else:
4120 return '<DB:PullRequest at %#x>' % id(self)
4122 return '<DB:PullRequest at %#x>' % id(self)
4121
4123
4122 reviewers = relationship('PullRequestReviewers',
4124 reviewers = relationship('PullRequestReviewers',
4123 cascade="all, delete-orphan")
4125 cascade="all, delete-orphan")
4124 statuses = relationship('ChangesetStatus',
4126 statuses = relationship('ChangesetStatus',
4125 cascade="all, delete-orphan")
4127 cascade="all, delete-orphan")
4126 comments = relationship('ChangesetComment',
4128 comments = relationship('ChangesetComment',
4127 cascade="all, delete-orphan")
4129 cascade="all, delete-orphan")
4128 versions = relationship('PullRequestVersion',
4130 versions = relationship('PullRequestVersion',
4129 cascade="all, delete-orphan",
4131 cascade="all, delete-orphan",
4130 lazy='dynamic')
4132 lazy='dynamic')
4131
4133
4132 @classmethod
4134 @classmethod
4133 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4135 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4134 internal_methods=None):
4136 internal_methods=None):
4135
4137
4136 class PullRequestDisplay(object):
4138 class PullRequestDisplay(object):
4137 """
4139 """
4138 Special object wrapper for showing PullRequest data via Versions
4140 Special object wrapper for showing PullRequest data via Versions
4139 It mimics PR object as close as possible. This is read only object
4141 It mimics PR object as close as possible. This is read only object
4140 just for display
4142 just for display
4141 """
4143 """
4142
4144
4143 def __init__(self, attrs, internal=None):
4145 def __init__(self, attrs, internal=None):
4144 self.attrs = attrs
4146 self.attrs = attrs
4145 # internal have priority over the given ones via attrs
4147 # internal have priority over the given ones via attrs
4146 self.internal = internal or ['versions']
4148 self.internal = internal or ['versions']
4147
4149
4148 def __getattr__(self, item):
4150 def __getattr__(self, item):
4149 if item in self.internal:
4151 if item in self.internal:
4150 return getattr(self, item)
4152 return getattr(self, item)
4151 try:
4153 try:
4152 return self.attrs[item]
4154 return self.attrs[item]
4153 except KeyError:
4155 except KeyError:
4154 raise AttributeError(
4156 raise AttributeError(
4155 '%s object has no attribute %s' % (self, item))
4157 '%s object has no attribute %s' % (self, item))
4156
4158
4157 def __repr__(self):
4159 def __repr__(self):
4158 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4160 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4159
4161
4160 def versions(self):
4162 def versions(self):
4161 return pull_request_obj.versions.order_by(
4163 return pull_request_obj.versions.order_by(
4162 PullRequestVersion.pull_request_version_id).all()
4164 PullRequestVersion.pull_request_version_id).all()
4163
4165
4164 def is_closed(self):
4166 def is_closed(self):
4165 return pull_request_obj.is_closed()
4167 return pull_request_obj.is_closed()
4166
4168
4167 @property
4169 @property
4168 def pull_request_version_id(self):
4170 def pull_request_version_id(self):
4169 return getattr(pull_request_obj, 'pull_request_version_id', None)
4171 return getattr(pull_request_obj, 'pull_request_version_id', None)
4170
4172
4171 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4173 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4172
4174
4173 attrs.author = StrictAttributeDict(
4175 attrs.author = StrictAttributeDict(
4174 pull_request_obj.author.get_api_data())
4176 pull_request_obj.author.get_api_data())
4175 if pull_request_obj.target_repo:
4177 if pull_request_obj.target_repo:
4176 attrs.target_repo = StrictAttributeDict(
4178 attrs.target_repo = StrictAttributeDict(
4177 pull_request_obj.target_repo.get_api_data())
4179 pull_request_obj.target_repo.get_api_data())
4178 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4180 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4179
4181
4180 if pull_request_obj.source_repo:
4182 if pull_request_obj.source_repo:
4181 attrs.source_repo = StrictAttributeDict(
4183 attrs.source_repo = StrictAttributeDict(
4182 pull_request_obj.source_repo.get_api_data())
4184 pull_request_obj.source_repo.get_api_data())
4183 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4185 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4184
4186
4185 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4187 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4186 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4188 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4187 attrs.revisions = pull_request_obj.revisions
4189 attrs.revisions = pull_request_obj.revisions
4188
4190
4189 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4191 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4190 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4192 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4191 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4193 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4192
4194
4193 return PullRequestDisplay(attrs, internal=internal_methods)
4195 return PullRequestDisplay(attrs, internal=internal_methods)
4194
4196
4195 def is_closed(self):
4197 def is_closed(self):
4196 return self.status == self.STATUS_CLOSED
4198 return self.status == self.STATUS_CLOSED
4197
4199
4198 def __json__(self):
4200 def __json__(self):
4199 return {
4201 return {
4200 'revisions': self.revisions,
4202 'revisions': self.revisions,
4201 }
4203 }
4202
4204
4203 def calculated_review_status(self):
4205 def calculated_review_status(self):
4204 from rhodecode.model.changeset_status import ChangesetStatusModel
4206 from rhodecode.model.changeset_status import ChangesetStatusModel
4205 return ChangesetStatusModel().calculated_review_status(self)
4207 return ChangesetStatusModel().calculated_review_status(self)
4206
4208
4207 def reviewers_statuses(self):
4209 def reviewers_statuses(self):
4208 from rhodecode.model.changeset_status import ChangesetStatusModel
4210 from rhodecode.model.changeset_status import ChangesetStatusModel
4209 return ChangesetStatusModel().reviewers_statuses(self)
4211 return ChangesetStatusModel().reviewers_statuses(self)
4210
4212
4211 @property
4213 @property
4212 def workspace_id(self):
4214 def workspace_id(self):
4213 from rhodecode.model.pull_request import PullRequestModel
4215 from rhodecode.model.pull_request import PullRequestModel
4214 return PullRequestModel()._workspace_id(self)
4216 return PullRequestModel()._workspace_id(self)
4215
4217
4216 def get_shadow_repo(self):
4218 def get_shadow_repo(self):
4217 workspace_id = self.workspace_id
4219 workspace_id = self.workspace_id
4218 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4220 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4219 if os.path.isdir(shadow_repository_path):
4221 if os.path.isdir(shadow_repository_path):
4220 vcs_obj = self.target_repo.scm_instance()
4222 vcs_obj = self.target_repo.scm_instance()
4221 return vcs_obj.get_shadow_instance(shadow_repository_path)
4223 return vcs_obj.get_shadow_instance(shadow_repository_path)
4222
4224
4223
4225
4224 class PullRequestVersion(Base, _PullRequestBase):
4226 class PullRequestVersion(Base, _PullRequestBase):
4225 __tablename__ = 'pull_request_versions'
4227 __tablename__ = 'pull_request_versions'
4226 __table_args__ = (
4228 __table_args__ = (
4227 base_table_args,
4229 base_table_args,
4228 )
4230 )
4229
4231
4230 pull_request_version_id = Column(
4232 pull_request_version_id = Column(
4231 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4233 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4232 pull_request_id = Column(
4234 pull_request_id = Column(
4233 'pull_request_id', Integer(),
4235 'pull_request_id', Integer(),
4234 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4236 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4235 pull_request = relationship('PullRequest')
4237 pull_request = relationship('PullRequest')
4236
4238
4237 def __repr__(self):
4239 def __repr__(self):
4238 if self.pull_request_version_id:
4240 if self.pull_request_version_id:
4239 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4241 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4240 else:
4242 else:
4241 return '<DB:PullRequestVersion at %#x>' % id(self)
4243 return '<DB:PullRequestVersion at %#x>' % id(self)
4242
4244
4243 @property
4245 @property
4244 def reviewers(self):
4246 def reviewers(self):
4245 return self.pull_request.reviewers
4247 return self.pull_request.reviewers
4246
4248
4247 @property
4249 @property
4248 def versions(self):
4250 def versions(self):
4249 return self.pull_request.versions
4251 return self.pull_request.versions
4250
4252
4251 def is_closed(self):
4253 def is_closed(self):
4252 # calculate from original
4254 # calculate from original
4253 return self.pull_request.status == self.STATUS_CLOSED
4255 return self.pull_request.status == self.STATUS_CLOSED
4254
4256
4255 def calculated_review_status(self):
4257 def calculated_review_status(self):
4256 return self.pull_request.calculated_review_status()
4258 return self.pull_request.calculated_review_status()
4257
4259
4258 def reviewers_statuses(self):
4260 def reviewers_statuses(self):
4259 return self.pull_request.reviewers_statuses()
4261 return self.pull_request.reviewers_statuses()
4260
4262
4261
4263
4262 class PullRequestReviewers(Base, BaseModel):
4264 class PullRequestReviewers(Base, BaseModel):
4263 __tablename__ = 'pull_request_reviewers'
4265 __tablename__ = 'pull_request_reviewers'
4264 __table_args__ = (
4266 __table_args__ = (
4265 base_table_args,
4267 base_table_args,
4266 )
4268 )
4267
4269
4268 @hybrid_property
4270 @hybrid_property
4269 def reasons(self):
4271 def reasons(self):
4270 if not self._reasons:
4272 if not self._reasons:
4271 return []
4273 return []
4272 return self._reasons
4274 return self._reasons
4273
4275
4274 @reasons.setter
4276 @reasons.setter
4275 def reasons(self, val):
4277 def reasons(self, val):
4276 val = val or []
4278 val = val or []
4277 if any(not isinstance(x, compat.string_types) for x in val):
4279 if any(not isinstance(x, compat.string_types) for x in val):
4278 raise Exception('invalid reasons type, must be list of strings')
4280 raise Exception('invalid reasons type, must be list of strings')
4279 self._reasons = val
4281 self._reasons = val
4280
4282
4281 pull_requests_reviewers_id = Column(
4283 pull_requests_reviewers_id = Column(
4282 'pull_requests_reviewers_id', Integer(), nullable=False,
4284 'pull_requests_reviewers_id', Integer(), nullable=False,
4283 primary_key=True)
4285 primary_key=True)
4284 pull_request_id = Column(
4286 pull_request_id = Column(
4285 "pull_request_id", Integer(),
4287 "pull_request_id", Integer(),
4286 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4288 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4287 user_id = Column(
4289 user_id = Column(
4288 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4290 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4289 _reasons = Column(
4291 _reasons = Column(
4290 'reason', MutationList.as_mutable(
4292 'reason', MutationList.as_mutable(
4291 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4293 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4292
4294
4293 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4295 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4294 user = relationship('User')
4296 user = relationship('User')
4295 pull_request = relationship('PullRequest')
4297 pull_request = relationship('PullRequest')
4296
4298
4297 rule_data = Column(
4299 rule_data = Column(
4298 'rule_data_json',
4300 'rule_data_json',
4299 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4301 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4300
4302
4301 def rule_user_group_data(self):
4303 def rule_user_group_data(self):
4302 """
4304 """
4303 Returns the voting user group rule data for this reviewer
4305 Returns the voting user group rule data for this reviewer
4304 """
4306 """
4305
4307
4306 if self.rule_data and 'vote_rule' in self.rule_data:
4308 if self.rule_data and 'vote_rule' in self.rule_data:
4307 user_group_data = {}
4309 user_group_data = {}
4308 if 'rule_user_group_entry_id' in self.rule_data:
4310 if 'rule_user_group_entry_id' in self.rule_data:
4309 # means a group with voting rules !
4311 # means a group with voting rules !
4310 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4312 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4311 user_group_data['name'] = self.rule_data['rule_name']
4313 user_group_data['name'] = self.rule_data['rule_name']
4312 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4314 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4313
4315
4314 return user_group_data
4316 return user_group_data
4315
4317
4316 def __unicode__(self):
4318 def __unicode__(self):
4317 return u"<%s('id:%s')>" % (self.__class__.__name__,
4319 return u"<%s('id:%s')>" % (self.__class__.__name__,
4318 self.pull_requests_reviewers_id)
4320 self.pull_requests_reviewers_id)
4319
4321
4320
4322
4321 class Notification(Base, BaseModel):
4323 class Notification(Base, BaseModel):
4322 __tablename__ = 'notifications'
4324 __tablename__ = 'notifications'
4323 __table_args__ = (
4325 __table_args__ = (
4324 Index('notification_type_idx', 'type'),
4326 Index('notification_type_idx', 'type'),
4325 base_table_args,
4327 base_table_args,
4326 )
4328 )
4327
4329
4328 TYPE_CHANGESET_COMMENT = u'cs_comment'
4330 TYPE_CHANGESET_COMMENT = u'cs_comment'
4329 TYPE_MESSAGE = u'message'
4331 TYPE_MESSAGE = u'message'
4330 TYPE_MENTION = u'mention'
4332 TYPE_MENTION = u'mention'
4331 TYPE_REGISTRATION = u'registration'
4333 TYPE_REGISTRATION = u'registration'
4332 TYPE_PULL_REQUEST = u'pull_request'
4334 TYPE_PULL_REQUEST = u'pull_request'
4333 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4335 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4334
4336
4335 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4337 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4336 subject = Column('subject', Unicode(512), nullable=True)
4338 subject = Column('subject', Unicode(512), nullable=True)
4337 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4339 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4338 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4340 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4339 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4341 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4340 type_ = Column('type', Unicode(255))
4342 type_ = Column('type', Unicode(255))
4341
4343
4342 created_by_user = relationship('User')
4344 created_by_user = relationship('User')
4343 notifications_to_users = relationship('UserNotification', lazy='joined',
4345 notifications_to_users = relationship('UserNotification', lazy='joined',
4344 cascade="all, delete-orphan")
4346 cascade="all, delete-orphan")
4345
4347
4346 @property
4348 @property
4347 def recipients(self):
4349 def recipients(self):
4348 return [x.user for x in UserNotification.query()\
4350 return [x.user for x in UserNotification.query()\
4349 .filter(UserNotification.notification == self)\
4351 .filter(UserNotification.notification == self)\
4350 .order_by(UserNotification.user_id.asc()).all()]
4352 .order_by(UserNotification.user_id.asc()).all()]
4351
4353
4352 @classmethod
4354 @classmethod
4353 def create(cls, created_by, subject, body, recipients, type_=None):
4355 def create(cls, created_by, subject, body, recipients, type_=None):
4354 if type_ is None:
4356 if type_ is None:
4355 type_ = Notification.TYPE_MESSAGE
4357 type_ = Notification.TYPE_MESSAGE
4356
4358
4357 notification = cls()
4359 notification = cls()
4358 notification.created_by_user = created_by
4360 notification.created_by_user = created_by
4359 notification.subject = subject
4361 notification.subject = subject
4360 notification.body = body
4362 notification.body = body
4361 notification.type_ = type_
4363 notification.type_ = type_
4362 notification.created_on = datetime.datetime.now()
4364 notification.created_on = datetime.datetime.now()
4363
4365
4364 # For each recipient link the created notification to his account
4366 # For each recipient link the created notification to his account
4365 for u in recipients:
4367 for u in recipients:
4366 assoc = UserNotification()
4368 assoc = UserNotification()
4367 assoc.user_id = u.user_id
4369 assoc.user_id = u.user_id
4368 assoc.notification = notification
4370 assoc.notification = notification
4369
4371
4370 # if created_by is inside recipients mark his notification
4372 # if created_by is inside recipients mark his notification
4371 # as read
4373 # as read
4372 if u.user_id == created_by.user_id:
4374 if u.user_id == created_by.user_id:
4373 assoc.read = True
4375 assoc.read = True
4374 Session().add(assoc)
4376 Session().add(assoc)
4375
4377
4376 Session().add(notification)
4378 Session().add(notification)
4377
4379
4378 return notification
4380 return notification
4379
4381
4380
4382
4381 class UserNotification(Base, BaseModel):
4383 class UserNotification(Base, BaseModel):
4382 __tablename__ = 'user_to_notification'
4384 __tablename__ = 'user_to_notification'
4383 __table_args__ = (
4385 __table_args__ = (
4384 UniqueConstraint('user_id', 'notification_id'),
4386 UniqueConstraint('user_id', 'notification_id'),
4385 base_table_args
4387 base_table_args
4386 )
4388 )
4387
4389
4388 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4390 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4389 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4391 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4390 read = Column('read', Boolean, default=False)
4392 read = Column('read', Boolean, default=False)
4391 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4393 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4392
4394
4393 user = relationship('User', lazy="joined")
4395 user = relationship('User', lazy="joined")
4394 notification = relationship('Notification', lazy="joined",
4396 notification = relationship('Notification', lazy="joined",
4395 order_by=lambda: Notification.created_on.desc(),)
4397 order_by=lambda: Notification.created_on.desc(),)
4396
4398
4397 def mark_as_read(self):
4399 def mark_as_read(self):
4398 self.read = True
4400 self.read = True
4399 Session().add(self)
4401 Session().add(self)
4400
4402
4401
4403
4402 class Gist(Base, BaseModel):
4404 class Gist(Base, BaseModel):
4403 __tablename__ = 'gists'
4405 __tablename__ = 'gists'
4404 __table_args__ = (
4406 __table_args__ = (
4405 Index('g_gist_access_id_idx', 'gist_access_id'),
4407 Index('g_gist_access_id_idx', 'gist_access_id'),
4406 Index('g_created_on_idx', 'created_on'),
4408 Index('g_created_on_idx', 'created_on'),
4407 base_table_args
4409 base_table_args
4408 )
4410 )
4409
4411
4410 GIST_PUBLIC = u'public'
4412 GIST_PUBLIC = u'public'
4411 GIST_PRIVATE = u'private'
4413 GIST_PRIVATE = u'private'
4412 DEFAULT_FILENAME = u'gistfile1.txt'
4414 DEFAULT_FILENAME = u'gistfile1.txt'
4413
4415
4414 ACL_LEVEL_PUBLIC = u'acl_public'
4416 ACL_LEVEL_PUBLIC = u'acl_public'
4415 ACL_LEVEL_PRIVATE = u'acl_private'
4417 ACL_LEVEL_PRIVATE = u'acl_private'
4416
4418
4417 gist_id = Column('gist_id', Integer(), primary_key=True)
4419 gist_id = Column('gist_id', Integer(), primary_key=True)
4418 gist_access_id = Column('gist_access_id', Unicode(250))
4420 gist_access_id = Column('gist_access_id', Unicode(250))
4419 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4421 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4420 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4422 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4421 gist_expires = Column('gist_expires', Float(53), nullable=False)
4423 gist_expires = Column('gist_expires', Float(53), nullable=False)
4422 gist_type = Column('gist_type', Unicode(128), nullable=False)
4424 gist_type = Column('gist_type', Unicode(128), nullable=False)
4423 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4425 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4424 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4426 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4425 acl_level = Column('acl_level', Unicode(128), nullable=True)
4427 acl_level = Column('acl_level', Unicode(128), nullable=True)
4426
4428
4427 owner = relationship('User')
4429 owner = relationship('User')
4428
4430
4429 def __repr__(self):
4431 def __repr__(self):
4430 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4432 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4431
4433
4432 @hybrid_property
4434 @hybrid_property
4433 def description_safe(self):
4435 def description_safe(self):
4434 from rhodecode.lib import helpers as h
4436 from rhodecode.lib import helpers as h
4435 return h.escape(self.gist_description)
4437 return h.escape(self.gist_description)
4436
4438
4437 @classmethod
4439 @classmethod
4438 def get_or_404(cls, id_):
4440 def get_or_404(cls, id_):
4439 from pyramid.httpexceptions import HTTPNotFound
4441 from pyramid.httpexceptions import HTTPNotFound
4440
4442
4441 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4443 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4442 if not res:
4444 if not res:
4443 raise HTTPNotFound()
4445 raise HTTPNotFound()
4444 return res
4446 return res
4445
4447
4446 @classmethod
4448 @classmethod
4447 def get_by_access_id(cls, gist_access_id):
4449 def get_by_access_id(cls, gist_access_id):
4448 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4450 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4449
4451
4450 def gist_url(self):
4452 def gist_url(self):
4451 from rhodecode.model.gist import GistModel
4453 from rhodecode.model.gist import GistModel
4452 return GistModel().get_url(self)
4454 return GistModel().get_url(self)
4453
4455
4454 @classmethod
4456 @classmethod
4455 def base_path(cls):
4457 def base_path(cls):
4456 """
4458 """
4457 Returns base path when all gists are stored
4459 Returns base path when all gists are stored
4458
4460
4459 :param cls:
4461 :param cls:
4460 """
4462 """
4461 from rhodecode.model.gist import GIST_STORE_LOC
4463 from rhodecode.model.gist import GIST_STORE_LOC
4462 q = Session().query(RhodeCodeUi)\
4464 q = Session().query(RhodeCodeUi)\
4463 .filter(RhodeCodeUi.ui_key == URL_SEP)
4465 .filter(RhodeCodeUi.ui_key == URL_SEP)
4464 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4466 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4465 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4467 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4466
4468
4467 def get_api_data(self):
4469 def get_api_data(self):
4468 """
4470 """
4469 Common function for generating gist related data for API
4471 Common function for generating gist related data for API
4470 """
4472 """
4471 gist = self
4473 gist = self
4472 data = {
4474 data = {
4473 'gist_id': gist.gist_id,
4475 'gist_id': gist.gist_id,
4474 'type': gist.gist_type,
4476 'type': gist.gist_type,
4475 'access_id': gist.gist_access_id,
4477 'access_id': gist.gist_access_id,
4476 'description': gist.gist_description,
4478 'description': gist.gist_description,
4477 'url': gist.gist_url(),
4479 'url': gist.gist_url(),
4478 'expires': gist.gist_expires,
4480 'expires': gist.gist_expires,
4479 'created_on': gist.created_on,
4481 'created_on': gist.created_on,
4480 'modified_at': gist.modified_at,
4482 'modified_at': gist.modified_at,
4481 'content': None,
4483 'content': None,
4482 'acl_level': gist.acl_level,
4484 'acl_level': gist.acl_level,
4483 }
4485 }
4484 return data
4486 return data
4485
4487
4486 def __json__(self):
4488 def __json__(self):
4487 data = dict(
4489 data = dict(
4488 )
4490 )
4489 data.update(self.get_api_data())
4491 data.update(self.get_api_data())
4490 return data
4492 return data
4491 # SCM functions
4493 # SCM functions
4492
4494
4493 def scm_instance(self, **kwargs):
4495 def scm_instance(self, **kwargs):
4494 """
4496 """
4495 Get an instance of VCS Repository
4497 Get an instance of VCS Repository
4496
4498
4497 :param kwargs:
4499 :param kwargs:
4498 """
4500 """
4499 from rhodecode.model.gist import GistModel
4501 from rhodecode.model.gist import GistModel
4500 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4502 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4501 return get_vcs_instance(
4503 return get_vcs_instance(
4502 repo_path=safe_str(full_repo_path), create=False,
4504 repo_path=safe_str(full_repo_path), create=False,
4503 _vcs_alias=GistModel.vcs_backend)
4505 _vcs_alias=GistModel.vcs_backend)
4504
4506
4505
4507
4506 class ExternalIdentity(Base, BaseModel):
4508 class ExternalIdentity(Base, BaseModel):
4507 __tablename__ = 'external_identities'
4509 __tablename__ = 'external_identities'
4508 __table_args__ = (
4510 __table_args__ = (
4509 Index('local_user_id_idx', 'local_user_id'),
4511 Index('local_user_id_idx', 'local_user_id'),
4510 Index('external_id_idx', 'external_id'),
4512 Index('external_id_idx', 'external_id'),
4511 base_table_args
4513 base_table_args
4512 )
4514 )
4513
4515
4514 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4516 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4515 external_username = Column('external_username', Unicode(1024), default=u'')
4517 external_username = Column('external_username', Unicode(1024), default=u'')
4516 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4518 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4517 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4519 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4518 access_token = Column('access_token', String(1024), default=u'')
4520 access_token = Column('access_token', String(1024), default=u'')
4519 alt_token = Column('alt_token', String(1024), default=u'')
4521 alt_token = Column('alt_token', String(1024), default=u'')
4520 token_secret = Column('token_secret', String(1024), default=u'')
4522 token_secret = Column('token_secret', String(1024), default=u'')
4521
4523
4522 @classmethod
4524 @classmethod
4523 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4525 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4524 """
4526 """
4525 Returns ExternalIdentity instance based on search params
4527 Returns ExternalIdentity instance based on search params
4526
4528
4527 :param external_id:
4529 :param external_id:
4528 :param provider_name:
4530 :param provider_name:
4529 :return: ExternalIdentity
4531 :return: ExternalIdentity
4530 """
4532 """
4531 query = cls.query()
4533 query = cls.query()
4532 query = query.filter(cls.external_id == external_id)
4534 query = query.filter(cls.external_id == external_id)
4533 query = query.filter(cls.provider_name == provider_name)
4535 query = query.filter(cls.provider_name == provider_name)
4534 if local_user_id:
4536 if local_user_id:
4535 query = query.filter(cls.local_user_id == local_user_id)
4537 query = query.filter(cls.local_user_id == local_user_id)
4536 return query.first()
4538 return query.first()
4537
4539
4538 @classmethod
4540 @classmethod
4539 def user_by_external_id_and_provider(cls, external_id, provider_name):
4541 def user_by_external_id_and_provider(cls, external_id, provider_name):
4540 """
4542 """
4541 Returns User instance based on search params
4543 Returns User instance based on search params
4542
4544
4543 :param external_id:
4545 :param external_id:
4544 :param provider_name:
4546 :param provider_name:
4545 :return: User
4547 :return: User
4546 """
4548 """
4547 query = User.query()
4549 query = User.query()
4548 query = query.filter(cls.external_id == external_id)
4550 query = query.filter(cls.external_id == external_id)
4549 query = query.filter(cls.provider_name == provider_name)
4551 query = query.filter(cls.provider_name == provider_name)
4550 query = query.filter(User.user_id == cls.local_user_id)
4552 query = query.filter(User.user_id == cls.local_user_id)
4551 return query.first()
4553 return query.first()
4552
4554
4553 @classmethod
4555 @classmethod
4554 def by_local_user_id(cls, local_user_id):
4556 def by_local_user_id(cls, local_user_id):
4555 """
4557 """
4556 Returns all tokens for user
4558 Returns all tokens for user
4557
4559
4558 :param local_user_id:
4560 :param local_user_id:
4559 :return: ExternalIdentity
4561 :return: ExternalIdentity
4560 """
4562 """
4561 query = cls.query()
4563 query = cls.query()
4562 query = query.filter(cls.local_user_id == local_user_id)
4564 query = query.filter(cls.local_user_id == local_user_id)
4563 return query
4565 return query
4564
4566
4565 @classmethod
4567 @classmethod
4566 def load_provider_plugin(cls, plugin_id):
4568 def load_provider_plugin(cls, plugin_id):
4567 from rhodecode.authentication.base import loadplugin
4569 from rhodecode.authentication.base import loadplugin
4568 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4570 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4569 auth_plugin = loadplugin(_plugin_id)
4571 auth_plugin = loadplugin(_plugin_id)
4570 return auth_plugin
4572 return auth_plugin
4571
4573
4572
4574
4573 class Integration(Base, BaseModel):
4575 class Integration(Base, BaseModel):
4574 __tablename__ = 'integrations'
4576 __tablename__ = 'integrations'
4575 __table_args__ = (
4577 __table_args__ = (
4576 base_table_args
4578 base_table_args
4577 )
4579 )
4578
4580
4579 integration_id = Column('integration_id', Integer(), primary_key=True)
4581 integration_id = Column('integration_id', Integer(), primary_key=True)
4580 integration_type = Column('integration_type', String(255))
4582 integration_type = Column('integration_type', String(255))
4581 enabled = Column('enabled', Boolean(), nullable=False)
4583 enabled = Column('enabled', Boolean(), nullable=False)
4582 name = Column('name', String(255), nullable=False)
4584 name = Column('name', String(255), nullable=False)
4583 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4585 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4584 default=False)
4586 default=False)
4585
4587
4586 settings = Column(
4588 settings = Column(
4587 'settings_json', MutationObj.as_mutable(
4589 'settings_json', MutationObj.as_mutable(
4588 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4590 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4589 repo_id = Column(
4591 repo_id = Column(
4590 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4592 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4591 nullable=True, unique=None, default=None)
4593 nullable=True, unique=None, default=None)
4592 repo = relationship('Repository', lazy='joined')
4594 repo = relationship('Repository', lazy='joined')
4593
4595
4594 repo_group_id = Column(
4596 repo_group_id = Column(
4595 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4597 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4596 nullable=True, unique=None, default=None)
4598 nullable=True, unique=None, default=None)
4597 repo_group = relationship('RepoGroup', lazy='joined')
4599 repo_group = relationship('RepoGroup', lazy='joined')
4598
4600
4599 @property
4601 @property
4600 def scope(self):
4602 def scope(self):
4601 if self.repo:
4603 if self.repo:
4602 return repr(self.repo)
4604 return repr(self.repo)
4603 if self.repo_group:
4605 if self.repo_group:
4604 if self.child_repos_only:
4606 if self.child_repos_only:
4605 return repr(self.repo_group) + ' (child repos only)'
4607 return repr(self.repo_group) + ' (child repos only)'
4606 else:
4608 else:
4607 return repr(self.repo_group) + ' (recursive)'
4609 return repr(self.repo_group) + ' (recursive)'
4608 if self.child_repos_only:
4610 if self.child_repos_only:
4609 return 'root_repos'
4611 return 'root_repos'
4610 return 'global'
4612 return 'global'
4611
4613
4612 def __repr__(self):
4614 def __repr__(self):
4613 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4615 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4614
4616
4615
4617
4616 class RepoReviewRuleUser(Base, BaseModel):
4618 class RepoReviewRuleUser(Base, BaseModel):
4617 __tablename__ = 'repo_review_rules_users'
4619 __tablename__ = 'repo_review_rules_users'
4618 __table_args__ = (
4620 __table_args__ = (
4619 base_table_args
4621 base_table_args
4620 )
4622 )
4621
4623
4622 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4624 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4623 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4625 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4624 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4626 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4625 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4627 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4626 user = relationship('User')
4628 user = relationship('User')
4627
4629
4628 def rule_data(self):
4630 def rule_data(self):
4629 return {
4631 return {
4630 'mandatory': self.mandatory
4632 'mandatory': self.mandatory
4631 }
4633 }
4632
4634
4633
4635
4634 class RepoReviewRuleUserGroup(Base, BaseModel):
4636 class RepoReviewRuleUserGroup(Base, BaseModel):
4635 __tablename__ = 'repo_review_rules_users_groups'
4637 __tablename__ = 'repo_review_rules_users_groups'
4636 __table_args__ = (
4638 __table_args__ = (
4637 base_table_args
4639 base_table_args
4638 )
4640 )
4639
4641
4640 VOTE_RULE_ALL = -1
4642 VOTE_RULE_ALL = -1
4641
4643
4642 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4644 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4643 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4645 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4644 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4646 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4645 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4647 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4646 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4648 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4647 users_group = relationship('UserGroup')
4649 users_group = relationship('UserGroup')
4648
4650
4649 def rule_data(self):
4651 def rule_data(self):
4650 return {
4652 return {
4651 'mandatory': self.mandatory,
4653 'mandatory': self.mandatory,
4652 'vote_rule': self.vote_rule
4654 'vote_rule': self.vote_rule
4653 }
4655 }
4654
4656
4655 @property
4657 @property
4656 def vote_rule_label(self):
4658 def vote_rule_label(self):
4657 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4659 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4658 return 'all must vote'
4660 return 'all must vote'
4659 else:
4661 else:
4660 return 'min. vote {}'.format(self.vote_rule)
4662 return 'min. vote {}'.format(self.vote_rule)
4661
4663
4662
4664
4663 class RepoReviewRule(Base, BaseModel):
4665 class RepoReviewRule(Base, BaseModel):
4664 __tablename__ = 'repo_review_rules'
4666 __tablename__ = 'repo_review_rules'
4665 __table_args__ = (
4667 __table_args__ = (
4666 base_table_args
4668 base_table_args
4667 )
4669 )
4668
4670
4669 repo_review_rule_id = Column(
4671 repo_review_rule_id = Column(
4670 'repo_review_rule_id', Integer(), primary_key=True)
4672 'repo_review_rule_id', Integer(), primary_key=True)
4671 repo_id = Column(
4673 repo_id = Column(
4672 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4674 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4673 repo = relationship('Repository', backref='review_rules')
4675 repo = relationship('Repository', backref='review_rules')
4674
4676
4675 review_rule_name = Column('review_rule_name', String(255))
4677 review_rule_name = Column('review_rule_name', String(255))
4676 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4678 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4677 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4679 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4678 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4680 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4679
4681
4680 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4682 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4681 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4683 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4682 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4684 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4683 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4685 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4684
4686
4685 rule_users = relationship('RepoReviewRuleUser')
4687 rule_users = relationship('RepoReviewRuleUser')
4686 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4688 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4687
4689
4688 def _validate_pattern(self, value):
4690 def _validate_pattern(self, value):
4689 re.compile('^' + glob2re(value) + '$')
4691 re.compile('^' + glob2re(value) + '$')
4690
4692
4691 @hybrid_property
4693 @hybrid_property
4692 def source_branch_pattern(self):
4694 def source_branch_pattern(self):
4693 return self._branch_pattern or '*'
4695 return self._branch_pattern or '*'
4694
4696
4695 @source_branch_pattern.setter
4697 @source_branch_pattern.setter
4696 def source_branch_pattern(self, value):
4698 def source_branch_pattern(self, value):
4697 self._validate_pattern(value)
4699 self._validate_pattern(value)
4698 self._branch_pattern = value or '*'
4700 self._branch_pattern = value or '*'
4699
4701
4700 @hybrid_property
4702 @hybrid_property
4701 def target_branch_pattern(self):
4703 def target_branch_pattern(self):
4702 return self._target_branch_pattern or '*'
4704 return self._target_branch_pattern or '*'
4703
4705
4704 @target_branch_pattern.setter
4706 @target_branch_pattern.setter
4705 def target_branch_pattern(self, value):
4707 def target_branch_pattern(self, value):
4706 self._validate_pattern(value)
4708 self._validate_pattern(value)
4707 self._target_branch_pattern = value or '*'
4709 self._target_branch_pattern = value or '*'
4708
4710
4709 @hybrid_property
4711 @hybrid_property
4710 def file_pattern(self):
4712 def file_pattern(self):
4711 return self._file_pattern or '*'
4713 return self._file_pattern or '*'
4712
4714
4713 @file_pattern.setter
4715 @file_pattern.setter
4714 def file_pattern(self, value):
4716 def file_pattern(self, value):
4715 self._validate_pattern(value)
4717 self._validate_pattern(value)
4716 self._file_pattern = value or '*'
4718 self._file_pattern = value or '*'
4717
4719
4718 def matches(self, source_branch, target_branch, files_changed):
4720 def matches(self, source_branch, target_branch, files_changed):
4719 """
4721 """
4720 Check if this review rule matches a branch/files in a pull request
4722 Check if this review rule matches a branch/files in a pull request
4721
4723
4722 :param source_branch: source branch name for the commit
4724 :param source_branch: source branch name for the commit
4723 :param target_branch: target branch name for the commit
4725 :param target_branch: target branch name for the commit
4724 :param files_changed: list of file paths changed in the pull request
4726 :param files_changed: list of file paths changed in the pull request
4725 """
4727 """
4726
4728
4727 source_branch = source_branch or ''
4729 source_branch = source_branch or ''
4728 target_branch = target_branch or ''
4730 target_branch = target_branch or ''
4729 files_changed = files_changed or []
4731 files_changed = files_changed or []
4730
4732
4731 branch_matches = True
4733 branch_matches = True
4732 if source_branch or target_branch:
4734 if source_branch or target_branch:
4733 if self.source_branch_pattern == '*':
4735 if self.source_branch_pattern == '*':
4734 source_branch_match = True
4736 source_branch_match = True
4735 else:
4737 else:
4736 if self.source_branch_pattern.startswith('re:'):
4738 if self.source_branch_pattern.startswith('re:'):
4737 source_pattern = self.source_branch_pattern[3:]
4739 source_pattern = self.source_branch_pattern[3:]
4738 else:
4740 else:
4739 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4741 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4740 source_branch_regex = re.compile(source_pattern)
4742 source_branch_regex = re.compile(source_pattern)
4741 source_branch_match = bool(source_branch_regex.search(source_branch))
4743 source_branch_match = bool(source_branch_regex.search(source_branch))
4742 if self.target_branch_pattern == '*':
4744 if self.target_branch_pattern == '*':
4743 target_branch_match = True
4745 target_branch_match = True
4744 else:
4746 else:
4745 if self.target_branch_pattern.startswith('re:'):
4747 if self.target_branch_pattern.startswith('re:'):
4746 target_pattern = self.target_branch_pattern[3:]
4748 target_pattern = self.target_branch_pattern[3:]
4747 else:
4749 else:
4748 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4750 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4749 target_branch_regex = re.compile(target_pattern)
4751 target_branch_regex = re.compile(target_pattern)
4750 target_branch_match = bool(target_branch_regex.search(target_branch))
4752 target_branch_match = bool(target_branch_regex.search(target_branch))
4751
4753
4752 branch_matches = source_branch_match and target_branch_match
4754 branch_matches = source_branch_match and target_branch_match
4753
4755
4754 files_matches = True
4756 files_matches = True
4755 if self.file_pattern != '*':
4757 if self.file_pattern != '*':
4756 files_matches = False
4758 files_matches = False
4757 if self.file_pattern.startswith('re:'):
4759 if self.file_pattern.startswith('re:'):
4758 file_pattern = self.file_pattern[3:]
4760 file_pattern = self.file_pattern[3:]
4759 else:
4761 else:
4760 file_pattern = glob2re(self.file_pattern)
4762 file_pattern = glob2re(self.file_pattern)
4761 file_regex = re.compile(file_pattern)
4763 file_regex = re.compile(file_pattern)
4762 for filename in files_changed:
4764 for filename in files_changed:
4763 if file_regex.search(filename):
4765 if file_regex.search(filename):
4764 files_matches = True
4766 files_matches = True
4765 break
4767 break
4766
4768
4767 return branch_matches and files_matches
4769 return branch_matches and files_matches
4768
4770
4769 @property
4771 @property
4770 def review_users(self):
4772 def review_users(self):
4771 """ Returns the users which this rule applies to """
4773 """ Returns the users which this rule applies to """
4772
4774
4773 users = collections.OrderedDict()
4775 users = collections.OrderedDict()
4774
4776
4775 for rule_user in self.rule_users:
4777 for rule_user in self.rule_users:
4776 if rule_user.user.active:
4778 if rule_user.user.active:
4777 if rule_user.user not in users:
4779 if rule_user.user not in users:
4778 users[rule_user.user.username] = {
4780 users[rule_user.user.username] = {
4779 'user': rule_user.user,
4781 'user': rule_user.user,
4780 'source': 'user',
4782 'source': 'user',
4781 'source_data': {},
4783 'source_data': {},
4782 'data': rule_user.rule_data()
4784 'data': rule_user.rule_data()
4783 }
4785 }
4784
4786
4785 for rule_user_group in self.rule_user_groups:
4787 for rule_user_group in self.rule_user_groups:
4786 source_data = {
4788 source_data = {
4787 'user_group_id': rule_user_group.users_group.users_group_id,
4789 'user_group_id': rule_user_group.users_group.users_group_id,
4788 'name': rule_user_group.users_group.users_group_name,
4790 'name': rule_user_group.users_group.users_group_name,
4789 'members': len(rule_user_group.users_group.members)
4791 'members': len(rule_user_group.users_group.members)
4790 }
4792 }
4791 for member in rule_user_group.users_group.members:
4793 for member in rule_user_group.users_group.members:
4792 if member.user.active:
4794 if member.user.active:
4793 key = member.user.username
4795 key = member.user.username
4794 if key in users:
4796 if key in users:
4795 # skip this member as we have him already
4797 # skip this member as we have him already
4796 # this prevents from override the "first" matched
4798 # this prevents from override the "first" matched
4797 # users with duplicates in multiple groups
4799 # users with duplicates in multiple groups
4798 continue
4800 continue
4799
4801
4800 users[key] = {
4802 users[key] = {
4801 'user': member.user,
4803 'user': member.user,
4802 'source': 'user_group',
4804 'source': 'user_group',
4803 'source_data': source_data,
4805 'source_data': source_data,
4804 'data': rule_user_group.rule_data()
4806 'data': rule_user_group.rule_data()
4805 }
4807 }
4806
4808
4807 return users
4809 return users
4808
4810
4809 def user_group_vote_rule(self, user_id):
4811 def user_group_vote_rule(self, user_id):
4810
4812
4811 rules = []
4813 rules = []
4812 if not self.rule_user_groups:
4814 if not self.rule_user_groups:
4813 return rules
4815 return rules
4814
4816
4815 for user_group in self.rule_user_groups:
4817 for user_group in self.rule_user_groups:
4816 user_group_members = [x.user_id for x in user_group.users_group.members]
4818 user_group_members = [x.user_id for x in user_group.users_group.members]
4817 if user_id in user_group_members:
4819 if user_id in user_group_members:
4818 rules.append(user_group)
4820 rules.append(user_group)
4819 return rules
4821 return rules
4820
4822
4821 def __repr__(self):
4823 def __repr__(self):
4822 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4824 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4823 self.repo_review_rule_id, self.repo)
4825 self.repo_review_rule_id, self.repo)
4824
4826
4825
4827
4826 class ScheduleEntry(Base, BaseModel):
4828 class ScheduleEntry(Base, BaseModel):
4827 __tablename__ = 'schedule_entries'
4829 __tablename__ = 'schedule_entries'
4828 __table_args__ = (
4830 __table_args__ = (
4829 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4831 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4830 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4832 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4831 base_table_args,
4833 base_table_args,
4832 )
4834 )
4833
4835
4834 schedule_types = ['crontab', 'timedelta', 'integer']
4836 schedule_types = ['crontab', 'timedelta', 'integer']
4835 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4837 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4836
4838
4837 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4839 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4838 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4840 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4839 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4841 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4840
4842
4841 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4843 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4842 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4844 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4843
4845
4844 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4846 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4845 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4847 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4846
4848
4847 # task
4849 # task
4848 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4850 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4849 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4851 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4850 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4852 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4851 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4853 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4852
4854
4853 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4855 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4854 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4856 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4855
4857
4856 @hybrid_property
4858 @hybrid_property
4857 def schedule_type(self):
4859 def schedule_type(self):
4858 return self._schedule_type
4860 return self._schedule_type
4859
4861
4860 @schedule_type.setter
4862 @schedule_type.setter
4861 def schedule_type(self, val):
4863 def schedule_type(self, val):
4862 if val not in self.schedule_types:
4864 if val not in self.schedule_types:
4863 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4865 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4864 val, self.schedule_type))
4866 val, self.schedule_type))
4865
4867
4866 self._schedule_type = val
4868 self._schedule_type = val
4867
4869
4868 @classmethod
4870 @classmethod
4869 def get_uid(cls, obj):
4871 def get_uid(cls, obj):
4870 args = obj.task_args
4872 args = obj.task_args
4871 kwargs = obj.task_kwargs
4873 kwargs = obj.task_kwargs
4872 if isinstance(args, JsonRaw):
4874 if isinstance(args, JsonRaw):
4873 try:
4875 try:
4874 args = json.loads(args)
4876 args = json.loads(args)
4875 except ValueError:
4877 except ValueError:
4876 args = tuple()
4878 args = tuple()
4877
4879
4878 if isinstance(kwargs, JsonRaw):
4880 if isinstance(kwargs, JsonRaw):
4879 try:
4881 try:
4880 kwargs = json.loads(kwargs)
4882 kwargs = json.loads(kwargs)
4881 except ValueError:
4883 except ValueError:
4882 kwargs = dict()
4884 kwargs = dict()
4883
4885
4884 dot_notation = obj.task_dot_notation
4886 dot_notation = obj.task_dot_notation
4885 val = '.'.join(map(safe_str, [
4887 val = '.'.join(map(safe_str, [
4886 sorted(dot_notation), args, sorted(kwargs.items())]))
4888 sorted(dot_notation), args, sorted(kwargs.items())]))
4887 return hashlib.sha1(val).hexdigest()
4889 return hashlib.sha1(val).hexdigest()
4888
4890
4889 @classmethod
4891 @classmethod
4890 def get_by_schedule_name(cls, schedule_name):
4892 def get_by_schedule_name(cls, schedule_name):
4891 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4893 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4892
4894
4893 @classmethod
4895 @classmethod
4894 def get_by_schedule_id(cls, schedule_id):
4896 def get_by_schedule_id(cls, schedule_id):
4895 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4897 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4896
4898
4897 @property
4899 @property
4898 def task(self):
4900 def task(self):
4899 return self.task_dot_notation
4901 return self.task_dot_notation
4900
4902
4901 @property
4903 @property
4902 def schedule(self):
4904 def schedule(self):
4903 from rhodecode.lib.celerylib.utils import raw_2_schedule
4905 from rhodecode.lib.celerylib.utils import raw_2_schedule
4904 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4906 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4905 return schedule
4907 return schedule
4906
4908
4907 @property
4909 @property
4908 def args(self):
4910 def args(self):
4909 try:
4911 try:
4910 return list(self.task_args or [])
4912 return list(self.task_args or [])
4911 except ValueError:
4913 except ValueError:
4912 return list()
4914 return list()
4913
4915
4914 @property
4916 @property
4915 def kwargs(self):
4917 def kwargs(self):
4916 try:
4918 try:
4917 return dict(self.task_kwargs or {})
4919 return dict(self.task_kwargs or {})
4918 except ValueError:
4920 except ValueError:
4919 return dict()
4921 return dict()
4920
4922
4921 def _as_raw(self, val):
4923 def _as_raw(self, val):
4922 if hasattr(val, 'de_coerce'):
4924 if hasattr(val, 'de_coerce'):
4923 val = val.de_coerce()
4925 val = val.de_coerce()
4924 if val:
4926 if val:
4925 val = json.dumps(val)
4927 val = json.dumps(val)
4926
4928
4927 return val
4929 return val
4928
4930
4929 @property
4931 @property
4930 def schedule_definition_raw(self):
4932 def schedule_definition_raw(self):
4931 return self._as_raw(self.schedule_definition)
4933 return self._as_raw(self.schedule_definition)
4932
4934
4933 @property
4935 @property
4934 def args_raw(self):
4936 def args_raw(self):
4935 return self._as_raw(self.task_args)
4937 return self._as_raw(self.task_args)
4936
4938
4937 @property
4939 @property
4938 def kwargs_raw(self):
4940 def kwargs_raw(self):
4939 return self._as_raw(self.task_kwargs)
4941 return self._as_raw(self.task_kwargs)
4940
4942
4941 def __repr__(self):
4943 def __repr__(self):
4942 return '<DB:ScheduleEntry({}:{})>'.format(
4944 return '<DB:ScheduleEntry({}:{})>'.format(
4943 self.schedule_entry_id, self.schedule_name)
4945 self.schedule_entry_id, self.schedule_name)
4944
4946
4945
4947
4946 @event.listens_for(ScheduleEntry, 'before_update')
4948 @event.listens_for(ScheduleEntry, 'before_update')
4947 def update_task_uid(mapper, connection, target):
4949 def update_task_uid(mapper, connection, target):
4948 target.task_uid = ScheduleEntry.get_uid(target)
4950 target.task_uid = ScheduleEntry.get_uid(target)
4949
4951
4950
4952
4951 @event.listens_for(ScheduleEntry, 'before_insert')
4953 @event.listens_for(ScheduleEntry, 'before_insert')
4952 def set_task_uid(mapper, connection, target):
4954 def set_task_uid(mapper, connection, target):
4953 target.task_uid = ScheduleEntry.get_uid(target)
4955 target.task_uid = ScheduleEntry.get_uid(target)
4954
4956
4955
4957
4956 class _BaseBranchPerms(BaseModel):
4958 class _BaseBranchPerms(BaseModel):
4957 @classmethod
4959 @classmethod
4958 def compute_hash(cls, value):
4960 def compute_hash(cls, value):
4959 return sha1_safe(value)
4961 return sha1_safe(value)
4960
4962
4961 @hybrid_property
4963 @hybrid_property
4962 def branch_pattern(self):
4964 def branch_pattern(self):
4963 return self._branch_pattern or '*'
4965 return self._branch_pattern or '*'
4964
4966
4965 @hybrid_property
4967 @hybrid_property
4966 def branch_hash(self):
4968 def branch_hash(self):
4967 return self._branch_hash
4969 return self._branch_hash
4968
4970
4969 def _validate_glob(self, value):
4971 def _validate_glob(self, value):
4970 re.compile('^' + glob2re(value) + '$')
4972 re.compile('^' + glob2re(value) + '$')
4971
4973
4972 @branch_pattern.setter
4974 @branch_pattern.setter
4973 def branch_pattern(self, value):
4975 def branch_pattern(self, value):
4974 self._validate_glob(value)
4976 self._validate_glob(value)
4975 self._branch_pattern = value or '*'
4977 self._branch_pattern = value or '*'
4976 # set the Hash when setting the branch pattern
4978 # set the Hash when setting the branch pattern
4977 self._branch_hash = self.compute_hash(self._branch_pattern)
4979 self._branch_hash = self.compute_hash(self._branch_pattern)
4978
4980
4979 def matches(self, branch):
4981 def matches(self, branch):
4980 """
4982 """
4981 Check if this the branch matches entry
4983 Check if this the branch matches entry
4982
4984
4983 :param branch: branch name for the commit
4985 :param branch: branch name for the commit
4984 """
4986 """
4985
4987
4986 branch = branch or ''
4988 branch = branch or ''
4987
4989
4988 branch_matches = True
4990 branch_matches = True
4989 if branch:
4991 if branch:
4990 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4992 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4991 branch_matches = bool(branch_regex.search(branch))
4993 branch_matches = bool(branch_regex.search(branch))
4992
4994
4993 return branch_matches
4995 return branch_matches
4994
4996
4995
4997
4996 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4998 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4997 __tablename__ = 'user_to_repo_branch_permissions'
4999 __tablename__ = 'user_to_repo_branch_permissions'
4998 __table_args__ = (
5000 __table_args__ = (
4999 base_table_args
5001 base_table_args
5000 )
5002 )
5001
5003
5002 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5004 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5003
5005
5004 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5006 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5005 repo = relationship('Repository', backref='user_branch_perms')
5007 repo = relationship('Repository', backref='user_branch_perms')
5006
5008
5007 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5009 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5008 permission = relationship('Permission')
5010 permission = relationship('Permission')
5009
5011
5010 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5012 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5011 user_repo_to_perm = relationship('UserRepoToPerm')
5013 user_repo_to_perm = relationship('UserRepoToPerm')
5012
5014
5013 rule_order = Column('rule_order', Integer(), nullable=False)
5015 rule_order = Column('rule_order', Integer(), nullable=False)
5014 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5016 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5015 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5017 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5016
5018
5017 def __unicode__(self):
5019 def __unicode__(self):
5018 return u'<UserBranchPermission(%s => %r)>' % (
5020 return u'<UserBranchPermission(%s => %r)>' % (
5019 self.user_repo_to_perm, self.branch_pattern)
5021 self.user_repo_to_perm, self.branch_pattern)
5020
5022
5021
5023
5022 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5024 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5023 __tablename__ = 'user_group_to_repo_branch_permissions'
5025 __tablename__ = 'user_group_to_repo_branch_permissions'
5024 __table_args__ = (
5026 __table_args__ = (
5025 base_table_args
5027 base_table_args
5026 )
5028 )
5027
5029
5028 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5030 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5029
5031
5030 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5032 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5031 repo = relationship('Repository', backref='user_group_branch_perms')
5033 repo = relationship('Repository', backref='user_group_branch_perms')
5032
5034
5033 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5035 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5034 permission = relationship('Permission')
5036 permission = relationship('Permission')
5035
5037
5036 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5038 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5037 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5039 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5038
5040
5039 rule_order = Column('rule_order', Integer(), nullable=False)
5041 rule_order = Column('rule_order', Integer(), nullable=False)
5040 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5042 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5041 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5043 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5042
5044
5043 def __unicode__(self):
5045 def __unicode__(self):
5044 return u'<UserBranchPermission(%s => %r)>' % (
5046 return u'<UserBranchPermission(%s => %r)>' % (
5045 self.user_group_repo_to_perm, self.branch_pattern)
5047 self.user_group_repo_to_perm, self.branch_pattern)
5046
5048
5047
5049
5048 class UserBookmark(Base, BaseModel):
5050 class UserBookmark(Base, BaseModel):
5049 __tablename__ = 'user_bookmarks'
5051 __tablename__ = 'user_bookmarks'
5050 __table_args__ = (
5052 __table_args__ = (
5051 UniqueConstraint('user_id', 'bookmark_repo_id'),
5053 UniqueConstraint('user_id', 'bookmark_repo_id'),
5052 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5054 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5053 UniqueConstraint('user_id', 'bookmark_position'),
5055 UniqueConstraint('user_id', 'bookmark_position'),
5054 base_table_args
5056 base_table_args
5055 )
5057 )
5056
5058
5057 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5059 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5058 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5060 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5059 position = Column("bookmark_position", Integer(), nullable=False)
5061 position = Column("bookmark_position", Integer(), nullable=False)
5060 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5062 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5061 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5063 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5062 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5064 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5063
5065
5064 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5066 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5065 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5067 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5066
5068
5067 user = relationship("User")
5069 user = relationship("User")
5068
5070
5069 repository = relationship("Repository")
5071 repository = relationship("Repository")
5070 repository_group = relationship("RepoGroup")
5072 repository_group = relationship("RepoGroup")
5071
5073
5072 @classmethod
5074 @classmethod
5073 def get_by_position_for_user(cls, position, user_id):
5075 def get_by_position_for_user(cls, position, user_id):
5074 return cls.query() \
5076 return cls.query() \
5075 .filter(UserBookmark.user_id == user_id) \
5077 .filter(UserBookmark.user_id == user_id) \
5076 .filter(UserBookmark.position == position).scalar()
5078 .filter(UserBookmark.position == position).scalar()
5077
5079
5078 @classmethod
5080 @classmethod
5079 def get_bookmarks_for_user(cls, user_id):
5081 def get_bookmarks_for_user(cls, user_id):
5080 return cls.query() \
5082 return cls.query() \
5081 .filter(UserBookmark.user_id == user_id) \
5083 .filter(UserBookmark.user_id == user_id) \
5082 .options(joinedload(UserBookmark.repository)) \
5084 .options(joinedload(UserBookmark.repository)) \
5083 .options(joinedload(UserBookmark.repository_group)) \
5085 .options(joinedload(UserBookmark.repository_group)) \
5084 .order_by(UserBookmark.position.asc()) \
5086 .order_by(UserBookmark.position.asc()) \
5085 .all()
5087 .all()
5086
5088
5087 def __unicode__(self):
5089 def __unicode__(self):
5088 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5090 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5089
5091
5090
5092
5091 class FileStore(Base, BaseModel):
5093 class FileStore(Base, BaseModel):
5092 __tablename__ = 'file_store'
5094 __tablename__ = 'file_store'
5093 __table_args__ = (
5095 __table_args__ = (
5094 base_table_args
5096 base_table_args
5095 )
5097 )
5096
5098
5097 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5099 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5098 file_uid = Column('file_uid', String(1024), nullable=False)
5100 file_uid = Column('file_uid', String(1024), nullable=False)
5099 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5101 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5100 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5102 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5101 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5103 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5102
5104
5103 # sha256 hash
5105 # sha256 hash
5104 file_hash = Column('file_hash', String(512), nullable=False)
5106 file_hash = Column('file_hash', String(512), nullable=False)
5105 file_size = Column('file_size', Integer(), nullable=False)
5107 file_size = Column('file_size', Integer(), nullable=False)
5106
5108
5107 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5109 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5108 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5110 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5109 accessed_count = Column('accessed_count', Integer(), default=0)
5111 accessed_count = Column('accessed_count', Integer(), default=0)
5110
5112
5111 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5113 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5112
5114
5113 # if repo/repo_group reference is set, check for permissions
5115 # if repo/repo_group reference is set, check for permissions
5114 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5116 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5115
5117
5116 # hidden defines an attachment that should be hidden from showing in artifact listing
5118 # hidden defines an attachment that should be hidden from showing in artifact listing
5117 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5119 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5118
5120
5119 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5121 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5120 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5122 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5121
5123
5122 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5124 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5123
5125
5124 # scope limited to user, which requester have access to
5126 # scope limited to user, which requester have access to
5125 scope_user_id = Column(
5127 scope_user_id = Column(
5126 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5128 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5127 nullable=True, unique=None, default=None)
5129 nullable=True, unique=None, default=None)
5128 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5130 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5129
5131
5130 # scope limited to user group, which requester have access to
5132 # scope limited to user group, which requester have access to
5131 scope_user_group_id = Column(
5133 scope_user_group_id = Column(
5132 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5134 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5133 nullable=True, unique=None, default=None)
5135 nullable=True, unique=None, default=None)
5134 user_group = relationship('UserGroup', lazy='joined')
5136 user_group = relationship('UserGroup', lazy='joined')
5135
5137
5136 # scope limited to repo, which requester have access to
5138 # scope limited to repo, which requester have access to
5137 scope_repo_id = Column(
5139 scope_repo_id = Column(
5138 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5140 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5139 nullable=True, unique=None, default=None)
5141 nullable=True, unique=None, default=None)
5140 repo = relationship('Repository', lazy='joined')
5142 repo = relationship('Repository', lazy='joined')
5141
5143
5142 # scope limited to repo group, which requester have access to
5144 # scope limited to repo group, which requester have access to
5143 scope_repo_group_id = Column(
5145 scope_repo_group_id = Column(
5144 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5146 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5145 nullable=True, unique=None, default=None)
5147 nullable=True, unique=None, default=None)
5146 repo_group = relationship('RepoGroup', lazy='joined')
5148 repo_group = relationship('RepoGroup', lazy='joined')
5147
5149
5148 @classmethod
5150 @classmethod
5151 def get_by_store_uid(cls, file_store_uid):
5152 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5153
5154 @classmethod
5149 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5155 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5150 file_description='', enabled=True, hidden=False, check_acl=True,
5156 file_description='', enabled=True, hidden=False, check_acl=True,
5151 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5157 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5152
5158
5153 store_entry = FileStore()
5159 store_entry = FileStore()
5154 store_entry.file_uid = file_uid
5160 store_entry.file_uid = file_uid
5155 store_entry.file_display_name = file_display_name
5161 store_entry.file_display_name = file_display_name
5156 store_entry.file_org_name = filename
5162 store_entry.file_org_name = filename
5157 store_entry.file_size = file_size
5163 store_entry.file_size = file_size
5158 store_entry.file_hash = file_hash
5164 store_entry.file_hash = file_hash
5159 store_entry.file_description = file_description
5165 store_entry.file_description = file_description
5160
5166
5161 store_entry.check_acl = check_acl
5167 store_entry.check_acl = check_acl
5162 store_entry.enabled = enabled
5168 store_entry.enabled = enabled
5163 store_entry.hidden = hidden
5169 store_entry.hidden = hidden
5164
5170
5165 store_entry.user_id = user_id
5171 store_entry.user_id = user_id
5166 store_entry.scope_user_id = scope_user_id
5172 store_entry.scope_user_id = scope_user_id
5167 store_entry.scope_repo_id = scope_repo_id
5173 store_entry.scope_repo_id = scope_repo_id
5168 store_entry.scope_repo_group_id = scope_repo_group_id
5174 store_entry.scope_repo_group_id = scope_repo_group_id
5169
5175
5170 return store_entry
5176 return store_entry
5171
5177
5172 @classmethod
5178 @classmethod
5173 def store_metadata(cls, file_store_id, args, commit=True):
5179 def store_metadata(cls, file_store_id, args, commit=True):
5174 file_store = FileStore.get(file_store_id)
5180 file_store = FileStore.get(file_store_id)
5175 if file_store is None:
5181 if file_store is None:
5176 return
5182 return
5177
5183
5178 for section, key, value, value_type in args:
5184 for section, key, value, value_type in args:
5185 has_key = FileStoreMetadata().query() \
5186 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5187 .filter(FileStoreMetadata.file_store_meta_section == section) \
5188 .filter(FileStoreMetadata.file_store_meta_key == key) \
5189 .scalar()
5190 if has_key:
5191 msg = 'key `{}` already defined under section `{}` for this file.'\
5192 .format(key, section)
5193 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5194
5195 # NOTE(marcink): raises ArtifactMetadataBadValueType
5196 FileStoreMetadata.valid_value_type(value_type)
5197
5179 meta_entry = FileStoreMetadata()
5198 meta_entry = FileStoreMetadata()
5180 meta_entry.file_store = file_store
5199 meta_entry.file_store = file_store
5181 meta_entry.file_store_meta_section = section
5200 meta_entry.file_store_meta_section = section
5182 meta_entry.file_store_meta_key = key
5201 meta_entry.file_store_meta_key = key
5183 meta_entry.file_store_meta_value_type = value_type
5202 meta_entry.file_store_meta_value_type = value_type
5184 meta_entry.file_store_meta_value = value
5203 meta_entry.file_store_meta_value = value
5185
5204
5186 Session().add(meta_entry)
5205 Session().add(meta_entry)
5187
5206
5188 if commit:
5207 try:
5189 Session().commit()
5208 if commit:
5209 Session().commit()
5210 except IntegrityError:
5211 Session().rollback()
5212 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5190
5213
5191 @classmethod
5214 @classmethod
5192 def bump_access_counter(cls, file_uid, commit=True):
5215 def bump_access_counter(cls, file_uid, commit=True):
5193 FileStore().query()\
5216 FileStore().query()\
5194 .filter(FileStore.file_uid == file_uid)\
5217 .filter(FileStore.file_uid == file_uid)\
5195 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5218 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5196 FileStore.accessed_on: datetime.datetime.now()})
5219 FileStore.accessed_on: datetime.datetime.now()})
5197 if commit:
5220 if commit:
5198 Session().commit()
5221 Session().commit()
5199
5222
5200 def __repr__(self):
5223 def __repr__(self):
5201 return '<FileStore({})>'.format(self.file_store_id)
5224 return '<FileStore({})>'.format(self.file_store_id)
5202
5225
5203
5226
5204 class FileStoreMetadata(Base, BaseModel):
5227 class FileStoreMetadata(Base, BaseModel):
5205 __tablename__ = 'file_store_metadata'
5228 __tablename__ = 'file_store_metadata'
5206 __table_args__ = (
5229 __table_args__ = (
5207 UniqueConstraint('file_store_id', 'file_store_meta_section', 'file_store_meta_key'),
5230 UniqueConstraint('file_store_id', 'file_store_meta_section', 'file_store_meta_key'),
5208 Index('file_store_meta_section_idx', 'file_store_meta_section'),
5231 Index('file_store_meta_section_idx', 'file_store_meta_section'),
5209 Index('file_store_meta_key_idx', 'file_store_meta_key'),
5232 Index('file_store_meta_key_idx', 'file_store_meta_key'),
5210 base_table_args
5233 base_table_args
5211 )
5234 )
5212 SETTINGS_TYPES = {
5235 SETTINGS_TYPES = {
5213 'str': safe_str,
5236 'str': safe_str,
5214 'int': safe_int,
5237 'int': safe_int,
5215 'unicode': safe_unicode,
5238 'unicode': safe_unicode,
5216 'bool': str2bool,
5239 'bool': str2bool,
5217 'list': functools.partial(aslist, sep=',')
5240 'list': functools.partial(aslist, sep=',')
5218 }
5241 }
5219
5242
5220 file_store_meta_id = Column(
5243 file_store_meta_id = Column(
5221 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5244 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5222 primary_key=True)
5245 primary_key=True)
5223 file_store_meta_section = Column(
5246 file_store_meta_section = Column(
5224 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5247 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5225 nullable=True, unique=None, default=None)
5248 nullable=True, unique=None, default=None)
5226 file_store_meta_key = Column(
5249 file_store_meta_key = Column(
5227 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5250 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5228 nullable=True, unique=None, default=None)
5251 nullable=True, unique=None, default=None)
5229 _file_store_meta_value = Column(
5252 _file_store_meta_value = Column(
5230 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5253 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5231 nullable=True, unique=None, default=None)
5254 nullable=True, unique=None, default=None)
5232 _file_store_meta_value_type = Column(
5255 _file_store_meta_value_type = Column(
5233 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5256 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5234 default='unicode')
5257 default='unicode')
5235
5258
5236 file_store_id = Column(
5259 file_store_id = Column(
5237 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5260 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5238 nullable=True, unique=None, default=None)
5261 nullable=True, unique=None, default=None)
5239
5262
5240 file_store = relationship('FileStore', lazy='joined')
5263 file_store = relationship('FileStore', lazy='joined')
5241
5264
5265 @classmethod
5266 def valid_value_type(cls, value):
5267 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5268 raise ArtifactMetadataBadValueType(
5269 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5270
5242 @hybrid_property
5271 @hybrid_property
5243 def file_store_meta_value(self):
5272 def file_store_meta_value(self):
5244 v = self._file_store_meta_value
5273 val = self._file_store_meta_value
5245 _type = self._file_store_meta_value
5274
5246 if _type:
5275 if self._file_store_meta_value_type:
5247 # e.g unicode.encrypted == unicode
5276 # e.g unicode.encrypted == unicode
5248 _type = self._file_store_meta_value.split('.')[0]
5277 _type = self._file_store_meta_value_type.split('.')[0]
5249 # decode the encrypted value
5278 # decode the encrypted value if it's encrypted field type
5250 if '.encrypted' in self._file_store_meta_value_type:
5279 if '.encrypted' in self._file_store_meta_value_type:
5251 cipher = EncryptedTextValue()
5280 cipher = EncryptedTextValue()
5252 v = safe_unicode(cipher.process_result_value(v, None))
5281 val = safe_unicode(cipher.process_result_value(val, None))
5253
5282 # do final type conversion
5254 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5283 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5255 return converter(v)
5284 val = converter(val)
5285
5286 return val
5256
5287
5257 @file_store_meta_value.setter
5288 @file_store_meta_value.setter
5258 def file_store_meta_value(self, val):
5289 def file_store_meta_value(self, val):
5259 val = safe_unicode(val)
5290 val = safe_unicode(val)
5260 # encode the encrypted value
5291 # encode the encrypted value
5261 if '.encrypted' in self.file_store_meta_value_type:
5292 if '.encrypted' in self.file_store_meta_value_type:
5262 cipher = EncryptedTextValue()
5293 cipher = EncryptedTextValue()
5263 val = safe_unicode(cipher.process_bind_param(val, None))
5294 val = safe_unicode(cipher.process_bind_param(val, None))
5264 self._file_store_meta_value = val
5295 self._file_store_meta_value = val
5265
5296
5266 @hybrid_property
5297 @hybrid_property
5267 def file_store_meta_value_type(self):
5298 def file_store_meta_value_type(self):
5268 return self._file_store_meta_value_type
5299 return self._file_store_meta_value_type
5269
5300
5270 @file_store_meta_value_type.setter
5301 @file_store_meta_value_type.setter
5271 def file_store_meta_value_type(self, val):
5302 def file_store_meta_value_type(self, val):
5272 # e.g unicode.encrypted
5303 # e.g unicode.encrypted
5273 if val.split('.')[0] not in self.SETTINGS_TYPES:
5304 self.valid_value_type(val)
5274 raise Exception('type must be one of %s got %s'
5275 % (self.SETTINGS_TYPES.keys(), val))
5276 self._file_store_meta_value_type = val
5305 self._file_store_meta_value_type = val
5277
5306
5307 def __json__(self):
5308 data = {
5309 'artifact': self.file_store.file_uid,
5310 'section': self.file_store_meta_section,
5311 'key': self.file_store_meta_key,
5312 'value': self.file_store_meta_value,
5313 }
5314
5315 return data
5316
5278 def __repr__(self):
5317 def __repr__(self):
5279 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5318 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5280 self.file_store_meta_key, self.file_store_meta_value)
5319 self.file_store_meta_key, self.file_store_meta_value)
5281
5320
5282
5321
5283 class DbMigrateVersion(Base, BaseModel):
5322 class DbMigrateVersion(Base, BaseModel):
5284 __tablename__ = 'db_migrate_version'
5323 __tablename__ = 'db_migrate_version'
5285 __table_args__ = (
5324 __table_args__ = (
5286 base_table_args,
5325 base_table_args,
5287 )
5326 )
5288
5327
5289 repository_id = Column('repository_id', String(250), primary_key=True)
5328 repository_id = Column('repository_id', String(250), primary_key=True)
5290 repository_path = Column('repository_path', Text)
5329 repository_path = Column('repository_path', Text)
5291 version = Column('version', Integer)
5330 version = Column('version', Integer)
5292
5331
5293 @classmethod
5332 @classmethod
5294 def set_version(cls, version):
5333 def set_version(cls, version):
5295 """
5334 """
5296 Helper for forcing a different version, usually for debugging purposes via ishell.
5335 Helper for forcing a different version, usually for debugging purposes via ishell.
5297 """
5336 """
5298 ver = DbMigrateVersion.query().first()
5337 ver = DbMigrateVersion.query().first()
5299 ver.version = version
5338 ver.version = version
5300 Session().commit()
5339 Session().commit()
5301
5340
5302
5341
5303 class DbSession(Base, BaseModel):
5342 class DbSession(Base, BaseModel):
5304 __tablename__ = 'db_session'
5343 __tablename__ = 'db_session'
5305 __table_args__ = (
5344 __table_args__ = (
5306 base_table_args,
5345 base_table_args,
5307 )
5346 )
5308
5347
5309 def __repr__(self):
5348 def __repr__(self):
5310 return '<DB:DbSession({})>'.format(self.id)
5349 return '<DB:DbSession({})>'.format(self.id)
5311
5350
5312 id = Column('id', Integer())
5351 id = Column('id', Integer())
5313 namespace = Column('namespace', String(255), primary_key=True)
5352 namespace = Column('namespace', String(255), primary_key=True)
5314 accessed = Column('accessed', DateTime, nullable=False)
5353 accessed = Column('accessed', DateTime, nullable=False)
5315 created = Column('created', DateTime, nullable=False)
5354 created = Column('created', DateTime, nullable=False)
5316 data = Column('data', PickleType, nullable=False)
5355 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now