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