##// END OF EJS Templates
vcs: moved svn proxy settings into vcs related settings...
marcink -
r754:f39fd7f4 default
parent child Browse files
Show More
@@ -1,793 +1,796 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 settings controller for rhodecode admin
23 settings controller for rhodecode admin
24 """
24 """
25
25
26 import collections
26 import collections
27 import logging
27 import logging
28 import urllib2
28 import urllib2
29
29
30 import datetime
30 import datetime
31 import formencode
31 import formencode
32 from formencode import htmlfill
32 from formencode import htmlfill
33 import packaging.version
33 import packaging.version
34 from pylons import request, tmpl_context as c, url, config
34 from pylons import request, tmpl_context as c, url, config
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36 from pylons.i18n.translation import _, lazy_ugettext
36 from pylons.i18n.translation import _, lazy_ugettext
37 from webob.exc import HTTPBadRequest
37 from webob.exc import HTTPBadRequest
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.admin.navigation import navigation_list
40 from rhodecode.admin.navigation import navigation_list
41 from rhodecode.lib import auth
41 from rhodecode.lib import auth
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.lib.celerylib import tasks, run_task
45 from rhodecode.lib.celerylib import tasks, run_task
46 from rhodecode.lib.utils import repo2db_mapper
46 from rhodecode.lib.utils import repo2db_mapper
47 from rhodecode.lib.utils2 import (
47 from rhodecode.lib.utils2 import (
48 str2bool, safe_unicode, AttributeDict, safe_int)
48 str2bool, safe_unicode, AttributeDict, safe_int)
49 from rhodecode.lib.compat import OrderedDict
49 from rhodecode.lib.compat import OrderedDict
50 from rhodecode.lib.ext_json import json
50 from rhodecode.lib.ext_json import json
51 from rhodecode.lib.utils import jsonify
51 from rhodecode.lib.utils import jsonify
52
52
53 from rhodecode.model.db import RhodeCodeUi, Repository
53 from rhodecode.model.db import RhodeCodeUi, Repository
54 from rhodecode.model.forms import ApplicationSettingsForm, \
54 from rhodecode.model.forms import ApplicationSettingsForm, \
55 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
55 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
56 LabsSettingsForm, IssueTrackerPatternsForm
56 LabsSettingsForm, IssueTrackerPatternsForm
57
57
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.notification import EmailNotificationModel
59 from rhodecode.model.notification import EmailNotificationModel
60 from rhodecode.model.meta import Session
60 from rhodecode.model.meta import Session
61 from rhodecode.model.settings import (
61 from rhodecode.model.settings import (
62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
63 SettingsModel)
63 SettingsModel)
64
64
65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
66
66
67
67
68 log = logging.getLogger(__name__)
68 log = logging.getLogger(__name__)
69
69
70
70
71 class SettingsController(BaseController):
71 class SettingsController(BaseController):
72 """REST Controller styled on the Atom Publishing Protocol"""
72 """REST Controller styled on the Atom Publishing Protocol"""
73 # To properly map this controller, ensure your config/routing.py
73 # To properly map this controller, ensure your config/routing.py
74 # file has a resource setup:
74 # file has a resource setup:
75 # map.resource('setting', 'settings', controller='admin/settings',
75 # map.resource('setting', 'settings', controller='admin/settings',
76 # path_prefix='/admin', name_prefix='admin_')
76 # path_prefix='/admin', name_prefix='admin_')
77
77
78 @LoginRequired()
78 @LoginRequired()
79 def __before__(self):
79 def __before__(self):
80 super(SettingsController, self).__before__()
80 super(SettingsController, self).__before__()
81 c.labs_active = str2bool(
81 c.labs_active = str2bool(
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 c.navlist = navigation_list(request)
83 c.navlist = navigation_list(request)
84
84
85 def _get_hg_ui_settings(self):
85 def _get_hg_ui_settings(self):
86 ret = RhodeCodeUi.query().all()
86 ret = RhodeCodeUi.query().all()
87
87
88 if not ret:
88 if not ret:
89 raise Exception('Could not get application ui settings !')
89 raise Exception('Could not get application ui settings !')
90 settings = {}
90 settings = {}
91 for each in ret:
91 for each in ret:
92 k = each.ui_key
92 k = each.ui_key
93 v = each.ui_value
93 v = each.ui_value
94 if k == '/':
94 if k == '/':
95 k = 'root_path'
95 k = 'root_path'
96
96
97 if k in ['push_ssl', 'publish']:
97 if k in ['push_ssl', 'publish']:
98 v = str2bool(v)
98 v = str2bool(v)
99
99
100 if k.find('.') != -1:
100 if k.find('.') != -1:
101 k = k.replace('.', '_')
101 k = k.replace('.', '_')
102
102
103 if each.ui_section in ['hooks', 'extensions']:
103 if each.ui_section in ['hooks', 'extensions']:
104 v = each.ui_active
104 v = each.ui_active
105
105
106 settings[each.ui_section + '_' + k] = v
106 settings[each.ui_section + '_' + k] = v
107 return settings
107 return settings
108
108
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 @auth.CSRFRequired()
110 @auth.CSRFRequired()
111 @jsonify
111 @jsonify
112 def delete_svn_pattern(self):
112 def delete_svn_pattern(self):
113 if not request.is_xhr:
113 if not request.is_xhr:
114 raise HTTPBadRequest()
114 raise HTTPBadRequest()
115
115
116 delete_pattern_id = request.POST.get('delete_svn_pattern')
116 delete_pattern_id = request.POST.get('delete_svn_pattern')
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 try:
118 try:
119 model.delete_global_svn_pattern(delete_pattern_id)
119 model.delete_global_svn_pattern(delete_pattern_id)
120 except SettingNotFound:
120 except SettingNotFound:
121 raise HTTPBadRequest()
121 raise HTTPBadRequest()
122
122
123 Session().commit()
123 Session().commit()
124 return True
124 return True
125
125
126 @HasPermissionAllDecorator('hg.admin')
126 @HasPermissionAllDecorator('hg.admin')
127 @auth.CSRFRequired()
127 @auth.CSRFRequired()
128 def settings_vcs_update(self):
128 def settings_vcs_update(self):
129 """POST /admin/settings: All items in the collection"""
129 """POST /admin/settings: All items in the collection"""
130 # url('admin_settings_vcs')
130 # url('admin_settings_vcs')
131 c.active = 'vcs'
131 c.active = 'vcs'
132
132
133 model = VcsSettingsModel()
133 model = VcsSettingsModel()
134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
136
136
137 application_form = ApplicationUiSettingsForm()()
137 application_form = ApplicationUiSettingsForm()()
138
138 try:
139 try:
139 form_result = application_form.to_python(dict(request.POST))
140 form_result = application_form.to_python(dict(request.POST))
140 except formencode.Invalid as errors:
141 except formencode.Invalid as errors:
141 h.flash(
142 h.flash(
142 _("Some form inputs contain invalid data."),
143 _("Some form inputs contain invalid data."),
143 category='error')
144 category='error')
144 return htmlfill.render(
145 return htmlfill.render(
145 render('admin/settings/settings.html'),
146 render('admin/settings/settings.html'),
146 defaults=errors.value,
147 defaults=errors.value,
147 errors=errors.error_dict or {},
148 errors=errors.error_dict or {},
148 prefix_error=False,
149 prefix_error=False,
149 encoding="UTF-8",
150 encoding="UTF-8",
150 force_defaults=False
151 force_defaults=False
151 )
152 )
152
153
153 try:
154 try:
154 model.update_global_ssl_setting(form_result['web_push_ssl'])
155 if c.visual.allow_repo_location_change:
155 if c.visual.allow_repo_location_change:
156 model.update_global_path_setting(
156 model.update_global_path_setting(
157 form_result['paths_root_path'])
157 form_result['paths_root_path'])
158
159 model.update_global_ssl_setting(form_result['web_push_ssl'])
158 model.update_global_hook_settings(form_result)
160 model.update_global_hook_settings(form_result)
159 model.create_global_svn_settings(form_result)
161
162 model.create_or_update_global_svn_settings(form_result)
160 model.create_or_update_global_hg_settings(form_result)
163 model.create_or_update_global_hg_settings(form_result)
161 model.create_or_update_global_pr_settings(form_result)
164 model.create_or_update_global_pr_settings(form_result)
162 except Exception:
165 except Exception:
163 log.exception("Exception while updating settings")
166 log.exception("Exception while updating settings")
164 h.flash(_('Error occurred during updating '
167 h.flash(_('Error occurred during updating '
165 'application settings'), category='error')
168 'application settings'), category='error')
166 else:
169 else:
167 Session().commit()
170 Session().commit()
168 h.flash(_('Updated VCS settings'), category='success')
171 h.flash(_('Updated VCS settings'), category='success')
169 return redirect(url('admin_settings_vcs'))
172 return redirect(url('admin_settings_vcs'))
170
173
171 return htmlfill.render(
174 return htmlfill.render(
172 render('admin/settings/settings.html'),
175 render('admin/settings/settings.html'),
173 defaults=self._form_defaults(),
176 defaults=self._form_defaults(),
174 encoding="UTF-8",
177 encoding="UTF-8",
175 force_defaults=False)
178 force_defaults=False)
176
179
177 @HasPermissionAllDecorator('hg.admin')
180 @HasPermissionAllDecorator('hg.admin')
178 def settings_vcs(self):
181 def settings_vcs(self):
179 """GET /admin/settings: All items in the collection"""
182 """GET /admin/settings: All items in the collection"""
180 # url('admin_settings_vcs')
183 # url('admin_settings_vcs')
181 c.active = 'vcs'
184 c.active = 'vcs'
182 model = VcsSettingsModel()
185 model = VcsSettingsModel()
183 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
186 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
184 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
187 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
185
188
186 return htmlfill.render(
189 return htmlfill.render(
187 render('admin/settings/settings.html'),
190 render('admin/settings/settings.html'),
188 defaults=self._form_defaults(),
191 defaults=self._form_defaults(),
189 encoding="UTF-8",
192 encoding="UTF-8",
190 force_defaults=False)
193 force_defaults=False)
191
194
192 @HasPermissionAllDecorator('hg.admin')
195 @HasPermissionAllDecorator('hg.admin')
193 @auth.CSRFRequired()
196 @auth.CSRFRequired()
194 def settings_mapping_update(self):
197 def settings_mapping_update(self):
195 """POST /admin/settings/mapping: All items in the collection"""
198 """POST /admin/settings/mapping: All items in the collection"""
196 # url('admin_settings_mapping')
199 # url('admin_settings_mapping')
197 c.active = 'mapping'
200 c.active = 'mapping'
198 rm_obsolete = request.POST.get('destroy', False)
201 rm_obsolete = request.POST.get('destroy', False)
199 invalidate_cache = request.POST.get('invalidate', False)
202 invalidate_cache = request.POST.get('invalidate', False)
200 log.debug(
203 log.debug(
201 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
204 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
202
205
203 if invalidate_cache:
206 if invalidate_cache:
204 log.debug('invalidating all repositories cache')
207 log.debug('invalidating all repositories cache')
205 for repo in Repository.get_all():
208 for repo in Repository.get_all():
206 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
209 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
207
210
208 filesystem_repos = ScmModel().repo_scan()
211 filesystem_repos = ScmModel().repo_scan()
209 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
212 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
210 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
213 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
211 h.flash(_('Repositories successfully '
214 h.flash(_('Repositories successfully '
212 'rescanned added: %s ; removed: %s') %
215 'rescanned added: %s ; removed: %s') %
213 (_repr(added), _repr(removed)),
216 (_repr(added), _repr(removed)),
214 category='success')
217 category='success')
215 return redirect(url('admin_settings_mapping'))
218 return redirect(url('admin_settings_mapping'))
216
219
217 @HasPermissionAllDecorator('hg.admin')
220 @HasPermissionAllDecorator('hg.admin')
218 def settings_mapping(self):
221 def settings_mapping(self):
219 """GET /admin/settings/mapping: All items in the collection"""
222 """GET /admin/settings/mapping: All items in the collection"""
220 # url('admin_settings_mapping')
223 # url('admin_settings_mapping')
221 c.active = 'mapping'
224 c.active = 'mapping'
222
225
223 return htmlfill.render(
226 return htmlfill.render(
224 render('admin/settings/settings.html'),
227 render('admin/settings/settings.html'),
225 defaults=self._form_defaults(),
228 defaults=self._form_defaults(),
226 encoding="UTF-8",
229 encoding="UTF-8",
227 force_defaults=False)
230 force_defaults=False)
228
231
229 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
230 @auth.CSRFRequired()
233 @auth.CSRFRequired()
231 def settings_global_update(self):
234 def settings_global_update(self):
232 """POST /admin/settings/global: All items in the collection"""
235 """POST /admin/settings/global: All items in the collection"""
233 # url('admin_settings_global')
236 # url('admin_settings_global')
234 c.active = 'global'
237 c.active = 'global'
235 application_form = ApplicationSettingsForm()()
238 application_form = ApplicationSettingsForm()()
236 try:
239 try:
237 form_result = application_form.to_python(dict(request.POST))
240 form_result = application_form.to_python(dict(request.POST))
238 except formencode.Invalid as errors:
241 except formencode.Invalid as errors:
239 return htmlfill.render(
242 return htmlfill.render(
240 render('admin/settings/settings.html'),
243 render('admin/settings/settings.html'),
241 defaults=errors.value,
244 defaults=errors.value,
242 errors=errors.error_dict or {},
245 errors=errors.error_dict or {},
243 prefix_error=False,
246 prefix_error=False,
244 encoding="UTF-8",
247 encoding="UTF-8",
245 force_defaults=False)
248 force_defaults=False)
246
249
247 try:
250 try:
248 settings = [
251 settings = [
249 ('title', 'rhodecode_title'),
252 ('title', 'rhodecode_title'),
250 ('realm', 'rhodecode_realm'),
253 ('realm', 'rhodecode_realm'),
251 ('pre_code', 'rhodecode_pre_code'),
254 ('pre_code', 'rhodecode_pre_code'),
252 ('post_code', 'rhodecode_post_code'),
255 ('post_code', 'rhodecode_post_code'),
253 ('captcha_public_key', 'rhodecode_captcha_public_key'),
256 ('captcha_public_key', 'rhodecode_captcha_public_key'),
254 ('captcha_private_key', 'rhodecode_captcha_private_key'),
257 ('captcha_private_key', 'rhodecode_captcha_private_key'),
255 ]
258 ]
256 for setting, form_key in settings:
259 for setting, form_key in settings:
257 sett = SettingsModel().create_or_update_setting(
260 sett = SettingsModel().create_or_update_setting(
258 setting, form_result[form_key])
261 setting, form_result[form_key])
259 Session().add(sett)
262 Session().add(sett)
260
263
261 Session().commit()
264 Session().commit()
262 SettingsModel().invalidate_settings_cache()
265 SettingsModel().invalidate_settings_cache()
263 h.flash(_('Updated application settings'), category='success')
266 h.flash(_('Updated application settings'), category='success')
264 except Exception:
267 except Exception:
265 log.exception("Exception while updating application settings")
268 log.exception("Exception while updating application settings")
266 h.flash(
269 h.flash(
267 _('Error occurred during updating application settings'),
270 _('Error occurred during updating application settings'),
268 category='error')
271 category='error')
269
272
270 return redirect(url('admin_settings_global'))
273 return redirect(url('admin_settings_global'))
271
274
272 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
273 def settings_global(self):
276 def settings_global(self):
274 """GET /admin/settings/global: All items in the collection"""
277 """GET /admin/settings/global: All items in the collection"""
275 # url('admin_settings_global')
278 # url('admin_settings_global')
276 c.active = 'global'
279 c.active = 'global'
277
280
278 return htmlfill.render(
281 return htmlfill.render(
279 render('admin/settings/settings.html'),
282 render('admin/settings/settings.html'),
280 defaults=self._form_defaults(),
283 defaults=self._form_defaults(),
281 encoding="UTF-8",
284 encoding="UTF-8",
282 force_defaults=False)
285 force_defaults=False)
283
286
284 @HasPermissionAllDecorator('hg.admin')
287 @HasPermissionAllDecorator('hg.admin')
285 @auth.CSRFRequired()
288 @auth.CSRFRequired()
286 def settings_visual_update(self):
289 def settings_visual_update(self):
287 """POST /admin/settings/visual: All items in the collection"""
290 """POST /admin/settings/visual: All items in the collection"""
288 # url('admin_settings_visual')
291 # url('admin_settings_visual')
289 c.active = 'visual'
292 c.active = 'visual'
290 application_form = ApplicationVisualisationForm()()
293 application_form = ApplicationVisualisationForm()()
291 try:
294 try:
292 form_result = application_form.to_python(dict(request.POST))
295 form_result = application_form.to_python(dict(request.POST))
293 except formencode.Invalid as errors:
296 except formencode.Invalid as errors:
294 return htmlfill.render(
297 return htmlfill.render(
295 render('admin/settings/settings.html'),
298 render('admin/settings/settings.html'),
296 defaults=errors.value,
299 defaults=errors.value,
297 errors=errors.error_dict or {},
300 errors=errors.error_dict or {},
298 prefix_error=False,
301 prefix_error=False,
299 encoding="UTF-8",
302 encoding="UTF-8",
300 force_defaults=False
303 force_defaults=False
301 )
304 )
302
305
303 try:
306 try:
304 settings = [
307 settings = [
305 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
308 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
306 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
309 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
307 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
310 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
308 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
311 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
309 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
312 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
310 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
313 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
311 ('show_version', 'rhodecode_show_version', 'bool'),
314 ('show_version', 'rhodecode_show_version', 'bool'),
312 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
315 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
313 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
316 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
314 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
317 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
315 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
318 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
316 ('support_url', 'rhodecode_support_url', 'unicode'),
319 ('support_url', 'rhodecode_support_url', 'unicode'),
317 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
320 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
318 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
321 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
319 ]
322 ]
320 for setting, form_key, type_ in settings:
323 for setting, form_key, type_ in settings:
321 sett = SettingsModel().create_or_update_setting(
324 sett = SettingsModel().create_or_update_setting(
322 setting, form_result[form_key], type_)
325 setting, form_result[form_key], type_)
323 Session().add(sett)
326 Session().add(sett)
324
327
325 Session().commit()
328 Session().commit()
326 SettingsModel().invalidate_settings_cache()
329 SettingsModel().invalidate_settings_cache()
327 h.flash(_('Updated visualisation settings'), category='success')
330 h.flash(_('Updated visualisation settings'), category='success')
328 except Exception:
331 except Exception:
329 log.exception("Exception updating visualization settings")
332 log.exception("Exception updating visualization settings")
330 h.flash(_('Error occurred during updating '
333 h.flash(_('Error occurred during updating '
331 'visualisation settings'),
334 'visualisation settings'),
332 category='error')
335 category='error')
333
336
334 return redirect(url('admin_settings_visual'))
337 return redirect(url('admin_settings_visual'))
335
338
336 @HasPermissionAllDecorator('hg.admin')
339 @HasPermissionAllDecorator('hg.admin')
337 def settings_visual(self):
340 def settings_visual(self):
338 """GET /admin/settings/visual: All items in the collection"""
341 """GET /admin/settings/visual: All items in the collection"""
339 # url('admin_settings_visual')
342 # url('admin_settings_visual')
340 c.active = 'visual'
343 c.active = 'visual'
341
344
342 return htmlfill.render(
345 return htmlfill.render(
343 render('admin/settings/settings.html'),
346 render('admin/settings/settings.html'),
344 defaults=self._form_defaults(),
347 defaults=self._form_defaults(),
345 encoding="UTF-8",
348 encoding="UTF-8",
346 force_defaults=False)
349 force_defaults=False)
347
350
348 @HasPermissionAllDecorator('hg.admin')
351 @HasPermissionAllDecorator('hg.admin')
349 @auth.CSRFRequired()
352 @auth.CSRFRequired()
350 def settings_issuetracker_test(self):
353 def settings_issuetracker_test(self):
351 if request.is_xhr:
354 if request.is_xhr:
352 return h.urlify_commit_message(
355 return h.urlify_commit_message(
353 request.POST.get('test_text', ''),
356 request.POST.get('test_text', ''),
354 'repo_group/test_repo1')
357 'repo_group/test_repo1')
355 else:
358 else:
356 raise HTTPBadRequest()
359 raise HTTPBadRequest()
357
360
358 @HasPermissionAllDecorator('hg.admin')
361 @HasPermissionAllDecorator('hg.admin')
359 @auth.CSRFRequired()
362 @auth.CSRFRequired()
360 def settings_issuetracker_delete(self):
363 def settings_issuetracker_delete(self):
361 uid = request.POST.get('uid')
364 uid = request.POST.get('uid')
362 IssueTrackerSettingsModel().delete_entries(uid)
365 IssueTrackerSettingsModel().delete_entries(uid)
363 h.flash(_('Removed issue tracker entry'), category='success')
366 h.flash(_('Removed issue tracker entry'), category='success')
364 return redirect(url('admin_settings_issuetracker'))
367 return redirect(url('admin_settings_issuetracker'))
365
368
366 @HasPermissionAllDecorator('hg.admin')
369 @HasPermissionAllDecorator('hg.admin')
367 def settings_issuetracker(self):
370 def settings_issuetracker(self):
368 """GET /admin/settings/issue-tracker: All items in the collection"""
371 """GET /admin/settings/issue-tracker: All items in the collection"""
369 # url('admin_settings_issuetracker')
372 # url('admin_settings_issuetracker')
370 c.active = 'issuetracker'
373 c.active = 'issuetracker'
371 defaults = SettingsModel().get_all_settings()
374 defaults = SettingsModel().get_all_settings()
372
375
373 entry_key = 'rhodecode_issuetracker_pat_'
376 entry_key = 'rhodecode_issuetracker_pat_'
374
377
375 c.issuetracker_entries = {}
378 c.issuetracker_entries = {}
376 for k, v in defaults.items():
379 for k, v in defaults.items():
377 if k.startswith(entry_key):
380 if k.startswith(entry_key):
378 uid = k[len(entry_key):]
381 uid = k[len(entry_key):]
379 c.issuetracker_entries[uid] = None
382 c.issuetracker_entries[uid] = None
380
383
381 for uid in c.issuetracker_entries:
384 for uid in c.issuetracker_entries:
382 c.issuetracker_entries[uid] = AttributeDict({
385 c.issuetracker_entries[uid] = AttributeDict({
383 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
386 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
384 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
387 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
385 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
388 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
386 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
389 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
387 })
390 })
388
391
389 return render('admin/settings/settings.html')
392 return render('admin/settings/settings.html')
390
393
391 @HasPermissionAllDecorator('hg.admin')
394 @HasPermissionAllDecorator('hg.admin')
392 @auth.CSRFRequired()
395 @auth.CSRFRequired()
393 def settings_issuetracker_save(self):
396 def settings_issuetracker_save(self):
394 settings_model = IssueTrackerSettingsModel()
397 settings_model = IssueTrackerSettingsModel()
395
398
396 form = IssueTrackerPatternsForm()().to_python(request.POST)
399 form = IssueTrackerPatternsForm()().to_python(request.POST)
397 for uid in form['delete_patterns']:
400 for uid in form['delete_patterns']:
398 settings_model.delete_entries(uid)
401 settings_model.delete_entries(uid)
399
402
400 for pattern in form['patterns']:
403 for pattern in form['patterns']:
401 for setting, value, type_ in pattern:
404 for setting, value, type_ in pattern:
402 sett = settings_model.create_or_update_setting(
405 sett = settings_model.create_or_update_setting(
403 setting, value, type_)
406 setting, value, type_)
404 Session().add(sett)
407 Session().add(sett)
405
408
406 Session().commit()
409 Session().commit()
407
410
408 SettingsModel().invalidate_settings_cache()
411 SettingsModel().invalidate_settings_cache()
409 h.flash(_('Updated issue tracker entries'), category='success')
412 h.flash(_('Updated issue tracker entries'), category='success')
410 return redirect(url('admin_settings_issuetracker'))
413 return redirect(url('admin_settings_issuetracker'))
411
414
412 @HasPermissionAllDecorator('hg.admin')
415 @HasPermissionAllDecorator('hg.admin')
413 @auth.CSRFRequired()
416 @auth.CSRFRequired()
414 def settings_email_update(self):
417 def settings_email_update(self):
415 """POST /admin/settings/email: All items in the collection"""
418 """POST /admin/settings/email: All items in the collection"""
416 # url('admin_settings_email')
419 # url('admin_settings_email')
417 c.active = 'email'
420 c.active = 'email'
418
421
419 test_email = request.POST.get('test_email')
422 test_email = request.POST.get('test_email')
420
423
421 if not test_email:
424 if not test_email:
422 h.flash(_('Please enter email address'), category='error')
425 h.flash(_('Please enter email address'), category='error')
423 return redirect(url('admin_settings_email'))
426 return redirect(url('admin_settings_email'))
424
427
425 email_kwargs = {
428 email_kwargs = {
426 'date': datetime.datetime.now(),
429 'date': datetime.datetime.now(),
427 'user': c.rhodecode_user,
430 'user': c.rhodecode_user,
428 'rhodecode_version': c.rhodecode_version
431 'rhodecode_version': c.rhodecode_version
429 }
432 }
430
433
431 (subject, headers, email_body,
434 (subject, headers, email_body,
432 email_body_plaintext) = EmailNotificationModel().render_email(
435 email_body_plaintext) = EmailNotificationModel().render_email(
433 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
436 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
434
437
435 recipients = [test_email] if test_email else None
438 recipients = [test_email] if test_email else None
436
439
437 run_task(tasks.send_email, recipients, subject,
440 run_task(tasks.send_email, recipients, subject,
438 email_body_plaintext, email_body)
441 email_body_plaintext, email_body)
439
442
440 h.flash(_('Send email task created'), category='success')
443 h.flash(_('Send email task created'), category='success')
441 return redirect(url('admin_settings_email'))
444 return redirect(url('admin_settings_email'))
442
445
443 @HasPermissionAllDecorator('hg.admin')
446 @HasPermissionAllDecorator('hg.admin')
444 def settings_email(self):
447 def settings_email(self):
445 """GET /admin/settings/email: All items in the collection"""
448 """GET /admin/settings/email: All items in the collection"""
446 # url('admin_settings_email')
449 # url('admin_settings_email')
447 c.active = 'email'
450 c.active = 'email'
448 c.rhodecode_ini = rhodecode.CONFIG
451 c.rhodecode_ini = rhodecode.CONFIG
449
452
450 return htmlfill.render(
453 return htmlfill.render(
451 render('admin/settings/settings.html'),
454 render('admin/settings/settings.html'),
452 defaults=self._form_defaults(),
455 defaults=self._form_defaults(),
453 encoding="UTF-8",
456 encoding="UTF-8",
454 force_defaults=False)
457 force_defaults=False)
455
458
456 @HasPermissionAllDecorator('hg.admin')
459 @HasPermissionAllDecorator('hg.admin')
457 @auth.CSRFRequired()
460 @auth.CSRFRequired()
458 def settings_hooks_update(self):
461 def settings_hooks_update(self):
459 """POST or DELETE /admin/settings/hooks: All items in the collection"""
462 """POST or DELETE /admin/settings/hooks: All items in the collection"""
460 # url('admin_settings_hooks')
463 # url('admin_settings_hooks')
461 c.active = 'hooks'
464 c.active = 'hooks'
462 if c.visual.allow_custom_hooks_settings:
465 if c.visual.allow_custom_hooks_settings:
463 ui_key = request.POST.get('new_hook_ui_key')
466 ui_key = request.POST.get('new_hook_ui_key')
464 ui_value = request.POST.get('new_hook_ui_value')
467 ui_value = request.POST.get('new_hook_ui_value')
465
468
466 hook_id = request.POST.get('hook_id')
469 hook_id = request.POST.get('hook_id')
467 new_hook = False
470 new_hook = False
468
471
469 model = SettingsModel()
472 model = SettingsModel()
470 try:
473 try:
471 if ui_value and ui_key:
474 if ui_value and ui_key:
472 model.create_or_update_hook(ui_key, ui_value)
475 model.create_or_update_hook(ui_key, ui_value)
473 h.flash(_('Added new hook'), category='success')
476 h.flash(_('Added new hook'), category='success')
474 new_hook = True
477 new_hook = True
475 elif hook_id:
478 elif hook_id:
476 RhodeCodeUi.delete(hook_id)
479 RhodeCodeUi.delete(hook_id)
477 Session().commit()
480 Session().commit()
478
481
479 # check for edits
482 # check for edits
480 update = False
483 update = False
481 _d = request.POST.dict_of_lists()
484 _d = request.POST.dict_of_lists()
482 for k, v in zip(_d.get('hook_ui_key', []),
485 for k, v in zip(_d.get('hook_ui_key', []),
483 _d.get('hook_ui_value_new', [])):
486 _d.get('hook_ui_value_new', [])):
484 model.create_or_update_hook(k, v)
487 model.create_or_update_hook(k, v)
485 update = True
488 update = True
486
489
487 if update and not new_hook:
490 if update and not new_hook:
488 h.flash(_('Updated hooks'), category='success')
491 h.flash(_('Updated hooks'), category='success')
489 Session().commit()
492 Session().commit()
490 except Exception:
493 except Exception:
491 log.exception("Exception during hook creation")
494 log.exception("Exception during hook creation")
492 h.flash(_('Error occurred during hook creation'),
495 h.flash(_('Error occurred during hook creation'),
493 category='error')
496 category='error')
494
497
495 return redirect(url('admin_settings_hooks'))
498 return redirect(url('admin_settings_hooks'))
496
499
497 @HasPermissionAllDecorator('hg.admin')
500 @HasPermissionAllDecorator('hg.admin')
498 def settings_hooks(self):
501 def settings_hooks(self):
499 """GET /admin/settings/hooks: All items in the collection"""
502 """GET /admin/settings/hooks: All items in the collection"""
500 # url('admin_settings_hooks')
503 # url('admin_settings_hooks')
501 c.active = 'hooks'
504 c.active = 'hooks'
502
505
503 model = SettingsModel()
506 model = SettingsModel()
504 c.hooks = model.get_builtin_hooks()
507 c.hooks = model.get_builtin_hooks()
505 c.custom_hooks = model.get_custom_hooks()
508 c.custom_hooks = model.get_custom_hooks()
506
509
507 return htmlfill.render(
510 return htmlfill.render(
508 render('admin/settings/settings.html'),
511 render('admin/settings/settings.html'),
509 defaults=self._form_defaults(),
512 defaults=self._form_defaults(),
510 encoding="UTF-8",
513 encoding="UTF-8",
511 force_defaults=False)
514 force_defaults=False)
512
515
513 @HasPermissionAllDecorator('hg.admin')
516 @HasPermissionAllDecorator('hg.admin')
514 def settings_search(self):
517 def settings_search(self):
515 """GET /admin/settings/search: All items in the collection"""
518 """GET /admin/settings/search: All items in the collection"""
516 # url('admin_settings_search')
519 # url('admin_settings_search')
517 c.active = 'search'
520 c.active = 'search'
518
521
519 from rhodecode.lib.index import searcher_from_config
522 from rhodecode.lib.index import searcher_from_config
520 searcher = searcher_from_config(config)
523 searcher = searcher_from_config(config)
521 c.statistics = searcher.statistics()
524 c.statistics = searcher.statistics()
522
525
523 return render('admin/settings/settings.html')
526 return render('admin/settings/settings.html')
524
527
525 @HasPermissionAllDecorator('hg.admin')
528 @HasPermissionAllDecorator('hg.admin')
526 def settings_system(self):
529 def settings_system(self):
527 """GET /admin/settings/system: All items in the collection"""
530 """GET /admin/settings/system: All items in the collection"""
528 # url('admin_settings_system')
531 # url('admin_settings_system')
529 snapshot = str2bool(request.GET.get('snapshot'))
532 snapshot = str2bool(request.GET.get('snapshot'))
530 c.active = 'system'
533 c.active = 'system'
531
534
532 defaults = self._form_defaults()
535 defaults = self._form_defaults()
533 c.rhodecode_ini = rhodecode.CONFIG
536 c.rhodecode_ini = rhodecode.CONFIG
534 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
537 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
535 server_info = ScmModel().get_server_info(request.environ)
538 server_info = ScmModel().get_server_info(request.environ)
536 for key, val in server_info.iteritems():
539 for key, val in server_info.iteritems():
537 setattr(c, key, val)
540 setattr(c, key, val)
538
541
539 if c.disk['percent'] > 90:
542 if c.disk['percent'] > 90:
540 h.flash(h.literal(_(
543 h.flash(h.literal(_(
541 'Critical: your disk space is very low <b>%s%%</b> used' %
544 'Critical: your disk space is very low <b>%s%%</b> used' %
542 c.disk['percent'])), 'error')
545 c.disk['percent'])), 'error')
543 elif c.disk['percent'] > 70:
546 elif c.disk['percent'] > 70:
544 h.flash(h.literal(_(
547 h.flash(h.literal(_(
545 'Warning: your disk space is running low <b>%s%%</b> used' %
548 'Warning: your disk space is running low <b>%s%%</b> used' %
546 c.disk['percent'])), 'warning')
549 c.disk['percent'])), 'warning')
547
550
548 try:
551 try:
549 c.uptime_age = h._age(
552 c.uptime_age = h._age(
550 h.time_to_datetime(c.boot_time), False, show_suffix=False)
553 h.time_to_datetime(c.boot_time), False, show_suffix=False)
551 except TypeError:
554 except TypeError:
552 c.uptime_age = c.boot_time
555 c.uptime_age = c.boot_time
553
556
554 try:
557 try:
555 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
558 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
556 h.format_byte_size_binary(c.memory['used']),
559 h.format_byte_size_binary(c.memory['used']),
557 h.format_byte_size_binary(c.memory['total']),
560 h.format_byte_size_binary(c.memory['total']),
558 c.memory['percent2'],
561 c.memory['percent2'],
559 c.memory['percent'],
562 c.memory['percent'],
560 ' %s' % c.memory['error'] if 'error' in c.memory else '')
563 ' %s' % c.memory['error'] if 'error' in c.memory else '')
561 except TypeError:
564 except TypeError:
562 c.system_memory = 'NOT AVAILABLE'
565 c.system_memory = 'NOT AVAILABLE'
563
566
564 rhodecode_ini_safe = rhodecode.CONFIG.copy()
567 rhodecode_ini_safe = rhodecode.CONFIG.copy()
565 blacklist = [
568 blacklist = [
566 'rhodecode_license_key',
569 'rhodecode_license_key',
567 'routes.map',
570 'routes.map',
568 'pylons.h',
571 'pylons.h',
569 'pylons.app_globals',
572 'pylons.app_globals',
570 'pylons.environ_config',
573 'pylons.environ_config',
571 'sqlalchemy.db1.url',
574 'sqlalchemy.db1.url',
572 ('app_conf', 'sqlalchemy.db1.url')
575 ('app_conf', 'sqlalchemy.db1.url')
573 ]
576 ]
574 for k in blacklist:
577 for k in blacklist:
575 if isinstance(k, tuple):
578 if isinstance(k, tuple):
576 section, key = k
579 section, key = k
577 if section in rhodecode_ini_safe:
580 if section in rhodecode_ini_safe:
578 rhodecode_ini_safe[section].pop(key, None)
581 rhodecode_ini_safe[section].pop(key, None)
579 else:
582 else:
580 rhodecode_ini_safe.pop(k, None)
583 rhodecode_ini_safe.pop(k, None)
581
584
582 c.rhodecode_ini_safe = rhodecode_ini_safe
585 c.rhodecode_ini_safe = rhodecode_ini_safe
583
586
584 # TODO: marcink, figure out how to allow only selected users to do this
587 # TODO: marcink, figure out how to allow only selected users to do this
585 c.allowed_to_snapshot = False
588 c.allowed_to_snapshot = False
586
589
587 if snapshot:
590 if snapshot:
588 if c.allowed_to_snapshot:
591 if c.allowed_to_snapshot:
589 return render('admin/settings/settings_system_snapshot.html')
592 return render('admin/settings/settings_system_snapshot.html')
590 else:
593 else:
591 h.flash('You are not allowed to do this', category='warning')
594 h.flash('You are not allowed to do this', category='warning')
592
595
593 return htmlfill.render(
596 return htmlfill.render(
594 render('admin/settings/settings.html'),
597 render('admin/settings/settings.html'),
595 defaults=defaults,
598 defaults=defaults,
596 encoding="UTF-8",
599 encoding="UTF-8",
597 force_defaults=False)
600 force_defaults=False)
598
601
599 @staticmethod
602 @staticmethod
600 def get_update_data(update_url):
603 def get_update_data(update_url):
601 """Return the JSON update data."""
604 """Return the JSON update data."""
602 ver = rhodecode.__version__
605 ver = rhodecode.__version__
603 log.debug('Checking for upgrade on `%s` server', update_url)
606 log.debug('Checking for upgrade on `%s` server', update_url)
604 opener = urllib2.build_opener()
607 opener = urllib2.build_opener()
605 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
608 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
606 response = opener.open(update_url)
609 response = opener.open(update_url)
607 response_data = response.read()
610 response_data = response.read()
608 data = json.loads(response_data)
611 data = json.loads(response_data)
609
612
610 return data
613 return data
611
614
612 @HasPermissionAllDecorator('hg.admin')
615 @HasPermissionAllDecorator('hg.admin')
613 def settings_system_update(self):
616 def settings_system_update(self):
614 """GET /admin/settings/system/updates: All items in the collection"""
617 """GET /admin/settings/system/updates: All items in the collection"""
615 # url('admin_settings_system_update')
618 # url('admin_settings_system_update')
616 defaults = self._form_defaults()
619 defaults = self._form_defaults()
617 update_url = defaults.get('rhodecode_update_url', '')
620 update_url = defaults.get('rhodecode_update_url', '')
618
621
619 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
622 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
620 try:
623 try:
621 data = self.get_update_data(update_url)
624 data = self.get_update_data(update_url)
622 except urllib2.URLError as e:
625 except urllib2.URLError as e:
623 log.exception("Exception contacting upgrade server")
626 log.exception("Exception contacting upgrade server")
624 return _err('Failed to contact upgrade server: %r' % e)
627 return _err('Failed to contact upgrade server: %r' % e)
625 except ValueError as e:
628 except ValueError as e:
626 log.exception("Bad data sent from update server")
629 log.exception("Bad data sent from update server")
627 return _err('Bad data sent from update server')
630 return _err('Bad data sent from update server')
628
631
629 latest = data['versions'][0]
632 latest = data['versions'][0]
630
633
631 c.update_url = update_url
634 c.update_url = update_url
632 c.latest_data = latest
635 c.latest_data = latest
633 c.latest_ver = latest['version']
636 c.latest_ver = latest['version']
634 c.cur_ver = rhodecode.__version__
637 c.cur_ver = rhodecode.__version__
635 c.should_upgrade = False
638 c.should_upgrade = False
636
639
637 if (packaging.version.Version(c.latest_ver) >
640 if (packaging.version.Version(c.latest_ver) >
638 packaging.version.Version(c.cur_ver)):
641 packaging.version.Version(c.cur_ver)):
639 c.should_upgrade = True
642 c.should_upgrade = True
640 c.important_notices = latest['general']
643 c.important_notices = latest['general']
641
644
642 return render('admin/settings/settings_system_update.html')
645 return render('admin/settings/settings_system_update.html')
643
646
644 @HasPermissionAllDecorator('hg.admin')
647 @HasPermissionAllDecorator('hg.admin')
645 def settings_supervisor(self):
648 def settings_supervisor(self):
646 c.rhodecode_ini = rhodecode.CONFIG
649 c.rhodecode_ini = rhodecode.CONFIG
647 c.active = 'supervisor'
650 c.active = 'supervisor'
648
651
649 c.supervisor_procs = OrderedDict([
652 c.supervisor_procs = OrderedDict([
650 (SUPERVISOR_MASTER, {}),
653 (SUPERVISOR_MASTER, {}),
651 ])
654 ])
652
655
653 c.log_size = 10240
656 c.log_size = 10240
654 supervisor = SupervisorModel()
657 supervisor = SupervisorModel()
655
658
656 _connection = supervisor.get_connection(
659 _connection = supervisor.get_connection(
657 c.rhodecode_ini.get('supervisor.uri'))
660 c.rhodecode_ini.get('supervisor.uri'))
658 c.connection_error = None
661 c.connection_error = None
659 try:
662 try:
660 _connection.supervisor.getAllProcessInfo()
663 _connection.supervisor.getAllProcessInfo()
661 except Exception as e:
664 except Exception as e:
662 c.connection_error = str(e)
665 c.connection_error = str(e)
663 log.exception("Exception reading supervisor data")
666 log.exception("Exception reading supervisor data")
664 return render('admin/settings/settings.html')
667 return render('admin/settings/settings.html')
665
668
666 groupid = c.rhodecode_ini.get('supervisor.group_id')
669 groupid = c.rhodecode_ini.get('supervisor.group_id')
667
670
668 # feed our group processes to the main
671 # feed our group processes to the main
669 for proc in supervisor.get_group_processes(_connection, groupid):
672 for proc in supervisor.get_group_processes(_connection, groupid):
670 c.supervisor_procs[proc['name']] = {}
673 c.supervisor_procs[proc['name']] = {}
671
674
672 for k in c.supervisor_procs.keys():
675 for k in c.supervisor_procs.keys():
673 try:
676 try:
674 # master process info
677 # master process info
675 if k == SUPERVISOR_MASTER:
678 if k == SUPERVISOR_MASTER:
676 _data = supervisor.get_master_state(_connection)
679 _data = supervisor.get_master_state(_connection)
677 _data['name'] = 'supervisor master'
680 _data['name'] = 'supervisor master'
678 _data['description'] = 'pid %s, id: %s, ver: %s' % (
681 _data['description'] = 'pid %s, id: %s, ver: %s' % (
679 _data['pid'], _data['id'], _data['ver'])
682 _data['pid'], _data['id'], _data['ver'])
680 c.supervisor_procs[k] = _data
683 c.supervisor_procs[k] = _data
681 else:
684 else:
682 procid = groupid + ":" + k
685 procid = groupid + ":" + k
683 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
686 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
684 except Exception as e:
687 except Exception as e:
685 log.exception("Exception reading supervisor data")
688 log.exception("Exception reading supervisor data")
686 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
689 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
687
690
688 return render('admin/settings/settings.html')
691 return render('admin/settings/settings.html')
689
692
690 @HasPermissionAllDecorator('hg.admin')
693 @HasPermissionAllDecorator('hg.admin')
691 def settings_supervisor_log(self, procid):
694 def settings_supervisor_log(self, procid):
692 import rhodecode
695 import rhodecode
693 c.rhodecode_ini = rhodecode.CONFIG
696 c.rhodecode_ini = rhodecode.CONFIG
694 c.active = 'supervisor_tail'
697 c.active = 'supervisor_tail'
695
698
696 supervisor = SupervisorModel()
699 supervisor = SupervisorModel()
697 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
700 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
698 groupid = c.rhodecode_ini.get('supervisor.group_id')
701 groupid = c.rhodecode_ini.get('supervisor.group_id')
699 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
702 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
700
703
701 c.log_size = 10240
704 c.log_size = 10240
702 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
705 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
703 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
706 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
704
707
705 return render('admin/settings/settings.html')
708 return render('admin/settings/settings.html')
706
709
707 @HasPermissionAllDecorator('hg.admin')
710 @HasPermissionAllDecorator('hg.admin')
708 @auth.CSRFRequired()
711 @auth.CSRFRequired()
709 def settings_labs_update(self):
712 def settings_labs_update(self):
710 """POST /admin/settings/labs: All items in the collection"""
713 """POST /admin/settings/labs: All items in the collection"""
711 # url('admin_settings/labs', method={'POST'})
714 # url('admin_settings/labs', method={'POST'})
712 c.active = 'labs'
715 c.active = 'labs'
713
716
714 application_form = LabsSettingsForm()()
717 application_form = LabsSettingsForm()()
715 try:
718 try:
716 form_result = application_form.to_python(dict(request.POST))
719 form_result = application_form.to_python(dict(request.POST))
717 except formencode.Invalid as errors:
720 except formencode.Invalid as errors:
718 h.flash(
721 h.flash(
719 _('Some form inputs contain invalid data.'),
722 _('Some form inputs contain invalid data.'),
720 category='error')
723 category='error')
721 return htmlfill.render(
724 return htmlfill.render(
722 render('admin/settings/settings.html'),
725 render('admin/settings/settings.html'),
723 defaults=errors.value,
726 defaults=errors.value,
724 errors=errors.error_dict or {},
727 errors=errors.error_dict or {},
725 prefix_error=False,
728 prefix_error=False,
726 encoding='UTF-8',
729 encoding='UTF-8',
727 force_defaults=False
730 force_defaults=False
728 )
731 )
729
732
730 try:
733 try:
731 session = Session()
734 session = Session()
732 for setting in _LAB_SETTINGS:
735 for setting in _LAB_SETTINGS:
733 setting_name = setting.key[len('rhodecode_'):]
736 setting_name = setting.key[len('rhodecode_'):]
734 sett = SettingsModel().create_or_update_setting(
737 sett = SettingsModel().create_or_update_setting(
735 setting_name, form_result[setting.key], setting.type)
738 setting_name, form_result[setting.key], setting.type)
736 session.add(sett)
739 session.add(sett)
737
740
738 except Exception:
741 except Exception:
739 log.exception('Exception while updating lab settings')
742 log.exception('Exception while updating lab settings')
740 h.flash(_('Error occurred during updating labs settings'),
743 h.flash(_('Error occurred during updating labs settings'),
741 category='error')
744 category='error')
742 else:
745 else:
743 Session().commit()
746 Session().commit()
744 SettingsModel().invalidate_settings_cache()
747 SettingsModel().invalidate_settings_cache()
745 h.flash(_('Updated Labs settings'), category='success')
748 h.flash(_('Updated Labs settings'), category='success')
746 return redirect(url('admin_settings_labs'))
749 return redirect(url('admin_settings_labs'))
747
750
748 return htmlfill.render(
751 return htmlfill.render(
749 render('admin/settings/settings.html'),
752 render('admin/settings/settings.html'),
750 defaults=self._form_defaults(),
753 defaults=self._form_defaults(),
751 encoding='UTF-8',
754 encoding='UTF-8',
752 force_defaults=False)
755 force_defaults=False)
753
756
754 @HasPermissionAllDecorator('hg.admin')
757 @HasPermissionAllDecorator('hg.admin')
755 def settings_labs(self):
758 def settings_labs(self):
756 """GET /admin/settings/labs: All items in the collection"""
759 """GET /admin/settings/labs: All items in the collection"""
757 # url('admin_settings_labs')
760 # url('admin_settings_labs')
758 if not c.labs_active:
761 if not c.labs_active:
759 redirect(url('admin_settings'))
762 redirect(url('admin_settings'))
760
763
761 c.active = 'labs'
764 c.active = 'labs'
762 c.lab_settings = _LAB_SETTINGS
765 c.lab_settings = _LAB_SETTINGS
763
766
764 return htmlfill.render(
767 return htmlfill.render(
765 render('admin/settings/settings.html'),
768 render('admin/settings/settings.html'),
766 defaults=self._form_defaults(),
769 defaults=self._form_defaults(),
767 encoding='UTF-8',
770 encoding='UTF-8',
768 force_defaults=False)
771 force_defaults=False)
769
772
770 def _form_defaults(self):
773 def _form_defaults(self):
771 defaults = SettingsModel().get_all_settings()
774 defaults = SettingsModel().get_all_settings()
772 defaults.update(self._get_hg_ui_settings())
775 defaults.update(self._get_hg_ui_settings())
773 defaults.update({
776 defaults.update({
774 'new_svn_branch': '',
777 'new_svn_branch': '',
775 'new_svn_tag': '',
778 'new_svn_tag': '',
776 })
779 })
777 return defaults
780 return defaults
778
781
779
782
780 # :param key: name of the setting including the 'rhodecode_' prefix
783 # :param key: name of the setting including the 'rhodecode_' prefix
781 # :param type: the RhodeCodeSetting type to use.
784 # :param type: the RhodeCodeSetting type to use.
782 # :param group: the i18ned group in which we should dispaly this setting
785 # :param group: the i18ned group in which we should dispaly this setting
783 # :param label: the i18ned label we should display for this setting
786 # :param label: the i18ned label we should display for this setting
784 # :param help: the i18ned help we should dispaly for this setting
787 # :param help: the i18ned help we should dispaly for this setting
785 LabSetting = collections.namedtuple(
788 LabSetting = collections.namedtuple(
786 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
789 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
787
790
788
791
789 # This list has to be kept in sync with the form
792 # This list has to be kept in sync with the form
790 # rhodecode.model.forms.LabsSettingsForm.
793 # rhodecode.model.forms.LabsSettingsForm.
791 _LAB_SETTINGS = [
794 _LAB_SETTINGS = [
792
795
793 ]
796 ]
@@ -1,587 +1,587 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31
31
32 from paste.auth.basic import AuthBasicAuthenticator
32 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
33 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
34 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from pylons import config, tmpl_context as c, request, session, url
35 from pylons import config, tmpl_context as c, request, session, url
36 from pylons.controllers import WSGIController
36 from pylons.controllers import WSGIController
37 from pylons.controllers.util import redirect
37 from pylons.controllers.util import redirect
38 from pylons.i18n import translation
38 from pylons.i18n import translation
39 # marcink: don't remove this import
39 # marcink: don't remove this import
40 from pylons.templating import render_mako as render # noqa
40 from pylons.templating import render_mako as render # noqa
41 from pylons.i18n.translation import _
41 from pylons.i18n.translation import _
42 from webob.exc import HTTPFound
42 from webob.exc import HTTPFound
43
43
44
44
45 import rhodecode
45 import rhodecode
46 from rhodecode.authentication.base import VCS_TYPE
46 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.lib import auth, utils2
47 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import helpers as h
48 from rhodecode.lib import helpers as h
49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
49 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.exceptions import UserCreationError
50 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.utils import (
51 from rhodecode.lib.utils import (
52 get_repo_slug, set_rhodecode_config, password_changed,
52 get_repo_slug, set_rhodecode_config, password_changed,
53 get_enabled_hook_classes)
53 get_enabled_hook_classes)
54 from rhodecode.lib.utils2 import (
54 from rhodecode.lib.utils2 import (
55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
55 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
56 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.model import meta
57 from rhodecode.model import meta
58 from rhodecode.model.db import Repository, User
58 from rhodecode.model.db import Repository, User
59 from rhodecode.model.notification import NotificationModel
59 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
61 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62
62
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 def _filter_proxy(ip):
67 def _filter_proxy(ip):
68 """
68 """
69 Passed in IP addresses in HEADERS can be in a special format of multiple
69 Passed in IP addresses in HEADERS can be in a special format of multiple
70 ips. Those comma separated IPs are passed from various proxies in the
70 ips. Those comma separated IPs are passed from various proxies in the
71 chain of request processing. The left-most being the original client.
71 chain of request processing. The left-most being the original client.
72 We only care about the first IP which came from the org. client.
72 We only care about the first IP which came from the org. client.
73
73
74 :param ip: ip string from headers
74 :param ip: ip string from headers
75 """
75 """
76 if ',' in ip:
76 if ',' in ip:
77 _ips = ip.split(',')
77 _ips = ip.split(',')
78 _first_ip = _ips[0].strip()
78 _first_ip = _ips[0].strip()
79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
79 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 return _first_ip
80 return _first_ip
81 return ip
81 return ip
82
82
83
83
84 def _filter_port(ip):
84 def _filter_port(ip):
85 """
85 """
86 Removes a port from ip, there are 4 main cases to handle here.
86 Removes a port from ip, there are 4 main cases to handle here.
87 - ipv4 eg. 127.0.0.1
87 - ipv4 eg. 127.0.0.1
88 - ipv6 eg. ::1
88 - ipv6 eg. ::1
89 - ipv4+port eg. 127.0.0.1:8080
89 - ipv4+port eg. 127.0.0.1:8080
90 - ipv6+port eg. [::1]:8080
90 - ipv6+port eg. [::1]:8080
91
91
92 :param ip:
92 :param ip:
93 """
93 """
94 def is_ipv6(ip_addr):
94 def is_ipv6(ip_addr):
95 if hasattr(socket, 'inet_pton'):
95 if hasattr(socket, 'inet_pton'):
96 try:
96 try:
97 socket.inet_pton(socket.AF_INET6, ip_addr)
97 socket.inet_pton(socket.AF_INET6, ip_addr)
98 except socket.error:
98 except socket.error:
99 return False
99 return False
100 else:
100 else:
101 # fallback to ipaddress
101 # fallback to ipaddress
102 try:
102 try:
103 ipaddress.IPv6Address(ip_addr)
103 ipaddress.IPv6Address(ip_addr)
104 except Exception:
104 except Exception:
105 return False
105 return False
106 return True
106 return True
107
107
108 if ':' not in ip: # must be ipv4 pure ip
108 if ':' not in ip: # must be ipv4 pure ip
109 return ip
109 return ip
110
110
111 if '[' in ip and ']' in ip: # ipv6 with port
111 if '[' in ip and ']' in ip: # ipv6 with port
112 return ip.split(']')[0][1:].lower()
112 return ip.split(']')[0][1:].lower()
113
113
114 # must be ipv6 or ipv4 with port
114 # must be ipv6 or ipv4 with port
115 if is_ipv6(ip):
115 if is_ipv6(ip):
116 return ip
116 return ip
117 else:
117 else:
118 ip, _port = ip.split(':')[:2] # means ipv4+port
118 ip, _port = ip.split(':')[:2] # means ipv4+port
119 return ip
119 return ip
120
120
121
121
122 def get_ip_addr(environ):
122 def get_ip_addr(environ):
123 proxy_key = 'HTTP_X_REAL_IP'
123 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
124 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 def_key = 'REMOTE_ADDR'
125 def_key = 'REMOTE_ADDR'
126 _filters = lambda x: _filter_port(_filter_proxy(x))
126 _filters = lambda x: _filter_port(_filter_proxy(x))
127
127
128 ip = environ.get(proxy_key)
128 ip = environ.get(proxy_key)
129 if ip:
129 if ip:
130 return _filters(ip)
130 return _filters(ip)
131
131
132 ip = environ.get(proxy_key2)
132 ip = environ.get(proxy_key2)
133 if ip:
133 if ip:
134 return _filters(ip)
134 return _filters(ip)
135
135
136 ip = environ.get(def_key, '0.0.0.0')
136 ip = environ.get(def_key, '0.0.0.0')
137 return _filters(ip)
137 return _filters(ip)
138
138
139
139
140 def get_server_ip_addr(environ, log_errors=True):
140 def get_server_ip_addr(environ, log_errors=True):
141 hostname = environ.get('SERVER_NAME')
141 hostname = environ.get('SERVER_NAME')
142 try:
142 try:
143 return socket.gethostbyname(hostname)
143 return socket.gethostbyname(hostname)
144 except Exception as e:
144 except Exception as e:
145 if log_errors:
145 if log_errors:
146 # in some cases this lookup is not possible, and we don't want to
146 # in some cases this lookup is not possible, and we don't want to
147 # make it an exception in logs
147 # make it an exception in logs
148 log.exception('Could not retrieve server ip address: %s', e)
148 log.exception('Could not retrieve server ip address: %s', e)
149 return hostname
149 return hostname
150
150
151
151
152 def get_server_port(environ):
152 def get_server_port(environ):
153 return environ.get('SERVER_PORT')
153 return environ.get('SERVER_PORT')
154
154
155
155
156 def get_access_path(environ):
156 def get_access_path(environ):
157 path = environ.get('PATH_INFO')
157 path = environ.get('PATH_INFO')
158 org_req = environ.get('pylons.original_request')
158 org_req = environ.get('pylons.original_request')
159 if org_req:
159 if org_req:
160 path = org_req.environ.get('PATH_INFO')
160 path = org_req.environ.get('PATH_INFO')
161 return path
161 return path
162
162
163
163
164 def vcs_operation_context(
164 def vcs_operation_context(
165 environ, repo_name, username, action, scm, check_locking=True):
165 environ, repo_name, username, action, scm, check_locking=True):
166 """
166 """
167 Generate the context for a vcs operation, e.g. push or pull.
167 Generate the context for a vcs operation, e.g. push or pull.
168
168
169 This context is passed over the layers so that hooks triggered by the
169 This context is passed over the layers so that hooks triggered by the
170 vcs operation know details like the user, the user's IP address etc.
170 vcs operation know details like the user, the user's IP address etc.
171
171
172 :param check_locking: Allows to switch of the computation of the locking
172 :param check_locking: Allows to switch of the computation of the locking
173 data. This serves mainly the need of the simplevcs middleware to be
173 data. This serves mainly the need of the simplevcs middleware to be
174 able to disable this for certain operations.
174 able to disable this for certain operations.
175
175
176 """
176 """
177 # Tri-state value: False: unlock, None: nothing, True: lock
177 # Tri-state value: False: unlock, None: nothing, True: lock
178 make_lock = None
178 make_lock = None
179 locked_by = [None, None, None]
179 locked_by = [None, None, None]
180 is_anonymous = username == User.DEFAULT_USER
180 is_anonymous = username == User.DEFAULT_USER
181 if not is_anonymous and check_locking:
181 if not is_anonymous and check_locking:
182 log.debug('Checking locking on repository "%s"', repo_name)
182 log.debug('Checking locking on repository "%s"', repo_name)
183 user = User.get_by_username(username)
183 user = User.get_by_username(username)
184 repo = Repository.get_by_repo_name(repo_name)
184 repo = Repository.get_by_repo_name(repo_name)
185 make_lock, __, locked_by = repo.get_locking_state(
185 make_lock, __, locked_by = repo.get_locking_state(
186 action, user.user_id)
186 action, user.user_id)
187
187
188 settings_model = VcsSettingsModel(repo=repo_name)
188 settings_model = VcsSettingsModel(repo=repo_name)
189 ui_settings = settings_model.get_ui_settings()
189 ui_settings = settings_model.get_ui_settings()
190
190
191 extras = {
191 extras = {
192 'ip': get_ip_addr(environ),
192 'ip': get_ip_addr(environ),
193 'username': username,
193 'username': username,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'make_lock': make_lock,
198 'make_lock': make_lock,
199 'locked_by': locked_by,
199 'locked_by': locked_by,
200 'server_url': utils2.get_server_url(environ),
200 'server_url': utils2.get_server_url(environ),
201 'hooks': get_enabled_hook_classes(ui_settings),
201 'hooks': get_enabled_hook_classes(ui_settings),
202 }
202 }
203 return extras
203 return extras
204
204
205
205
206 class BasicAuth(AuthBasicAuthenticator):
206 class BasicAuth(AuthBasicAuthenticator):
207
207
208 def __init__(self, realm, authfunc, registry, auth_http_code=None,
208 def __init__(self, realm, authfunc, registry, auth_http_code=None,
209 initial_call_detection=False):
209 initial_call_detection=False):
210 self.realm = realm
210 self.realm = realm
211 self.initial_call = initial_call_detection
211 self.initial_call = initial_call_detection
212 self.authfunc = authfunc
212 self.authfunc = authfunc
213 self.registry = registry
213 self.registry = registry
214 self._rc_auth_http_code = auth_http_code
214 self._rc_auth_http_code = auth_http_code
215
215
216 def _get_response_from_code(self, http_code):
216 def _get_response_from_code(self, http_code):
217 try:
217 try:
218 return get_exception(safe_int(http_code))
218 return get_exception(safe_int(http_code))
219 except Exception:
219 except Exception:
220 log.exception('Failed to fetch response for code %s' % http_code)
220 log.exception('Failed to fetch response for code %s' % http_code)
221 return HTTPForbidden
221 return HTTPForbidden
222
222
223 def build_authentication(self):
223 def build_authentication(self):
224 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
224 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
225 if self._rc_auth_http_code and not self.initial_call:
225 if self._rc_auth_http_code and not self.initial_call:
226 # return alternative HTTP code if alternative http return code
226 # return alternative HTTP code if alternative http return code
227 # is specified in RhodeCode config, but ONLY if it's not the
227 # is specified in RhodeCode config, but ONLY if it's not the
228 # FIRST call
228 # FIRST call
229 custom_response_klass = self._get_response_from_code(
229 custom_response_klass = self._get_response_from_code(
230 self._rc_auth_http_code)
230 self._rc_auth_http_code)
231 return custom_response_klass(headers=head)
231 return custom_response_klass(headers=head)
232 return HTTPUnauthorized(headers=head)
232 return HTTPUnauthorized(headers=head)
233
233
234 def authenticate(self, environ):
234 def authenticate(self, environ):
235 authorization = AUTHORIZATION(environ)
235 authorization = AUTHORIZATION(environ)
236 if not authorization:
236 if not authorization:
237 return self.build_authentication()
237 return self.build_authentication()
238 (authmeth, auth) = authorization.split(' ', 1)
238 (authmeth, auth) = authorization.split(' ', 1)
239 if 'basic' != authmeth.lower():
239 if 'basic' != authmeth.lower():
240 return self.build_authentication()
240 return self.build_authentication()
241 auth = auth.strip().decode('base64')
241 auth = auth.strip().decode('base64')
242 _parts = auth.split(':', 1)
242 _parts = auth.split(':', 1)
243 if len(_parts) == 2:
243 if len(_parts) == 2:
244 username, password = _parts
244 username, password = _parts
245 if self.authfunc(
245 if self.authfunc(
246 username, password, environ, VCS_TYPE,
246 username, password, environ, VCS_TYPE,
247 registry=self.registry):
247 registry=self.registry):
248 return username
248 return username
249 if username and password:
249 if username and password:
250 # we mark that we actually executed authentication once, at
250 # we mark that we actually executed authentication once, at
251 # that point we can use the alternative auth code
251 # that point we can use the alternative auth code
252 self.initial_call = False
252 self.initial_call = False
253
253
254 return self.build_authentication()
254 return self.build_authentication()
255
255
256 __call__ = authenticate
256 __call__ = authenticate
257
257
258
258
259 def attach_context_attributes(context, request):
259 def attach_context_attributes(context, request):
260 """
260 """
261 Attach variables into template context called `c`, please note that
261 Attach variables into template context called `c`, please note that
262 request could be pylons or pyramid request in here.
262 request could be pylons or pyramid request in here.
263 """
263 """
264 rc_config = SettingsModel().get_all_settings(cache=True)
264 rc_config = SettingsModel().get_all_settings(cache=True)
265
265
266 context.rhodecode_version = rhodecode.__version__
266 context.rhodecode_version = rhodecode.__version__
267 context.rhodecode_edition = config.get('rhodecode.edition')
267 context.rhodecode_edition = config.get('rhodecode.edition')
268 # unique secret + version does not leak the version but keep consistency
268 # unique secret + version does not leak the version but keep consistency
269 context.rhodecode_version_hash = md5(
269 context.rhodecode_version_hash = md5(
270 config.get('beaker.session.secret', '') +
270 config.get('beaker.session.secret', '') +
271 rhodecode.__version__)[:8]
271 rhodecode.__version__)[:8]
272
272
273 # Default language set for the incoming request
273 # Default language set for the incoming request
274 context.language = translation.get_lang()[0]
274 context.language = translation.get_lang()[0]
275
275
276 # Visual options
276 # Visual options
277 context.visual = AttributeDict({})
277 context.visual = AttributeDict({})
278
278
279 # DB store
279 # DB stored Visual Items
280 context.visual.show_public_icon = str2bool(
280 context.visual.show_public_icon = str2bool(
281 rc_config.get('rhodecode_show_public_icon'))
281 rc_config.get('rhodecode_show_public_icon'))
282 context.visual.show_private_icon = str2bool(
282 context.visual.show_private_icon = str2bool(
283 rc_config.get('rhodecode_show_private_icon'))
283 rc_config.get('rhodecode_show_private_icon'))
284 context.visual.stylify_metatags = str2bool(
284 context.visual.stylify_metatags = str2bool(
285 rc_config.get('rhodecode_stylify_metatags'))
285 rc_config.get('rhodecode_stylify_metatags'))
286 context.visual.dashboard_items = safe_int(
286 context.visual.dashboard_items = safe_int(
287 rc_config.get('rhodecode_dashboard_items', 100))
287 rc_config.get('rhodecode_dashboard_items', 100))
288 context.visual.admin_grid_items = safe_int(
288 context.visual.admin_grid_items = safe_int(
289 rc_config.get('rhodecode_admin_grid_items', 100))
289 rc_config.get('rhodecode_admin_grid_items', 100))
290 context.visual.repository_fields = str2bool(
290 context.visual.repository_fields = str2bool(
291 rc_config.get('rhodecode_repository_fields'))
291 rc_config.get('rhodecode_repository_fields'))
292 context.visual.show_version = str2bool(
292 context.visual.show_version = str2bool(
293 rc_config.get('rhodecode_show_version'))
293 rc_config.get('rhodecode_show_version'))
294 context.visual.use_gravatar = str2bool(
294 context.visual.use_gravatar = str2bool(
295 rc_config.get('rhodecode_use_gravatar'))
295 rc_config.get('rhodecode_use_gravatar'))
296 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
296 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
297 context.visual.default_renderer = rc_config.get(
297 context.visual.default_renderer = rc_config.get(
298 'rhodecode_markup_renderer', 'rst')
298 'rhodecode_markup_renderer', 'rst')
299 context.visual.rhodecode_support_url = \
299 context.visual.rhodecode_support_url = \
300 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
300 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
301
301
302 context.pre_code = rc_config.get('rhodecode_pre_code')
302 context.pre_code = rc_config.get('rhodecode_pre_code')
303 context.post_code = rc_config.get('rhodecode_post_code')
303 context.post_code = rc_config.get('rhodecode_post_code')
304 context.rhodecode_name = rc_config.get('rhodecode_title')
304 context.rhodecode_name = rc_config.get('rhodecode_title')
305 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
305 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
306 # if we have specified default_encoding in the request, it has more
306 # if we have specified default_encoding in the request, it has more
307 # priority
307 # priority
308 if request.GET.get('default_encoding'):
308 if request.GET.get('default_encoding'):
309 context.default_encodings.insert(0, request.GET.get('default_encoding'))
309 context.default_encodings.insert(0, request.GET.get('default_encoding'))
310 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
310 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
311
311
312 # INI stored
312 # INI stored
313 context.labs_active = str2bool(
313 context.labs_active = str2bool(
314 config.get('labs_settings_active', 'false'))
314 config.get('labs_settings_active', 'false'))
315 context.visual.allow_repo_location_change = str2bool(
315 context.visual.allow_repo_location_change = str2bool(
316 config.get('allow_repo_location_change', True))
316 config.get('allow_repo_location_change', True))
317 context.visual.allow_custom_hooks_settings = str2bool(
317 context.visual.allow_custom_hooks_settings = str2bool(
318 config.get('allow_custom_hooks_settings', True))
318 config.get('allow_custom_hooks_settings', True))
319 context.debug_style = str2bool(config.get('debug_style', False))
319 context.debug_style = str2bool(config.get('debug_style', False))
320
320
321 context.rhodecode_instanceid = config.get('instance_id')
321 context.rhodecode_instanceid = config.get('instance_id')
322
322
323 # AppEnlight
323 # AppEnlight
324 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
324 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
325 context.appenlight_api_public_key = config.get(
325 context.appenlight_api_public_key = config.get(
326 'appenlight.api_public_key', '')
326 'appenlight.api_public_key', '')
327 context.appenlight_server_url = config.get('appenlight.server_url', '')
327 context.appenlight_server_url = config.get('appenlight.server_url', '')
328
328
329 # JS template context
329 # JS template context
330 context.template_context = {
330 context.template_context = {
331 'repo_name': None,
331 'repo_name': None,
332 'repo_type': None,
332 'repo_type': None,
333 'repo_landing_commit': None,
333 'repo_landing_commit': None,
334 'rhodecode_user': {
334 'rhodecode_user': {
335 'username': None,
335 'username': None,
336 'email': None,
336 'email': None,
337 'notification_status': False
337 'notification_status': False
338 },
338 },
339 'visual': {
339 'visual': {
340 'default_renderer': None
340 'default_renderer': None
341 },
341 },
342 'commit_data': {
342 'commit_data': {
343 'commit_id': None
343 'commit_id': None
344 },
344 },
345 'pull_request_data': {'pull_request_id': None},
345 'pull_request_data': {'pull_request_id': None},
346 'timeago': {
346 'timeago': {
347 'refresh_time': 120 * 1000,
347 'refresh_time': 120 * 1000,
348 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
348 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
349 },
349 },
350 'pylons_dispatch': {
350 'pylons_dispatch': {
351 # 'controller': request.environ['pylons.routes_dict']['controller'],
351 # 'controller': request.environ['pylons.routes_dict']['controller'],
352 # 'action': request.environ['pylons.routes_dict']['action'],
352 # 'action': request.environ['pylons.routes_dict']['action'],
353 },
353 },
354 'pyramid_dispatch': {
354 'pyramid_dispatch': {
355
355
356 },
356 },
357 'extra': {'plugins': {}}
357 'extra': {'plugins': {}}
358 }
358 }
359 # END CONFIG VARS
359 # END CONFIG VARS
360
360
361 # TODO: This dosn't work when called from pylons compatibility tween.
361 # TODO: This dosn't work when called from pylons compatibility tween.
362 # Fix this and remove it from base controller.
362 # Fix this and remove it from base controller.
363 # context.repo_name = get_repo_slug(request) # can be empty
363 # context.repo_name = get_repo_slug(request) # can be empty
364
364
365 context.csrf_token = auth.get_csrf_token()
365 context.csrf_token = auth.get_csrf_token()
366 context.backends = rhodecode.BACKENDS.keys()
366 context.backends = rhodecode.BACKENDS.keys()
367 context.backends.sort()
367 context.backends.sort()
368 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
368 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
369 context.rhodecode_user.user_id)
369 context.rhodecode_user.user_id)
370
370
371
371
372 def get_auth_user(environ):
372 def get_auth_user(environ):
373 ip_addr = get_ip_addr(environ)
373 ip_addr = get_ip_addr(environ)
374 # make sure that we update permissions each time we call controller
374 # make sure that we update permissions each time we call controller
375 _auth_token = (request.GET.get('auth_token', '') or
375 _auth_token = (request.GET.get('auth_token', '') or
376 request.GET.get('api_key', ''))
376 request.GET.get('api_key', ''))
377
377
378 if _auth_token:
378 if _auth_token:
379 # when using API_KEY we are sure user exists.
379 # when using API_KEY we are sure user exists.
380 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
380 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
381 authenticated = False
381 authenticated = False
382 else:
382 else:
383 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
383 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
384 try:
384 try:
385 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
385 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
386 ip_addr=ip_addr)
386 ip_addr=ip_addr)
387 except UserCreationError as e:
387 except UserCreationError as e:
388 h.flash(e, 'error')
388 h.flash(e, 'error')
389 # container auth or other auth functions that create users
389 # container auth or other auth functions that create users
390 # on the fly can throw this exception signaling that there's
390 # on the fly can throw this exception signaling that there's
391 # issue with user creation, explanation should be provided
391 # issue with user creation, explanation should be provided
392 # in Exception itself. We then create a simple blank
392 # in Exception itself. We then create a simple blank
393 # AuthUser
393 # AuthUser
394 auth_user = AuthUser(ip_addr=ip_addr)
394 auth_user = AuthUser(ip_addr=ip_addr)
395
395
396 if password_changed(auth_user, session):
396 if password_changed(auth_user, session):
397 session.invalidate()
397 session.invalidate()
398 cookie_store = CookieStoreWrapper(
398 cookie_store = CookieStoreWrapper(
399 session.get('rhodecode_user'))
399 session.get('rhodecode_user'))
400 auth_user = AuthUser(ip_addr=ip_addr)
400 auth_user = AuthUser(ip_addr=ip_addr)
401
401
402 authenticated = cookie_store.get('is_authenticated')
402 authenticated = cookie_store.get('is_authenticated')
403
403
404 if not auth_user.is_authenticated and auth_user.is_user_object:
404 if not auth_user.is_authenticated and auth_user.is_user_object:
405 # user is not authenticated and not empty
405 # user is not authenticated and not empty
406 auth_user.set_authenticated(authenticated)
406 auth_user.set_authenticated(authenticated)
407
407
408 return auth_user
408 return auth_user
409
409
410
410
411 class BaseController(WSGIController):
411 class BaseController(WSGIController):
412
412
413 def __before__(self):
413 def __before__(self):
414 """
414 """
415 __before__ is called before controller methods and after __call__
415 __before__ is called before controller methods and after __call__
416 """
416 """
417 # on each call propagate settings calls into global settings.
417 # on each call propagate settings calls into global settings.
418 set_rhodecode_config(config)
418 set_rhodecode_config(config)
419 attach_context_attributes(c, request)
419 attach_context_attributes(c, request)
420
420
421 # TODO: Remove this when fixed in attach_context_attributes()
421 # TODO: Remove this when fixed in attach_context_attributes()
422 c.repo_name = get_repo_slug(request) # can be empty
422 c.repo_name = get_repo_slug(request) # can be empty
423
423
424 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
424 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
425 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
425 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
426 self.sa = meta.Session
426 self.sa = meta.Session
427 self.scm_model = ScmModel(self.sa)
427 self.scm_model = ScmModel(self.sa)
428
428
429 default_lang = c.language
429 default_lang = c.language
430 user_lang = c.language
430 user_lang = c.language
431 try:
431 try:
432 user_obj = self._rhodecode_user.get_instance()
432 user_obj = self._rhodecode_user.get_instance()
433 if user_obj:
433 if user_obj:
434 user_lang = user_obj.user_data.get('language')
434 user_lang = user_obj.user_data.get('language')
435 except Exception:
435 except Exception:
436 log.exception('Failed to fetch user language for user %s',
436 log.exception('Failed to fetch user language for user %s',
437 self._rhodecode_user)
437 self._rhodecode_user)
438
438
439 if user_lang and user_lang != default_lang:
439 if user_lang and user_lang != default_lang:
440 log.debug('set language to %s for user %s', user_lang,
440 log.debug('set language to %s for user %s', user_lang,
441 self._rhodecode_user)
441 self._rhodecode_user)
442 translation.set_lang(user_lang)
442 translation.set_lang(user_lang)
443
443
444 def _dispatch_redirect(self, with_url, environ, start_response):
444 def _dispatch_redirect(self, with_url, environ, start_response):
445 resp = HTTPFound(with_url)
445 resp = HTTPFound(with_url)
446 environ['SCRIPT_NAME'] = '' # handle prefix middleware
446 environ['SCRIPT_NAME'] = '' # handle prefix middleware
447 environ['PATH_INFO'] = with_url
447 environ['PATH_INFO'] = with_url
448 return resp(environ, start_response)
448 return resp(environ, start_response)
449
449
450 def __call__(self, environ, start_response):
450 def __call__(self, environ, start_response):
451 """Invoke the Controller"""
451 """Invoke the Controller"""
452 # WSGIController.__call__ dispatches to the Controller method
452 # WSGIController.__call__ dispatches to the Controller method
453 # the request is routed to. This routing information is
453 # the request is routed to. This routing information is
454 # available in environ['pylons.routes_dict']
454 # available in environ['pylons.routes_dict']
455 from rhodecode.lib import helpers as h
455 from rhodecode.lib import helpers as h
456
456
457 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
457 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
458 if environ.get('debugtoolbar.wants_pylons_context', False):
458 if environ.get('debugtoolbar.wants_pylons_context', False):
459 environ['debugtoolbar.pylons_context'] = c._current_obj()
459 environ['debugtoolbar.pylons_context'] = c._current_obj()
460
460
461 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
461 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
462 environ['pylons.routes_dict']['action']])
462 environ['pylons.routes_dict']['action']])
463
463
464 self.rc_config = SettingsModel().get_all_settings(cache=True)
464 self.rc_config = SettingsModel().get_all_settings(cache=True)
465 self.ip_addr = get_ip_addr(environ)
465 self.ip_addr = get_ip_addr(environ)
466
466
467 # The rhodecode auth user is looked up and passed through the
467 # The rhodecode auth user is looked up and passed through the
468 # environ by the pylons compatibility tween in pyramid.
468 # environ by the pylons compatibility tween in pyramid.
469 # So we can just grab it from there.
469 # So we can just grab it from there.
470 auth_user = environ['rc_auth_user']
470 auth_user = environ['rc_auth_user']
471
471
472 # set globals for auth user
472 # set globals for auth user
473 request.user = auth_user
473 request.user = auth_user
474 c.rhodecode_user = self._rhodecode_user = auth_user
474 c.rhodecode_user = self._rhodecode_user = auth_user
475
475
476 log.info('IP: %s User: %s accessed %s [%s]' % (
476 log.info('IP: %s User: %s accessed %s [%s]' % (
477 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
477 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
478 _route_name)
478 _route_name)
479 )
479 )
480
480
481 # TODO: Maybe this should be move to pyramid to cover all views.
481 # TODO: Maybe this should be move to pyramid to cover all views.
482 # check user attributes for password change flag
482 # check user attributes for password change flag
483 user_obj = auth_user.get_instance()
483 user_obj = auth_user.get_instance()
484 if user_obj and user_obj.user_data.get('force_password_change'):
484 if user_obj and user_obj.user_data.get('force_password_change'):
485 h.flash('You are required to change your password', 'warning',
485 h.flash('You are required to change your password', 'warning',
486 ignore_duplicate=True)
486 ignore_duplicate=True)
487
487
488 skip_user_check_urls = [
488 skip_user_check_urls = [
489 'error.document', 'login.logout', 'login.index',
489 'error.document', 'login.logout', 'login.index',
490 'admin/my_account.my_account_password',
490 'admin/my_account.my_account_password',
491 'admin/my_account.my_account_password_update'
491 'admin/my_account.my_account_password_update'
492 ]
492 ]
493 if _route_name not in skip_user_check_urls:
493 if _route_name not in skip_user_check_urls:
494 return self._dispatch_redirect(
494 return self._dispatch_redirect(
495 url('my_account_password'), environ, start_response)
495 url('my_account_password'), environ, start_response)
496
496
497 return WSGIController.__call__(self, environ, start_response)
497 return WSGIController.__call__(self, environ, start_response)
498
498
499
499
500 class BaseRepoController(BaseController):
500 class BaseRepoController(BaseController):
501 """
501 """
502 Base class for controllers responsible for loading all needed data for
502 Base class for controllers responsible for loading all needed data for
503 repository loaded items are
503 repository loaded items are
504
504
505 c.rhodecode_repo: instance of scm repository
505 c.rhodecode_repo: instance of scm repository
506 c.rhodecode_db_repo: instance of db
506 c.rhodecode_db_repo: instance of db
507 c.repository_requirements_missing: shows that repository specific data
507 c.repository_requirements_missing: shows that repository specific data
508 could not be displayed due to the missing requirements
508 could not be displayed due to the missing requirements
509 c.repository_pull_requests: show number of open pull requests
509 c.repository_pull_requests: show number of open pull requests
510 """
510 """
511
511
512 def __before__(self):
512 def __before__(self):
513 super(BaseRepoController, self).__before__()
513 super(BaseRepoController, self).__before__()
514 if c.repo_name: # extracted from routes
514 if c.repo_name: # extracted from routes
515 db_repo = Repository.get_by_repo_name(c.repo_name)
515 db_repo = Repository.get_by_repo_name(c.repo_name)
516 if not db_repo:
516 if not db_repo:
517 return
517 return
518
518
519 log.debug(
519 log.debug(
520 'Found repository in database %s with state `%s`',
520 'Found repository in database %s with state `%s`',
521 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
521 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
522 route = getattr(request.environ.get('routes.route'), 'name', '')
522 route = getattr(request.environ.get('routes.route'), 'name', '')
523
523
524 # allow to delete repos that are somehow damages in filesystem
524 # allow to delete repos that are somehow damages in filesystem
525 if route in ['delete_repo']:
525 if route in ['delete_repo']:
526 return
526 return
527
527
528 if db_repo.repo_state in [Repository.STATE_PENDING]:
528 if db_repo.repo_state in [Repository.STATE_PENDING]:
529 if route in ['repo_creating_home']:
529 if route in ['repo_creating_home']:
530 return
530 return
531 check_url = url('repo_creating_home', repo_name=c.repo_name)
531 check_url = url('repo_creating_home', repo_name=c.repo_name)
532 return redirect(check_url)
532 return redirect(check_url)
533
533
534 self.rhodecode_db_repo = db_repo
534 self.rhodecode_db_repo = db_repo
535
535
536 missing_requirements = False
536 missing_requirements = False
537 try:
537 try:
538 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
538 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
539 except RepositoryRequirementError as e:
539 except RepositoryRequirementError as e:
540 missing_requirements = True
540 missing_requirements = True
541 self._handle_missing_requirements(e)
541 self._handle_missing_requirements(e)
542
542
543 if self.rhodecode_repo is None and not missing_requirements:
543 if self.rhodecode_repo is None and not missing_requirements:
544 log.error('%s this repository is present in database but it '
544 log.error('%s this repository is present in database but it '
545 'cannot be created as an scm instance', c.repo_name)
545 'cannot be created as an scm instance', c.repo_name)
546
546
547 h.flash(_(
547 h.flash(_(
548 "The repository at %(repo_name)s cannot be located.") %
548 "The repository at %(repo_name)s cannot be located.") %
549 {'repo_name': c.repo_name},
549 {'repo_name': c.repo_name},
550 category='error', ignore_duplicate=True)
550 category='error', ignore_duplicate=True)
551 redirect(url('home'))
551 redirect(url('home'))
552
552
553 # update last change according to VCS data
553 # update last change according to VCS data
554 if not missing_requirements:
554 if not missing_requirements:
555 commit = db_repo.get_commit(
555 commit = db_repo.get_commit(
556 pre_load=["author", "date", "message", "parents"])
556 pre_load=["author", "date", "message", "parents"])
557 db_repo.update_commit_cache(commit)
557 db_repo.update_commit_cache(commit)
558
558
559 # Prepare context
559 # Prepare context
560 c.rhodecode_db_repo = db_repo
560 c.rhodecode_db_repo = db_repo
561 c.rhodecode_repo = self.rhodecode_repo
561 c.rhodecode_repo = self.rhodecode_repo
562 c.repository_requirements_missing = missing_requirements
562 c.repository_requirements_missing = missing_requirements
563
563
564 self._update_global_counters(self.scm_model, db_repo)
564 self._update_global_counters(self.scm_model, db_repo)
565
565
566 def _update_global_counters(self, scm_model, db_repo):
566 def _update_global_counters(self, scm_model, db_repo):
567 """
567 """
568 Base variables that are exposed to every page of repository
568 Base variables that are exposed to every page of repository
569 """
569 """
570 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
570 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
571
571
572 def _handle_missing_requirements(self, error):
572 def _handle_missing_requirements(self, error):
573 self.rhodecode_repo = None
573 self.rhodecode_repo = None
574 log.error(
574 log.error(
575 'Requirements are missing for repository %s: %s',
575 'Requirements are missing for repository %s: %s',
576 c.repo_name, error.message)
576 c.repo_name, error.message)
577
577
578 summary_url = url('summary_home', repo_name=c.repo_name)
578 summary_url = url('summary_home', repo_name=c.repo_name)
579 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
579 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
580 settings_update_url = url('repo', repo_name=c.repo_name)
580 settings_update_url = url('repo', repo_name=c.repo_name)
581 path = request.path
581 path = request.path
582 should_redirect = (
582 should_redirect = (
583 path not in (summary_url, settings_update_url)
583 path not in (summary_url, settings_update_url)
584 and '/settings' not in path or path == statistics_url
584 and '/settings' not in path or path == statistics_url
585 )
585 )
586 if should_redirect:
586 if should_redirect:
587 redirect(summary_url)
587 redirect(summary_url)
@@ -1,1966 +1,1966 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import random
28 import random
29 import hashlib
29 import hashlib
30 import StringIO
30 import StringIO
31 import urllib
31 import urllib
32 import math
32 import math
33 import logging
33 import logging
34 import re
34 import re
35 import urlparse
35 import urlparse
36 import time
36 import time
37 import string
37 import string
38 import hashlib
38 import hashlib
39 import pygments
39 import pygments
40
40
41 from datetime import datetime
41 from datetime import datetime
42 from functools import partial
42 from functools import partial
43 from pygments.formatters.html import HtmlFormatter
43 from pygments.formatters.html import HtmlFormatter
44 from pygments import highlight as code_highlight
44 from pygments import highlight as code_highlight
45 from pygments.lexers import (
45 from pygments.lexers import (
46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
47 from pylons import url as pylons_url
47 from pylons import url as pylons_url
48 from pylons.i18n.translation import _, ungettext
48 from pylons.i18n.translation import _, ungettext
49 from pyramid.threadlocal import get_current_request
49 from pyramid.threadlocal import get_current_request
50
50
51 from webhelpers.html import literal, HTML, escape
51 from webhelpers.html import literal, HTML, escape
52 from webhelpers.html.tools import *
52 from webhelpers.html.tools import *
53 from webhelpers.html.builder import make_tag
53 from webhelpers.html.builder import make_tag
54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
57 submit, text, password, textarea, title, ul, xml_declaration, radio
57 submit, text, password, textarea, title, ul, xml_declaration, radio
58 from webhelpers.html.tools import auto_link, button_to, highlight, \
58 from webhelpers.html.tools import auto_link, button_to, highlight, \
59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
60 from webhelpers.pylonslib import Flash as _Flash
60 from webhelpers.pylonslib import Flash as _Flash
61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
63 replace_whitespace, urlify, truncate, wrap_paragraphs
63 replace_whitespace, urlify, truncate, wrap_paragraphs
64 from webhelpers.date import time_ago_in_words
64 from webhelpers.date import time_ago_in_words
65 from webhelpers.paginate import Page as _Page
65 from webhelpers.paginate import Page as _Page
66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
67 convert_boolean_attrs, NotGiven, _make_safe_id_component
67 convert_boolean_attrs, NotGiven, _make_safe_id_component
68 from webhelpers2.number import format_byte_size
68 from webhelpers2.number import format_byte_size
69
69
70 from rhodecode.lib.annotate import annotate_highlight
70 from rhodecode.lib.annotate import annotate_highlight
71 from rhodecode.lib.action_parser import action_parser
71 from rhodecode.lib.action_parser import action_parser
72 from rhodecode.lib.ext_json import json
72 from rhodecode.lib.ext_json import json
73 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
73 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
74 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
74 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
75 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
75 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
76 AttributeDict, safe_int, md5, md5_safe
76 AttributeDict, safe_int, md5, md5_safe
77 from rhodecode.lib.markup_renderer import MarkupRenderer
77 from rhodecode.lib.markup_renderer import MarkupRenderer
78 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
78 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
79 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
79 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
80 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
80 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
81 from rhodecode.model.changeset_status import ChangesetStatusModel
81 from rhodecode.model.changeset_status import ChangesetStatusModel
82 from rhodecode.model.db import Permission, User, Repository
82 from rhodecode.model.db import Permission, User, Repository
83 from rhodecode.model.repo_group import RepoGroupModel
83 from rhodecode.model.repo_group import RepoGroupModel
84 from rhodecode.model.settings import IssueTrackerSettingsModel
84 from rhodecode.model.settings import IssueTrackerSettingsModel
85
85
86 log = logging.getLogger(__name__)
86 log = logging.getLogger(__name__)
87
87
88
88
89 DEFAULT_USER = User.DEFAULT_USER
89 DEFAULT_USER = User.DEFAULT_USER
90 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
90 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
91
91
92
92
93 def url(*args, **kw):
93 def url(*args, **kw):
94 return pylons_url(*args, **kw)
94 return pylons_url(*args, **kw)
95
95
96
96
97 def pylons_url_current(*args, **kw):
97 def pylons_url_current(*args, **kw):
98 """
98 """
99 This function overrides pylons.url.current() which returns the current
99 This function overrides pylons.url.current() which returns the current
100 path so that it will also work from a pyramid only context. This
100 path so that it will also work from a pyramid only context. This
101 should be removed once port to pyramid is complete.
101 should be removed once port to pyramid is complete.
102 """
102 """
103 if not args and not kw:
103 if not args and not kw:
104 request = get_current_request()
104 request = get_current_request()
105 return request.path
105 return request.path
106 return pylons_url.current(*args, **kw)
106 return pylons_url.current(*args, **kw)
107
107
108 url.current = pylons_url_current
108 url.current = pylons_url_current
109
109
110
110
111 def asset(path, ver=None):
111 def asset(path, ver=None):
112 """
112 """
113 Helper to generate a static asset file path for rhodecode assets
113 Helper to generate a static asset file path for rhodecode assets
114
114
115 eg. h.asset('images/image.png', ver='3923')
115 eg. h.asset('images/image.png', ver='3923')
116
116
117 :param path: path of asset
117 :param path: path of asset
118 :param ver: optional version query param to append as ?ver=
118 :param ver: optional version query param to append as ?ver=
119 """
119 """
120 request = get_current_request()
120 request = get_current_request()
121 query = {}
121 query = {}
122 if ver:
122 if ver:
123 query = {'ver': ver}
123 query = {'ver': ver}
124 return request.static_path(
124 return request.static_path(
125 'rhodecode:public/{}'.format(path), _query=query)
125 'rhodecode:public/{}'.format(path), _query=query)
126
126
127
127
128 def html_escape(text, html_escape_table=None):
128 def html_escape(text, html_escape_table=None):
129 """Produce entities within text."""
129 """Produce entities within text."""
130 if not html_escape_table:
130 if not html_escape_table:
131 html_escape_table = {
131 html_escape_table = {
132 "&": "&amp;",
132 "&": "&amp;",
133 '"': "&quot;",
133 '"': "&quot;",
134 "'": "&apos;",
134 "'": "&apos;",
135 ">": "&gt;",
135 ">": "&gt;",
136 "<": "&lt;",
136 "<": "&lt;",
137 }
137 }
138 return "".join(html_escape_table.get(c, c) for c in text)
138 return "".join(html_escape_table.get(c, c) for c in text)
139
139
140
140
141 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
141 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
142 """
142 """
143 Truncate string ``s`` at the first occurrence of ``sub``.
143 Truncate string ``s`` at the first occurrence of ``sub``.
144
144
145 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
145 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
146 """
146 """
147 suffix_if_chopped = suffix_if_chopped or ''
147 suffix_if_chopped = suffix_if_chopped or ''
148 pos = s.find(sub)
148 pos = s.find(sub)
149 if pos == -1:
149 if pos == -1:
150 return s
150 return s
151
151
152 if inclusive:
152 if inclusive:
153 pos += len(sub)
153 pos += len(sub)
154
154
155 chopped = s[:pos]
155 chopped = s[:pos]
156 left = s[pos:].strip()
156 left = s[pos:].strip()
157
157
158 if left and suffix_if_chopped:
158 if left and suffix_if_chopped:
159 chopped += suffix_if_chopped
159 chopped += suffix_if_chopped
160
160
161 return chopped
161 return chopped
162
162
163
163
164 def shorter(text, size=20):
164 def shorter(text, size=20):
165 postfix = '...'
165 postfix = '...'
166 if len(text) > size:
166 if len(text) > size:
167 return text[:size - len(postfix)] + postfix
167 return text[:size - len(postfix)] + postfix
168 return text
168 return text
169
169
170
170
171 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
171 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
172 """
172 """
173 Reset button
173 Reset button
174 """
174 """
175 _set_input_attrs(attrs, type, name, value)
175 _set_input_attrs(attrs, type, name, value)
176 _set_id_attr(attrs, id, name)
176 _set_id_attr(attrs, id, name)
177 convert_boolean_attrs(attrs, ["disabled"])
177 convert_boolean_attrs(attrs, ["disabled"])
178 return HTML.input(**attrs)
178 return HTML.input(**attrs)
179
179
180 reset = _reset
180 reset = _reset
181 safeid = _make_safe_id_component
181 safeid = _make_safe_id_component
182
182
183
183
184 def branding(name, length=40):
184 def branding(name, length=40):
185 return truncate(name, length, indicator="")
185 return truncate(name, length, indicator="")
186
186
187
187
188 def FID(raw_id, path):
188 def FID(raw_id, path):
189 """
189 """
190 Creates a unique ID for filenode based on it's hash of path and commit
190 Creates a unique ID for filenode based on it's hash of path and commit
191 it's safe to use in urls
191 it's safe to use in urls
192
192
193 :param raw_id:
193 :param raw_id:
194 :param path:
194 :param path:
195 """
195 """
196
196
197 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
197 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
198
198
199
199
200 class _GetError(object):
200 class _GetError(object):
201 """Get error from form_errors, and represent it as span wrapped error
201 """Get error from form_errors, and represent it as span wrapped error
202 message
202 message
203
203
204 :param field_name: field to fetch errors for
204 :param field_name: field to fetch errors for
205 :param form_errors: form errors dict
205 :param form_errors: form errors dict
206 """
206 """
207
207
208 def __call__(self, field_name, form_errors):
208 def __call__(self, field_name, form_errors):
209 tmpl = """<span class="error_msg">%s</span>"""
209 tmpl = """<span class="error_msg">%s</span>"""
210 if form_errors and field_name in form_errors:
210 if form_errors and field_name in form_errors:
211 return literal(tmpl % form_errors.get(field_name))
211 return literal(tmpl % form_errors.get(field_name))
212
212
213 get_error = _GetError()
213 get_error = _GetError()
214
214
215
215
216 class _ToolTip(object):
216 class _ToolTip(object):
217
217
218 def __call__(self, tooltip_title, trim_at=50):
218 def __call__(self, tooltip_title, trim_at=50):
219 """
219 """
220 Special function just to wrap our text into nice formatted
220 Special function just to wrap our text into nice formatted
221 autowrapped text
221 autowrapped text
222
222
223 :param tooltip_title:
223 :param tooltip_title:
224 """
224 """
225 tooltip_title = escape(tooltip_title)
225 tooltip_title = escape(tooltip_title)
226 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
226 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
227 return tooltip_title
227 return tooltip_title
228 tooltip = _ToolTip()
228 tooltip = _ToolTip()
229
229
230
230
231 def files_breadcrumbs(repo_name, commit_id, file_path):
231 def files_breadcrumbs(repo_name, commit_id, file_path):
232 if isinstance(file_path, str):
232 if isinstance(file_path, str):
233 file_path = safe_unicode(file_path)
233 file_path = safe_unicode(file_path)
234
234
235 # TODO: johbo: Is this always a url like path, or is this operating
235 # TODO: johbo: Is this always a url like path, or is this operating
236 # system dependent?
236 # system dependent?
237 path_segments = file_path.split('/')
237 path_segments = file_path.split('/')
238
238
239 repo_name_html = escape(repo_name)
239 repo_name_html = escape(repo_name)
240 if len(path_segments) == 1 and path_segments[0] == '':
240 if len(path_segments) == 1 and path_segments[0] == '':
241 url_segments = [repo_name_html]
241 url_segments = [repo_name_html]
242 else:
242 else:
243 url_segments = [
243 url_segments = [
244 link_to(
244 link_to(
245 repo_name_html,
245 repo_name_html,
246 url('files_home',
246 url('files_home',
247 repo_name=repo_name,
247 repo_name=repo_name,
248 revision=commit_id,
248 revision=commit_id,
249 f_path=''),
249 f_path=''),
250 class_='pjax-link')]
250 class_='pjax-link')]
251
251
252 last_cnt = len(path_segments) - 1
252 last_cnt = len(path_segments) - 1
253 for cnt, segment in enumerate(path_segments):
253 for cnt, segment in enumerate(path_segments):
254 if not segment:
254 if not segment:
255 continue
255 continue
256 segment_html = escape(segment)
256 segment_html = escape(segment)
257
257
258 if cnt != last_cnt:
258 if cnt != last_cnt:
259 url_segments.append(
259 url_segments.append(
260 link_to(
260 link_to(
261 segment_html,
261 segment_html,
262 url('files_home',
262 url('files_home',
263 repo_name=repo_name,
263 repo_name=repo_name,
264 revision=commit_id,
264 revision=commit_id,
265 f_path='/'.join(path_segments[:cnt + 1])),
265 f_path='/'.join(path_segments[:cnt + 1])),
266 class_='pjax-link'))
266 class_='pjax-link'))
267 else:
267 else:
268 url_segments.append(segment_html)
268 url_segments.append(segment_html)
269
269
270 return literal('/'.join(url_segments))
270 return literal('/'.join(url_segments))
271
271
272
272
273 class CodeHtmlFormatter(HtmlFormatter):
273 class CodeHtmlFormatter(HtmlFormatter):
274 """
274 """
275 My code Html Formatter for source codes
275 My code Html Formatter for source codes
276 """
276 """
277
277
278 def wrap(self, source, outfile):
278 def wrap(self, source, outfile):
279 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
279 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
280
280
281 def _wrap_code(self, source):
281 def _wrap_code(self, source):
282 for cnt, it in enumerate(source):
282 for cnt, it in enumerate(source):
283 i, t = it
283 i, t = it
284 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
284 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
285 yield i, t
285 yield i, t
286
286
287 def _wrap_tablelinenos(self, inner):
287 def _wrap_tablelinenos(self, inner):
288 dummyoutfile = StringIO.StringIO()
288 dummyoutfile = StringIO.StringIO()
289 lncount = 0
289 lncount = 0
290 for t, line in inner:
290 for t, line in inner:
291 if t:
291 if t:
292 lncount += 1
292 lncount += 1
293 dummyoutfile.write(line)
293 dummyoutfile.write(line)
294
294
295 fl = self.linenostart
295 fl = self.linenostart
296 mw = len(str(lncount + fl - 1))
296 mw = len(str(lncount + fl - 1))
297 sp = self.linenospecial
297 sp = self.linenospecial
298 st = self.linenostep
298 st = self.linenostep
299 la = self.lineanchors
299 la = self.lineanchors
300 aln = self.anchorlinenos
300 aln = self.anchorlinenos
301 nocls = self.noclasses
301 nocls = self.noclasses
302 if sp:
302 if sp:
303 lines = []
303 lines = []
304
304
305 for i in range(fl, fl + lncount):
305 for i in range(fl, fl + lncount):
306 if i % st == 0:
306 if i % st == 0:
307 if i % sp == 0:
307 if i % sp == 0:
308 if aln:
308 if aln:
309 lines.append('<a href="#%s%d" class="special">%*d</a>' %
309 lines.append('<a href="#%s%d" class="special">%*d</a>' %
310 (la, i, mw, i))
310 (la, i, mw, i))
311 else:
311 else:
312 lines.append('<span class="special">%*d</span>' % (mw, i))
312 lines.append('<span class="special">%*d</span>' % (mw, i))
313 else:
313 else:
314 if aln:
314 if aln:
315 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
315 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
316 else:
316 else:
317 lines.append('%*d' % (mw, i))
317 lines.append('%*d' % (mw, i))
318 else:
318 else:
319 lines.append('')
319 lines.append('')
320 ls = '\n'.join(lines)
320 ls = '\n'.join(lines)
321 else:
321 else:
322 lines = []
322 lines = []
323 for i in range(fl, fl + lncount):
323 for i in range(fl, fl + lncount):
324 if i % st == 0:
324 if i % st == 0:
325 if aln:
325 if aln:
326 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
326 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
327 else:
327 else:
328 lines.append('%*d' % (mw, i))
328 lines.append('%*d' % (mw, i))
329 else:
329 else:
330 lines.append('')
330 lines.append('')
331 ls = '\n'.join(lines)
331 ls = '\n'.join(lines)
332
332
333 # in case you wonder about the seemingly redundant <div> here: since the
333 # in case you wonder about the seemingly redundant <div> here: since the
334 # content in the other cell also is wrapped in a div, some browsers in
334 # content in the other cell also is wrapped in a div, some browsers in
335 # some configurations seem to mess up the formatting...
335 # some configurations seem to mess up the formatting...
336 if nocls:
336 if nocls:
337 yield 0, ('<table class="%stable">' % self.cssclass +
337 yield 0, ('<table class="%stable">' % self.cssclass +
338 '<tr><td><div class="linenodiv" '
338 '<tr><td><div class="linenodiv" '
339 'style="background-color: #f0f0f0; padding-right: 10px">'
339 'style="background-color: #f0f0f0; padding-right: 10px">'
340 '<pre style="line-height: 125%">' +
340 '<pre style="line-height: 125%">' +
341 ls + '</pre></div></td><td id="hlcode" class="code">')
341 ls + '</pre></div></td><td id="hlcode" class="code">')
342 else:
342 else:
343 yield 0, ('<table class="%stable">' % self.cssclass +
343 yield 0, ('<table class="%stable">' % self.cssclass +
344 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
344 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
345 ls + '</pre></div></td><td id="hlcode" class="code">')
345 ls + '</pre></div></td><td id="hlcode" class="code">')
346 yield 0, dummyoutfile.getvalue()
346 yield 0, dummyoutfile.getvalue()
347 yield 0, '</td></tr></table>'
347 yield 0, '</td></tr></table>'
348
348
349
349
350 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
350 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
351 def __init__(self, **kw):
351 def __init__(self, **kw):
352 # only show these line numbers if set
352 # only show these line numbers if set
353 self.only_lines = kw.pop('only_line_numbers', [])
353 self.only_lines = kw.pop('only_line_numbers', [])
354 self.query_terms = kw.pop('query_terms', [])
354 self.query_terms = kw.pop('query_terms', [])
355 self.max_lines = kw.pop('max_lines', 5)
355 self.max_lines = kw.pop('max_lines', 5)
356 self.line_context = kw.pop('line_context', 3)
356 self.line_context = kw.pop('line_context', 3)
357 self.url = kw.pop('url', None)
357 self.url = kw.pop('url', None)
358
358
359 super(CodeHtmlFormatter, self).__init__(**kw)
359 super(CodeHtmlFormatter, self).__init__(**kw)
360
360
361 def _wrap_code(self, source):
361 def _wrap_code(self, source):
362 for cnt, it in enumerate(source):
362 for cnt, it in enumerate(source):
363 i, t = it
363 i, t = it
364 t = '<pre>%s</pre>' % t
364 t = '<pre>%s</pre>' % t
365 yield i, t
365 yield i, t
366
366
367 def _wrap_tablelinenos(self, inner):
367 def _wrap_tablelinenos(self, inner):
368 yield 0, '<table class="code-highlight %stable">' % self.cssclass
368 yield 0, '<table class="code-highlight %stable">' % self.cssclass
369
369
370 last_shown_line_number = 0
370 last_shown_line_number = 0
371 current_line_number = 1
371 current_line_number = 1
372
372
373 for t, line in inner:
373 for t, line in inner:
374 if not t:
374 if not t:
375 yield t, line
375 yield t, line
376 continue
376 continue
377
377
378 if current_line_number in self.only_lines:
378 if current_line_number in self.only_lines:
379 if last_shown_line_number + 1 != current_line_number:
379 if last_shown_line_number + 1 != current_line_number:
380 yield 0, '<tr>'
380 yield 0, '<tr>'
381 yield 0, '<td class="line">...</td>'
381 yield 0, '<td class="line">...</td>'
382 yield 0, '<td id="hlcode" class="code"></td>'
382 yield 0, '<td id="hlcode" class="code"></td>'
383 yield 0, '</tr>'
383 yield 0, '</tr>'
384
384
385 yield 0, '<tr>'
385 yield 0, '<tr>'
386 if self.url:
386 if self.url:
387 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
387 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
388 self.url, current_line_number, current_line_number)
388 self.url, current_line_number, current_line_number)
389 else:
389 else:
390 yield 0, '<td class="line"><a href="">%i</a></td>' % (
390 yield 0, '<td class="line"><a href="">%i</a></td>' % (
391 current_line_number)
391 current_line_number)
392 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
392 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
393 yield 0, '</tr>'
393 yield 0, '</tr>'
394
394
395 last_shown_line_number = current_line_number
395 last_shown_line_number = current_line_number
396
396
397 current_line_number += 1
397 current_line_number += 1
398
398
399
399
400 yield 0, '</table>'
400 yield 0, '</table>'
401
401
402
402
403 def extract_phrases(text_query):
403 def extract_phrases(text_query):
404 """
404 """
405 Extracts phrases from search term string making sure phrases
405 Extracts phrases from search term string making sure phrases
406 contained in double quotes are kept together - and discarding empty values
406 contained in double quotes are kept together - and discarding empty values
407 or fully whitespace values eg.
407 or fully whitespace values eg.
408
408
409 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
409 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
410
410
411 """
411 """
412
412
413 in_phrase = False
413 in_phrase = False
414 buf = ''
414 buf = ''
415 phrases = []
415 phrases = []
416 for char in text_query:
416 for char in text_query:
417 if in_phrase:
417 if in_phrase:
418 if char == '"': # end phrase
418 if char == '"': # end phrase
419 phrases.append(buf)
419 phrases.append(buf)
420 buf = ''
420 buf = ''
421 in_phrase = False
421 in_phrase = False
422 continue
422 continue
423 else:
423 else:
424 buf += char
424 buf += char
425 continue
425 continue
426 else:
426 else:
427 if char == '"': # start phrase
427 if char == '"': # start phrase
428 in_phrase = True
428 in_phrase = True
429 phrases.append(buf)
429 phrases.append(buf)
430 buf = ''
430 buf = ''
431 continue
431 continue
432 elif char == ' ':
432 elif char == ' ':
433 phrases.append(buf)
433 phrases.append(buf)
434 buf = ''
434 buf = ''
435 continue
435 continue
436 else:
436 else:
437 buf += char
437 buf += char
438
438
439 phrases.append(buf)
439 phrases.append(buf)
440 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
440 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
441 return phrases
441 return phrases
442
442
443
443
444 def get_matching_offsets(text, phrases):
444 def get_matching_offsets(text, phrases):
445 """
445 """
446 Returns a list of string offsets in `text` that the list of `terms` match
446 Returns a list of string offsets in `text` that the list of `terms` match
447
447
448 >>> get_matching_offsets('some text here', ['some', 'here'])
448 >>> get_matching_offsets('some text here', ['some', 'here'])
449 [(0, 4), (10, 14)]
449 [(0, 4), (10, 14)]
450
450
451 """
451 """
452 offsets = []
452 offsets = []
453 for phrase in phrases:
453 for phrase in phrases:
454 for match in re.finditer(phrase, text):
454 for match in re.finditer(phrase, text):
455 offsets.append((match.start(), match.end()))
455 offsets.append((match.start(), match.end()))
456
456
457 return offsets
457 return offsets
458
458
459
459
460 def normalize_text_for_matching(x):
460 def normalize_text_for_matching(x):
461 """
461 """
462 Replaces all non alnum characters to spaces and lower cases the string,
462 Replaces all non alnum characters to spaces and lower cases the string,
463 useful for comparing two text strings without punctuation
463 useful for comparing two text strings without punctuation
464 """
464 """
465 return re.sub(r'[^\w]', ' ', x.lower())
465 return re.sub(r'[^\w]', ' ', x.lower())
466
466
467
467
468 def get_matching_line_offsets(lines, terms):
468 def get_matching_line_offsets(lines, terms):
469 """ Return a set of `lines` indices (starting from 1) matching a
469 """ Return a set of `lines` indices (starting from 1) matching a
470 text search query, along with `context` lines above/below matching lines
470 text search query, along with `context` lines above/below matching lines
471
471
472 :param lines: list of strings representing lines
472 :param lines: list of strings representing lines
473 :param terms: search term string to match in lines eg. 'some text'
473 :param terms: search term string to match in lines eg. 'some text'
474 :param context: number of lines above/below a matching line to add to result
474 :param context: number of lines above/below a matching line to add to result
475 :param max_lines: cut off for lines of interest
475 :param max_lines: cut off for lines of interest
476 eg.
476 eg.
477
477
478 text = '''
478 text = '''
479 words words words
479 words words words
480 words words words
480 words words words
481 some text some
481 some text some
482 words words words
482 words words words
483 words words words
483 words words words
484 text here what
484 text here what
485 '''
485 '''
486 get_matching_line_offsets(text, 'text', context=1)
486 get_matching_line_offsets(text, 'text', context=1)
487 {3: [(5, 9)], 6: [(0, 4)]]
487 {3: [(5, 9)], 6: [(0, 4)]]
488
488
489 """
489 """
490 matching_lines = {}
490 matching_lines = {}
491 phrases = [normalize_text_for_matching(phrase)
491 phrases = [normalize_text_for_matching(phrase)
492 for phrase in extract_phrases(terms)]
492 for phrase in extract_phrases(terms)]
493
493
494 for line_index, line in enumerate(lines, start=1):
494 for line_index, line in enumerate(lines, start=1):
495 match_offsets = get_matching_offsets(
495 match_offsets = get_matching_offsets(
496 normalize_text_for_matching(line), phrases)
496 normalize_text_for_matching(line), phrases)
497 if match_offsets:
497 if match_offsets:
498 matching_lines[line_index] = match_offsets
498 matching_lines[line_index] = match_offsets
499
499
500 return matching_lines
500 return matching_lines
501
501
502
502
503 def get_lexer_safe(mimetype=None, filepath=None):
503 def get_lexer_safe(mimetype=None, filepath=None):
504 """
504 """
505 Tries to return a relevant pygments lexer using mimetype/filepath name,
505 Tries to return a relevant pygments lexer using mimetype/filepath name,
506 defaulting to plain text if none could be found
506 defaulting to plain text if none could be found
507 """
507 """
508 lexer = None
508 lexer = None
509 try:
509 try:
510 if mimetype:
510 if mimetype:
511 lexer = get_lexer_for_mimetype(mimetype)
511 lexer = get_lexer_for_mimetype(mimetype)
512 if not lexer:
512 if not lexer:
513 lexer = get_lexer_for_filename(filepath)
513 lexer = get_lexer_for_filename(filepath)
514 except pygments.util.ClassNotFound:
514 except pygments.util.ClassNotFound:
515 pass
515 pass
516
516
517 if not lexer:
517 if not lexer:
518 lexer = get_lexer_by_name('text')
518 lexer = get_lexer_by_name('text')
519
519
520 return lexer
520 return lexer
521
521
522
522
523 def pygmentize(filenode, **kwargs):
523 def pygmentize(filenode, **kwargs):
524 """
524 """
525 pygmentize function using pygments
525 pygmentize function using pygments
526
526
527 :param filenode:
527 :param filenode:
528 """
528 """
529 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
529 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
530 return literal(code_highlight(filenode.content, lexer,
530 return literal(code_highlight(filenode.content, lexer,
531 CodeHtmlFormatter(**kwargs)))
531 CodeHtmlFormatter(**kwargs)))
532
532
533
533
534 def pygmentize_annotation(repo_name, filenode, **kwargs):
534 def pygmentize_annotation(repo_name, filenode, **kwargs):
535 """
535 """
536 pygmentize function for annotation
536 pygmentize function for annotation
537
537
538 :param filenode:
538 :param filenode:
539 """
539 """
540
540
541 color_dict = {}
541 color_dict = {}
542
542
543 def gen_color(n=10000):
543 def gen_color(n=10000):
544 """generator for getting n of evenly distributed colors using
544 """generator for getting n of evenly distributed colors using
545 hsv color and golden ratio. It always return same order of colors
545 hsv color and golden ratio. It always return same order of colors
546
546
547 :returns: RGB tuple
547 :returns: RGB tuple
548 """
548 """
549
549
550 def hsv_to_rgb(h, s, v):
550 def hsv_to_rgb(h, s, v):
551 if s == 0.0:
551 if s == 0.0:
552 return v, v, v
552 return v, v, v
553 i = int(h * 6.0) # XXX assume int() truncates!
553 i = int(h * 6.0) # XXX assume int() truncates!
554 f = (h * 6.0) - i
554 f = (h * 6.0) - i
555 p = v * (1.0 - s)
555 p = v * (1.0 - s)
556 q = v * (1.0 - s * f)
556 q = v * (1.0 - s * f)
557 t = v * (1.0 - s * (1.0 - f))
557 t = v * (1.0 - s * (1.0 - f))
558 i = i % 6
558 i = i % 6
559 if i == 0:
559 if i == 0:
560 return v, t, p
560 return v, t, p
561 if i == 1:
561 if i == 1:
562 return q, v, p
562 return q, v, p
563 if i == 2:
563 if i == 2:
564 return p, v, t
564 return p, v, t
565 if i == 3:
565 if i == 3:
566 return p, q, v
566 return p, q, v
567 if i == 4:
567 if i == 4:
568 return t, p, v
568 return t, p, v
569 if i == 5:
569 if i == 5:
570 return v, p, q
570 return v, p, q
571
571
572 golden_ratio = 0.618033988749895
572 golden_ratio = 0.618033988749895
573 h = 0.22717784590367374
573 h = 0.22717784590367374
574
574
575 for _ in xrange(n):
575 for _ in xrange(n):
576 h += golden_ratio
576 h += golden_ratio
577 h %= 1
577 h %= 1
578 HSV_tuple = [h, 0.95, 0.95]
578 HSV_tuple = [h, 0.95, 0.95]
579 RGB_tuple = hsv_to_rgb(*HSV_tuple)
579 RGB_tuple = hsv_to_rgb(*HSV_tuple)
580 yield map(lambda x: str(int(x * 256)), RGB_tuple)
580 yield map(lambda x: str(int(x * 256)), RGB_tuple)
581
581
582 cgenerator = gen_color()
582 cgenerator = gen_color()
583
583
584 def get_color_string(commit_id):
584 def get_color_string(commit_id):
585 if commit_id in color_dict:
585 if commit_id in color_dict:
586 col = color_dict[commit_id]
586 col = color_dict[commit_id]
587 else:
587 else:
588 col = color_dict[commit_id] = cgenerator.next()
588 col = color_dict[commit_id] = cgenerator.next()
589 return "color: rgb(%s)! important;" % (', '.join(col))
589 return "color: rgb(%s)! important;" % (', '.join(col))
590
590
591 def url_func(repo_name):
591 def url_func(repo_name):
592
592
593 def _url_func(commit):
593 def _url_func(commit):
594 author = commit.author
594 author = commit.author
595 date = commit.date
595 date = commit.date
596 message = tooltip(commit.message)
596 message = tooltip(commit.message)
597
597
598 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
598 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
599 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
599 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
600 "</b> %s<br/></div>")
600 "</b> %s<br/></div>")
601
601
602 tooltip_html = tooltip_html % (author, date, message)
602 tooltip_html = tooltip_html % (author, date, message)
603 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
603 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
604 uri = link_to(
604 uri = link_to(
605 lnk_format,
605 lnk_format,
606 url('changeset_home', repo_name=repo_name,
606 url('changeset_home', repo_name=repo_name,
607 revision=commit.raw_id),
607 revision=commit.raw_id),
608 style=get_color_string(commit.raw_id),
608 style=get_color_string(commit.raw_id),
609 class_='tooltip',
609 class_='tooltip',
610 title=tooltip_html
610 title=tooltip_html
611 )
611 )
612
612
613 uri += '\n'
613 uri += '\n'
614 return uri
614 return uri
615 return _url_func
615 return _url_func
616
616
617 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
617 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
618
618
619
619
620 def is_following_repo(repo_name, user_id):
620 def is_following_repo(repo_name, user_id):
621 from rhodecode.model.scm import ScmModel
621 from rhodecode.model.scm import ScmModel
622 return ScmModel().is_following_repo(repo_name, user_id)
622 return ScmModel().is_following_repo(repo_name, user_id)
623
623
624
624
625 class _Message(object):
625 class _Message(object):
626 """A message returned by ``Flash.pop_messages()``.
626 """A message returned by ``Flash.pop_messages()``.
627
627
628 Converting the message to a string returns the message text. Instances
628 Converting the message to a string returns the message text. Instances
629 also have the following attributes:
629 also have the following attributes:
630
630
631 * ``message``: the message text.
631 * ``message``: the message text.
632 * ``category``: the category specified when the message was created.
632 * ``category``: the category specified when the message was created.
633 """
633 """
634
634
635 def __init__(self, category, message):
635 def __init__(self, category, message):
636 self.category = category
636 self.category = category
637 self.message = message
637 self.message = message
638
638
639 def __str__(self):
639 def __str__(self):
640 return self.message
640 return self.message
641
641
642 __unicode__ = __str__
642 __unicode__ = __str__
643
643
644 def __html__(self):
644 def __html__(self):
645 return escape(safe_unicode(self.message))
645 return escape(safe_unicode(self.message))
646
646
647
647
648 class Flash(_Flash):
648 class Flash(_Flash):
649
649
650 def pop_messages(self):
650 def pop_messages(self):
651 """Return all accumulated messages and delete them from the session.
651 """Return all accumulated messages and delete them from the session.
652
652
653 The return value is a list of ``Message`` objects.
653 The return value is a list of ``Message`` objects.
654 """
654 """
655 from pylons import session
655 from pylons import session
656
656
657 messages = []
657 messages = []
658
658
659 # Pop the 'old' pylons flash messages. They are tuples of the form
659 # Pop the 'old' pylons flash messages. They are tuples of the form
660 # (category, message)
660 # (category, message)
661 for cat, msg in session.pop(self.session_key, []):
661 for cat, msg in session.pop(self.session_key, []):
662 messages.append(_Message(cat, msg))
662 messages.append(_Message(cat, msg))
663
663
664 # Pop the 'new' pyramid flash messages for each category as list
664 # Pop the 'new' pyramid flash messages for each category as list
665 # of strings.
665 # of strings.
666 for cat in self.categories:
666 for cat in self.categories:
667 for msg in session.pop_flash(queue=cat):
667 for msg in session.pop_flash(queue=cat):
668 messages.append(_Message(cat, msg))
668 messages.append(_Message(cat, msg))
669 # Map messages from the default queue to the 'notice' category.
669 # Map messages from the default queue to the 'notice' category.
670 for msg in session.pop_flash():
670 for msg in session.pop_flash():
671 messages.append(_Message('notice', msg))
671 messages.append(_Message('notice', msg))
672
672
673 session.save()
673 session.save()
674 return messages
674 return messages
675
675
676 flash = Flash()
676 flash = Flash()
677
677
678 #==============================================================================
678 #==============================================================================
679 # SCM FILTERS available via h.
679 # SCM FILTERS available via h.
680 #==============================================================================
680 #==============================================================================
681 from rhodecode.lib.vcs.utils import author_name, author_email
681 from rhodecode.lib.vcs.utils import author_name, author_email
682 from rhodecode.lib.utils2 import credentials_filter, age as _age
682 from rhodecode.lib.utils2 import credentials_filter, age as _age
683 from rhodecode.model.db import User, ChangesetStatus
683 from rhodecode.model.db import User, ChangesetStatus
684
684
685 age = _age
685 age = _age
686 capitalize = lambda x: x.capitalize()
686 capitalize = lambda x: x.capitalize()
687 email = author_email
687 email = author_email
688 short_id = lambda x: x[:12]
688 short_id = lambda x: x[:12]
689 hide_credentials = lambda x: ''.join(credentials_filter(x))
689 hide_credentials = lambda x: ''.join(credentials_filter(x))
690
690
691
691
692 def age_component(datetime_iso, value=None, time_is_local=False):
692 def age_component(datetime_iso, value=None, time_is_local=False):
693 title = value or format_date(datetime_iso)
693 title = value or format_date(datetime_iso)
694
694
695 # detect if we have a timezone info, otherwise, add it
695 # detect if we have a timezone info, otherwise, add it
696 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
696 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
697 tzinfo = '+00:00'
697 tzinfo = '+00:00'
698
698
699 if time_is_local:
699 if time_is_local:
700 tzinfo = time.strftime("+%H:%M",
700 tzinfo = time.strftime("+%H:%M",
701 time.gmtime(
701 time.gmtime(
702 (datetime.now() - datetime.utcnow()).seconds + 1
702 (datetime.now() - datetime.utcnow()).seconds + 1
703 )
703 )
704 )
704 )
705
705
706 return literal(
706 return literal(
707 '<time class="timeago tooltip" '
707 '<time class="timeago tooltip" '
708 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
708 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
709 datetime_iso, title, tzinfo))
709 datetime_iso, title, tzinfo))
710
710
711
711
712 def _shorten_commit_id(commit_id):
712 def _shorten_commit_id(commit_id):
713 from rhodecode import CONFIG
713 from rhodecode import CONFIG
714 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
714 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
715 return commit_id[:def_len]
715 return commit_id[:def_len]
716
716
717
717
718 def show_id(commit):
718 def show_id(commit):
719 """
719 """
720 Configurable function that shows ID
720 Configurable function that shows ID
721 by default it's r123:fffeeefffeee
721 by default it's r123:fffeeefffeee
722
722
723 :param commit: commit instance
723 :param commit: commit instance
724 """
724 """
725 from rhodecode import CONFIG
725 from rhodecode import CONFIG
726 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
726 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
727
727
728 raw_id = _shorten_commit_id(commit.raw_id)
728 raw_id = _shorten_commit_id(commit.raw_id)
729 if show_idx:
729 if show_idx:
730 return 'r%s:%s' % (commit.idx, raw_id)
730 return 'r%s:%s' % (commit.idx, raw_id)
731 else:
731 else:
732 return '%s' % (raw_id, )
732 return '%s' % (raw_id, )
733
733
734
734
735 def format_date(date):
735 def format_date(date):
736 """
736 """
737 use a standardized formatting for dates used in RhodeCode
737 use a standardized formatting for dates used in RhodeCode
738
738
739 :param date: date/datetime object
739 :param date: date/datetime object
740 :return: formatted date
740 :return: formatted date
741 """
741 """
742
742
743 if date:
743 if date:
744 _fmt = "%a, %d %b %Y %H:%M:%S"
744 _fmt = "%a, %d %b %Y %H:%M:%S"
745 return safe_unicode(date.strftime(_fmt))
745 return safe_unicode(date.strftime(_fmt))
746
746
747 return u""
747 return u""
748
748
749
749
750 class _RepoChecker(object):
750 class _RepoChecker(object):
751
751
752 def __init__(self, backend_alias):
752 def __init__(self, backend_alias):
753 self._backend_alias = backend_alias
753 self._backend_alias = backend_alias
754
754
755 def __call__(self, repository):
755 def __call__(self, repository):
756 if hasattr(repository, 'alias'):
756 if hasattr(repository, 'alias'):
757 _type = repository.alias
757 _type = repository.alias
758 elif hasattr(repository, 'repo_type'):
758 elif hasattr(repository, 'repo_type'):
759 _type = repository.repo_type
759 _type = repository.repo_type
760 else:
760 else:
761 _type = repository
761 _type = repository
762 return _type == self._backend_alias
762 return _type == self._backend_alias
763
763
764 is_git = _RepoChecker('git')
764 is_git = _RepoChecker('git')
765 is_hg = _RepoChecker('hg')
765 is_hg = _RepoChecker('hg')
766 is_svn = _RepoChecker('svn')
766 is_svn = _RepoChecker('svn')
767
767
768
768
769 def get_repo_type_by_name(repo_name):
769 def get_repo_type_by_name(repo_name):
770 repo = Repository.get_by_repo_name(repo_name)
770 repo = Repository.get_by_repo_name(repo_name)
771 return repo.repo_type
771 return repo.repo_type
772
772
773
773
774 def is_svn_without_proxy(repository):
774 def is_svn_without_proxy(repository):
775 from rhodecode import CONFIG
776 if is_svn(repository):
775 if is_svn(repository):
777 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
776 from rhodecode.model.settings import VcsSettingsModel
778 return True
777 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
778 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
779 return False
779 return False
780
780
781
781
782 def discover_user(author):
782 def discover_user(author):
783 """
783 """
784 Tries to discover RhodeCode User based on the autho string. Author string
784 Tries to discover RhodeCode User based on the autho string. Author string
785 is typically `FirstName LastName <email@address.com>`
785 is typically `FirstName LastName <email@address.com>`
786 """
786 """
787
787
788 # if author is already an instance use it for extraction
788 # if author is already an instance use it for extraction
789 if isinstance(author, User):
789 if isinstance(author, User):
790 return author
790 return author
791
791
792 # Valid email in the attribute passed, see if they're in the system
792 # Valid email in the attribute passed, see if they're in the system
793 _email = author_email(author)
793 _email = author_email(author)
794 if _email != '':
794 if _email != '':
795 user = User.get_by_email(_email, case_insensitive=True, cache=True)
795 user = User.get_by_email(_email, case_insensitive=True, cache=True)
796 if user is not None:
796 if user is not None:
797 return user
797 return user
798
798
799 # Maybe it's a username, we try to extract it and fetch by username ?
799 # Maybe it's a username, we try to extract it and fetch by username ?
800 _author = author_name(author)
800 _author = author_name(author)
801 user = User.get_by_username(_author, case_insensitive=True, cache=True)
801 user = User.get_by_username(_author, case_insensitive=True, cache=True)
802 if user is not None:
802 if user is not None:
803 return user
803 return user
804
804
805 return None
805 return None
806
806
807
807
808 def email_or_none(author):
808 def email_or_none(author):
809 # extract email from the commit string
809 # extract email from the commit string
810 _email = author_email(author)
810 _email = author_email(author)
811
811
812 # If we have an email, use it, otherwise
812 # If we have an email, use it, otherwise
813 # see if it contains a username we can get an email from
813 # see if it contains a username we can get an email from
814 if _email != '':
814 if _email != '':
815 return _email
815 return _email
816 else:
816 else:
817 user = User.get_by_username(
817 user = User.get_by_username(
818 author_name(author), case_insensitive=True, cache=True)
818 author_name(author), case_insensitive=True, cache=True)
819
819
820 if user is not None:
820 if user is not None:
821 return user.email
821 return user.email
822
822
823 # No valid email, not a valid user in the system, none!
823 # No valid email, not a valid user in the system, none!
824 return None
824 return None
825
825
826
826
827 def link_to_user(author, length=0, **kwargs):
827 def link_to_user(author, length=0, **kwargs):
828 user = discover_user(author)
828 user = discover_user(author)
829 # user can be None, but if we have it already it means we can re-use it
829 # user can be None, but if we have it already it means we can re-use it
830 # in the person() function, so we save 1 intensive-query
830 # in the person() function, so we save 1 intensive-query
831 if user:
831 if user:
832 author = user
832 author = user
833
833
834 display_person = person(author, 'username_or_name_or_email')
834 display_person = person(author, 'username_or_name_or_email')
835 if length:
835 if length:
836 display_person = shorter(display_person, length)
836 display_person = shorter(display_person, length)
837
837
838 if user:
838 if user:
839 return link_to(
839 return link_to(
840 escape(display_person),
840 escape(display_person),
841 url('user_profile', username=user.username),
841 url('user_profile', username=user.username),
842 **kwargs)
842 **kwargs)
843 else:
843 else:
844 return escape(display_person)
844 return escape(display_person)
845
845
846
846
847 def person(author, show_attr="username_and_name"):
847 def person(author, show_attr="username_and_name"):
848 user = discover_user(author)
848 user = discover_user(author)
849 if user:
849 if user:
850 return getattr(user, show_attr)
850 return getattr(user, show_attr)
851 else:
851 else:
852 _author = author_name(author)
852 _author = author_name(author)
853 _email = email(author)
853 _email = email(author)
854 return _author or _email
854 return _author or _email
855
855
856
856
857 def author_string(email):
857 def author_string(email):
858 if email:
858 if email:
859 user = User.get_by_email(email, case_insensitive=True, cache=True)
859 user = User.get_by_email(email, case_insensitive=True, cache=True)
860 if user:
860 if user:
861 if user.firstname or user.lastname:
861 if user.firstname or user.lastname:
862 return '%s %s &lt;%s&gt;' % (user.firstname, user.lastname, email)
862 return '%s %s &lt;%s&gt;' % (user.firstname, user.lastname, email)
863 else:
863 else:
864 return email
864 return email
865 else:
865 else:
866 return email
866 return email
867 else:
867 else:
868 return None
868 return None
869
869
870
870
871 def person_by_id(id_, show_attr="username_and_name"):
871 def person_by_id(id_, show_attr="username_and_name"):
872 # attr to return from fetched user
872 # attr to return from fetched user
873 person_getter = lambda usr: getattr(usr, show_attr)
873 person_getter = lambda usr: getattr(usr, show_attr)
874
874
875 #maybe it's an ID ?
875 #maybe it's an ID ?
876 if str(id_).isdigit() or isinstance(id_, int):
876 if str(id_).isdigit() or isinstance(id_, int):
877 id_ = int(id_)
877 id_ = int(id_)
878 user = User.get(id_)
878 user = User.get(id_)
879 if user is not None:
879 if user is not None:
880 return person_getter(user)
880 return person_getter(user)
881 return id_
881 return id_
882
882
883
883
884 def gravatar_with_user(author, show_disabled=False):
884 def gravatar_with_user(author, show_disabled=False):
885 from rhodecode.lib.utils import PartialRenderer
885 from rhodecode.lib.utils import PartialRenderer
886 _render = PartialRenderer('base/base.html')
886 _render = PartialRenderer('base/base.html')
887 return _render('gravatar_with_user', author, show_disabled=show_disabled)
887 return _render('gravatar_with_user', author, show_disabled=show_disabled)
888
888
889
889
890 def desc_stylize(value):
890 def desc_stylize(value):
891 """
891 """
892 converts tags from value into html equivalent
892 converts tags from value into html equivalent
893
893
894 :param value:
894 :param value:
895 """
895 """
896 if not value:
896 if not value:
897 return ''
897 return ''
898
898
899 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
899 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
900 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
900 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
901 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
901 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
902 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
902 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
903 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
903 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
904 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
904 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
905 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
905 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
906 '<div class="metatag" tag="lang">\\2</div>', value)
906 '<div class="metatag" tag="lang">\\2</div>', value)
907 value = re.sub(r'\[([a-z]+)\]',
907 value = re.sub(r'\[([a-z]+)\]',
908 '<div class="metatag" tag="\\1">\\1</div>', value)
908 '<div class="metatag" tag="\\1">\\1</div>', value)
909
909
910 return value
910 return value
911
911
912
912
913 def escaped_stylize(value):
913 def escaped_stylize(value):
914 """
914 """
915 converts tags from value into html equivalent, but escaping its value first
915 converts tags from value into html equivalent, but escaping its value first
916 """
916 """
917 if not value:
917 if not value:
918 return ''
918 return ''
919
919
920 # Using default webhelper escape method, but has to force it as a
920 # Using default webhelper escape method, but has to force it as a
921 # plain unicode instead of a markup tag to be used in regex expressions
921 # plain unicode instead of a markup tag to be used in regex expressions
922 value = unicode(escape(safe_unicode(value)))
922 value = unicode(escape(safe_unicode(value)))
923
923
924 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
924 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
925 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
925 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
926 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
926 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
927 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
927 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
928 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
928 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
929 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
929 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
930 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
930 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
931 '<div class="metatag" tag="lang">\\2</div>', value)
931 '<div class="metatag" tag="lang">\\2</div>', value)
932 value = re.sub(r'\[([a-z]+)\]',
932 value = re.sub(r'\[([a-z]+)\]',
933 '<div class="metatag" tag="\\1">\\1</div>', value)
933 '<div class="metatag" tag="\\1">\\1</div>', value)
934
934
935 return value
935 return value
936
936
937
937
938 def bool2icon(value):
938 def bool2icon(value):
939 """
939 """
940 Returns boolean value of a given value, represented as html element with
940 Returns boolean value of a given value, represented as html element with
941 classes that will represent icons
941 classes that will represent icons
942
942
943 :param value: given value to convert to html node
943 :param value: given value to convert to html node
944 """
944 """
945
945
946 if value: # does bool conversion
946 if value: # does bool conversion
947 return HTML.tag('i', class_="icon-true")
947 return HTML.tag('i', class_="icon-true")
948 else: # not true as bool
948 else: # not true as bool
949 return HTML.tag('i', class_="icon-false")
949 return HTML.tag('i', class_="icon-false")
950
950
951
951
952 #==============================================================================
952 #==============================================================================
953 # PERMS
953 # PERMS
954 #==============================================================================
954 #==============================================================================
955 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
955 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
956 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
956 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
957 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
957 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
958 csrf_token_key
958 csrf_token_key
959
959
960
960
961 #==============================================================================
961 #==============================================================================
962 # GRAVATAR URL
962 # GRAVATAR URL
963 #==============================================================================
963 #==============================================================================
964 class InitialsGravatar(object):
964 class InitialsGravatar(object):
965 def __init__(self, email_address, first_name, last_name, size=30,
965 def __init__(self, email_address, first_name, last_name, size=30,
966 background=None, text_color='#fff'):
966 background=None, text_color='#fff'):
967 self.size = size
967 self.size = size
968 self.first_name = first_name
968 self.first_name = first_name
969 self.last_name = last_name
969 self.last_name = last_name
970 self.email_address = email_address
970 self.email_address = email_address
971 self.background = background or self.str2color(email_address)
971 self.background = background or self.str2color(email_address)
972 self.text_color = text_color
972 self.text_color = text_color
973
973
974 def get_color_bank(self):
974 def get_color_bank(self):
975 """
975 """
976 returns a predefined list of colors that gravatars can use.
976 returns a predefined list of colors that gravatars can use.
977 Those are randomized distinct colors that guarantee readability and
977 Those are randomized distinct colors that guarantee readability and
978 uniqueness.
978 uniqueness.
979
979
980 generated with: http://phrogz.net/css/distinct-colors.html
980 generated with: http://phrogz.net/css/distinct-colors.html
981 """
981 """
982 return [
982 return [
983 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
983 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
984 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
984 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
985 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
985 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
986 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
986 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
987 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
987 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
988 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
988 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
989 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
989 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
990 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
990 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
991 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
991 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
992 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
992 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
993 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
993 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
994 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
994 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
995 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
995 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
996 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
996 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
997 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
997 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
998 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
998 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
999 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
999 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1000 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1000 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1001 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1001 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1002 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1002 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1003 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1003 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1004 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1004 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1005 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1005 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1006 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1006 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1007 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1007 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1008 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1008 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1009 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1009 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1010 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1010 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1011 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1011 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1012 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1012 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1013 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1013 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1014 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1014 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1015 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1015 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1016 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1016 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1017 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1017 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1018 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1018 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1019 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1019 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1020 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1020 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1021 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1021 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1022 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1022 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1023 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1023 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1024 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1024 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1025 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1025 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1026 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1026 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1027 '#4f8c46', '#368dd9', '#5c0073'
1027 '#4f8c46', '#368dd9', '#5c0073'
1028 ]
1028 ]
1029
1029
1030 def rgb_to_hex_color(self, rgb_tuple):
1030 def rgb_to_hex_color(self, rgb_tuple):
1031 """
1031 """
1032 Converts an rgb_tuple passed to an hex color.
1032 Converts an rgb_tuple passed to an hex color.
1033
1033
1034 :param rgb_tuple: tuple with 3 ints represents rgb color space
1034 :param rgb_tuple: tuple with 3 ints represents rgb color space
1035 """
1035 """
1036 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1036 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1037
1037
1038 def email_to_int_list(self, email_str):
1038 def email_to_int_list(self, email_str):
1039 """
1039 """
1040 Get every byte of the hex digest value of email and turn it to integer.
1040 Get every byte of the hex digest value of email and turn it to integer.
1041 It's going to be always between 0-255
1041 It's going to be always between 0-255
1042 """
1042 """
1043 digest = md5_safe(email_str.lower())
1043 digest = md5_safe(email_str.lower())
1044 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1044 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1045
1045
1046 def pick_color_bank_index(self, email_str, color_bank):
1046 def pick_color_bank_index(self, email_str, color_bank):
1047 return self.email_to_int_list(email_str)[0] % len(color_bank)
1047 return self.email_to_int_list(email_str)[0] % len(color_bank)
1048
1048
1049 def str2color(self, email_str):
1049 def str2color(self, email_str):
1050 """
1050 """
1051 Tries to map in a stable algorithm an email to color
1051 Tries to map in a stable algorithm an email to color
1052
1052
1053 :param email_str:
1053 :param email_str:
1054 """
1054 """
1055 color_bank = self.get_color_bank()
1055 color_bank = self.get_color_bank()
1056 # pick position (module it's length so we always find it in the
1056 # pick position (module it's length so we always find it in the
1057 # bank even if it's smaller than 256 values
1057 # bank even if it's smaller than 256 values
1058 pos = self.pick_color_bank_index(email_str, color_bank)
1058 pos = self.pick_color_bank_index(email_str, color_bank)
1059 return color_bank[pos]
1059 return color_bank[pos]
1060
1060
1061 def normalize_email(self, email_address):
1061 def normalize_email(self, email_address):
1062 import unicodedata
1062 import unicodedata
1063 # default host used to fill in the fake/missing email
1063 # default host used to fill in the fake/missing email
1064 default_host = u'localhost'
1064 default_host = u'localhost'
1065
1065
1066 if not email_address:
1066 if not email_address:
1067 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1067 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1068
1068
1069 email_address = safe_unicode(email_address)
1069 email_address = safe_unicode(email_address)
1070
1070
1071 if u'@' not in email_address:
1071 if u'@' not in email_address:
1072 email_address = u'%s@%s' % (email_address, default_host)
1072 email_address = u'%s@%s' % (email_address, default_host)
1073
1073
1074 if email_address.endswith(u'@'):
1074 if email_address.endswith(u'@'):
1075 email_address = u'%s%s' % (email_address, default_host)
1075 email_address = u'%s%s' % (email_address, default_host)
1076
1076
1077 email_address = unicodedata.normalize('NFKD', email_address)\
1077 email_address = unicodedata.normalize('NFKD', email_address)\
1078 .encode('ascii', 'ignore')
1078 .encode('ascii', 'ignore')
1079 return email_address
1079 return email_address
1080
1080
1081 def get_initials(self):
1081 def get_initials(self):
1082 """
1082 """
1083 Returns 2 letter initials calculated based on the input.
1083 Returns 2 letter initials calculated based on the input.
1084 The algorithm picks first given email address, and takes first letter
1084 The algorithm picks first given email address, and takes first letter
1085 of part before @, and then the first letter of server name. In case
1085 of part before @, and then the first letter of server name. In case
1086 the part before @ is in a format of `somestring.somestring2` it replaces
1086 the part before @ is in a format of `somestring.somestring2` it replaces
1087 the server letter with first letter of somestring2
1087 the server letter with first letter of somestring2
1088
1088
1089 In case function was initialized with both first and lastname, this
1089 In case function was initialized with both first and lastname, this
1090 overrides the extraction from email by first letter of the first and
1090 overrides the extraction from email by first letter of the first and
1091 last name. We add special logic to that functionality, In case Full name
1091 last name. We add special logic to that functionality, In case Full name
1092 is compound, like Guido Von Rossum, we use last part of the last name
1092 is compound, like Guido Von Rossum, we use last part of the last name
1093 (Von Rossum) picking `R`.
1093 (Von Rossum) picking `R`.
1094
1094
1095 Function also normalizes the non-ascii characters to they ascii
1095 Function also normalizes the non-ascii characters to they ascii
1096 representation, eg Δ„ => A
1096 representation, eg Δ„ => A
1097 """
1097 """
1098 import unicodedata
1098 import unicodedata
1099 # replace non-ascii to ascii
1099 # replace non-ascii to ascii
1100 first_name = unicodedata.normalize(
1100 first_name = unicodedata.normalize(
1101 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1101 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1102 last_name = unicodedata.normalize(
1102 last_name = unicodedata.normalize(
1103 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1103 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1104
1104
1105 # do NFKD encoding, and also make sure email has proper format
1105 # do NFKD encoding, and also make sure email has proper format
1106 email_address = self.normalize_email(self.email_address)
1106 email_address = self.normalize_email(self.email_address)
1107
1107
1108 # first push the email initials
1108 # first push the email initials
1109 prefix, server = email_address.split('@', 1)
1109 prefix, server = email_address.split('@', 1)
1110
1110
1111 # check if prefix is maybe a 'firstname.lastname' syntax
1111 # check if prefix is maybe a 'firstname.lastname' syntax
1112 _dot_split = prefix.rsplit('.', 1)
1112 _dot_split = prefix.rsplit('.', 1)
1113 if len(_dot_split) == 2:
1113 if len(_dot_split) == 2:
1114 initials = [_dot_split[0][0], _dot_split[1][0]]
1114 initials = [_dot_split[0][0], _dot_split[1][0]]
1115 else:
1115 else:
1116 initials = [prefix[0], server[0]]
1116 initials = [prefix[0], server[0]]
1117
1117
1118 # then try to replace either firtname or lastname
1118 # then try to replace either firtname or lastname
1119 fn_letter = (first_name or " ")[0].strip()
1119 fn_letter = (first_name or " ")[0].strip()
1120 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1120 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1121
1121
1122 if fn_letter:
1122 if fn_letter:
1123 initials[0] = fn_letter
1123 initials[0] = fn_letter
1124
1124
1125 if ln_letter:
1125 if ln_letter:
1126 initials[1] = ln_letter
1126 initials[1] = ln_letter
1127
1127
1128 return ''.join(initials).upper()
1128 return ''.join(initials).upper()
1129
1129
1130 def get_img_data_by_type(self, font_family, img_type):
1130 def get_img_data_by_type(self, font_family, img_type):
1131 default_user = """
1131 default_user = """
1132 <svg xmlns="http://www.w3.org/2000/svg"
1132 <svg xmlns="http://www.w3.org/2000/svg"
1133 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1133 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1134 viewBox="-15 -10 439.165 429.164"
1134 viewBox="-15 -10 439.165 429.164"
1135
1135
1136 xml:space="preserve"
1136 xml:space="preserve"
1137 style="background:{background};" >
1137 style="background:{background};" >
1138
1138
1139 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1139 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1140 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1140 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1141 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1141 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1142 168.596,153.916,216.671,
1142 168.596,153.916,216.671,
1143 204.583,216.671z" fill="{text_color}"/>
1143 204.583,216.671z" fill="{text_color}"/>
1144 <path d="M407.164,374.717L360.88,
1144 <path d="M407.164,374.717L360.88,
1145 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1145 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1146 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1146 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1147 15.366-44.203,23.488-69.076,23.488c-24.877,
1147 15.366-44.203,23.488-69.076,23.488c-24.877,
1148 0-48.762-8.122-69.078-23.488
1148 0-48.762-8.122-69.078-23.488
1149 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1149 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1150 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1150 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1151 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1151 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1152 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1152 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1153 19.402-10.527 C409.699,390.129,
1153 19.402-10.527 C409.699,390.129,
1154 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1154 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1155 </svg>""".format(
1155 </svg>""".format(
1156 size=self.size,
1156 size=self.size,
1157 background='#979797', # @grey4
1157 background='#979797', # @grey4
1158 text_color=self.text_color,
1158 text_color=self.text_color,
1159 font_family=font_family)
1159 font_family=font_family)
1160
1160
1161 return {
1161 return {
1162 "default_user": default_user
1162 "default_user": default_user
1163 }[img_type]
1163 }[img_type]
1164
1164
1165 def get_img_data(self, svg_type=None):
1165 def get_img_data(self, svg_type=None):
1166 """
1166 """
1167 generates the svg metadata for image
1167 generates the svg metadata for image
1168 """
1168 """
1169
1169
1170 font_family = ','.join([
1170 font_family = ','.join([
1171 'proximanovaregular',
1171 'proximanovaregular',
1172 'Proxima Nova Regular',
1172 'Proxima Nova Regular',
1173 'Proxima Nova',
1173 'Proxima Nova',
1174 'Arial',
1174 'Arial',
1175 'Lucida Grande',
1175 'Lucida Grande',
1176 'sans-serif'
1176 'sans-serif'
1177 ])
1177 ])
1178 if svg_type:
1178 if svg_type:
1179 return self.get_img_data_by_type(font_family, svg_type)
1179 return self.get_img_data_by_type(font_family, svg_type)
1180
1180
1181 initials = self.get_initials()
1181 initials = self.get_initials()
1182 img_data = """
1182 img_data = """
1183 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1183 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1184 width="{size}" height="{size}"
1184 width="{size}" height="{size}"
1185 style="width: 100%; height: 100%; background-color: {background}"
1185 style="width: 100%; height: 100%; background-color: {background}"
1186 viewBox="0 0 {size} {size}">
1186 viewBox="0 0 {size} {size}">
1187 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1187 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1188 pointer-events="auto" fill="{text_color}"
1188 pointer-events="auto" fill="{text_color}"
1189 font-family="{font_family}"
1189 font-family="{font_family}"
1190 style="font-weight: 400; font-size: {f_size}px;">{text}
1190 style="font-weight: 400; font-size: {f_size}px;">{text}
1191 </text>
1191 </text>
1192 </svg>""".format(
1192 </svg>""".format(
1193 size=self.size,
1193 size=self.size,
1194 f_size=self.size/1.85, # scale the text inside the box nicely
1194 f_size=self.size/1.85, # scale the text inside the box nicely
1195 background=self.background,
1195 background=self.background,
1196 text_color=self.text_color,
1196 text_color=self.text_color,
1197 text=initials.upper(),
1197 text=initials.upper(),
1198 font_family=font_family)
1198 font_family=font_family)
1199
1199
1200 return img_data
1200 return img_data
1201
1201
1202 def generate_svg(self, svg_type=None):
1202 def generate_svg(self, svg_type=None):
1203 img_data = self.get_img_data(svg_type)
1203 img_data = self.get_img_data(svg_type)
1204 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1204 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1205
1205
1206
1206
1207 def initials_gravatar(email_address, first_name, last_name, size=30):
1207 def initials_gravatar(email_address, first_name, last_name, size=30):
1208 svg_type = None
1208 svg_type = None
1209 if email_address == User.DEFAULT_USER_EMAIL:
1209 if email_address == User.DEFAULT_USER_EMAIL:
1210 svg_type = 'default_user'
1210 svg_type = 'default_user'
1211 klass = InitialsGravatar(email_address, first_name, last_name, size)
1211 klass = InitialsGravatar(email_address, first_name, last_name, size)
1212 return klass.generate_svg(svg_type=svg_type)
1212 return klass.generate_svg(svg_type=svg_type)
1213
1213
1214
1214
1215 def gravatar_url(email_address, size=30):
1215 def gravatar_url(email_address, size=30):
1216 # doh, we need to re-import those to mock it later
1216 # doh, we need to re-import those to mock it later
1217 from pylons import tmpl_context as c
1217 from pylons import tmpl_context as c
1218
1218
1219 _use_gravatar = c.visual.use_gravatar
1219 _use_gravatar = c.visual.use_gravatar
1220 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1220 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1221
1221
1222 email_address = email_address or User.DEFAULT_USER_EMAIL
1222 email_address = email_address or User.DEFAULT_USER_EMAIL
1223 if isinstance(email_address, unicode):
1223 if isinstance(email_address, unicode):
1224 # hashlib crashes on unicode items
1224 # hashlib crashes on unicode items
1225 email_address = safe_str(email_address)
1225 email_address = safe_str(email_address)
1226
1226
1227 # empty email or default user
1227 # empty email or default user
1228 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1228 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1229 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1229 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1230
1230
1231 if _use_gravatar:
1231 if _use_gravatar:
1232 # TODO: Disuse pyramid thread locals. Think about another solution to
1232 # TODO: Disuse pyramid thread locals. Think about another solution to
1233 # get the host and schema here.
1233 # get the host and schema here.
1234 request = get_current_request()
1234 request = get_current_request()
1235 tmpl = safe_str(_gravatar_url)
1235 tmpl = safe_str(_gravatar_url)
1236 tmpl = tmpl.replace('{email}', email_address)\
1236 tmpl = tmpl.replace('{email}', email_address)\
1237 .replace('{md5email}', md5_safe(email_address.lower())) \
1237 .replace('{md5email}', md5_safe(email_address.lower())) \
1238 .replace('{netloc}', request.host)\
1238 .replace('{netloc}', request.host)\
1239 .replace('{scheme}', request.scheme)\
1239 .replace('{scheme}', request.scheme)\
1240 .replace('{size}', safe_str(size))
1240 .replace('{size}', safe_str(size))
1241 return tmpl
1241 return tmpl
1242 else:
1242 else:
1243 return initials_gravatar(email_address, '', '', size=size)
1243 return initials_gravatar(email_address, '', '', size=size)
1244
1244
1245
1245
1246 class Page(_Page):
1246 class Page(_Page):
1247 """
1247 """
1248 Custom pager to match rendering style with paginator
1248 Custom pager to match rendering style with paginator
1249 """
1249 """
1250
1250
1251 def _get_pos(self, cur_page, max_page, items):
1251 def _get_pos(self, cur_page, max_page, items):
1252 edge = (items / 2) + 1
1252 edge = (items / 2) + 1
1253 if (cur_page <= edge):
1253 if (cur_page <= edge):
1254 radius = max(items / 2, items - cur_page)
1254 radius = max(items / 2, items - cur_page)
1255 elif (max_page - cur_page) < edge:
1255 elif (max_page - cur_page) < edge:
1256 radius = (items - 1) - (max_page - cur_page)
1256 radius = (items - 1) - (max_page - cur_page)
1257 else:
1257 else:
1258 radius = items / 2
1258 radius = items / 2
1259
1259
1260 left = max(1, (cur_page - (radius)))
1260 left = max(1, (cur_page - (radius)))
1261 right = min(max_page, cur_page + (radius))
1261 right = min(max_page, cur_page + (radius))
1262 return left, cur_page, right
1262 return left, cur_page, right
1263
1263
1264 def _range(self, regexp_match):
1264 def _range(self, regexp_match):
1265 """
1265 """
1266 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1266 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1267
1267
1268 Arguments:
1268 Arguments:
1269
1269
1270 regexp_match
1270 regexp_match
1271 A "re" (regular expressions) match object containing the
1271 A "re" (regular expressions) match object containing the
1272 radius of linked pages around the current page in
1272 radius of linked pages around the current page in
1273 regexp_match.group(1) as a string
1273 regexp_match.group(1) as a string
1274
1274
1275 This function is supposed to be called as a callable in
1275 This function is supposed to be called as a callable in
1276 re.sub.
1276 re.sub.
1277
1277
1278 """
1278 """
1279 radius = int(regexp_match.group(1))
1279 radius = int(regexp_match.group(1))
1280
1280
1281 # Compute the first and last page number within the radius
1281 # Compute the first and last page number within the radius
1282 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1282 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1283 # -> leftmost_page = 5
1283 # -> leftmost_page = 5
1284 # -> rightmost_page = 9
1284 # -> rightmost_page = 9
1285 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1285 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1286 self.last_page,
1286 self.last_page,
1287 (radius * 2) + 1)
1287 (radius * 2) + 1)
1288 nav_items = []
1288 nav_items = []
1289
1289
1290 # Create a link to the first page (unless we are on the first page
1290 # Create a link to the first page (unless we are on the first page
1291 # or there would be no need to insert '..' spacers)
1291 # or there would be no need to insert '..' spacers)
1292 if self.page != self.first_page and self.first_page < leftmost_page:
1292 if self.page != self.first_page and self.first_page < leftmost_page:
1293 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1293 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1294
1294
1295 # Insert dots if there are pages between the first page
1295 # Insert dots if there are pages between the first page
1296 # and the currently displayed page range
1296 # and the currently displayed page range
1297 if leftmost_page - self.first_page > 1:
1297 if leftmost_page - self.first_page > 1:
1298 # Wrap in a SPAN tag if nolink_attr is set
1298 # Wrap in a SPAN tag if nolink_attr is set
1299 text = '..'
1299 text = '..'
1300 if self.dotdot_attr:
1300 if self.dotdot_attr:
1301 text = HTML.span(c=text, **self.dotdot_attr)
1301 text = HTML.span(c=text, **self.dotdot_attr)
1302 nav_items.append(text)
1302 nav_items.append(text)
1303
1303
1304 for thispage in xrange(leftmost_page, rightmost_page + 1):
1304 for thispage in xrange(leftmost_page, rightmost_page + 1):
1305 # Hilight the current page number and do not use a link
1305 # Hilight the current page number and do not use a link
1306 if thispage == self.page:
1306 if thispage == self.page:
1307 text = '%s' % (thispage,)
1307 text = '%s' % (thispage,)
1308 # Wrap in a SPAN tag if nolink_attr is set
1308 # Wrap in a SPAN tag if nolink_attr is set
1309 if self.curpage_attr:
1309 if self.curpage_attr:
1310 text = HTML.span(c=text, **self.curpage_attr)
1310 text = HTML.span(c=text, **self.curpage_attr)
1311 nav_items.append(text)
1311 nav_items.append(text)
1312 # Otherwise create just a link to that page
1312 # Otherwise create just a link to that page
1313 else:
1313 else:
1314 text = '%s' % (thispage,)
1314 text = '%s' % (thispage,)
1315 nav_items.append(self._pagerlink(thispage, text))
1315 nav_items.append(self._pagerlink(thispage, text))
1316
1316
1317 # Insert dots if there are pages between the displayed
1317 # Insert dots if there are pages between the displayed
1318 # page numbers and the end of the page range
1318 # page numbers and the end of the page range
1319 if self.last_page - rightmost_page > 1:
1319 if self.last_page - rightmost_page > 1:
1320 text = '..'
1320 text = '..'
1321 # Wrap in a SPAN tag if nolink_attr is set
1321 # Wrap in a SPAN tag if nolink_attr is set
1322 if self.dotdot_attr:
1322 if self.dotdot_attr:
1323 text = HTML.span(c=text, **self.dotdot_attr)
1323 text = HTML.span(c=text, **self.dotdot_attr)
1324 nav_items.append(text)
1324 nav_items.append(text)
1325
1325
1326 # Create a link to the very last page (unless we are on the last
1326 # Create a link to the very last page (unless we are on the last
1327 # page or there would be no need to insert '..' spacers)
1327 # page or there would be no need to insert '..' spacers)
1328 if self.page != self.last_page and rightmost_page < self.last_page:
1328 if self.page != self.last_page and rightmost_page < self.last_page:
1329 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1329 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1330
1330
1331 ## prerender links
1331 ## prerender links
1332 #_page_link = url.current()
1332 #_page_link = url.current()
1333 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1333 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1334 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1334 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1335 return self.separator.join(nav_items)
1335 return self.separator.join(nav_items)
1336
1336
1337 def pager(self, format='~2~', page_param='page', partial_param='partial',
1337 def pager(self, format='~2~', page_param='page', partial_param='partial',
1338 show_if_single_page=False, separator=' ', onclick=None,
1338 show_if_single_page=False, separator=' ', onclick=None,
1339 symbol_first='<<', symbol_last='>>',
1339 symbol_first='<<', symbol_last='>>',
1340 symbol_previous='<', symbol_next='>',
1340 symbol_previous='<', symbol_next='>',
1341 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1341 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1342 curpage_attr={'class': 'pager_curpage'},
1342 curpage_attr={'class': 'pager_curpage'},
1343 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1343 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1344
1344
1345 self.curpage_attr = curpage_attr
1345 self.curpage_attr = curpage_attr
1346 self.separator = separator
1346 self.separator = separator
1347 self.pager_kwargs = kwargs
1347 self.pager_kwargs = kwargs
1348 self.page_param = page_param
1348 self.page_param = page_param
1349 self.partial_param = partial_param
1349 self.partial_param = partial_param
1350 self.onclick = onclick
1350 self.onclick = onclick
1351 self.link_attr = link_attr
1351 self.link_attr = link_attr
1352 self.dotdot_attr = dotdot_attr
1352 self.dotdot_attr = dotdot_attr
1353
1353
1354 # Don't show navigator if there is no more than one page
1354 # Don't show navigator if there is no more than one page
1355 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1355 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1356 return ''
1356 return ''
1357
1357
1358 from string import Template
1358 from string import Template
1359 # Replace ~...~ in token format by range of pages
1359 # Replace ~...~ in token format by range of pages
1360 result = re.sub(r'~(\d+)~', self._range, format)
1360 result = re.sub(r'~(\d+)~', self._range, format)
1361
1361
1362 # Interpolate '%' variables
1362 # Interpolate '%' variables
1363 result = Template(result).safe_substitute({
1363 result = Template(result).safe_substitute({
1364 'first_page': self.first_page,
1364 'first_page': self.first_page,
1365 'last_page': self.last_page,
1365 'last_page': self.last_page,
1366 'page': self.page,
1366 'page': self.page,
1367 'page_count': self.page_count,
1367 'page_count': self.page_count,
1368 'items_per_page': self.items_per_page,
1368 'items_per_page': self.items_per_page,
1369 'first_item': self.first_item,
1369 'first_item': self.first_item,
1370 'last_item': self.last_item,
1370 'last_item': self.last_item,
1371 'item_count': self.item_count,
1371 'item_count': self.item_count,
1372 'link_first': self.page > self.first_page and \
1372 'link_first': self.page > self.first_page and \
1373 self._pagerlink(self.first_page, symbol_first) or '',
1373 self._pagerlink(self.first_page, symbol_first) or '',
1374 'link_last': self.page < self.last_page and \
1374 'link_last': self.page < self.last_page and \
1375 self._pagerlink(self.last_page, symbol_last) or '',
1375 self._pagerlink(self.last_page, symbol_last) or '',
1376 'link_previous': self.previous_page and \
1376 'link_previous': self.previous_page and \
1377 self._pagerlink(self.previous_page, symbol_previous) \
1377 self._pagerlink(self.previous_page, symbol_previous) \
1378 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1378 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1379 'link_next': self.next_page and \
1379 'link_next': self.next_page and \
1380 self._pagerlink(self.next_page, symbol_next) \
1380 self._pagerlink(self.next_page, symbol_next) \
1381 or HTML.span(symbol_next, class_="pg-next disabled")
1381 or HTML.span(symbol_next, class_="pg-next disabled")
1382 })
1382 })
1383
1383
1384 return literal(result)
1384 return literal(result)
1385
1385
1386
1386
1387 #==============================================================================
1387 #==============================================================================
1388 # REPO PAGER, PAGER FOR REPOSITORY
1388 # REPO PAGER, PAGER FOR REPOSITORY
1389 #==============================================================================
1389 #==============================================================================
1390 class RepoPage(Page):
1390 class RepoPage(Page):
1391
1391
1392 def __init__(self, collection, page=1, items_per_page=20,
1392 def __init__(self, collection, page=1, items_per_page=20,
1393 item_count=None, url=None, **kwargs):
1393 item_count=None, url=None, **kwargs):
1394
1394
1395 """Create a "RepoPage" instance. special pager for paging
1395 """Create a "RepoPage" instance. special pager for paging
1396 repository
1396 repository
1397 """
1397 """
1398 self._url_generator = url
1398 self._url_generator = url
1399
1399
1400 # Safe the kwargs class-wide so they can be used in the pager() method
1400 # Safe the kwargs class-wide so they can be used in the pager() method
1401 self.kwargs = kwargs
1401 self.kwargs = kwargs
1402
1402
1403 # Save a reference to the collection
1403 # Save a reference to the collection
1404 self.original_collection = collection
1404 self.original_collection = collection
1405
1405
1406 self.collection = collection
1406 self.collection = collection
1407
1407
1408 # The self.page is the number of the current page.
1408 # The self.page is the number of the current page.
1409 # The first page has the number 1!
1409 # The first page has the number 1!
1410 try:
1410 try:
1411 self.page = int(page) # make it int() if we get it as a string
1411 self.page = int(page) # make it int() if we get it as a string
1412 except (ValueError, TypeError):
1412 except (ValueError, TypeError):
1413 self.page = 1
1413 self.page = 1
1414
1414
1415 self.items_per_page = items_per_page
1415 self.items_per_page = items_per_page
1416
1416
1417 # Unless the user tells us how many items the collections has
1417 # Unless the user tells us how many items the collections has
1418 # we calculate that ourselves.
1418 # we calculate that ourselves.
1419 if item_count is not None:
1419 if item_count is not None:
1420 self.item_count = item_count
1420 self.item_count = item_count
1421 else:
1421 else:
1422 self.item_count = len(self.collection)
1422 self.item_count = len(self.collection)
1423
1423
1424 # Compute the number of the first and last available page
1424 # Compute the number of the first and last available page
1425 if self.item_count > 0:
1425 if self.item_count > 0:
1426 self.first_page = 1
1426 self.first_page = 1
1427 self.page_count = int(math.ceil(float(self.item_count) /
1427 self.page_count = int(math.ceil(float(self.item_count) /
1428 self.items_per_page))
1428 self.items_per_page))
1429 self.last_page = self.first_page + self.page_count - 1
1429 self.last_page = self.first_page + self.page_count - 1
1430
1430
1431 # Make sure that the requested page number is the range of
1431 # Make sure that the requested page number is the range of
1432 # valid pages
1432 # valid pages
1433 if self.page > self.last_page:
1433 if self.page > self.last_page:
1434 self.page = self.last_page
1434 self.page = self.last_page
1435 elif self.page < self.first_page:
1435 elif self.page < self.first_page:
1436 self.page = self.first_page
1436 self.page = self.first_page
1437
1437
1438 # Note: the number of items on this page can be less than
1438 # Note: the number of items on this page can be less than
1439 # items_per_page if the last page is not full
1439 # items_per_page if the last page is not full
1440 self.first_item = max(0, (self.item_count) - (self.page *
1440 self.first_item = max(0, (self.item_count) - (self.page *
1441 items_per_page))
1441 items_per_page))
1442 self.last_item = ((self.item_count - 1) - items_per_page *
1442 self.last_item = ((self.item_count - 1) - items_per_page *
1443 (self.page - 1))
1443 (self.page - 1))
1444
1444
1445 self.items = list(self.collection[self.first_item:self.last_item + 1])
1445 self.items = list(self.collection[self.first_item:self.last_item + 1])
1446
1446
1447 # Links to previous and next page
1447 # Links to previous and next page
1448 if self.page > self.first_page:
1448 if self.page > self.first_page:
1449 self.previous_page = self.page - 1
1449 self.previous_page = self.page - 1
1450 else:
1450 else:
1451 self.previous_page = None
1451 self.previous_page = None
1452
1452
1453 if self.page < self.last_page:
1453 if self.page < self.last_page:
1454 self.next_page = self.page + 1
1454 self.next_page = self.page + 1
1455 else:
1455 else:
1456 self.next_page = None
1456 self.next_page = None
1457
1457
1458 # No items available
1458 # No items available
1459 else:
1459 else:
1460 self.first_page = None
1460 self.first_page = None
1461 self.page_count = 0
1461 self.page_count = 0
1462 self.last_page = None
1462 self.last_page = None
1463 self.first_item = None
1463 self.first_item = None
1464 self.last_item = None
1464 self.last_item = None
1465 self.previous_page = None
1465 self.previous_page = None
1466 self.next_page = None
1466 self.next_page = None
1467 self.items = []
1467 self.items = []
1468
1468
1469 # This is a subclass of the 'list' type. Initialise the list now.
1469 # This is a subclass of the 'list' type. Initialise the list now.
1470 list.__init__(self, reversed(self.items))
1470 list.__init__(self, reversed(self.items))
1471
1471
1472
1472
1473 def changed_tooltip(nodes):
1473 def changed_tooltip(nodes):
1474 """
1474 """
1475 Generates a html string for changed nodes in commit page.
1475 Generates a html string for changed nodes in commit page.
1476 It limits the output to 30 entries
1476 It limits the output to 30 entries
1477
1477
1478 :param nodes: LazyNodesGenerator
1478 :param nodes: LazyNodesGenerator
1479 """
1479 """
1480 if nodes:
1480 if nodes:
1481 pref = ': <br/> '
1481 pref = ': <br/> '
1482 suf = ''
1482 suf = ''
1483 if len(nodes) > 30:
1483 if len(nodes) > 30:
1484 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1484 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1485 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1485 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1486 for x in nodes[:30]]) + suf)
1486 for x in nodes[:30]]) + suf)
1487 else:
1487 else:
1488 return ': ' + _('No Files')
1488 return ': ' + _('No Files')
1489
1489
1490
1490
1491 def breadcrumb_repo_link(repo):
1491 def breadcrumb_repo_link(repo):
1492 """
1492 """
1493 Makes a breadcrumbs path link to repo
1493 Makes a breadcrumbs path link to repo
1494
1494
1495 ex::
1495 ex::
1496 group >> subgroup >> repo
1496 group >> subgroup >> repo
1497
1497
1498 :param repo: a Repository instance
1498 :param repo: a Repository instance
1499 """
1499 """
1500
1500
1501 path = [
1501 path = [
1502 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1502 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1503 for group in repo.groups_with_parents
1503 for group in repo.groups_with_parents
1504 ] + [
1504 ] + [
1505 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1505 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1506 ]
1506 ]
1507
1507
1508 return literal(' &raquo; '.join(path))
1508 return literal(' &raquo; '.join(path))
1509
1509
1510
1510
1511 def format_byte_size_binary(file_size):
1511 def format_byte_size_binary(file_size):
1512 """
1512 """
1513 Formats file/folder sizes to standard.
1513 Formats file/folder sizes to standard.
1514 """
1514 """
1515 formatted_size = format_byte_size(file_size, binary=True)
1515 formatted_size = format_byte_size(file_size, binary=True)
1516 return formatted_size
1516 return formatted_size
1517
1517
1518
1518
1519 def fancy_file_stats(stats):
1519 def fancy_file_stats(stats):
1520 """
1520 """
1521 Displays a fancy two colored bar for number of added/deleted
1521 Displays a fancy two colored bar for number of added/deleted
1522 lines of code on file
1522 lines of code on file
1523
1523
1524 :param stats: two element list of added/deleted lines of code
1524 :param stats: two element list of added/deleted lines of code
1525 """
1525 """
1526 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1526 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1527 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1527 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1528
1528
1529 def cgen(l_type, a_v, d_v):
1529 def cgen(l_type, a_v, d_v):
1530 mapping = {'tr': 'top-right-rounded-corner-mid',
1530 mapping = {'tr': 'top-right-rounded-corner-mid',
1531 'tl': 'top-left-rounded-corner-mid',
1531 'tl': 'top-left-rounded-corner-mid',
1532 'br': 'bottom-right-rounded-corner-mid',
1532 'br': 'bottom-right-rounded-corner-mid',
1533 'bl': 'bottom-left-rounded-corner-mid'}
1533 'bl': 'bottom-left-rounded-corner-mid'}
1534 map_getter = lambda x: mapping[x]
1534 map_getter = lambda x: mapping[x]
1535
1535
1536 if l_type == 'a' and d_v:
1536 if l_type == 'a' and d_v:
1537 #case when added and deleted are present
1537 #case when added and deleted are present
1538 return ' '.join(map(map_getter, ['tl', 'bl']))
1538 return ' '.join(map(map_getter, ['tl', 'bl']))
1539
1539
1540 if l_type == 'a' and not d_v:
1540 if l_type == 'a' and not d_v:
1541 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1541 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1542
1542
1543 if l_type == 'd' and a_v:
1543 if l_type == 'd' and a_v:
1544 return ' '.join(map(map_getter, ['tr', 'br']))
1544 return ' '.join(map(map_getter, ['tr', 'br']))
1545
1545
1546 if l_type == 'd' and not a_v:
1546 if l_type == 'd' and not a_v:
1547 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1547 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1548
1548
1549 a, d = stats['added'], stats['deleted']
1549 a, d = stats['added'], stats['deleted']
1550 width = 100
1550 width = 100
1551
1551
1552 if stats['binary']: # binary operations like chmod/rename etc
1552 if stats['binary']: # binary operations like chmod/rename etc
1553 lbl = []
1553 lbl = []
1554 bin_op = 0 # undefined
1554 bin_op = 0 # undefined
1555
1555
1556 # prefix with bin for binary files
1556 # prefix with bin for binary files
1557 if BIN_FILENODE in stats['ops']:
1557 if BIN_FILENODE in stats['ops']:
1558 lbl += ['bin']
1558 lbl += ['bin']
1559
1559
1560 if NEW_FILENODE in stats['ops']:
1560 if NEW_FILENODE in stats['ops']:
1561 lbl += [_('new file')]
1561 lbl += [_('new file')]
1562 bin_op = NEW_FILENODE
1562 bin_op = NEW_FILENODE
1563 elif MOD_FILENODE in stats['ops']:
1563 elif MOD_FILENODE in stats['ops']:
1564 lbl += [_('mod')]
1564 lbl += [_('mod')]
1565 bin_op = MOD_FILENODE
1565 bin_op = MOD_FILENODE
1566 elif DEL_FILENODE in stats['ops']:
1566 elif DEL_FILENODE in stats['ops']:
1567 lbl += [_('del')]
1567 lbl += [_('del')]
1568 bin_op = DEL_FILENODE
1568 bin_op = DEL_FILENODE
1569 elif RENAMED_FILENODE in stats['ops']:
1569 elif RENAMED_FILENODE in stats['ops']:
1570 lbl += [_('rename')]
1570 lbl += [_('rename')]
1571 bin_op = RENAMED_FILENODE
1571 bin_op = RENAMED_FILENODE
1572
1572
1573 # chmod can go with other operations, so we add a + to lbl if needed
1573 # chmod can go with other operations, so we add a + to lbl if needed
1574 if CHMOD_FILENODE in stats['ops']:
1574 if CHMOD_FILENODE in stats['ops']:
1575 lbl += [_('chmod')]
1575 lbl += [_('chmod')]
1576 if bin_op == 0:
1576 if bin_op == 0:
1577 bin_op = CHMOD_FILENODE
1577 bin_op = CHMOD_FILENODE
1578
1578
1579 lbl = '+'.join(lbl)
1579 lbl = '+'.join(lbl)
1580 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1580 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1581 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1581 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1582 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1582 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1583 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1583 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1584
1584
1585 t = stats['added'] + stats['deleted']
1585 t = stats['added'] + stats['deleted']
1586 unit = float(width) / (t or 1)
1586 unit = float(width) / (t or 1)
1587
1587
1588 # needs > 9% of width to be visible or 0 to be hidden
1588 # needs > 9% of width to be visible or 0 to be hidden
1589 a_p = max(9, unit * a) if a > 0 else 0
1589 a_p = max(9, unit * a) if a > 0 else 0
1590 d_p = max(9, unit * d) if d > 0 else 0
1590 d_p = max(9, unit * d) if d > 0 else 0
1591 p_sum = a_p + d_p
1591 p_sum = a_p + d_p
1592
1592
1593 if p_sum > width:
1593 if p_sum > width:
1594 #adjust the percentage to be == 100% since we adjusted to 9
1594 #adjust the percentage to be == 100% since we adjusted to 9
1595 if a_p > d_p:
1595 if a_p > d_p:
1596 a_p = a_p - (p_sum - width)
1596 a_p = a_p - (p_sum - width)
1597 else:
1597 else:
1598 d_p = d_p - (p_sum - width)
1598 d_p = d_p - (p_sum - width)
1599
1599
1600 a_v = a if a > 0 else ''
1600 a_v = a if a > 0 else ''
1601 d_v = d if d > 0 else ''
1601 d_v = d if d > 0 else ''
1602
1602
1603 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1603 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1604 cgen('a', a_v, d_v), a_p, a_v
1604 cgen('a', a_v, d_v), a_p, a_v
1605 )
1605 )
1606 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1606 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1607 cgen('d', a_v, d_v), d_p, d_v
1607 cgen('d', a_v, d_v), d_p, d_v
1608 )
1608 )
1609 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1609 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1610
1610
1611
1611
1612 def urlify_text(text_, safe=True):
1612 def urlify_text(text_, safe=True):
1613 """
1613 """
1614 Extrac urls from text and make html links out of them
1614 Extrac urls from text and make html links out of them
1615
1615
1616 :param text_:
1616 :param text_:
1617 """
1617 """
1618
1618
1619 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1619 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1620 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1620 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1621
1621
1622 def url_func(match_obj):
1622 def url_func(match_obj):
1623 url_full = match_obj.groups()[0]
1623 url_full = match_obj.groups()[0]
1624 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1624 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1625 _newtext = url_pat.sub(url_func, text_)
1625 _newtext = url_pat.sub(url_func, text_)
1626 if safe:
1626 if safe:
1627 return literal(_newtext)
1627 return literal(_newtext)
1628 return _newtext
1628 return _newtext
1629
1629
1630
1630
1631 def urlify_commits(text_, repository):
1631 def urlify_commits(text_, repository):
1632 """
1632 """
1633 Extract commit ids from text and make link from them
1633 Extract commit ids from text and make link from them
1634
1634
1635 :param text_:
1635 :param text_:
1636 :param repository: repo name to build the URL with
1636 :param repository: repo name to build the URL with
1637 """
1637 """
1638 from pylons import url # doh, we need to re-import url to mock it later
1638 from pylons import url # doh, we need to re-import url to mock it later
1639 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1639 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1640
1640
1641 def url_func(match_obj):
1641 def url_func(match_obj):
1642 commit_id = match_obj.groups()[1]
1642 commit_id = match_obj.groups()[1]
1643 pref = match_obj.groups()[0]
1643 pref = match_obj.groups()[0]
1644 suf = match_obj.groups()[2]
1644 suf = match_obj.groups()[2]
1645
1645
1646 tmpl = (
1646 tmpl = (
1647 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1647 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1648 '%(commit_id)s</a>%(suf)s'
1648 '%(commit_id)s</a>%(suf)s'
1649 )
1649 )
1650 return tmpl % {
1650 return tmpl % {
1651 'pref': pref,
1651 'pref': pref,
1652 'cls': 'revision-link',
1652 'cls': 'revision-link',
1653 'url': url('changeset_home', repo_name=repository,
1653 'url': url('changeset_home', repo_name=repository,
1654 revision=commit_id, qualified=True),
1654 revision=commit_id, qualified=True),
1655 'commit_id': commit_id,
1655 'commit_id': commit_id,
1656 'suf': suf
1656 'suf': suf
1657 }
1657 }
1658
1658
1659 newtext = URL_PAT.sub(url_func, text_)
1659 newtext = URL_PAT.sub(url_func, text_)
1660
1660
1661 return newtext
1661 return newtext
1662
1662
1663
1663
1664 def _process_url_func(match_obj, repo_name, uid, entry,
1664 def _process_url_func(match_obj, repo_name, uid, entry,
1665 return_raw_data=False):
1665 return_raw_data=False):
1666 pref = ''
1666 pref = ''
1667 if match_obj.group().startswith(' '):
1667 if match_obj.group().startswith(' '):
1668 pref = ' '
1668 pref = ' '
1669
1669
1670 issue_id = ''.join(match_obj.groups())
1670 issue_id = ''.join(match_obj.groups())
1671 tmpl = (
1671 tmpl = (
1672 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1672 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1673 '%(issue-prefix)s%(id-repr)s'
1673 '%(issue-prefix)s%(id-repr)s'
1674 '</a>')
1674 '</a>')
1675
1675
1676 (repo_name_cleaned,
1676 (repo_name_cleaned,
1677 parent_group_name) = RepoGroupModel().\
1677 parent_group_name) = RepoGroupModel().\
1678 _get_group_name_and_parent(repo_name)
1678 _get_group_name_and_parent(repo_name)
1679
1679
1680 # variables replacement
1680 # variables replacement
1681 named_vars = {
1681 named_vars = {
1682 'id': issue_id,
1682 'id': issue_id,
1683 'repo': repo_name,
1683 'repo': repo_name,
1684 'repo_name': repo_name_cleaned,
1684 'repo_name': repo_name_cleaned,
1685 'group_name': parent_group_name
1685 'group_name': parent_group_name
1686 }
1686 }
1687 # named regex variables
1687 # named regex variables
1688 named_vars.update(match_obj.groupdict())
1688 named_vars.update(match_obj.groupdict())
1689 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1689 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1690
1690
1691 data = {
1691 data = {
1692 'pref': pref,
1692 'pref': pref,
1693 'cls': 'issue-tracker-link',
1693 'cls': 'issue-tracker-link',
1694 'url': _url,
1694 'url': _url,
1695 'id-repr': issue_id,
1695 'id-repr': issue_id,
1696 'issue-prefix': entry['pref'],
1696 'issue-prefix': entry['pref'],
1697 'serv': entry['url'],
1697 'serv': entry['url'],
1698 }
1698 }
1699 if return_raw_data:
1699 if return_raw_data:
1700 return {
1700 return {
1701 'id': issue_id,
1701 'id': issue_id,
1702 'url': _url
1702 'url': _url
1703 }
1703 }
1704 return tmpl % data
1704 return tmpl % data
1705
1705
1706
1706
1707 def process_patterns(text_string, repo_name, config=None):
1707 def process_patterns(text_string, repo_name, config=None):
1708 repo = None
1708 repo = None
1709 if repo_name:
1709 if repo_name:
1710 # Retrieving repo_name to avoid invalid repo_name to explode on
1710 # Retrieving repo_name to avoid invalid repo_name to explode on
1711 # IssueTrackerSettingsModel but still passing invalid name further down
1711 # IssueTrackerSettingsModel but still passing invalid name further down
1712 repo = Repository.get_by_repo_name(repo_name, cache=True)
1712 repo = Repository.get_by_repo_name(repo_name, cache=True)
1713
1713
1714 settings_model = IssueTrackerSettingsModel(repo=repo)
1714 settings_model = IssueTrackerSettingsModel(repo=repo)
1715 active_entries = settings_model.get_settings(cache=True)
1715 active_entries = settings_model.get_settings(cache=True)
1716
1716
1717 issues_data = []
1717 issues_data = []
1718 newtext = text_string
1718 newtext = text_string
1719 for uid, entry in active_entries.items():
1719 for uid, entry in active_entries.items():
1720 log.debug('found issue tracker entry with uid %s' % (uid,))
1720 log.debug('found issue tracker entry with uid %s' % (uid,))
1721
1721
1722 if not (entry['pat'] and entry['url']):
1722 if not (entry['pat'] and entry['url']):
1723 log.debug('skipping due to missing data')
1723 log.debug('skipping due to missing data')
1724 continue
1724 continue
1725
1725
1726 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1726 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1727 % (uid, entry['pat'], entry['url'], entry['pref']))
1727 % (uid, entry['pat'], entry['url'], entry['pref']))
1728
1728
1729 try:
1729 try:
1730 pattern = re.compile(r'%s' % entry['pat'])
1730 pattern = re.compile(r'%s' % entry['pat'])
1731 except re.error:
1731 except re.error:
1732 log.exception(
1732 log.exception(
1733 'issue tracker pattern: `%s` failed to compile',
1733 'issue tracker pattern: `%s` failed to compile',
1734 entry['pat'])
1734 entry['pat'])
1735 continue
1735 continue
1736
1736
1737 data_func = partial(
1737 data_func = partial(
1738 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1738 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1739 return_raw_data=True)
1739 return_raw_data=True)
1740
1740
1741 for match_obj in pattern.finditer(text_string):
1741 for match_obj in pattern.finditer(text_string):
1742 issues_data.append(data_func(match_obj))
1742 issues_data.append(data_func(match_obj))
1743
1743
1744 url_func = partial(
1744 url_func = partial(
1745 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1745 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1746
1746
1747 newtext = pattern.sub(url_func, newtext)
1747 newtext = pattern.sub(url_func, newtext)
1748 log.debug('processed prefix:uid `%s`' % (uid,))
1748 log.debug('processed prefix:uid `%s`' % (uid,))
1749
1749
1750 return newtext, issues_data
1750 return newtext, issues_data
1751
1751
1752
1752
1753 def urlify_commit_message(commit_text, repository=None):
1753 def urlify_commit_message(commit_text, repository=None):
1754 """
1754 """
1755 Parses given text message and makes proper links.
1755 Parses given text message and makes proper links.
1756 issues are linked to given issue-server, and rest is a commit link
1756 issues are linked to given issue-server, and rest is a commit link
1757
1757
1758 :param commit_text:
1758 :param commit_text:
1759 :param repository:
1759 :param repository:
1760 """
1760 """
1761 from pylons import url # doh, we need to re-import url to mock it later
1761 from pylons import url # doh, we need to re-import url to mock it later
1762
1762
1763 def escaper(string):
1763 def escaper(string):
1764 return string.replace('<', '&lt;').replace('>', '&gt;')
1764 return string.replace('<', '&lt;').replace('>', '&gt;')
1765
1765
1766 newtext = escaper(commit_text)
1766 newtext = escaper(commit_text)
1767
1767
1768 # extract http/https links and make them real urls
1768 # extract http/https links and make them real urls
1769 newtext = urlify_text(newtext, safe=False)
1769 newtext = urlify_text(newtext, safe=False)
1770
1770
1771 # urlify commits - extract commit ids and make link out of them, if we have
1771 # urlify commits - extract commit ids and make link out of them, if we have
1772 # the scope of repository present.
1772 # the scope of repository present.
1773 if repository:
1773 if repository:
1774 newtext = urlify_commits(newtext, repository)
1774 newtext = urlify_commits(newtext, repository)
1775
1775
1776 # process issue tracker patterns
1776 # process issue tracker patterns
1777 newtext, issues = process_patterns(newtext, repository or '')
1777 newtext, issues = process_patterns(newtext, repository or '')
1778
1778
1779 return literal(newtext)
1779 return literal(newtext)
1780
1780
1781
1781
1782 def rst(source, mentions=False):
1782 def rst(source, mentions=False):
1783 return literal('<div class="rst-block">%s</div>' %
1783 return literal('<div class="rst-block">%s</div>' %
1784 MarkupRenderer.rst(source, mentions=mentions))
1784 MarkupRenderer.rst(source, mentions=mentions))
1785
1785
1786
1786
1787 def markdown(source, mentions=False):
1787 def markdown(source, mentions=False):
1788 return literal('<div class="markdown-block">%s</div>' %
1788 return literal('<div class="markdown-block">%s</div>' %
1789 MarkupRenderer.markdown(source, flavored=True,
1789 MarkupRenderer.markdown(source, flavored=True,
1790 mentions=mentions))
1790 mentions=mentions))
1791
1791
1792 def renderer_from_filename(filename, exclude=None):
1792 def renderer_from_filename(filename, exclude=None):
1793 return MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1793 return MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1794
1794
1795
1795
1796 def render(source, renderer='rst', mentions=False):
1796 def render(source, renderer='rst', mentions=False):
1797 if renderer == 'rst':
1797 if renderer == 'rst':
1798 return rst(source, mentions=mentions)
1798 return rst(source, mentions=mentions)
1799 if renderer == 'markdown':
1799 if renderer == 'markdown':
1800 return markdown(source, mentions=mentions)
1800 return markdown(source, mentions=mentions)
1801
1801
1802
1802
1803 def commit_status(repo, commit_id):
1803 def commit_status(repo, commit_id):
1804 return ChangesetStatusModel().get_status(repo, commit_id)
1804 return ChangesetStatusModel().get_status(repo, commit_id)
1805
1805
1806
1806
1807 def commit_status_lbl(commit_status):
1807 def commit_status_lbl(commit_status):
1808 return dict(ChangesetStatus.STATUSES).get(commit_status)
1808 return dict(ChangesetStatus.STATUSES).get(commit_status)
1809
1809
1810
1810
1811 def commit_time(repo_name, commit_id):
1811 def commit_time(repo_name, commit_id):
1812 repo = Repository.get_by_repo_name(repo_name)
1812 repo = Repository.get_by_repo_name(repo_name)
1813 commit = repo.get_commit(commit_id=commit_id)
1813 commit = repo.get_commit(commit_id=commit_id)
1814 return commit.date
1814 return commit.date
1815
1815
1816
1816
1817 def get_permission_name(key):
1817 def get_permission_name(key):
1818 return dict(Permission.PERMS).get(key)
1818 return dict(Permission.PERMS).get(key)
1819
1819
1820
1820
1821 def journal_filter_help():
1821 def journal_filter_help():
1822 return _(
1822 return _(
1823 'Example filter terms:\n' +
1823 'Example filter terms:\n' +
1824 ' repository:vcs\n' +
1824 ' repository:vcs\n' +
1825 ' username:marcin\n' +
1825 ' username:marcin\n' +
1826 ' action:*push*\n' +
1826 ' action:*push*\n' +
1827 ' ip:127.0.0.1\n' +
1827 ' ip:127.0.0.1\n' +
1828 ' date:20120101\n' +
1828 ' date:20120101\n' +
1829 ' date:[20120101100000 TO 20120102]\n' +
1829 ' date:[20120101100000 TO 20120102]\n' +
1830 '\n' +
1830 '\n' +
1831 'Generate wildcards using \'*\' character:\n' +
1831 'Generate wildcards using \'*\' character:\n' +
1832 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1832 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1833 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1833 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1834 '\n' +
1834 '\n' +
1835 'Optional AND / OR operators in queries\n' +
1835 'Optional AND / OR operators in queries\n' +
1836 ' "repository:vcs OR repository:test"\n' +
1836 ' "repository:vcs OR repository:test"\n' +
1837 ' "username:test AND repository:test*"\n'
1837 ' "username:test AND repository:test*"\n'
1838 )
1838 )
1839
1839
1840
1840
1841 def not_mapped_error(repo_name):
1841 def not_mapped_error(repo_name):
1842 flash(_('%s repository is not mapped to db perhaps'
1842 flash(_('%s repository is not mapped to db perhaps'
1843 ' it was created or renamed from the filesystem'
1843 ' it was created or renamed from the filesystem'
1844 ' please run the application again'
1844 ' please run the application again'
1845 ' in order to rescan repositories') % repo_name, category='error')
1845 ' in order to rescan repositories') % repo_name, category='error')
1846
1846
1847
1847
1848 def ip_range(ip_addr):
1848 def ip_range(ip_addr):
1849 from rhodecode.model.db import UserIpMap
1849 from rhodecode.model.db import UserIpMap
1850 s, e = UserIpMap._get_ip_range(ip_addr)
1850 s, e = UserIpMap._get_ip_range(ip_addr)
1851 return '%s - %s' % (s, e)
1851 return '%s - %s' % (s, e)
1852
1852
1853
1853
1854 def form(url, method='post', needs_csrf_token=True, **attrs):
1854 def form(url, method='post', needs_csrf_token=True, **attrs):
1855 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1855 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1856 if method.lower() != 'get' and needs_csrf_token:
1856 if method.lower() != 'get' and needs_csrf_token:
1857 raise Exception(
1857 raise Exception(
1858 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1858 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1859 'CSRF token. If the endpoint does not require such token you can ' +
1859 'CSRF token. If the endpoint does not require such token you can ' +
1860 'explicitly set the parameter needs_csrf_token to false.')
1860 'explicitly set the parameter needs_csrf_token to false.')
1861
1861
1862 return wh_form(url, method=method, **attrs)
1862 return wh_form(url, method=method, **attrs)
1863
1863
1864
1864
1865 def secure_form(url, method="POST", multipart=False, **attrs):
1865 def secure_form(url, method="POST", multipart=False, **attrs):
1866 """Start a form tag that points the action to an url. This
1866 """Start a form tag that points the action to an url. This
1867 form tag will also include the hidden field containing
1867 form tag will also include the hidden field containing
1868 the auth token.
1868 the auth token.
1869
1869
1870 The url options should be given either as a string, or as a
1870 The url options should be given either as a string, or as a
1871 ``url()`` function. The method for the form defaults to POST.
1871 ``url()`` function. The method for the form defaults to POST.
1872
1872
1873 Options:
1873 Options:
1874
1874
1875 ``multipart``
1875 ``multipart``
1876 If set to True, the enctype is set to "multipart/form-data".
1876 If set to True, the enctype is set to "multipart/form-data".
1877 ``method``
1877 ``method``
1878 The method to use when submitting the form, usually either
1878 The method to use when submitting the form, usually either
1879 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1879 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1880 hidden input with name _method is added to simulate the verb
1880 hidden input with name _method is added to simulate the verb
1881 over POST.
1881 over POST.
1882
1882
1883 """
1883 """
1884 from webhelpers.pylonslib.secure_form import insecure_form
1884 from webhelpers.pylonslib.secure_form import insecure_form
1885 form = insecure_form(url, method, multipart, **attrs)
1885 form = insecure_form(url, method, multipart, **attrs)
1886 token = csrf_input()
1886 token = csrf_input()
1887 return literal("%s\n%s" % (form, token))
1887 return literal("%s\n%s" % (form, token))
1888
1888
1889 def csrf_input():
1889 def csrf_input():
1890 return literal(
1890 return literal(
1891 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1891 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1892 csrf_token_key, csrf_token_key, get_csrf_token()))
1892 csrf_token_key, csrf_token_key, get_csrf_token()))
1893
1893
1894 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1894 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1895 select_html = select(name, selected, options, **attrs)
1895 select_html = select(name, selected, options, **attrs)
1896 select2 = """
1896 select2 = """
1897 <script>
1897 <script>
1898 $(document).ready(function() {
1898 $(document).ready(function() {
1899 $('#%s').select2({
1899 $('#%s').select2({
1900 containerCssClass: 'drop-menu',
1900 containerCssClass: 'drop-menu',
1901 dropdownCssClass: 'drop-menu-dropdown',
1901 dropdownCssClass: 'drop-menu-dropdown',
1902 dropdownAutoWidth: true%s
1902 dropdownAutoWidth: true%s
1903 });
1903 });
1904 });
1904 });
1905 </script>
1905 </script>
1906 """
1906 """
1907 filter_option = """,
1907 filter_option = """,
1908 minimumResultsForSearch: -1
1908 minimumResultsForSearch: -1
1909 """
1909 """
1910 input_id = attrs.get('id') or name
1910 input_id = attrs.get('id') or name
1911 filter_enabled = "" if enable_filter else filter_option
1911 filter_enabled = "" if enable_filter else filter_option
1912 select_script = literal(select2 % (input_id, filter_enabled))
1912 select_script = literal(select2 % (input_id, filter_enabled))
1913
1913
1914 return literal(select_html+select_script)
1914 return literal(select_html+select_script)
1915
1915
1916
1916
1917 def get_visual_attr(tmpl_context_var, attr_name):
1917 def get_visual_attr(tmpl_context_var, attr_name):
1918 """
1918 """
1919 A safe way to get a variable from visual variable of template context
1919 A safe way to get a variable from visual variable of template context
1920
1920
1921 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1921 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1922 :param attr_name: name of the attribute we fetch from the c.visual
1922 :param attr_name: name of the attribute we fetch from the c.visual
1923 """
1923 """
1924 visual = getattr(tmpl_context_var, 'visual', None)
1924 visual = getattr(tmpl_context_var, 'visual', None)
1925 if not visual:
1925 if not visual:
1926 return
1926 return
1927 else:
1927 else:
1928 return getattr(visual, attr_name, None)
1928 return getattr(visual, attr_name, None)
1929
1929
1930
1930
1931 def get_last_path_part(file_node):
1931 def get_last_path_part(file_node):
1932 if not file_node.path:
1932 if not file_node.path:
1933 return u''
1933 return u''
1934
1934
1935 path = safe_unicode(file_node.path.split('/')[-1])
1935 path = safe_unicode(file_node.path.split('/')[-1])
1936 return u'../' + path
1936 return u'../' + path
1937
1937
1938
1938
1939 def route_path(*args, **kwds):
1939 def route_path(*args, **kwds):
1940 """
1940 """
1941 Wrapper around pyramids `route_path` function. It is used to generate
1941 Wrapper around pyramids `route_path` function. It is used to generate
1942 URLs from within pylons views or templates. This will be removed when
1942 URLs from within pylons views or templates. This will be removed when
1943 pyramid migration if finished.
1943 pyramid migration if finished.
1944 """
1944 """
1945 req = get_current_request()
1945 req = get_current_request()
1946 return req.route_path(*args, **kwds)
1946 return req.route_path(*args, **kwds)
1947
1947
1948
1948
1949 def static_url(*args, **kwds):
1949 def static_url(*args, **kwds):
1950 """
1950 """
1951 Wrapper around pyramids `route_path` function. It is used to generate
1951 Wrapper around pyramids `route_path` function. It is used to generate
1952 URLs from within pylons views or templates. This will be removed when
1952 URLs from within pylons views or templates. This will be removed when
1953 pyramid migration if finished.
1953 pyramid migration if finished.
1954 """
1954 """
1955 req = get_current_request()
1955 req = get_current_request()
1956 return req.static_url(*args, **kwds)
1956 return req.static_url(*args, **kwds)
1957
1957
1958
1958
1959 def resource_path(*args, **kwds):
1959 def resource_path(*args, **kwds):
1960 """
1960 """
1961 Wrapper around pyramids `route_path` function. It is used to generate
1961 Wrapper around pyramids `route_path` function. It is used to generate
1962 URLs from within pylons views or templates. This will be removed when
1962 URLs from within pylons views or templates. This will be removed when
1963 pyramid migration if finished.
1963 pyramid migration if finished.
1964 """
1964 """
1965 req = get_current_request()
1965 req = get_current_request()
1966 return req.resource_path(*args, **kwds)
1966 return req.resource_path(*args, **kwds)
@@ -1,139 +1,159 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 from urlparse import urljoin
22 from urlparse import urljoin
23
23
24 import requests
24 import requests
25 from webob.exc import HTTPNotAcceptable
25
26
26 import rhodecode
27 from rhodecode.lib.middleware import simplevcs
27 from rhodecode.lib.middleware import simplevcs
28 from rhodecode.lib.utils import is_valid_repo
28 from rhodecode.lib.utils import is_valid_repo
29 from rhodecode.lib.utils2 import str2bool
29
30
30 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
31
32
32
33
33 class SimpleSvnApp(object):
34 class SimpleSvnApp(object):
34 IGNORED_HEADERS = [
35 IGNORED_HEADERS = [
35 'connection', 'keep-alive', 'content-encoding',
36 'connection', 'keep-alive', 'content-encoding',
36 'transfer-encoding', 'content-length']
37 'transfer-encoding', 'content-length']
37
38
38 def __init__(self, config):
39 def __init__(self, config):
39 self.config = config
40 self.config = config
40
41
41 def __call__(self, environ, start_response):
42 def __call__(self, environ, start_response):
42 request_headers = self._get_request_headers(environ)
43 request_headers = self._get_request_headers(environ)
43
44
44 data = environ['wsgi.input']
45 data = environ['wsgi.input']
45 # johbo: Avoid that we end up with sending the request in chunked
46 # johbo: Avoid that we end up with sending the request in chunked
46 # transfer encoding (mainly on Gunicorn). If we know the content
47 # transfer encoding (mainly on Gunicorn). If we know the content
47 # length, then we should transfer the payload in one request.
48 # length, then we should transfer the payload in one request.
48 if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
49 if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
49 data = data.read()
50 data = data.read()
50
51
51 response = requests.request(
52 response = requests.request(
52 environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
53 environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
53 data=data, headers=request_headers)
54 data=data, headers=request_headers)
54
55
55 response_headers = self._get_response_headers(response.headers)
56 response_headers = self._get_response_headers(response.headers)
56 start_response(
57 start_response(
57 '{} {}'.format(response.status_code, response.reason),
58 '{} {}'.format(response.status_code, response.reason),
58 response_headers)
59 response_headers)
59 return response.iter_content(chunk_size=1024)
60 return response.iter_content(chunk_size=1024)
60
61
61 def _get_url(self, path):
62 def _get_url(self, path):
62 return urljoin(
63 return urljoin(
63 self.config.get('subversion_http_server_url', ''), path)
64 self.config.get('subversion_http_server_url', ''), path)
64
65
65 def _get_request_headers(self, environ):
66 def _get_request_headers(self, environ):
66 headers = {}
67 headers = {}
67
68
68 for key in environ:
69 for key in environ:
69 if not key.startswith('HTTP_'):
70 if not key.startswith('HTTP_'):
70 continue
71 continue
71 new_key = key.split('_')
72 new_key = key.split('_')
72 new_key = [k.capitalize() for k in new_key[1:]]
73 new_key = [k.capitalize() for k in new_key[1:]]
73 new_key = '-'.join(new_key)
74 new_key = '-'.join(new_key)
74 headers[new_key] = environ[key]
75 headers[new_key] = environ[key]
75
76
76 if 'CONTENT_TYPE' in environ:
77 if 'CONTENT_TYPE' in environ:
77 headers['Content-Type'] = environ['CONTENT_TYPE']
78 headers['Content-Type'] = environ['CONTENT_TYPE']
78
79
79 if 'CONTENT_LENGTH' in environ:
80 if 'CONTENT_LENGTH' in environ:
80 headers['Content-Length'] = environ['CONTENT_LENGTH']
81 headers['Content-Length'] = environ['CONTENT_LENGTH']
81
82
82 return headers
83 return headers
83
84
84 def _get_response_headers(self, headers):
85 def _get_response_headers(self, headers):
85 headers = [
86 headers = [
86 (h, headers[h])
87 (h, headers[h])
87 for h in headers
88 for h in headers
88 if h.lower() not in self.IGNORED_HEADERS
89 if h.lower() not in self.IGNORED_HEADERS
89 ]
90 ]
90
91
91 # Add custom response header to indicate that this is a VCS response
92 # Add custom response header to indicate that this is a VCS response
92 # and which backend is used.
93 # and which backend is used.
93 headers.append(('X-RhodeCode-Backend', 'svn'))
94 headers.append(('X-RhodeCode-Backend', 'svn'))
94
95
95 return headers
96 return headers
96
97
97
98
99 class DisabledSimpleSvnApp(object):
100 def __init__(self, config):
101 self.config = config
102
103 def __call__(self, environ, start_response):
104 reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
105 log.warning(reason)
106 return HTTPNotAcceptable(reason)(environ, start_response)
107
108
98 class SimpleSvn(simplevcs.SimpleVCS):
109 class SimpleSvn(simplevcs.SimpleVCS):
99
110
100 SCM = 'svn'
111 SCM = 'svn'
101 READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
112 READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
113 DEFAULT_HTTP_SERVER = 'http://localhost:8090'
102
114
103 def _get_repository_name(self, environ):
115 def _get_repository_name(self, environ):
104 """
116 """
105 Gets repository name out of PATH_INFO header
117 Gets repository name out of PATH_INFO header
106
118
107 :param environ: environ where PATH_INFO is stored
119 :param environ: environ where PATH_INFO is stored
108 """
120 """
109 path = environ['PATH_INFO'].split('!')
121 path = environ['PATH_INFO'].split('!')
110 repo_name = path[0].strip('/')
122 repo_name = path[0].strip('/')
111
123
112 # SVN includes the whole path in it's requests, including
124 # SVN includes the whole path in it's requests, including
113 # subdirectories inside the repo. Therefore we have to search for
125 # subdirectories inside the repo. Therefore we have to search for
114 # the repo root directory.
126 # the repo root directory.
115 if not is_valid_repo(repo_name, self.basepath, self.SCM):
127 if not is_valid_repo(repo_name, self.basepath, self.SCM):
116 current_path = ''
128 current_path = ''
117 for component in repo_name.split('/'):
129 for component in repo_name.split('/'):
118 current_path += component
130 current_path += component
119 if is_valid_repo(current_path, self.basepath, self.SCM):
131 if is_valid_repo(current_path, self.basepath, self.SCM):
120 return current_path
132 return current_path
121 current_path += '/'
133 current_path += '/'
122
134
123 return repo_name
135 return repo_name
124
136
125 def _get_action(self, environ):
137 def _get_action(self, environ):
126 return (
138 return (
127 'pull'
139 'pull'
128 if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
140 if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
129 else 'push')
141 else 'push')
130
142
131 def _create_wsgi_app(self, repo_path, repo_name, config):
143 def _create_wsgi_app(self, repo_path, repo_name, config):
132 return SimpleSvnApp(config)
144 if self._is_svn_enabled():
145 return SimpleSvnApp(config)
146 # we don't have http proxy enabled return dummy request handler
147 return DisabledSimpleSvnApp(config)
148
149 def _is_svn_enabled(self):
150 conf = self.repo_vcs_config
151 return str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
133
152
134 def _create_config(self, extras, repo_name):
153 def _create_config(self, extras, repo_name):
135 server_url = rhodecode.CONFIG.get(
154 conf = self.repo_vcs_config
136 'rhodecode_subversion_http_server_url', '')
155 server_url = conf.get('vcs_svn_proxy', 'http_server_url')
137 extras['subversion_http_server_url'] = (
156 server_url = server_url or self.DEFAULT_HTTP_SERVER
138 server_url or 'http://localhost/')
157
158 extras['subversion_http_server_url'] = server_url
139 return extras
159 return extras
@@ -1,448 +1,452 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import importlib
28 import importlib
29 from functools import wraps
29 from functools import wraps
30
30
31 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
31 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from webob.exc import (
32 from webob.exc import (
33 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
33 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34
34
35 import rhodecode
35 import rhodecode
36 from rhodecode.authentication.base import authenticate, VCS_TYPE
36 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
37 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
38 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.exceptions import (
39 from rhodecode.lib.exceptions import (
40 HTTPLockedRC, HTTPRequirementError, UserCreationError,
40 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 NotAllowedToCreateUserError)
41 NotAllowedToCreateUserError)
42 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
42 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 from rhodecode.lib.middleware import appenlight
43 from rhodecode.lib.middleware import appenlight
44 from rhodecode.lib.middleware.utils import scm_app
44 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.utils import (
45 from rhodecode.lib.utils import (
46 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
46 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
47 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.vcs.conf import settings as vcs_settings
48 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 from rhodecode.lib.vcs.backends import base
49 from rhodecode.model import meta
50 from rhodecode.model import meta
50 from rhodecode.model.db import User, Repository
51 from rhodecode.model.db import User, Repository
51 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.settings import SettingsModel
53 from rhodecode.model.settings import SettingsModel
53
54
55
54 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
55
57
56
58
57 def initialize_generator(factory):
59 def initialize_generator(factory):
58 """
60 """
59 Initializes the returned generator by draining its first element.
61 Initializes the returned generator by draining its first element.
60
62
61 This can be used to give a generator an initializer, which is the code
63 This can be used to give a generator an initializer, which is the code
62 up to the first yield statement. This decorator enforces that the first
64 up to the first yield statement. This decorator enforces that the first
63 produced element has the value ``"__init__"`` to make its special
65 produced element has the value ``"__init__"`` to make its special
64 purpose very explicit in the using code.
66 purpose very explicit in the using code.
65 """
67 """
66
68
67 @wraps(factory)
69 @wraps(factory)
68 def wrapper(*args, **kwargs):
70 def wrapper(*args, **kwargs):
69 gen = factory(*args, **kwargs)
71 gen = factory(*args, **kwargs)
70 try:
72 try:
71 init = gen.next()
73 init = gen.next()
72 except StopIteration:
74 except StopIteration:
73 raise ValueError('Generator must yield at least one element.')
75 raise ValueError('Generator must yield at least one element.')
74 if init != "__init__":
76 if init != "__init__":
75 raise ValueError('First yielded element must be "__init__".')
77 raise ValueError('First yielded element must be "__init__".')
76 return gen
78 return gen
77 return wrapper
79 return wrapper
78
80
79
81
80 class SimpleVCS(object):
82 class SimpleVCS(object):
81 """Common functionality for SCM HTTP handlers."""
83 """Common functionality for SCM HTTP handlers."""
82
84
83 SCM = 'unknown'
85 SCM = 'unknown'
84
86
85 def __init__(self, application, config, registry):
87 def __init__(self, application, config, registry):
86 self.registry = registry
88 self.registry = registry
87 self.application = application
89 self.application = application
88 self.config = config
90 self.config = config
91 # re-populated by specialized middlewares
92 self.repo_vcs_config = base.Config()
89 # base path of repo locations
93 # base path of repo locations
90 self.basepath = get_rhodecode_base_path()
94 self.basepath = get_rhodecode_base_path()
91 # authenticate this VCS request using authfunc
95 # authenticate this VCS request using authfunc
92 auth_ret_code_detection = \
96 auth_ret_code_detection = \
93 str2bool(self.config.get('auth_ret_code_detection', False))
97 str2bool(self.config.get('auth_ret_code_detection', False))
94 self.authenticate = BasicAuth(
98 self.authenticate = BasicAuth(
95 '', authenticate, registry, config.get('auth_ret_code'),
99 '', authenticate, registry, config.get('auth_ret_code'),
96 auth_ret_code_detection)
100 auth_ret_code_detection)
97 self.ip_addr = '0.0.0.0'
101 self.ip_addr = '0.0.0.0'
98
102
99 @property
103 @property
100 def scm_app(self):
104 def scm_app(self):
101 custom_implementation = self.config.get('vcs.scm_app_implementation')
105 custom_implementation = self.config.get('vcs.scm_app_implementation')
102 if custom_implementation and custom_implementation != 'pyro4':
106 if custom_implementation and custom_implementation != 'pyro4':
103 log.info(
107 log.info(
104 "Using custom implementation of scm_app: %s",
108 "Using custom implementation of scm_app: %s",
105 custom_implementation)
109 custom_implementation)
106 scm_app_impl = importlib.import_module(custom_implementation)
110 scm_app_impl = importlib.import_module(custom_implementation)
107 else:
111 else:
108 scm_app_impl = scm_app
112 scm_app_impl = scm_app
109 return scm_app_impl
113 return scm_app_impl
110
114
111 def _get_by_id(self, repo_name):
115 def _get_by_id(self, repo_name):
112 """
116 """
113 Gets a special pattern _<ID> from clone url and tries to replace it
117 Gets a special pattern _<ID> from clone url and tries to replace it
114 with a repository_name for support of _<ID> non changable urls
118 with a repository_name for support of _<ID> non changable urls
115
119
116 :param repo_name:
120 :param repo_name:
117 """
121 """
118
122
119 data = repo_name.split('/')
123 data = repo_name.split('/')
120 if len(data) >= 2:
124 if len(data) >= 2:
121 from rhodecode.model.repo import RepoModel
125 from rhodecode.model.repo import RepoModel
122 by_id_match = RepoModel().get_repo_by_id(repo_name)
126 by_id_match = RepoModel().get_repo_by_id(repo_name)
123 if by_id_match:
127 if by_id_match:
124 data[1] = by_id_match.repo_name
128 data[1] = by_id_match.repo_name
125
129
126 return safe_str('/'.join(data))
130 return safe_str('/'.join(data))
127
131
128 def _invalidate_cache(self, repo_name):
132 def _invalidate_cache(self, repo_name):
129 """
133 """
130 Set's cache for this repository for invalidation on next access
134 Set's cache for this repository for invalidation on next access
131
135
132 :param repo_name: full repo name, also a cache key
136 :param repo_name: full repo name, also a cache key
133 """
137 """
134 ScmModel().mark_for_invalidation(repo_name)
138 ScmModel().mark_for_invalidation(repo_name)
135
139
136 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
140 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
137 db_repo = Repository.get_by_repo_name(repo_name)
141 db_repo = Repository.get_by_repo_name(repo_name)
138 if not db_repo:
142 if not db_repo:
139 log.debug('Repository `%s` not found inside the database.',
143 log.debug('Repository `%s` not found inside the database.',
140 repo_name)
144 repo_name)
141 return False
145 return False
142
146
143 if db_repo.repo_type != scm_type:
147 if db_repo.repo_type != scm_type:
144 log.warning(
148 log.warning(
145 'Repository `%s` have incorrect scm_type, expected %s got %s',
149 'Repository `%s` have incorrect scm_type, expected %s got %s',
146 repo_name, db_repo.repo_type, scm_type)
150 repo_name, db_repo.repo_type, scm_type)
147 return False
151 return False
148
152
149 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
153 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
150
154
151 def valid_and_active_user(self, user):
155 def valid_and_active_user(self, user):
152 """
156 """
153 Checks if that user is not empty, and if it's actually object it checks
157 Checks if that user is not empty, and if it's actually object it checks
154 if he's active.
158 if he's active.
155
159
156 :param user: user object or None
160 :param user: user object or None
157 :return: boolean
161 :return: boolean
158 """
162 """
159 if user is None:
163 if user is None:
160 return False
164 return False
161
165
162 elif user.active:
166 elif user.active:
163 return True
167 return True
164
168
165 return False
169 return False
166
170
167 def _check_permission(self, action, user, repo_name, ip_addr=None):
171 def _check_permission(self, action, user, repo_name, ip_addr=None):
168 """
172 """
169 Checks permissions using action (push/pull) user and repository
173 Checks permissions using action (push/pull) user and repository
170 name
174 name
171
175
172 :param action: push or pull action
176 :param action: push or pull action
173 :param user: user instance
177 :param user: user instance
174 :param repo_name: repository name
178 :param repo_name: repository name
175 """
179 """
176 # check IP
180 # check IP
177 inherit = user.inherit_default_permissions
181 inherit = user.inherit_default_permissions
178 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
182 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
179 inherit_from_default=inherit)
183 inherit_from_default=inherit)
180 if ip_allowed:
184 if ip_allowed:
181 log.info('Access for IP:%s allowed', ip_addr)
185 log.info('Access for IP:%s allowed', ip_addr)
182 else:
186 else:
183 return False
187 return False
184
188
185 if action == 'push':
189 if action == 'push':
186 if not HasPermissionAnyMiddleware('repository.write',
190 if not HasPermissionAnyMiddleware('repository.write',
187 'repository.admin')(user,
191 'repository.admin')(user,
188 repo_name):
192 repo_name):
189 return False
193 return False
190
194
191 else:
195 else:
192 # any other action need at least read permission
196 # any other action need at least read permission
193 if not HasPermissionAnyMiddleware('repository.read',
197 if not HasPermissionAnyMiddleware('repository.read',
194 'repository.write',
198 'repository.write',
195 'repository.admin')(user,
199 'repository.admin')(user,
196 repo_name):
200 repo_name):
197 return False
201 return False
198
202
199 return True
203 return True
200
204
201 def _check_ssl(self, environ, start_response):
205 def _check_ssl(self, environ, start_response):
202 """
206 """
203 Checks the SSL check flag and returns False if SSL is not present
207 Checks the SSL check flag and returns False if SSL is not present
204 and required True otherwise
208 and required True otherwise
205 """
209 """
206 org_proto = environ['wsgi._org_proto']
210 org_proto = environ['wsgi._org_proto']
207 # check if we have SSL required ! if not it's a bad request !
211 # check if we have SSL required ! if not it's a bad request !
208 require_ssl = str2bool(
212 require_ssl = str2bool(
209 SettingsModel().get_ui_by_key('push_ssl').ui_value)
213 SettingsModel().get_ui_by_key('push_ssl').ui_value)
210 if require_ssl and org_proto == 'http':
214 if require_ssl and org_proto == 'http':
211 log.debug('proto is %s and SSL is required BAD REQUEST !',
215 log.debug('proto is %s and SSL is required BAD REQUEST !',
212 org_proto)
216 org_proto)
213 return False
217 return False
214 return True
218 return True
215
219
216 def __call__(self, environ, start_response):
220 def __call__(self, environ, start_response):
217 try:
221 try:
218 return self._handle_request(environ, start_response)
222 return self._handle_request(environ, start_response)
219 except Exception:
223 except Exception:
220 log.exception("Exception while handling request")
224 log.exception("Exception while handling request")
221 appenlight.track_exception(environ)
225 appenlight.track_exception(environ)
222 return HTTPInternalServerError()(environ, start_response)
226 return HTTPInternalServerError()(environ, start_response)
223 finally:
227 finally:
224 meta.Session.remove()
228 meta.Session.remove()
225
229
226 def _handle_request(self, environ, start_response):
230 def _handle_request(self, environ, start_response):
227
231
228 if not self._check_ssl(environ, start_response):
232 if not self._check_ssl(environ, start_response):
229 reason = ('SSL required, while RhodeCode was unable '
233 reason = ('SSL required, while RhodeCode was unable '
230 'to detect this as SSL request')
234 'to detect this as SSL request')
231 log.debug('User not allowed to proceed, %s', reason)
235 log.debug('User not allowed to proceed, %s', reason)
232 return HTTPNotAcceptable(reason)(environ, start_response)
236 return HTTPNotAcceptable(reason)(environ, start_response)
233
237
234 ip_addr = get_ip_addr(environ)
238 ip_addr = get_ip_addr(environ)
235 username = None
239 username = None
236
240
237 # skip passing error to error controller
241 # skip passing error to error controller
238 environ['pylons.status_code_redirect'] = True
242 environ['pylons.status_code_redirect'] = True
239
243
240 # ======================================================================
244 # ======================================================================
241 # EXTRACT REPOSITORY NAME FROM ENV
245 # EXTRACT REPOSITORY NAME FROM ENV
242 # ======================================================================
246 # ======================================================================
243 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
247 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
244 repo_name = self._get_repository_name(environ)
248 repo_name = self._get_repository_name(environ)
245 environ['REPO_NAME'] = repo_name
249 environ['REPO_NAME'] = repo_name
246 log.debug('Extracted repo name is %s', repo_name)
250 log.debug('Extracted repo name is %s', repo_name)
247
251
248 # check for type, presence in database and on filesystem
252 # check for type, presence in database and on filesystem
249 if not self.is_valid_and_existing_repo(
253 if not self.is_valid_and_existing_repo(
250 repo_name, self.basepath, self.SCM):
254 repo_name, self.basepath, self.SCM):
251 return HTTPNotFound()(environ, start_response)
255 return HTTPNotFound()(environ, start_response)
252
256
253 # ======================================================================
257 # ======================================================================
254 # GET ACTION PULL or PUSH
258 # GET ACTION PULL or PUSH
255 # ======================================================================
259 # ======================================================================
256 action = self._get_action(environ)
260 action = self._get_action(environ)
257
261
258 # ======================================================================
262 # ======================================================================
259 # CHECK ANONYMOUS PERMISSION
263 # CHECK ANONYMOUS PERMISSION
260 # ======================================================================
264 # ======================================================================
261 if action in ['pull', 'push']:
265 if action in ['pull', 'push']:
262 anonymous_user = User.get_default_user()
266 anonymous_user = User.get_default_user()
263 username = anonymous_user.username
267 username = anonymous_user.username
264 if anonymous_user.active:
268 if anonymous_user.active:
265 # ONLY check permissions if the user is activated
269 # ONLY check permissions if the user is activated
266 anonymous_perm = self._check_permission(
270 anonymous_perm = self._check_permission(
267 action, anonymous_user, repo_name, ip_addr)
271 action, anonymous_user, repo_name, ip_addr)
268 else:
272 else:
269 anonymous_perm = False
273 anonymous_perm = False
270
274
271 if not anonymous_user.active or not anonymous_perm:
275 if not anonymous_user.active or not anonymous_perm:
272 if not anonymous_user.active:
276 if not anonymous_user.active:
273 log.debug('Anonymous access is disabled, running '
277 log.debug('Anonymous access is disabled, running '
274 'authentication')
278 'authentication')
275
279
276 if not anonymous_perm:
280 if not anonymous_perm:
277 log.debug('Not enough credentials to access this '
281 log.debug('Not enough credentials to access this '
278 'repository as anonymous user')
282 'repository as anonymous user')
279
283
280 username = None
284 username = None
281 # ==============================================================
285 # ==============================================================
282 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
286 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
283 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
287 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
284 # ==============================================================
288 # ==============================================================
285
289
286 # try to auth based on environ, container auth methods
290 # try to auth based on environ, container auth methods
287 log.debug('Running PRE-AUTH for container based authentication')
291 log.debug('Running PRE-AUTH for container based authentication')
288 pre_auth = authenticate(
292 pre_auth = authenticate(
289 '', '', environ, VCS_TYPE, registry=self.registry)
293 '', '', environ, VCS_TYPE, registry=self.registry)
290 if pre_auth and pre_auth.get('username'):
294 if pre_auth and pre_auth.get('username'):
291 username = pre_auth['username']
295 username = pre_auth['username']
292 log.debug('PRE-AUTH got %s as username', username)
296 log.debug('PRE-AUTH got %s as username', username)
293
297
294 # If not authenticated by the container, running basic auth
298 # If not authenticated by the container, running basic auth
295 if not username:
299 if not username:
296 self.authenticate.realm = get_rhodecode_realm()
300 self.authenticate.realm = get_rhodecode_realm()
297
301
298 try:
302 try:
299 result = self.authenticate(environ)
303 result = self.authenticate(environ)
300 except (UserCreationError, NotAllowedToCreateUserError) as e:
304 except (UserCreationError, NotAllowedToCreateUserError) as e:
301 log.error(e)
305 log.error(e)
302 reason = safe_str(e)
306 reason = safe_str(e)
303 return HTTPNotAcceptable(reason)(environ, start_response)
307 return HTTPNotAcceptable(reason)(environ, start_response)
304
308
305 if isinstance(result, str):
309 if isinstance(result, str):
306 AUTH_TYPE.update(environ, 'basic')
310 AUTH_TYPE.update(environ, 'basic')
307 REMOTE_USER.update(environ, result)
311 REMOTE_USER.update(environ, result)
308 username = result
312 username = result
309 else:
313 else:
310 return result.wsgi_application(environ, start_response)
314 return result.wsgi_application(environ, start_response)
311
315
312 # ==============================================================
316 # ==============================================================
313 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
317 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
314 # ==============================================================
318 # ==============================================================
315 user = User.get_by_username(username)
319 user = User.get_by_username(username)
316 if not self.valid_and_active_user(user):
320 if not self.valid_and_active_user(user):
317 return HTTPForbidden()(environ, start_response)
321 return HTTPForbidden()(environ, start_response)
318 username = user.username
322 username = user.username
319 user.update_lastactivity()
323 user.update_lastactivity()
320 meta.Session().commit()
324 meta.Session().commit()
321
325
322 # check user attributes for password change flag
326 # check user attributes for password change flag
323 user_obj = user
327 user_obj = user
324 if user_obj and user_obj.username != User.DEFAULT_USER and \
328 if user_obj and user_obj.username != User.DEFAULT_USER and \
325 user_obj.user_data.get('force_password_change'):
329 user_obj.user_data.get('force_password_change'):
326 reason = 'password change required'
330 reason = 'password change required'
327 log.debug('User not allowed to authenticate, %s', reason)
331 log.debug('User not allowed to authenticate, %s', reason)
328 return HTTPNotAcceptable(reason)(environ, start_response)
332 return HTTPNotAcceptable(reason)(environ, start_response)
329
333
330 # check permissions for this repository
334 # check permissions for this repository
331 perm = self._check_permission(action, user, repo_name, ip_addr)
335 perm = self._check_permission(action, user, repo_name, ip_addr)
332 if not perm:
336 if not perm:
333 return HTTPForbidden()(environ, start_response)
337 return HTTPForbidden()(environ, start_response)
334
338
335 # extras are injected into UI object and later available
339 # extras are injected into UI object and later available
336 # in hooks executed by rhodecode
340 # in hooks executed by rhodecode
337 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
341 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
338 extras = vcs_operation_context(
342 extras = vcs_operation_context(
339 environ, repo_name=repo_name, username=username,
343 environ, repo_name=repo_name, username=username,
340 action=action, scm=self.SCM,
344 action=action, scm=self.SCM,
341 check_locking=check_locking)
345 check_locking=check_locking)
342
346
343 # ======================================================================
347 # ======================================================================
344 # REQUEST HANDLING
348 # REQUEST HANDLING
345 # ======================================================================
349 # ======================================================================
346 str_repo_name = safe_str(repo_name)
350 str_repo_name = safe_str(repo_name)
347 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
351 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
348 log.debug('Repository path is %s', repo_path)
352 log.debug('Repository path is %s', repo_path)
349
353
350 fix_PATH()
354 fix_PATH()
351
355
352 log.info(
356 log.info(
353 '%s action on %s repo "%s" by "%s" from %s',
357 '%s action on %s repo "%s" by "%s" from %s',
354 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
358 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
355
359
356 return self._generate_vcs_response(
360 return self._generate_vcs_response(
357 environ, start_response, repo_path, repo_name, extras, action)
361 environ, start_response, repo_path, repo_name, extras, action)
358
362
359 @initialize_generator
363 @initialize_generator
360 def _generate_vcs_response(
364 def _generate_vcs_response(
361 self, environ, start_response, repo_path, repo_name, extras,
365 self, environ, start_response, repo_path, repo_name, extras,
362 action):
366 action):
363 """
367 """
364 Returns a generator for the response content.
368 Returns a generator for the response content.
365
369
366 This method is implemented as a generator, so that it can trigger
370 This method is implemented as a generator, so that it can trigger
367 the cache validation after all content sent back to the client. It
371 the cache validation after all content sent back to the client. It
368 also handles the locking exceptions which will be triggered when
372 also handles the locking exceptions which will be triggered when
369 the first chunk is produced by the underlying WSGI application.
373 the first chunk is produced by the underlying WSGI application.
370 """
374 """
371 callback_daemon, extras = self._prepare_callback_daemon(extras)
375 callback_daemon, extras = self._prepare_callback_daemon(extras)
372 config = self._create_config(extras, repo_name)
376 config = self._create_config(extras, repo_name)
373 log.debug('HOOKS extras is %s', extras)
377 log.debug('HOOKS extras is %s', extras)
374 app = self._create_wsgi_app(repo_path, repo_name, config)
378 app = self._create_wsgi_app(repo_path, repo_name, config)
375
379
376 try:
380 try:
377 with callback_daemon:
381 with callback_daemon:
378 try:
382 try:
379 response = app(environ, start_response)
383 response = app(environ, start_response)
380 finally:
384 finally:
381 # This statement works together with the decorator
385 # This statement works together with the decorator
382 # "initialize_generator" above. The decorator ensures that
386 # "initialize_generator" above. The decorator ensures that
383 # we hit the first yield statement before the generator is
387 # we hit the first yield statement before the generator is
384 # returned back to the WSGI server. This is needed to
388 # returned back to the WSGI server. This is needed to
385 # ensure that the call to "app" above triggers the
389 # ensure that the call to "app" above triggers the
386 # needed callback to "start_response" before the
390 # needed callback to "start_response" before the
387 # generator is actually used.
391 # generator is actually used.
388 yield "__init__"
392 yield "__init__"
389
393
390 for chunk in response:
394 for chunk in response:
391 yield chunk
395 yield chunk
392 except Exception as exc:
396 except Exception as exc:
393 # TODO: johbo: Improve "translating" back the exception.
397 # TODO: johbo: Improve "translating" back the exception.
394 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
398 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
395 exc = HTTPLockedRC(*exc.args)
399 exc = HTTPLockedRC(*exc.args)
396 _code = rhodecode.CONFIG.get('lock_ret_code')
400 _code = rhodecode.CONFIG.get('lock_ret_code')
397 log.debug('Repository LOCKED ret code %s!', (_code,))
401 log.debug('Repository LOCKED ret code %s!', (_code,))
398 elif getattr(exc, '_vcs_kind', None) == 'requirement':
402 elif getattr(exc, '_vcs_kind', None) == 'requirement':
399 log.debug(
403 log.debug(
400 'Repository requires features unknown to this Mercurial')
404 'Repository requires features unknown to this Mercurial')
401 exc = HTTPRequirementError(*exc.args)
405 exc = HTTPRequirementError(*exc.args)
402 else:
406 else:
403 raise
407 raise
404
408
405 for chunk in exc(environ, start_response):
409 for chunk in exc(environ, start_response):
406 yield chunk
410 yield chunk
407 finally:
411 finally:
408 # invalidate cache on push
412 # invalidate cache on push
409 try:
413 try:
410 if action == 'push':
414 if action == 'push':
411 self._invalidate_cache(repo_name)
415 self._invalidate_cache(repo_name)
412 finally:
416 finally:
413 meta.Session.remove()
417 meta.Session.remove()
414
418
415 def _get_repository_name(self, environ):
419 def _get_repository_name(self, environ):
416 """Get repository name out of the environmnent
420 """Get repository name out of the environmnent
417
421
418 :param environ: WSGI environment
422 :param environ: WSGI environment
419 """
423 """
420 raise NotImplementedError()
424 raise NotImplementedError()
421
425
422 def _get_action(self, environ):
426 def _get_action(self, environ):
423 """Map request commands into a pull or push command.
427 """Map request commands into a pull or push command.
424
428
425 :param environ: WSGI environment
429 :param environ: WSGI environment
426 """
430 """
427 raise NotImplementedError()
431 raise NotImplementedError()
428
432
429 def _create_wsgi_app(self, repo_path, repo_name, config):
433 def _create_wsgi_app(self, repo_path, repo_name, config):
430 """Return the WSGI app that will finally handle the request."""
434 """Return the WSGI app that will finally handle the request."""
431 raise NotImplementedError()
435 raise NotImplementedError()
432
436
433 def _create_config(self, extras, repo_name):
437 def _create_config(self, extras, repo_name):
434 """Create a Pyro safe config representation."""
438 """Create a Pyro safe config representation."""
435 raise NotImplementedError()
439 raise NotImplementedError()
436
440
437 def _prepare_callback_daemon(self, extras):
441 def _prepare_callback_daemon(self, extras):
438 return prepare_callback_daemon(
442 return prepare_callback_daemon(
439 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
443 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
440 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
444 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
441
445
442
446
443 def _should_check_locking(query_string):
447 def _should_check_locking(query_string):
444 # this is kind of hacky, but due to how mercurial handles client-server
448 # this is kind of hacky, but due to how mercurial handles client-server
445 # server see all operation on commit; bookmarks, phases and
449 # server see all operation on commit; bookmarks, phases and
446 # obsolescence marker in different transaction, we don't want to check
450 # obsolescence marker in different transaction, we don't want to check
447 # locking on those
451 # locking on those
448 return query_string not in ['cmd=listkeys']
452 return query_string not in ['cmd=listkeys']
@@ -1,161 +1,178 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import gzip
21 import gzip
22 import shutil
22 import shutil
23 import logging
23 import logging
24 import tempfile
24 import tempfile
25 import urlparse
25 import urlparse
26
26
27 import rhodecode
27 import rhodecode
28 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
28 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
29 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
29 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
30 from rhodecode.lib.middleware.simplehg import SimpleHg
30 from rhodecode.lib.middleware.simplehg import SimpleHg
31 from rhodecode.lib.middleware.simplesvn import SimpleSvn
31 from rhodecode.lib.middleware.simplesvn import SimpleSvn
32
32 from rhodecode.lib.vcs.backends import base
33 from rhodecode.model.settings import VcsSettingsModel
33
34
34 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
35
36
36
37
37 def is_git(environ):
38 def is_git(environ):
38 """
39 """
39 Returns True if requests should be handled by GIT wsgi middleware
40 Returns True if requests should be handled by GIT wsgi middleware
40 """
41 """
41 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
42 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
42 log.debug(
43 log.debug(
43 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
44 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
44 is_git_path is not None)
45 is_git_path is not None)
45
46
46 return is_git_path
47 return is_git_path
47
48
48
49
49 def is_hg(environ):
50 def is_hg(environ):
50 """
51 """
51 Returns True if requests target is mercurial server - header
52 Returns True if requests target is mercurial server - header
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 """
54 """
54 is_hg_path = False
55 is_hg_path = False
55
56
56 http_accept = environ.get('HTTP_ACCEPT')
57 http_accept = environ.get('HTTP_ACCEPT')
57
58
58 if http_accept and http_accept.startswith('application/mercurial'):
59 if http_accept and http_accept.startswith('application/mercurial'):
59 query = urlparse.parse_qs(environ['QUERY_STRING'])
60 query = urlparse.parse_qs(environ['QUERY_STRING'])
60 if 'cmd' in query:
61 if 'cmd' in query:
61 is_hg_path = True
62 is_hg_path = True
62
63
63 log.debug(
64 log.debug(
64 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
65 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
65 is_hg_path)
66 is_hg_path)
66
67
67 return is_hg_path
68 return is_hg_path
68
69
69
70
70 def is_svn(environ):
71 def is_svn(environ):
71 """
72 """
72 Returns True if requests target is Subversion server
73 Returns True if requests target is Subversion server
73 """
74 """
74 http_dav = environ.get('HTTP_DAV', '')
75 http_dav = environ.get('HTTP_DAV', '')
75 magic_path_segment = rhodecode.CONFIG.get(
76 magic_path_segment = rhodecode.CONFIG.get(
76 'rhodecode_subversion_magic_path', '/!svn')
77 'rhodecode_subversion_magic_path', '/!svn')
77 is_svn_path = (
78 is_svn_path = (
78 'subversion' in http_dav or
79 'subversion' in http_dav or
79 magic_path_segment in environ['PATH_INFO'])
80 magic_path_segment in environ['PATH_INFO'])
80 log.debug(
81 log.debug(
81 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
82 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
82 is_svn_path)
83 is_svn_path)
83
84
84 return is_svn_path
85 return is_svn_path
85
86
86
87
87 class GunzipMiddleware(object):
88 class GunzipMiddleware(object):
88 """
89 """
89 WSGI middleware that unzips gzip-encoded requests before
90 WSGI middleware that unzips gzip-encoded requests before
90 passing on to the underlying application.
91 passing on to the underlying application.
91 """
92 """
92
93
93 def __init__(self, application):
94 def __init__(self, application):
94 self.app = application
95 self.app = application
95
96
96 def __call__(self, environ, start_response):
97 def __call__(self, environ, start_response):
97 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
98 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
98
99
99 if b'gzip' in accepts_encoding_header:
100 if b'gzip' in accepts_encoding_header:
100 log.debug('gzip detected, now running gunzip wrapper')
101 log.debug('gzip detected, now running gunzip wrapper')
101 wsgi_input = environ['wsgi.input']
102 wsgi_input = environ['wsgi.input']
102
103
103 if not hasattr(environ['wsgi.input'], 'seek'):
104 if not hasattr(environ['wsgi.input'], 'seek'):
104 # The gzip implementation in the standard library of Python 2.x
105 # The gzip implementation in the standard library of Python 2.x
105 # requires the '.seek()' and '.tell()' methods to be available
106 # requires the '.seek()' and '.tell()' methods to be available
106 # on the input stream. Read the data into a temporary file to
107 # on the input stream. Read the data into a temporary file to
107 # work around this limitation.
108 # work around this limitation.
108
109
109 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
110 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
110 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
111 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
111 wsgi_input.seek(0)
112 wsgi_input.seek(0)
112
113
113 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
114 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
114 # since we "Ungzipped" the content we say now it's no longer gzip
115 # since we "Ungzipped" the content we say now it's no longer gzip
115 # content encoding
116 # content encoding
116 del environ['HTTP_CONTENT_ENCODING']
117 del environ['HTTP_CONTENT_ENCODING']
117
118
118 # content length has changes ? or i'm not sure
119 # content length has changes ? or i'm not sure
119 if 'CONTENT_LENGTH' in environ:
120 if 'CONTENT_LENGTH' in environ:
120 del environ['CONTENT_LENGTH']
121 del environ['CONTENT_LENGTH']
121 else:
122 else:
122 log.debug('content not gzipped, gzipMiddleware passing '
123 log.debug('content not gzipped, gzipMiddleware passing '
123 'request further')
124 'request further')
124 return self.app(environ, start_response)
125 return self.app(environ, start_response)
125
126
126
127
127 class VCSMiddleware(object):
128 class VCSMiddleware(object):
128
129
129 def __init__(self, app, config, appenlight_client, registry):
130 def __init__(self, app, config, appenlight_client, registry):
130 self.application = app
131 self.application = app
131 self.config = config
132 self.config = config
132 self.appenlight_client = appenlight_client
133 self.appenlight_client = appenlight_client
133 self.registry = registry
134 self.registry = registry
135 self.repo_vcs_config = base.Config()
136 self.use_gzip = True
137
138 def vcs_config(self, repo_name=None):
139 """
140 returns serialized VcsSettings
141 """
142 return VcsSettingsModel(repo=repo_name).get_ui_settings_as_config_obj()
143
144 def wrap_in_gzip_if_enabled(self, app):
145 if self.use_gzip:
146 app = GunzipMiddleware(app)
147 return app
134
148
135 def _get_handler_app(self, environ):
149 def _get_handler_app(self, environ):
136 app = None
150 app = None
151
137 if is_hg(environ):
152 if is_hg(environ):
138 app = SimpleHg(self.application, self.config, self.registry)
153 app = SimpleHg(self.application, self.config, self.registry)
139
154
140 if is_git(environ):
155 if is_git(environ):
141 app = SimpleGit(self.application, self.config, self.registry)
156 app = SimpleGit(self.application, self.config, self.registry)
142
157
143 proxy_svn = rhodecode.CONFIG.get(
158 if is_svn(environ):
144 'rhodecode_proxy_subversion_http_requests', False)
145 if proxy_svn and is_svn(environ):
146 app = SimpleSvn(self.application, self.config, self.registry)
159 app = SimpleSvn(self.application, self.config, self.registry)
147
160
148 if app:
161 if app:
149 app = GunzipMiddleware(app)
162 repo_name = app._get_repository_name(environ)
163 self.repo_vcs_config = self.vcs_config(repo_name)
164 app.repo_vcs_config = self.repo_vcs_config
165
166 app = self.wrap_in_gzip_if_enabled(app)
150 app, _ = wrap_in_appenlight_if_enabled(
167 app, _ = wrap_in_appenlight_if_enabled(
151 app, self.config, self.appenlight_client)
168 app, self.config, self.appenlight_client)
152
169
153 return app
170 return app
154
171
155 def __call__(self, environ, start_response):
172 def __call__(self, environ, start_response):
156 # check if we handle one of interesting protocols ?
173 # check if we handle one of interesting protocols ?
157 vcs_handler = self._get_handler_app(environ)
174 vcs_handler = self._get_handler_app(environ)
158 if vcs_handler:
175 if vcs_handler:
159 return vcs_handler(environ, start_response)
176 return vcs_handler(environ, start_response)
160
177
161 return self.application(environ, start_response)
178 return self.application(environ, start_response)
@@ -1,547 +1,547 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pylons.i18n.translation import _
51 from pylons.i18n.translation import _
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 return self.load(template_name)(**kw)
69 return self.load(template_name)(**kw)
70
70
71
71
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 deform.Form.set_default_renderer(form_renderer)
73 deform.Form.set_default_renderer(form_renderer)
74
74
75
75
76 def LoginForm():
76 def LoginForm():
77 class _LoginForm(formencode.Schema):
77 class _LoginForm(formencode.Schema):
78 allow_extra_fields = True
78 allow_extra_fields = True
79 filter_extra_fields = True
79 filter_extra_fields = True
80 username = v.UnicodeString(
80 username = v.UnicodeString(
81 strip=True,
81 strip=True,
82 min=1,
82 min=1,
83 not_empty=True,
83 not_empty=True,
84 messages={
84 messages={
85 'empty': _(u'Please enter a login'),
85 'empty': _(u'Please enter a login'),
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 }
87 }
88 )
88 )
89
89
90 password = v.UnicodeString(
90 password = v.UnicodeString(
91 strip=False,
91 strip=False,
92 min=3,
92 min=3,
93 not_empty=True,
93 not_empty=True,
94 messages={
94 messages={
95 'empty': _(u'Please enter a password'),
95 'empty': _(u'Please enter a password'),
96 'tooShort': _(u'Enter %(min)i characters or more')}
96 'tooShort': _(u'Enter %(min)i characters or more')}
97 )
97 )
98
98
99 remember = v.StringBoolean(if_missing=False)
99 remember = v.StringBoolean(if_missing=False)
100
100
101 chained_validators = [v.ValidAuth()]
101 chained_validators = [v.ValidAuth()]
102 return _LoginForm
102 return _LoginForm
103
103
104
104
105 def UserForm(edit=False, available_languages=[], old_data={}):
105 def UserForm(edit=False, available_languages=[], old_data={}):
106 class _UserForm(formencode.Schema):
106 class _UserForm(formencode.Schema):
107 allow_extra_fields = True
107 allow_extra_fields = True
108 filter_extra_fields = True
108 filter_extra_fields = True
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 v.ValidUsername(edit, old_data))
110 v.ValidUsername(edit, old_data))
111 if edit:
111 if edit:
112 new_password = All(
112 new_password = All(
113 v.ValidPassword(),
113 v.ValidPassword(),
114 v.UnicodeString(strip=False, min=6, not_empty=False)
114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 )
115 )
116 password_confirmation = All(
116 password_confirmation = All(
117 v.ValidPassword(),
117 v.ValidPassword(),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 )
119 )
120 admin = v.StringBoolean(if_missing=False)
120 admin = v.StringBoolean(if_missing=False)
121 else:
121 else:
122 password = All(
122 password = All(
123 v.ValidPassword(),
123 v.ValidPassword(),
124 v.UnicodeString(strip=False, min=6, not_empty=True)
124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 )
125 )
126 password_confirmation = All(
126 password_confirmation = All(
127 v.ValidPassword(),
127 v.ValidPassword(),
128 v.UnicodeString(strip=False, min=6, not_empty=False)
128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 )
129 )
130
130
131 password_change = v.StringBoolean(if_missing=False)
131 password_change = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
133
133
134 active = v.StringBoolean(if_missing=False)
134 active = v.StringBoolean(if_missing=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 extern_name = v.UnicodeString(strip=True)
138 extern_name = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
140 language = v.OneOf(available_languages, hideList=False,
140 language = v.OneOf(available_languages, hideList=False,
141 testValueList=True, if_missing=None)
141 testValueList=True, if_missing=None)
142 chained_validators = [v.ValidPasswordsMatch()]
142 chained_validators = [v.ValidPasswordsMatch()]
143 return _UserForm
143 return _UserForm
144
144
145
145
146 def UserGroupForm(edit=False, old_data=None, available_members=None,
146 def UserGroupForm(edit=False, old_data=None, available_members=None,
147 allow_disabled=False):
147 allow_disabled=False):
148 old_data = old_data or {}
148 old_data = old_data or {}
149 available_members = available_members or []
149 available_members = available_members or []
150
150
151 class _UserGroupForm(formencode.Schema):
151 class _UserGroupForm(formencode.Schema):
152 allow_extra_fields = True
152 allow_extra_fields = True
153 filter_extra_fields = True
153 filter_extra_fields = True
154
154
155 users_group_name = All(
155 users_group_name = All(
156 v.UnicodeString(strip=True, min=1, not_empty=True),
156 v.UnicodeString(strip=True, min=1, not_empty=True),
157 v.ValidUserGroup(edit, old_data)
157 v.ValidUserGroup(edit, old_data)
158 )
158 )
159 user_group_description = v.UnicodeString(strip=True, min=1,
159 user_group_description = v.UnicodeString(strip=True, min=1,
160 not_empty=False)
160 not_empty=False)
161
161
162 users_group_active = v.StringBoolean(if_missing=False)
162 users_group_active = v.StringBoolean(if_missing=False)
163
163
164 if edit:
164 if edit:
165 users_group_members = v.OneOf(
165 users_group_members = v.OneOf(
166 available_members, hideList=False, testValueList=True,
166 available_members, hideList=False, testValueList=True,
167 if_missing=None, not_empty=False
167 if_missing=None, not_empty=False
168 )
168 )
169 # this is user group owner
169 # this is user group owner
170 user = All(
170 user = All(
171 v.UnicodeString(not_empty=True),
171 v.UnicodeString(not_empty=True),
172 v.ValidRepoUser(allow_disabled))
172 v.ValidRepoUser(allow_disabled))
173 return _UserGroupForm
173 return _UserGroupForm
174
174
175
175
176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
177 can_create_in_root=False, allow_disabled=False):
177 can_create_in_root=False, allow_disabled=False):
178 old_data = old_data or {}
178 old_data = old_data or {}
179 available_groups = available_groups or []
179 available_groups = available_groups or []
180
180
181 class _RepoGroupForm(formencode.Schema):
181 class _RepoGroupForm(formencode.Schema):
182 allow_extra_fields = True
182 allow_extra_fields = True
183 filter_extra_fields = False
183 filter_extra_fields = False
184
184
185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
186 v.SlugifyName(),)
186 v.SlugifyName(),)
187 group_description = v.UnicodeString(strip=True, min=1,
187 group_description = v.UnicodeString(strip=True, min=1,
188 not_empty=False)
188 not_empty=False)
189 group_copy_permissions = v.StringBoolean(if_missing=False)
189 group_copy_permissions = v.StringBoolean(if_missing=False)
190
190
191 group_parent_id = v.OneOf(available_groups, hideList=False,
191 group_parent_id = v.OneOf(available_groups, hideList=False,
192 testValueList=True, not_empty=True)
192 testValueList=True, not_empty=True)
193 enable_locking = v.StringBoolean(if_missing=False)
193 enable_locking = v.StringBoolean(if_missing=False)
194 chained_validators = [
194 chained_validators = [
195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
196
196
197 if edit:
197 if edit:
198 # this is repo group owner
198 # this is repo group owner
199 user = All(
199 user = All(
200 v.UnicodeString(not_empty=True),
200 v.UnicodeString(not_empty=True),
201 v.ValidRepoUser(allow_disabled))
201 v.ValidRepoUser(allow_disabled))
202
202
203 return _RepoGroupForm
203 return _RepoGroupForm
204
204
205
205
206 def RegisterForm(edit=False, old_data={}):
206 def RegisterForm(edit=False, old_data={}):
207 class _RegisterForm(formencode.Schema):
207 class _RegisterForm(formencode.Schema):
208 allow_extra_fields = True
208 allow_extra_fields = True
209 filter_extra_fields = True
209 filter_extra_fields = True
210 username = All(
210 username = All(
211 v.ValidUsername(edit, old_data),
211 v.ValidUsername(edit, old_data),
212 v.UnicodeString(strip=True, min=1, not_empty=True)
212 v.UnicodeString(strip=True, min=1, not_empty=True)
213 )
213 )
214 password = All(
214 password = All(
215 v.ValidPassword(),
215 v.ValidPassword(),
216 v.UnicodeString(strip=False, min=6, not_empty=True)
216 v.UnicodeString(strip=False, min=6, not_empty=True)
217 )
217 )
218 password_confirmation = All(
218 password_confirmation = All(
219 v.ValidPassword(),
219 v.ValidPassword(),
220 v.UnicodeString(strip=False, min=6, not_empty=True)
220 v.UnicodeString(strip=False, min=6, not_empty=True)
221 )
221 )
222 active = v.StringBoolean(if_missing=False)
222 active = v.StringBoolean(if_missing=False)
223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
226
226
227 chained_validators = [v.ValidPasswordsMatch()]
227 chained_validators = [v.ValidPasswordsMatch()]
228
228
229 return _RegisterForm
229 return _RegisterForm
230
230
231
231
232 def PasswordResetForm():
232 def PasswordResetForm():
233 class _PasswordResetForm(formencode.Schema):
233 class _PasswordResetForm(formencode.Schema):
234 allow_extra_fields = True
234 allow_extra_fields = True
235 filter_extra_fields = True
235 filter_extra_fields = True
236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
237 return _PasswordResetForm
237 return _PasswordResetForm
238
238
239
239
240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
241 allow_disabled=False):
241 allow_disabled=False):
242 old_data = old_data or {}
242 old_data = old_data or {}
243 repo_groups = repo_groups or []
243 repo_groups = repo_groups or []
244 landing_revs = landing_revs or []
244 landing_revs = landing_revs or []
245 supported_backends = BACKENDS.keys()
245 supported_backends = BACKENDS.keys()
246
246
247 class _RepoForm(formencode.Schema):
247 class _RepoForm(formencode.Schema):
248 allow_extra_fields = True
248 allow_extra_fields = True
249 filter_extra_fields = False
249 filter_extra_fields = False
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
251 v.SlugifyName())
251 v.SlugifyName())
252 repo_group = All(v.CanWriteGroup(old_data),
252 repo_group = All(v.CanWriteGroup(old_data),
253 v.OneOf(repo_groups, hideList=True))
253 v.OneOf(repo_groups, hideList=True))
254 repo_type = v.OneOf(supported_backends, required=False,
254 repo_type = v.OneOf(supported_backends, required=False,
255 if_missing=old_data.get('repo_type'))
255 if_missing=old_data.get('repo_type'))
256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
257 repo_private = v.StringBoolean(if_missing=False)
257 repo_private = v.StringBoolean(if_missing=False)
258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
259 repo_copy_permissions = v.StringBoolean(if_missing=False)
259 repo_copy_permissions = v.StringBoolean(if_missing=False)
260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
261
261
262 repo_enable_statistics = v.StringBoolean(if_missing=False)
262 repo_enable_statistics = v.StringBoolean(if_missing=False)
263 repo_enable_downloads = v.StringBoolean(if_missing=False)
263 repo_enable_downloads = v.StringBoolean(if_missing=False)
264 repo_enable_locking = v.StringBoolean(if_missing=False)
264 repo_enable_locking = v.StringBoolean(if_missing=False)
265
265
266 if edit:
266 if edit:
267 # this is repo owner
267 # this is repo owner
268 user = All(
268 user = All(
269 v.UnicodeString(not_empty=True),
269 v.UnicodeString(not_empty=True),
270 v.ValidRepoUser(allow_disabled))
270 v.ValidRepoUser(allow_disabled))
271 clone_uri_change = v.UnicodeString(
271 clone_uri_change = v.UnicodeString(
272 not_empty=False, if_missing=v.Missing)
272 not_empty=False, if_missing=v.Missing)
273
273
274 chained_validators = [v.ValidCloneUri(),
274 chained_validators = [v.ValidCloneUri(),
275 v.ValidRepoName(edit, old_data)]
275 v.ValidRepoName(edit, old_data)]
276 return _RepoForm
276 return _RepoForm
277
277
278
278
279 def RepoPermsForm():
279 def RepoPermsForm():
280 class _RepoPermsForm(formencode.Schema):
280 class _RepoPermsForm(formencode.Schema):
281 allow_extra_fields = True
281 allow_extra_fields = True
282 filter_extra_fields = False
282 filter_extra_fields = False
283 chained_validators = [v.ValidPerms(type_='repo')]
283 chained_validators = [v.ValidPerms(type_='repo')]
284 return _RepoPermsForm
284 return _RepoPermsForm
285
285
286
286
287 def RepoGroupPermsForm(valid_recursive_choices):
287 def RepoGroupPermsForm(valid_recursive_choices):
288 class _RepoGroupPermsForm(formencode.Schema):
288 class _RepoGroupPermsForm(formencode.Schema):
289 allow_extra_fields = True
289 allow_extra_fields = True
290 filter_extra_fields = False
290 filter_extra_fields = False
291 recursive = v.OneOf(valid_recursive_choices)
291 recursive = v.OneOf(valid_recursive_choices)
292 chained_validators = [v.ValidPerms(type_='repo_group')]
292 chained_validators = [v.ValidPerms(type_='repo_group')]
293 return _RepoGroupPermsForm
293 return _RepoGroupPermsForm
294
294
295
295
296 def UserGroupPermsForm():
296 def UserGroupPermsForm():
297 class _UserPermsForm(formencode.Schema):
297 class _UserPermsForm(formencode.Schema):
298 allow_extra_fields = True
298 allow_extra_fields = True
299 filter_extra_fields = False
299 filter_extra_fields = False
300 chained_validators = [v.ValidPerms(type_='user_group')]
300 chained_validators = [v.ValidPerms(type_='user_group')]
301 return _UserPermsForm
301 return _UserPermsForm
302
302
303
303
304 def RepoFieldForm():
304 def RepoFieldForm():
305 class _RepoFieldForm(formencode.Schema):
305 class _RepoFieldForm(formencode.Schema):
306 filter_extra_fields = True
306 filter_extra_fields = True
307 allow_extra_fields = True
307 allow_extra_fields = True
308
308
309 new_field_key = All(v.FieldKey(),
309 new_field_key = All(v.FieldKey(),
310 v.UnicodeString(strip=True, min=3, not_empty=True))
310 v.UnicodeString(strip=True, min=3, not_empty=True))
311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
313 if_missing='str')
313 if_missing='str')
314 new_field_label = v.UnicodeString(not_empty=False)
314 new_field_label = v.UnicodeString(not_empty=False)
315 new_field_desc = v.UnicodeString(not_empty=False)
315 new_field_desc = v.UnicodeString(not_empty=False)
316
316
317 return _RepoFieldForm
317 return _RepoFieldForm
318
318
319
319
320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
321 repo_groups=[], landing_revs=[]):
321 repo_groups=[], landing_revs=[]):
322 class _RepoForkForm(formencode.Schema):
322 class _RepoForkForm(formencode.Schema):
323 allow_extra_fields = True
323 allow_extra_fields = True
324 filter_extra_fields = False
324 filter_extra_fields = False
325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
326 v.SlugifyName())
326 v.SlugifyName())
327 repo_group = All(v.CanWriteGroup(),
327 repo_group = All(v.CanWriteGroup(),
328 v.OneOf(repo_groups, hideList=True))
328 v.OneOf(repo_groups, hideList=True))
329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
331 private = v.StringBoolean(if_missing=False)
331 private = v.StringBoolean(if_missing=False)
332 copy_permissions = v.StringBoolean(if_missing=False)
332 copy_permissions = v.StringBoolean(if_missing=False)
333 fork_parent_id = v.UnicodeString()
333 fork_parent_id = v.UnicodeString()
334 chained_validators = [v.ValidForkName(edit, old_data)]
334 chained_validators = [v.ValidForkName(edit, old_data)]
335 landing_rev = v.OneOf(landing_revs, hideList=True)
335 landing_rev = v.OneOf(landing_revs, hideList=True)
336
336
337 return _RepoForkForm
337 return _RepoForkForm
338
338
339
339
340 def ApplicationSettingsForm():
340 def ApplicationSettingsForm():
341 class _ApplicationSettingsForm(formencode.Schema):
341 class _ApplicationSettingsForm(formencode.Schema):
342 allow_extra_fields = True
342 allow_extra_fields = True
343 filter_extra_fields = False
343 filter_extra_fields = False
344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
350
350
351 return _ApplicationSettingsForm
351 return _ApplicationSettingsForm
352
352
353
353
354 def ApplicationVisualisationForm():
354 def ApplicationVisualisationForm():
355 class _ApplicationVisualisationForm(formencode.Schema):
355 class _ApplicationVisualisationForm(formencode.Schema):
356 allow_extra_fields = True
356 allow_extra_fields = True
357 filter_extra_fields = False
357 filter_extra_fields = False
358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
361
361
362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
366 rhodecode_show_version = v.StringBoolean(if_missing=False)
366 rhodecode_show_version = v.StringBoolean(if_missing=False)
367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
369 rhodecode_gravatar_url = v.UnicodeString(min=3)
369 rhodecode_gravatar_url = v.UnicodeString(min=3)
370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
371 rhodecode_support_url = v.UnicodeString()
371 rhodecode_support_url = v.UnicodeString()
372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
374
374
375 return _ApplicationVisualisationForm
375 return _ApplicationVisualisationForm
376
376
377
377
378 class _BaseVcsSettingsForm(formencode.Schema):
378 class _BaseVcsSettingsForm(formencode.Schema):
379 allow_extra_fields = True
379 allow_extra_fields = True
380 filter_extra_fields = False
380 filter_extra_fields = False
381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
384
384
385 extensions_largefiles = v.StringBoolean(if_missing=False)
385 extensions_largefiles = v.StringBoolean(if_missing=False)
386 phases_publish = v.StringBoolean(if_missing=False)
386 phases_publish = v.StringBoolean(if_missing=False)
387
387
388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
391
391
392 rhodecode_proxy_subversion_http_requests = v.StringBoolean(if_missing=False)
392 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
393 rhodecode_subversion_http_server_url = v.UnicodeString(
393 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
394 strip=True, if_missing=None)
394
395
395
396 def ApplicationUiSettingsForm():
396 def ApplicationUiSettingsForm():
397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
398 web_push_ssl = v.StringBoolean(if_missing=False)
398 web_push_ssl = v.StringBoolean(if_missing=False)
399 paths_root_path = All(
399 paths_root_path = All(
400 v.ValidPath(),
400 v.ValidPath(),
401 v.UnicodeString(strip=True, min=1, not_empty=True)
401 v.UnicodeString(strip=True, min=1, not_empty=True)
402 )
402 )
403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
404 extensions_hggit = v.StringBoolean(if_missing=False)
404 extensions_hggit = v.StringBoolean(if_missing=False)
405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
407
407
408 return _ApplicationUiSettingsForm
408 return _ApplicationUiSettingsForm
409
409
410
410
411 def RepoVcsSettingsForm(repo_name):
411 def RepoVcsSettingsForm(repo_name):
412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
413 inherit_global_settings = v.StringBoolean(if_missing=False)
413 inherit_global_settings = v.StringBoolean(if_missing=False)
414 new_svn_branch = v.ValidSvnPattern(
414 new_svn_branch = v.ValidSvnPattern(
415 section='vcs_svn_branch', repo_name=repo_name)
415 section='vcs_svn_branch', repo_name=repo_name)
416 new_svn_tag = v.ValidSvnPattern(
416 new_svn_tag = v.ValidSvnPattern(
417 section='vcs_svn_tag', repo_name=repo_name)
417 section='vcs_svn_tag', repo_name=repo_name)
418
418
419 return _RepoVcsSettingsForm
419 return _RepoVcsSettingsForm
420
420
421
421
422 def LabsSettingsForm():
422 def LabsSettingsForm():
423 class _LabSettingsForm(formencode.Schema):
423 class _LabSettingsForm(formencode.Schema):
424 allow_extra_fields = True
424 allow_extra_fields = True
425 filter_extra_fields = False
425 filter_extra_fields = False
426
426
427 return _LabSettingsForm
427 return _LabSettingsForm
428
428
429
429
430 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
430 def ApplicationPermissionsForm(register_choices, extern_activate_choices):
431 class _DefaultPermissionsForm(formencode.Schema):
431 class _DefaultPermissionsForm(formencode.Schema):
432 allow_extra_fields = True
432 allow_extra_fields = True
433 filter_extra_fields = True
433 filter_extra_fields = True
434
434
435 anonymous = v.StringBoolean(if_missing=False)
435 anonymous = v.StringBoolean(if_missing=False)
436 default_register = v.OneOf(register_choices)
436 default_register = v.OneOf(register_choices)
437 default_register_message = v.UnicodeString()
437 default_register_message = v.UnicodeString()
438 default_extern_activate = v.OneOf(extern_activate_choices)
438 default_extern_activate = v.OneOf(extern_activate_choices)
439
439
440 return _DefaultPermissionsForm
440 return _DefaultPermissionsForm
441
441
442
442
443 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
443 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
444 user_group_perms_choices):
444 user_group_perms_choices):
445 class _ObjectPermissionsForm(formencode.Schema):
445 class _ObjectPermissionsForm(formencode.Schema):
446 allow_extra_fields = True
446 allow_extra_fields = True
447 filter_extra_fields = True
447 filter_extra_fields = True
448 overwrite_default_repo = v.StringBoolean(if_missing=False)
448 overwrite_default_repo = v.StringBoolean(if_missing=False)
449 overwrite_default_group = v.StringBoolean(if_missing=False)
449 overwrite_default_group = v.StringBoolean(if_missing=False)
450 overwrite_default_user_group = v.StringBoolean(if_missing=False)
450 overwrite_default_user_group = v.StringBoolean(if_missing=False)
451 default_repo_perm = v.OneOf(repo_perms_choices)
451 default_repo_perm = v.OneOf(repo_perms_choices)
452 default_group_perm = v.OneOf(group_perms_choices)
452 default_group_perm = v.OneOf(group_perms_choices)
453 default_user_group_perm = v.OneOf(user_group_perms_choices)
453 default_user_group_perm = v.OneOf(user_group_perms_choices)
454
454
455 return _ObjectPermissionsForm
455 return _ObjectPermissionsForm
456
456
457
457
458 def UserPermissionsForm(create_choices, create_on_write_choices,
458 def UserPermissionsForm(create_choices, create_on_write_choices,
459 repo_group_create_choices, user_group_create_choices,
459 repo_group_create_choices, user_group_create_choices,
460 fork_choices, inherit_default_permissions_choices):
460 fork_choices, inherit_default_permissions_choices):
461 class _DefaultPermissionsForm(formencode.Schema):
461 class _DefaultPermissionsForm(formencode.Schema):
462 allow_extra_fields = True
462 allow_extra_fields = True
463 filter_extra_fields = True
463 filter_extra_fields = True
464
464
465 anonymous = v.StringBoolean(if_missing=False)
465 anonymous = v.StringBoolean(if_missing=False)
466
466
467 default_repo_create = v.OneOf(create_choices)
467 default_repo_create = v.OneOf(create_choices)
468 default_repo_create_on_write = v.OneOf(create_on_write_choices)
468 default_repo_create_on_write = v.OneOf(create_on_write_choices)
469 default_user_group_create = v.OneOf(user_group_create_choices)
469 default_user_group_create = v.OneOf(user_group_create_choices)
470 default_repo_group_create = v.OneOf(repo_group_create_choices)
470 default_repo_group_create = v.OneOf(repo_group_create_choices)
471 default_fork_create = v.OneOf(fork_choices)
471 default_fork_create = v.OneOf(fork_choices)
472 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
472 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
473
473
474 return _DefaultPermissionsForm
474 return _DefaultPermissionsForm
475
475
476
476
477 def UserIndividualPermissionsForm():
477 def UserIndividualPermissionsForm():
478 class _DefaultPermissionsForm(formencode.Schema):
478 class _DefaultPermissionsForm(formencode.Schema):
479 allow_extra_fields = True
479 allow_extra_fields = True
480 filter_extra_fields = True
480 filter_extra_fields = True
481
481
482 inherit_default_permissions = v.StringBoolean(if_missing=False)
482 inherit_default_permissions = v.StringBoolean(if_missing=False)
483
483
484 return _DefaultPermissionsForm
484 return _DefaultPermissionsForm
485
485
486
486
487 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
487 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
488 class _DefaultsForm(formencode.Schema):
488 class _DefaultsForm(formencode.Schema):
489 allow_extra_fields = True
489 allow_extra_fields = True
490 filter_extra_fields = True
490 filter_extra_fields = True
491 default_repo_type = v.OneOf(supported_backends)
491 default_repo_type = v.OneOf(supported_backends)
492 default_repo_private = v.StringBoolean(if_missing=False)
492 default_repo_private = v.StringBoolean(if_missing=False)
493 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
493 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
494 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
494 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
495 default_repo_enable_locking = v.StringBoolean(if_missing=False)
495 default_repo_enable_locking = v.StringBoolean(if_missing=False)
496
496
497 return _DefaultsForm
497 return _DefaultsForm
498
498
499
499
500 def AuthSettingsForm():
500 def AuthSettingsForm():
501 class _AuthSettingsForm(formencode.Schema):
501 class _AuthSettingsForm(formencode.Schema):
502 allow_extra_fields = True
502 allow_extra_fields = True
503 filter_extra_fields = True
503 filter_extra_fields = True
504 auth_plugins = All(v.ValidAuthPlugins(),
504 auth_plugins = All(v.ValidAuthPlugins(),
505 v.UniqueListFromString()(not_empty=True))
505 v.UniqueListFromString()(not_empty=True))
506
506
507 return _AuthSettingsForm
507 return _AuthSettingsForm
508
508
509
509
510 def UserExtraEmailForm():
510 def UserExtraEmailForm():
511 class _UserExtraEmailForm(formencode.Schema):
511 class _UserExtraEmailForm(formencode.Schema):
512 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
512 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
513 return _UserExtraEmailForm
513 return _UserExtraEmailForm
514
514
515
515
516 def UserExtraIpForm():
516 def UserExtraIpForm():
517 class _UserExtraIpForm(formencode.Schema):
517 class _UserExtraIpForm(formencode.Schema):
518 ip = v.ValidIp()(not_empty=True)
518 ip = v.ValidIp()(not_empty=True)
519 return _UserExtraIpForm
519 return _UserExtraIpForm
520
520
521
521
522 def PullRequestForm(repo_id):
522 def PullRequestForm(repo_id):
523 class _PullRequestForm(formencode.Schema):
523 class _PullRequestForm(formencode.Schema):
524 allow_extra_fields = True
524 allow_extra_fields = True
525 filter_extra_fields = True
525 filter_extra_fields = True
526
526
527 user = v.UnicodeString(strip=True, required=True)
527 user = v.UnicodeString(strip=True, required=True)
528 source_repo = v.UnicodeString(strip=True, required=True)
528 source_repo = v.UnicodeString(strip=True, required=True)
529 source_ref = v.UnicodeString(strip=True, required=True)
529 source_ref = v.UnicodeString(strip=True, required=True)
530 target_repo = v.UnicodeString(strip=True, required=True)
530 target_repo = v.UnicodeString(strip=True, required=True)
531 target_ref = v.UnicodeString(strip=True, required=True)
531 target_ref = v.UnicodeString(strip=True, required=True)
532 revisions = All(#v.NotReviewedRevisions(repo_id)(),
532 revisions = All(#v.NotReviewedRevisions(repo_id)(),
533 v.UniqueList()(not_empty=True))
533 v.UniqueList()(not_empty=True))
534 review_members = v.UniqueList(convert=int)(not_empty=True)
534 review_members = v.UniqueList(convert=int)(not_empty=True)
535
535
536 pullrequest_title = v.UnicodeString(strip=True, required=True)
536 pullrequest_title = v.UnicodeString(strip=True, required=True)
537 pullrequest_desc = v.UnicodeString(strip=True, required=False)
537 pullrequest_desc = v.UnicodeString(strip=True, required=False)
538
538
539 return _PullRequestForm
539 return _PullRequestForm
540
540
541
541
542 def IssueTrackerPatternsForm():
542 def IssueTrackerPatternsForm():
543 class _IssueTrackerPatternsForm(formencode.Schema):
543 class _IssueTrackerPatternsForm(formencode.Schema):
544 allow_extra_fields = True
544 allow_extra_fields = True
545 filter_extra_fields = False
545 filter_extra_fields = False
546 chained_validators = [v.ValidPattern()]
546 chained_validators = [v.ValidPattern()]
547 return _IssueTrackerPatternsForm
547 return _IssueTrackerPatternsForm
@@ -1,716 +1,730 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import hashlib
21 import hashlib
22 import logging
22 import logging
23 from collections import namedtuple
23 from collections import namedtuple
24 from functools import wraps
24 from functools import wraps
25
25
26 from rhodecode.lib import caches
26 from rhodecode.lib import caches
27 from rhodecode.lib.caching_query import FromCache
28 from rhodecode.lib.utils2 import (
27 from rhodecode.lib.utils2 import (
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
28 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 from rhodecode.lib.vcs.backends import base
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import (
31 from rhodecode.model.db import (
32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 UiSetting = namedtuple(
39 UiSetting = namedtuple(
40 'UiSetting', ['section', 'key', 'value', 'active'])
40 'UiSetting', ['section', 'key', 'value', 'active'])
41
41
42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43
43
44
44
45 class SettingNotFound(Exception):
45 class SettingNotFound(Exception):
46 def __init__(self):
46 def __init__(self):
47 super(SettingNotFound, self).__init__('Setting is not found')
47 super(SettingNotFound, self).__init__('Setting is not found')
48
48
49
49
50 class SettingsModel(BaseModel):
50 class SettingsModel(BaseModel):
51 BUILTIN_HOOKS = (
51 BUILTIN_HOOKS = (
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
54 RhodeCodeUi.HOOK_PRE_PULL)
54 RhodeCodeUi.HOOK_PRE_PULL)
55 HOOKS_SECTION = 'hooks'
55 HOOKS_SECTION = 'hooks'
56
56
57 def __init__(self, sa=None, repo=None):
57 def __init__(self, sa=None, repo=None):
58 self.repo = repo
58 self.repo = repo
59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
60 self.SettingsDbModel = (
60 self.SettingsDbModel = (
61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
62 super(SettingsModel, self).__init__(sa)
62 super(SettingsModel, self).__init__(sa)
63
63
64 def get_ui_by_key(self, key):
64 def get_ui_by_key(self, key):
65 q = self.UiDbModel.query()
65 q = self.UiDbModel.query()
66 q = q.filter(self.UiDbModel.ui_key == key)
66 q = q.filter(self.UiDbModel.ui_key == key)
67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
68 return q.scalar()
68 return q.scalar()
69
69
70 def get_ui_by_section(self, section):
70 def get_ui_by_section(self, section):
71 q = self.UiDbModel.query()
71 q = self.UiDbModel.query()
72 q = q.filter(self.UiDbModel.ui_section == section)
72 q = q.filter(self.UiDbModel.ui_section == section)
73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 return q.all()
74 return q.all()
75
75
76 def get_ui_by_section_and_key(self, section, key):
76 def get_ui_by_section_and_key(self, section, key):
77 q = self.UiDbModel.query()
77 q = self.UiDbModel.query()
78 q = q.filter(self.UiDbModel.ui_section == section)
78 q = q.filter(self.UiDbModel.ui_section == section)
79 q = q.filter(self.UiDbModel.ui_key == key)
79 q = q.filter(self.UiDbModel.ui_key == key)
80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
81 return q.scalar()
81 return q.scalar()
82
82
83 def get_ui(self, section=None, key=None):
83 def get_ui(self, section=None, key=None):
84 q = self.UiDbModel.query()
84 q = self.UiDbModel.query()
85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
86
86
87 if section:
87 if section:
88 q = q.filter(self.UiDbModel.ui_section == section)
88 q = q.filter(self.UiDbModel.ui_section == section)
89 if key:
89 if key:
90 q = q.filter(self.UiDbModel.ui_key == key)
90 q = q.filter(self.UiDbModel.ui_key == key)
91
91
92 # TODO: mikhail: add caching
92 # TODO: mikhail: add caching
93 result = [
93 result = [
94 UiSetting(
94 UiSetting(
95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
96 value=safe_str(r.ui_value), active=r.ui_active
96 value=safe_str(r.ui_value), active=r.ui_active
97 )
97 )
98 for r in q.all()
98 for r in q.all()
99 ]
99 ]
100 return result
100 return result
101
101
102 def get_builtin_hooks(self):
102 def get_builtin_hooks(self):
103 q = self.UiDbModel.query()
103 q = self.UiDbModel.query()
104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
105 return self._get_hooks(q)
105 return self._get_hooks(q)
106
106
107 def get_custom_hooks(self):
107 def get_custom_hooks(self):
108 q = self.UiDbModel.query()
108 q = self.UiDbModel.query()
109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
110 return self._get_hooks(q)
110 return self._get_hooks(q)
111
111
112 def create_ui_section_value(self, section, val, key=None, active=True):
112 def create_ui_section_value(self, section, val, key=None, active=True):
113 new_ui = self.UiDbModel()
113 new_ui = self.UiDbModel()
114 new_ui.ui_section = section
114 new_ui.ui_section = section
115 new_ui.ui_value = val
115 new_ui.ui_value = val
116 new_ui.ui_active = active
116 new_ui.ui_active = active
117
117
118 if self.repo:
118 if self.repo:
119 repo = self._get_repo(self.repo)
119 repo = self._get_repo(self.repo)
120 repository_id = repo.repo_id
120 repository_id = repo.repo_id
121 new_ui.repository_id = repository_id
121 new_ui.repository_id = repository_id
122
122
123 if not key:
123 if not key:
124 # keys are unique so they need appended info
124 # keys are unique so they need appended info
125 if self.repo:
125 if self.repo:
126 key = hashlib.sha1(
126 key = hashlib.sha1(
127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
128 else:
128 else:
129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
130
130
131 new_ui.ui_key = key
131 new_ui.ui_key = key
132
132
133 Session().add(new_ui)
133 Session().add(new_ui)
134 return new_ui
134 return new_ui
135
135
136 def create_or_update_hook(self, key, value):
136 def create_or_update_hook(self, key, value):
137 ui = (
137 ui = (
138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
139 self.UiDbModel())
139 self.UiDbModel())
140 ui.ui_section = self.HOOKS_SECTION
140 ui.ui_section = self.HOOKS_SECTION
141 ui.ui_active = True
141 ui.ui_active = True
142 ui.ui_key = key
142 ui.ui_key = key
143 ui.ui_value = value
143 ui.ui_value = value
144
144
145 if self.repo:
145 if self.repo:
146 repo = self._get_repo(self.repo)
146 repo = self._get_repo(self.repo)
147 repository_id = repo.repo_id
147 repository_id = repo.repo_id
148 ui.repository_id = repository_id
148 ui.repository_id = repository_id
149
149
150 Session().add(ui)
150 Session().add(ui)
151 return ui
151 return ui
152
152
153 def delete_ui(self, id_):
153 def delete_ui(self, id_):
154 ui = self.UiDbModel.get(id_)
154 ui = self.UiDbModel.get(id_)
155 if not ui:
155 if not ui:
156 raise SettingNotFound()
156 raise SettingNotFound()
157 Session().delete(ui)
157 Session().delete(ui)
158
158
159 def get_setting_by_name(self, name):
159 def get_setting_by_name(self, name):
160 q = self._get_settings_query()
160 q = self._get_settings_query()
161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
162 return q.scalar()
162 return q.scalar()
163
163
164 def create_or_update_setting(
164 def create_or_update_setting(
165 self, name, val=Optional(''), type_=Optional('unicode')):
165 self, name, val=Optional(''), type_=Optional('unicode')):
166 """
166 """
167 Creates or updates RhodeCode setting. If updates is triggered it will
167 Creates or updates RhodeCode setting. If updates is triggered it will
168 only update parameters that are explicityl set Optional instance will
168 only update parameters that are explicityl set Optional instance will
169 be skipped
169 be skipped
170
170
171 :param name:
171 :param name:
172 :param val:
172 :param val:
173 :param type_:
173 :param type_:
174 :return:
174 :return:
175 """
175 """
176
176
177 res = self.get_setting_by_name(name)
177 res = self.get_setting_by_name(name)
178 repo = self._get_repo(self.repo) if self.repo else None
178 repo = self._get_repo(self.repo) if self.repo else None
179
179
180 if not res:
180 if not res:
181 val = Optional.extract(val)
181 val = Optional.extract(val)
182 type_ = Optional.extract(type_)
182 type_ = Optional.extract(type_)
183
183
184 args = (
184 args = (
185 (repo.repo_id, name, val, type_)
185 (repo.repo_id, name, val, type_)
186 if repo else (name, val, type_))
186 if repo else (name, val, type_))
187 res = self.SettingsDbModel(*args)
187 res = self.SettingsDbModel(*args)
188
188
189 else:
189 else:
190 if self.repo:
190 if self.repo:
191 res.repository_id = repo.repo_id
191 res.repository_id = repo.repo_id
192
192
193 res.app_settings_name = name
193 res.app_settings_name = name
194 if not isinstance(type_, Optional):
194 if not isinstance(type_, Optional):
195 # update if set
195 # update if set
196 res.app_settings_type = type_
196 res.app_settings_type = type_
197 if not isinstance(val, Optional):
197 if not isinstance(val, Optional):
198 # update if set
198 # update if set
199 res.app_settings_value = val
199 res.app_settings_value = val
200
200
201 Session().add(res)
201 Session().add(res)
202 return res
202 return res
203
203
204 def invalidate_settings_cache(self):
204 def invalidate_settings_cache(self):
205 namespace = 'rhodecode_settings'
205 namespace = 'rhodecode_settings'
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 caches.clear_cache_manager(cache_manager)
207 caches.clear_cache_manager(cache_manager)
208
208
209 def get_all_settings(self, cache=False):
209 def get_all_settings(self, cache=False):
210 def _compute():
210 def _compute():
211 q = self._get_settings_query()
211 q = self._get_settings_query()
212 if not q:
212 if not q:
213 raise Exception('Could not get application settings !')
213 raise Exception('Could not get application settings !')
214
214
215 settings = {
215 settings = {
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 for result in q
217 for result in q
218 }
218 }
219 return settings
219 return settings
220
220
221 if cache:
221 if cache:
222 log.debug('Fetching app settings using cache')
222 log.debug('Fetching app settings using cache')
223 repo = self._get_repo(self.repo) if self.repo else None
223 repo = self._get_repo(self.repo) if self.repo else None
224 namespace = 'rhodecode_settings'
224 namespace = 'rhodecode_settings'
225 cache_manager = caches.get_cache_manager(
225 cache_manager = caches.get_cache_manager(
226 'sql_cache_short', namespace)
226 'sql_cache_short', namespace)
227 _cache_key = (
227 _cache_key = (
228 "get_repo_{}_settings".format(repo.repo_id)
228 "get_repo_{}_settings".format(repo.repo_id)
229 if repo else "get_app_settings")
229 if repo else "get_app_settings")
230
230
231 return cache_manager.get(_cache_key, createfunc=_compute)
231 return cache_manager.get(_cache_key, createfunc=_compute)
232
232
233 else:
233 else:
234 return _compute()
234 return _compute()
235
235
236 def get_auth_settings(self):
236 def get_auth_settings(self):
237 q = self._get_settings_query()
237 q = self._get_settings_query()
238 q = q.filter(
238 q = q.filter(
239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
240 rows = q.all()
240 rows = q.all()
241 auth_settings = {
241 auth_settings = {
242 row.app_settings_name: row.app_settings_value for row in rows}
242 row.app_settings_name: row.app_settings_value for row in rows}
243 return auth_settings
243 return auth_settings
244
244
245 def get_auth_plugins(self):
245 def get_auth_plugins(self):
246 auth_plugins = self.get_setting_by_name("auth_plugins")
246 auth_plugins = self.get_setting_by_name("auth_plugins")
247 return auth_plugins.app_settings_value
247 return auth_plugins.app_settings_value
248
248
249 def get_default_repo_settings(self, strip_prefix=False):
249 def get_default_repo_settings(self, strip_prefix=False):
250 q = self._get_settings_query()
250 q = self._get_settings_query()
251 q = q.filter(
251 q = q.filter(
252 self.SettingsDbModel.app_settings_name.startswith('default_'))
252 self.SettingsDbModel.app_settings_name.startswith('default_'))
253 rows = q.all()
253 rows = q.all()
254
254
255 result = {}
255 result = {}
256 for row in rows:
256 for row in rows:
257 key = row.app_settings_name
257 key = row.app_settings_name
258 if strip_prefix:
258 if strip_prefix:
259 key = remove_prefix(key, prefix='default_')
259 key = remove_prefix(key, prefix='default_')
260 result.update({key: row.app_settings_value})
260 result.update({key: row.app_settings_value})
261 return result
261 return result
262
262
263 def get_repo(self):
263 def get_repo(self):
264 repo = self._get_repo(self.repo)
264 repo = self._get_repo(self.repo)
265 if not repo:
265 if not repo:
266 raise Exception(
266 raise Exception(
267 'Repository {} cannot be found'.format(self.repo))
267 'Repository {} cannot be found'.format(self.repo))
268 return repo
268 return repo
269
269
270 def _filter_by_repo(self, model, query):
270 def _filter_by_repo(self, model, query):
271 if self.repo:
271 if self.repo:
272 repo = self.get_repo()
272 repo = self.get_repo()
273 query = query.filter(model.repository_id == repo.repo_id)
273 query = query.filter(model.repository_id == repo.repo_id)
274 return query
274 return query
275
275
276 def _get_hooks(self, query):
276 def _get_hooks(self, query):
277 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
277 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
278 query = self._filter_by_repo(RepoRhodeCodeUi, query)
278 query = self._filter_by_repo(RepoRhodeCodeUi, query)
279 return query.all()
279 return query.all()
280
280
281 def _get_settings_query(self):
281 def _get_settings_query(self):
282 q = self.SettingsDbModel.query()
282 q = self.SettingsDbModel.query()
283 return self._filter_by_repo(RepoRhodeCodeSetting, q)
283 return self._filter_by_repo(RepoRhodeCodeSetting, q)
284
284
285 def list_enabled_social_plugins(self, settings):
285 def list_enabled_social_plugins(self, settings):
286 enabled = []
286 enabled = []
287 for plug in SOCIAL_PLUGINS_LIST:
287 for plug in SOCIAL_PLUGINS_LIST:
288 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
288 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
289 )):
289 )):
290 enabled.append(plug)
290 enabled.append(plug)
291 return enabled
291 return enabled
292
292
293
293
294 def assert_repo_settings(func):
294 def assert_repo_settings(func):
295 @wraps(func)
295 @wraps(func)
296 def _wrapper(self, *args, **kwargs):
296 def _wrapper(self, *args, **kwargs):
297 if not self.repo_settings:
297 if not self.repo_settings:
298 raise Exception('Repository is not specified')
298 raise Exception('Repository is not specified')
299 return func(self, *args, **kwargs)
299 return func(self, *args, **kwargs)
300 return _wrapper
300 return _wrapper
301
301
302
302
303 class IssueTrackerSettingsModel(object):
303 class IssueTrackerSettingsModel(object):
304 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
304 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
305 SETTINGS_PREFIX = 'issuetracker_'
305 SETTINGS_PREFIX = 'issuetracker_'
306
306
307 def __init__(self, sa=None, repo=None):
307 def __init__(self, sa=None, repo=None):
308 self.global_settings = SettingsModel(sa=sa)
308 self.global_settings = SettingsModel(sa=sa)
309 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
309 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
310
310
311 @property
311 @property
312 def inherit_global_settings(self):
312 def inherit_global_settings(self):
313 if not self.repo_settings:
313 if not self.repo_settings:
314 return True
314 return True
315 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
315 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
316 return setting.app_settings_value if setting else True
316 return setting.app_settings_value if setting else True
317
317
318 @inherit_global_settings.setter
318 @inherit_global_settings.setter
319 def inherit_global_settings(self, value):
319 def inherit_global_settings(self, value):
320 if self.repo_settings:
320 if self.repo_settings:
321 settings = self.repo_settings.create_or_update_setting(
321 settings = self.repo_settings.create_or_update_setting(
322 self.INHERIT_SETTINGS, value, type_='bool')
322 self.INHERIT_SETTINGS, value, type_='bool')
323 Session().add(settings)
323 Session().add(settings)
324
324
325 def _get_keyname(self, key, uid, prefix=''):
325 def _get_keyname(self, key, uid, prefix=''):
326 return '{0}{1}{2}_{3}'.format(
326 return '{0}{1}{2}_{3}'.format(
327 prefix, self.SETTINGS_PREFIX, key, uid)
327 prefix, self.SETTINGS_PREFIX, key, uid)
328
328
329 def _make_dict_for_settings(self, qs):
329 def _make_dict_for_settings(self, qs):
330 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
330 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
331
331
332 issuetracker_entries = {}
332 issuetracker_entries = {}
333 # create keys
333 # create keys
334 for k, v in qs.items():
334 for k, v in qs.items():
335 if k.startswith(prefix_match):
335 if k.startswith(prefix_match):
336 uid = k[len(prefix_match):]
336 uid = k[len(prefix_match):]
337 issuetracker_entries[uid] = None
337 issuetracker_entries[uid] = None
338
338
339 # populate
339 # populate
340 for uid in issuetracker_entries:
340 for uid in issuetracker_entries:
341 issuetracker_entries[uid] = AttributeDict({
341 issuetracker_entries[uid] = AttributeDict({
342 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
342 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
343 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
343 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
344 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
344 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
345 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
345 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
346 })
346 })
347 return issuetracker_entries
347 return issuetracker_entries
348
348
349 def get_global_settings(self, cache=False):
349 def get_global_settings(self, cache=False):
350 """
350 """
351 Returns list of global issue tracker settings
351 Returns list of global issue tracker settings
352 """
352 """
353 defaults = self.global_settings.get_all_settings(cache=cache)
353 defaults = self.global_settings.get_all_settings(cache=cache)
354 settings = self._make_dict_for_settings(defaults)
354 settings = self._make_dict_for_settings(defaults)
355 return settings
355 return settings
356
356
357 def get_repo_settings(self, cache=False):
357 def get_repo_settings(self, cache=False):
358 """
358 """
359 Returns list of issue tracker settings per repository
359 Returns list of issue tracker settings per repository
360 """
360 """
361 if not self.repo_settings:
361 if not self.repo_settings:
362 raise Exception('Repository is not specified')
362 raise Exception('Repository is not specified')
363 all_settings = self.repo_settings.get_all_settings(cache=cache)
363 all_settings = self.repo_settings.get_all_settings(cache=cache)
364 settings = self._make_dict_for_settings(all_settings)
364 settings = self._make_dict_for_settings(all_settings)
365 return settings
365 return settings
366
366
367 def get_settings(self, cache=False):
367 def get_settings(self, cache=False):
368 if self.inherit_global_settings:
368 if self.inherit_global_settings:
369 return self.get_global_settings(cache=cache)
369 return self.get_global_settings(cache=cache)
370 else:
370 else:
371 return self.get_repo_settings(cache=cache)
371 return self.get_repo_settings(cache=cache)
372
372
373 def delete_entries(self, uid):
373 def delete_entries(self, uid):
374 if self.repo_settings:
374 if self.repo_settings:
375 all_patterns = self.get_repo_settings()
375 all_patterns = self.get_repo_settings()
376 settings_model = self.repo_settings
376 settings_model = self.repo_settings
377 else:
377 else:
378 all_patterns = self.get_global_settings()
378 all_patterns = self.get_global_settings()
379 settings_model = self.global_settings
379 settings_model = self.global_settings
380 entries = all_patterns.get(uid)
380 entries = all_patterns.get(uid)
381
381
382 for del_key in entries:
382 for del_key in entries:
383 setting_name = self._get_keyname(del_key, uid)
383 setting_name = self._get_keyname(del_key, uid)
384 entry = settings_model.get_setting_by_name(setting_name)
384 entry = settings_model.get_setting_by_name(setting_name)
385 if entry:
385 if entry:
386 Session().delete(entry)
386 Session().delete(entry)
387
387
388 Session().commit()
388 Session().commit()
389
389
390 def create_or_update_setting(
390 def create_or_update_setting(
391 self, name, val=Optional(''), type_=Optional('unicode')):
391 self, name, val=Optional(''), type_=Optional('unicode')):
392 if self.repo_settings:
392 if self.repo_settings:
393 setting = self.repo_settings.create_or_update_setting(
393 setting = self.repo_settings.create_or_update_setting(
394 name, val, type_)
394 name, val, type_)
395 else:
395 else:
396 setting = self.global_settings.create_or_update_setting(
396 setting = self.global_settings.create_or_update_setting(
397 name, val, type_)
397 name, val, type_)
398 return setting
398 return setting
399
399
400
400
401 class VcsSettingsModel(object):
401 class VcsSettingsModel(object):
402
402
403 INHERIT_SETTINGS = 'inherit_vcs_settings'
403 INHERIT_SETTINGS = 'inherit_vcs_settings'
404 GENERAL_SETTINGS = (
404 GENERAL_SETTINGS = (
405 'use_outdated_comments', 'pr_merge_enabled',
405 'use_outdated_comments',
406 'pr_merge_enabled',
406 'hg_use_rebase_for_merging')
407 'hg_use_rebase_for_merging')
408
407 HOOKS_SETTINGS = (
409 HOOKS_SETTINGS = (
408 ('hooks', 'changegroup.repo_size'),
410 ('hooks', 'changegroup.repo_size'),
409 ('hooks', 'changegroup.push_logger'),
411 ('hooks', 'changegroup.push_logger'),
410 ('hooks', 'outgoing.pull_logger'))
412 ('hooks', 'outgoing.pull_logger'))
411 HG_SETTINGS = (
413 HG_SETTINGS = (
412 ('extensions', 'largefiles'), ('phases', 'publish'))
414 ('extensions', 'largefiles'),
413 GLOBAL_HG_SETTINGS = HG_SETTINGS + (('extensions', 'hgsubversion'), )
415 ('phases', 'publish'))
416 GLOBAL_HG_SETTINGS = (
417 ('extensions', 'largefiles'),
418 ('phases', 'publish'),
419 ('extensions', 'hgsubversion'))
414 GLOBAL_SVN_SETTINGS = (
420 GLOBAL_SVN_SETTINGS = (
415 'rhodecode_proxy_subversion_http_requests',
421 ('vcs_svn_proxy', 'http_requests_enabled'),
416 'rhodecode_subversion_http_server_url')
422 ('vcs_svn_proxy', 'http_server_url'))
423
417 SVN_BRANCH_SECTION = 'vcs_svn_branch'
424 SVN_BRANCH_SECTION = 'vcs_svn_branch'
418 SVN_TAG_SECTION = 'vcs_svn_tag'
425 SVN_TAG_SECTION = 'vcs_svn_tag'
419 SSL_SETTING = ('web', 'push_ssl')
426 SSL_SETTING = ('web', 'push_ssl')
420 PATH_SETTING = ('paths', '/')
427 PATH_SETTING = ('paths', '/')
421
428
422 def __init__(self, sa=None, repo=None):
429 def __init__(self, sa=None, repo=None):
423 self.global_settings = SettingsModel(sa=sa)
430 self.global_settings = SettingsModel(sa=sa)
424 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
431 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
425 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
432 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
426 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
433 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
427
434
428 @property
435 @property
429 @assert_repo_settings
436 @assert_repo_settings
430 def inherit_global_settings(self):
437 def inherit_global_settings(self):
431 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
438 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
432 return setting.app_settings_value if setting else True
439 return setting.app_settings_value if setting else True
433
440
434 @inherit_global_settings.setter
441 @inherit_global_settings.setter
435 @assert_repo_settings
442 @assert_repo_settings
436 def inherit_global_settings(self, value):
443 def inherit_global_settings(self, value):
437 self.repo_settings.create_or_update_setting(
444 self.repo_settings.create_or_update_setting(
438 self.INHERIT_SETTINGS, value, type_='bool')
445 self.INHERIT_SETTINGS, value, type_='bool')
439
446
440 def get_global_svn_branch_patterns(self):
447 def get_global_svn_branch_patterns(self):
441 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
448 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
442
449
443 @assert_repo_settings
450 @assert_repo_settings
444 def get_repo_svn_branch_patterns(self):
451 def get_repo_svn_branch_patterns(self):
445 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
452 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
446
453
447 def get_global_svn_tag_patterns(self):
454 def get_global_svn_tag_patterns(self):
448 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
455 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
449
456
450 @assert_repo_settings
457 @assert_repo_settings
451 def get_repo_svn_tag_patterns(self):
458 def get_repo_svn_tag_patterns(self):
452 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
459 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
453
460
454 def get_global_settings(self):
461 def get_global_settings(self):
455 return self._collect_all_settings(global_=True)
462 return self._collect_all_settings(global_=True)
456
463
457 @assert_repo_settings
464 @assert_repo_settings
458 def get_repo_settings(self):
465 def get_repo_settings(self):
459 return self._collect_all_settings(global_=False)
466 return self._collect_all_settings(global_=False)
460
467
461 @assert_repo_settings
468 @assert_repo_settings
462 def create_or_update_repo_settings(
469 def create_or_update_repo_settings(
463 self, data, inherit_global_settings=False):
470 self, data, inherit_global_settings=False):
464 from rhodecode.model.scm import ScmModel
471 from rhodecode.model.scm import ScmModel
465
472
466 self.inherit_global_settings = inherit_global_settings
473 self.inherit_global_settings = inherit_global_settings
467
474
468 repo = self.repo_settings.get_repo()
475 repo = self.repo_settings.get_repo()
469 if not inherit_global_settings:
476 if not inherit_global_settings:
470 if repo.repo_type == 'svn':
477 if repo.repo_type == 'svn':
471 self.create_repo_svn_settings(data)
478 self.create_repo_svn_settings(data)
472 else:
479 else:
473 self.create_or_update_repo_hook_settings(data)
480 self.create_or_update_repo_hook_settings(data)
474 self.create_or_update_repo_pr_settings(data)
481 self.create_or_update_repo_pr_settings(data)
475
482
476 if repo.repo_type == 'hg':
483 if repo.repo_type == 'hg':
477 self.create_or_update_repo_hg_settings(data)
484 self.create_or_update_repo_hg_settings(data)
478
485
479 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
486 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
480
487
481 @assert_repo_settings
488 @assert_repo_settings
482 def create_or_update_repo_hook_settings(self, data):
489 def create_or_update_repo_hook_settings(self, data):
483 for section, key in self.HOOKS_SETTINGS:
490 for section, key in self.HOOKS_SETTINGS:
484 data_key = self._get_form_ui_key(section, key)
491 data_key = self._get_form_ui_key(section, key)
485 if data_key not in data:
492 if data_key not in data:
486 raise ValueError(
493 raise ValueError(
487 'The given data does not contain {} key'.format(data_key))
494 'The given data does not contain {} key'.format(data_key))
488
495
489 active = data.get(data_key)
496 active = data.get(data_key)
490 repo_setting = self.repo_settings.get_ui_by_section_and_key(
497 repo_setting = self.repo_settings.get_ui_by_section_and_key(
491 section, key)
498 section, key)
492 if not repo_setting:
499 if not repo_setting:
493 global_setting = self.global_settings.\
500 global_setting = self.global_settings.\
494 get_ui_by_section_and_key(section, key)
501 get_ui_by_section_and_key(section, key)
495 self.repo_settings.create_ui_section_value(
502 self.repo_settings.create_ui_section_value(
496 section, global_setting.ui_value, key=key, active=active)
503 section, global_setting.ui_value, key=key, active=active)
497 else:
504 else:
498 repo_setting.ui_active = active
505 repo_setting.ui_active = active
499 Session().add(repo_setting)
506 Session().add(repo_setting)
500
507
501 def update_global_hook_settings(self, data):
508 def update_global_hook_settings(self, data):
502 for section, key in self.HOOKS_SETTINGS:
509 for section, key in self.HOOKS_SETTINGS:
503 data_key = self._get_form_ui_key(section, key)
510 data_key = self._get_form_ui_key(section, key)
504 if data_key not in data:
511 if data_key not in data:
505 raise ValueError(
512 raise ValueError(
506 'The given data does not contain {} key'.format(data_key))
513 'The given data does not contain {} key'.format(data_key))
507 active = data.get(data_key)
514 active = data.get(data_key)
508 repo_setting = self.global_settings.get_ui_by_section_and_key(
515 repo_setting = self.global_settings.get_ui_by_section_and_key(
509 section, key)
516 section, key)
510 repo_setting.ui_active = active
517 repo_setting.ui_active = active
511 Session().add(repo_setting)
518 Session().add(repo_setting)
512
519
513 @assert_repo_settings
520 @assert_repo_settings
514 def create_or_update_repo_pr_settings(self, data):
521 def create_or_update_repo_pr_settings(self, data):
515 return self._create_or_update_general_settings(
522 return self._create_or_update_general_settings(
516 self.repo_settings, data)
523 self.repo_settings, data)
517
524
518 def create_or_update_global_pr_settings(self, data):
525 def create_or_update_global_pr_settings(self, data):
519 return self._create_or_update_general_settings(
526 return self._create_or_update_general_settings(
520 self.global_settings, data)
527 self.global_settings, data)
521
528
522 @assert_repo_settings
529 @assert_repo_settings
523 def create_repo_svn_settings(self, data):
530 def create_repo_svn_settings(self, data):
524 return self._create_svn_settings(self.repo_settings, data)
531 return self._create_svn_settings(self.repo_settings, data)
525
532
526 def create_global_svn_settings(self, data):
527 return self._create_svn_settings(self.global_settings, data)
528
529 @assert_repo_settings
533 @assert_repo_settings
530 def create_or_update_repo_hg_settings(self, data):
534 def create_or_update_repo_hg_settings(self, data):
531 largefiles, phases = self.HG_SETTINGS
535 largefiles, phases = self.HG_SETTINGS
532 largefiles_key, phases_key = self._get_hg_settings(
536 largefiles_key, phases_key = self._get_settings_keys(
533 self.HG_SETTINGS, data)
537 self.HG_SETTINGS, data)
534 self._create_or_update_ui(
538 self._create_or_update_ui(
535 self.repo_settings, *largefiles, value='',
539 self.repo_settings, *largefiles, value='',
536 active=data[largefiles_key])
540 active=data[largefiles_key])
537 self._create_or_update_ui(
541 self._create_or_update_ui(
538 self.repo_settings, *phases, value=safe_str(data[phases_key]))
542 self.repo_settings, *phases, value=safe_str(data[phases_key]))
539
543
540 def create_or_update_global_hg_settings(self, data):
544 def create_or_update_global_hg_settings(self, data):
541 largefiles, phases, subversion = self.GLOBAL_HG_SETTINGS
545 largefiles, phases, hgsubversion = self.GLOBAL_HG_SETTINGS
542 largefiles_key, phases_key, subversion_key = self._get_hg_settings(
546 largefiles_key, phases_key, subversion_key = self._get_settings_keys(
543 self.GLOBAL_HG_SETTINGS, data)
547 self.GLOBAL_HG_SETTINGS, data)
544 self._create_or_update_ui(
548 self._create_or_update_ui(
545 self.global_settings, *largefiles, value='',
549 self.global_settings, *largefiles, value='',
546 active=data[largefiles_key])
550 active=data[largefiles_key])
547 self._create_or_update_ui(
551 self._create_or_update_ui(
548 self.global_settings, *phases, value=safe_str(data[phases_key]))
552 self.global_settings, *phases, value=safe_str(data[phases_key]))
549 self._create_or_update_ui(
553 self._create_or_update_ui(
550 self.global_settings, *subversion, active=data[subversion_key])
554 self.global_settings, *hgsubversion, active=data[subversion_key])
551
555
552 def create_or_update_global_svn_settings(self, data):
556 def create_or_update_global_svn_settings(self, data):
553 rhodecode_proxy_subversion_http_requests,
557 # branch/tags patterns
554 rhodecode_subversion_http_server_url = self.GLOBAL_SVN_SETTINGS
558 self._create_svn_settings(self.global_settings, data)
555 rhodecode_proxy_subversion_http_requests_key,
559
556 rhodecode_subversion_http_server_url_key = self._get_svn_settings(
560 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
561 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
557 self.GLOBAL_SVN_SETTINGS, data)
562 self.GLOBAL_SVN_SETTINGS, data)
563
558 self._create_or_update_ui(
564 self._create_or_update_ui(
559 self.global_settings,
565 self.global_settings, *http_requests_enabled,
560 *rhodecode_proxy_subversion_http_requests,
566 value=safe_str(data[http_requests_enabled_key]))
561 value=safe_str(data[rhodecode_proxy_subversion_http_requests_key]))
562 self._create_or_update_ui(
567 self._create_or_update_ui(
563 self.global_settings,
568 self.global_settings, *http_server_url,
564 *rhodecode_subversion_http_server_url,
569 value=data[http_server_url_key])
565 active=data[rhodecode_subversion_http_server_url_key])
566
570
567 def update_global_ssl_setting(self, value):
571 def update_global_ssl_setting(self, value):
568 self._create_or_update_ui(
572 self._create_or_update_ui(
569 self.global_settings, *self.SSL_SETTING, value=value)
573 self.global_settings, *self.SSL_SETTING, value=value)
570
574
571 def update_global_path_setting(self, value):
575 def update_global_path_setting(self, value):
572 self._create_or_update_ui(
576 self._create_or_update_ui(
573 self.global_settings, *self.PATH_SETTING, value=value)
577 self.global_settings, *self.PATH_SETTING, value=value)
574
578
575 @assert_repo_settings
579 @assert_repo_settings
576 def delete_repo_svn_pattern(self, id_):
580 def delete_repo_svn_pattern(self, id_):
577 self.repo_settings.delete_ui(id_)
581 self.repo_settings.delete_ui(id_)
578
582
579 def delete_global_svn_pattern(self, id_):
583 def delete_global_svn_pattern(self, id_):
580 self.global_settings.delete_ui(id_)
584 self.global_settings.delete_ui(id_)
581
585
582 @assert_repo_settings
586 @assert_repo_settings
583 def get_repo_ui_settings(self, section=None, key=None):
587 def get_repo_ui_settings(self, section=None, key=None):
584 global_uis = self.global_settings.get_ui(section, key)
588 global_uis = self.global_settings.get_ui(section, key)
585 repo_uis = self.repo_settings.get_ui(section, key)
589 repo_uis = self.repo_settings.get_ui(section, key)
586 filtered_repo_uis = self._filter_ui_settings(repo_uis)
590 filtered_repo_uis = self._filter_ui_settings(repo_uis)
587 filtered_repo_uis_keys = [
591 filtered_repo_uis_keys = [
588 (s.section, s.key) for s in filtered_repo_uis]
592 (s.section, s.key) for s in filtered_repo_uis]
589
593
590 def _is_global_ui_filtered(ui):
594 def _is_global_ui_filtered(ui):
591 return (
595 return (
592 (ui.section, ui.key) in filtered_repo_uis_keys
596 (ui.section, ui.key) in filtered_repo_uis_keys
593 or ui.section in self._svn_sections)
597 or ui.section in self._svn_sections)
594
598
595 filtered_global_uis = [
599 filtered_global_uis = [
596 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
600 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
597
601
598 return filtered_global_uis + filtered_repo_uis
602 return filtered_global_uis + filtered_repo_uis
599
603
600 def get_global_ui_settings(self, section=None, key=None):
604 def get_global_ui_settings(self, section=None, key=None):
601 return self.global_settings.get_ui(section, key)
605 return self.global_settings.get_ui(section, key)
602
606
607 def get_ui_settings_as_config_obj(self, section=None, key=None):
608 config = base.Config()
609
610 ui_settings = self.get_ui_settings(section=section, key=key)
611
612 for entry in ui_settings:
613 config.set(entry.section, entry.key, entry.value)
614
615 return config
616
603 def get_ui_settings(self, section=None, key=None):
617 def get_ui_settings(self, section=None, key=None):
604 if not self.repo_settings or self.inherit_global_settings:
618 if not self.repo_settings or self.inherit_global_settings:
605 return self.get_global_ui_settings(section, key)
619 return self.get_global_ui_settings(section, key)
606 else:
620 else:
607 return self.get_repo_ui_settings(section, key)
621 return self.get_repo_ui_settings(section, key)
608
622
609 def get_svn_patterns(self, section=None):
623 def get_svn_patterns(self, section=None):
610 if not self.repo_settings:
624 if not self.repo_settings:
611 return self.get_global_ui_settings(section)
625 return self.get_global_ui_settings(section)
612 else:
626 else:
613 return self.get_repo_ui_settings(section)
627 return self.get_repo_ui_settings(section)
614
628
615 @assert_repo_settings
629 @assert_repo_settings
616 def get_repo_general_settings(self):
630 def get_repo_general_settings(self):
617 global_settings = self.global_settings.get_all_settings()
631 global_settings = self.global_settings.get_all_settings()
618 repo_settings = self.repo_settings.get_all_settings()
632 repo_settings = self.repo_settings.get_all_settings()
619 filtered_repo_settings = self._filter_general_settings(repo_settings)
633 filtered_repo_settings = self._filter_general_settings(repo_settings)
620 global_settings.update(filtered_repo_settings)
634 global_settings.update(filtered_repo_settings)
621 return global_settings
635 return global_settings
622
636
623 def get_global_general_settings(self):
637 def get_global_general_settings(self):
624 return self.global_settings.get_all_settings()
638 return self.global_settings.get_all_settings()
625
639
626 def get_general_settings(self):
640 def get_general_settings(self):
627 if not self.repo_settings or self.inherit_global_settings:
641 if not self.repo_settings or self.inherit_global_settings:
628 return self.get_global_general_settings()
642 return self.get_global_general_settings()
629 else:
643 else:
630 return self.get_repo_general_settings()
644 return self.get_repo_general_settings()
631
645
632 def get_repos_location(self):
646 def get_repos_location(self):
633 return self.global_settings.get_ui_by_key('/').ui_value
647 return self.global_settings.get_ui_by_key('/').ui_value
634
648
635 def _filter_ui_settings(self, settings):
649 def _filter_ui_settings(self, settings):
636 filtered_settings = [
650 filtered_settings = [
637 s for s in settings if self._should_keep_setting(s)]
651 s for s in settings if self._should_keep_setting(s)]
638 return filtered_settings
652 return filtered_settings
639
653
640 def _should_keep_setting(self, setting):
654 def _should_keep_setting(self, setting):
641 keep = (
655 keep = (
642 (setting.section, setting.key) in self._ui_settings or
656 (setting.section, setting.key) in self._ui_settings or
643 setting.section in self._svn_sections)
657 setting.section in self._svn_sections)
644 return keep
658 return keep
645
659
646 def _filter_general_settings(self, settings):
660 def _filter_general_settings(self, settings):
647 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
661 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
648 return {
662 return {
649 k: settings[k]
663 k: settings[k]
650 for k in settings if k in keys}
664 for k in settings if k in keys}
651
665
652 def _collect_all_settings(self, global_=False):
666 def _collect_all_settings(self, global_=False):
653 settings = self.global_settings if global_ else self.repo_settings
667 settings = self.global_settings if global_ else self.repo_settings
654 result = {}
668 result = {}
655
669
656 for section, key in self._ui_settings:
670 for section, key in self._ui_settings:
657 ui = settings.get_ui_by_section_and_key(section, key)
671 ui = settings.get_ui_by_section_and_key(section, key)
658 result_key = self._get_form_ui_key(section, key)
672 result_key = self._get_form_ui_key(section, key)
659 if ui:
673 if ui:
660 if section in ('hooks', 'extensions'):
674 if section in ('hooks', 'extensions'):
661 result[result_key] = ui.ui_active
675 result[result_key] = ui.ui_active
662 else:
676 else:
663 result[result_key] = ui.ui_value
677 result[result_key] = ui.ui_value
664
678
665 for name in self.GENERAL_SETTINGS:
679 for name in self.GENERAL_SETTINGS:
666 setting = settings.get_setting_by_name(name)
680 setting = settings.get_setting_by_name(name)
667 if setting:
681 if setting:
668 result_key = 'rhodecode_{}'.format(name)
682 result_key = 'rhodecode_{}'.format(name)
669 result[result_key] = setting.app_settings_value
683 result[result_key] = setting.app_settings_value
670
684
671 return result
685 return result
672
686
673 def _get_form_ui_key(self, section, key):
687 def _get_form_ui_key(self, section, key):
674 return '{section}_{key}'.format(
688 return '{section}_{key}'.format(
675 section=section, key=key.replace('.', '_'))
689 section=section, key=key.replace('.', '_'))
676
690
677 def _create_or_update_ui(
691 def _create_or_update_ui(
678 self, settings, section, key, value=None, active=None):
692 self, settings, section, key, value=None, active=None):
679 ui = settings.get_ui_by_section_and_key(section, key)
693 ui = settings.get_ui_by_section_and_key(section, key)
680 if not ui:
694 if not ui:
681 active = True if active is None else active
695 active = True if active is None else active
682 settings.create_ui_section_value(
696 settings.create_ui_section_value(
683 section, value, key=key, active=active)
697 section, value, key=key, active=active)
684 else:
698 else:
685 if active is not None:
699 if active is not None:
686 ui.ui_active = active
700 ui.ui_active = active
687 if value is not None:
701 if value is not None:
688 ui.ui_value = value
702 ui.ui_value = value
689 Session().add(ui)
703 Session().add(ui)
690
704
691 def _create_svn_settings(self, settings, data):
705 def _create_svn_settings(self, settings, data):
692 svn_settings = {
706 svn_settings = {
693 'new_svn_branch': self.SVN_BRANCH_SECTION,
707 'new_svn_branch': self.SVN_BRANCH_SECTION,
694 'new_svn_tag': self.SVN_TAG_SECTION
708 'new_svn_tag': self.SVN_TAG_SECTION
695 }
709 }
696 for key in svn_settings:
710 for key in svn_settings:
697 if data.get(key):
711 if data.get(key):
698 settings.create_ui_section_value(svn_settings[key], data[key])
712 settings.create_ui_section_value(svn_settings[key], data[key])
699
713
700 def _create_or_update_general_settings(self, settings, data):
714 def _create_or_update_general_settings(self, settings, data):
701 for name in self.GENERAL_SETTINGS:
715 for name in self.GENERAL_SETTINGS:
702 data_key = 'rhodecode_{}'.format(name)
716 data_key = 'rhodecode_{}'.format(name)
703 if data_key not in data:
717 if data_key not in data:
704 raise ValueError(
718 raise ValueError(
705 'The given data does not contain {} key'.format(data_key))
719 'The given data does not contain {} key'.format(data_key))
706 setting = settings.create_or_update_setting(
720 setting = settings.create_or_update_setting(
707 name, data[data_key], 'bool')
721 name, data[data_key], 'bool')
708 Session().add(setting)
722 Session().add(setting)
709
723
710 def _get_hg_settings(self, settings, data):
724 def _get_settings_keys(self, settings, data):
711 data_keys = [self._get_form_ui_key(*s) for s in settings]
725 data_keys = [self._get_form_ui_key(*s) for s in settings]
712 for data_key in data_keys:
726 for data_key in data_keys:
713 if data_key not in data:
727 if data_key not in data:
714 raise ValueError(
728 raise ValueError(
715 'The given data does not contain {} key'.format(data_key))
729 'The given data does not contain {} key'.format(data_key))
716 return data_keys
730 return data_keys
@@ -1,259 +1,267 b''
1 ## snippet for displaying vcs settings
1 ## snippet for displaying vcs settings
2 ## usage:
2 ## usage:
3 ## <%namespace name="vcss" file="/base/vcssettings.html"/>
3 ## <%namespace name="vcss" file="/base/vcssettings.html"/>
4 ## ${vcss.vcs_settings_fields()}
4 ## ${vcss.vcs_settings_fields()}
5
5
6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, allow_repo_location_change=False, **kwargs)">
6 <%def name="vcs_settings_fields(suffix='', svn_branch_patterns=None, svn_tag_patterns=None, repo_type=None, display_globals=False, allow_repo_location_change=False, **kwargs)">
7 % if display_globals:
7 % if display_globals:
8 <div class="panel panel-default">
8 <div class="panel panel-default">
9 <div class="panel-heading" id="general">
9 <div class="panel-heading" id="general">
10 <h3 class="panel-title">${_('General')}</h3>
10 <h3 class="panel-title">${_('General')}</h3>
11 </div>
11 </div>
12 <div class="panel-body">
12 <div class="panel-body">
13 <div class="field">
13 <div class="field">
14 <div class="checkbox">
14 <div class="checkbox">
15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
15 ${h.checkbox('web_push_ssl' + suffix, 'True')}
16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
16 <label for="web_push_ssl${suffix}">${_('Require SSL for vcs operations')}</label>
17 </div>
17 </div>
18 <div class="label">
18 <div class="label">
19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
19 <span class="help-block">${_('Activate to set RhodeCode to require SSL for pushing or pulling. If SSL certificate is missing it will return a HTTP Error 406: Not Acceptable.')}</span>
20 </div>
20 </div>
21 </div>
21 </div>
22 </div>
22 </div>
23 </div>
23 </div>
24 % endif
24 % endif
25
25
26 % if display_globals:
26 % if display_globals:
27 <div class="panel panel-default">
27 <div class="panel panel-default">
28 <div class="panel-heading">
28 <div class="panel-heading">
29 <h3 class="panel-title">${_('Main Storage Location')}</h3>
29 <h3 class="panel-title">${_('Main Storage Location')}</h3>
30 </div>
30 </div>
31 <div class="panel-body">
31 <div class="panel-body">
32 <div class="field">
32 <div class="field">
33 <div class="inputx locked_input">
33 <div class="inputx locked_input">
34 %if allow_repo_location_change:
34 %if allow_repo_location_change:
35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
35 ${h.text('paths_root_path',size=59,readonly="readonly", class_="disabled")}
36 <span id="path_unlock" class="tooltip"
36 <span id="path_unlock" class="tooltip"
37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
37 title="${h.tooltip(_('Click to unlock. You must restart RhodeCode in order to make this setting take effect.'))}">
38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
38 <div class="btn btn-default lock_input_button"><i id="path_unlock_icon" class="icon-lock"></i></div>
39 </span>
39 </span>
40 %else:
40 %else:
41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
41 ${_('Repository location change is disabled. You can enable this by changing the `allow_repo_location_change` inside .ini file.')}
42 ## form still requires this but we cannot internally change it anyway
42 ## form still requires this but we cannot internally change it anyway
43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
43 ${h.hidden('paths_root_path',size=30,readonly="readonly", class_="disabled")}
44 %endif
44 %endif
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="label">
47 <div class="label">
48 <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
48 <span class="help-block">${_('Filesystem location where repositories should be stored. After changing this value a restart and rescan of the repository folder are required.')}</span>
49 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51 </div>
52 % endif
52 % endif
53
53
54 % if display_globals or repo_type in ['git', 'hg']:
54 % if display_globals or repo_type in ['git', 'hg']:
55 <div class="panel panel-default">
55 <div class="panel panel-default">
56 <div class="panel-heading" id="general">
56 <div class="panel-heading" id="general">
57 <h3 class="panel-title">${_('Internal Hooks')}</h3>
57 <h3 class="panel-title">${_('Internal Hooks')}</h3>
58 </div>
58 </div>
59 <div class="panel-body">
59 <div class="panel-body">
60 <div class="field">
60 <div class="field">
61 <div class="checkbox">
61 <div class="checkbox">
62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
62 ${h.checkbox('hooks_changegroup_repo_size' + suffix, 'True', **kwargs)}
63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
63 <label for="hooks_changegroup_repo_size${suffix}">${_('Show repository size after push')}</label>
64 </div>
64 </div>
65
65
66 <div class="label">
66 <div class="label">
67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
67 <span class="help-block">${_('Trigger a hook that calculates repository size after each push.')}</span>
68 </div>
68 </div>
69 <div class="checkbox">
69 <div class="checkbox">
70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
70 ${h.checkbox('hooks_changegroup_push_logger' + suffix, 'True', **kwargs)}
71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
71 <label for="hooks_changegroup_push_logger${suffix}">${_('Execute pre/post push hooks')}</label>
72 </div>
72 </div>
73 <div class="label">
73 <div class="label">
74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
74 <span class="help-block">${_('Execute Built in pre/post push hooks. This also executes rcextensions hooks.')}</span>
75 </div>
75 </div>
76 <div class="checkbox">
76 <div class="checkbox">
77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
77 ${h.checkbox('hooks_outgoing_pull_logger' + suffix, 'True', **kwargs)}
78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
78 <label for="hooks_outgoing_pull_logger${suffix}">${_('Execute pre/post pull hooks')}</label>
79 </div>
79 </div>
80 <div class="label">
80 <div class="label">
81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
81 <span class="help-block">${_('Execute Built in pre/post pull hooks. This also executes rcextensions hooks.')}</span>
82 </div>
82 </div>
83 </div>
83 </div>
84 </div>
84 </div>
85 </div>
85 </div>
86 % endif
86 % endif
87
87
88 % if display_globals or repo_type in ['hg']:
88 % if display_globals or repo_type in ['hg']:
89 <div class="panel panel-default">
89 <div class="panel panel-default">
90 <div class="panel-heading">
90 <div class="panel-heading">
91 <h3 class="panel-title">${_('Mercurial Settings')}</h3>
91 <h3 class="panel-title">${_('Mercurial Settings')}</h3>
92 </div>
92 </div>
93 <div class="panel-body">
93 <div class="panel-body">
94 <div class="checkbox">
94 <div class="checkbox">
95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
95 ${h.checkbox('extensions_largefiles' + suffix, 'True', **kwargs)}
96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
96 <label for="extensions_largefiles${suffix}">${_('Enable largefiles extension')}</label>
97 </div>
97 </div>
98 <div class="label">
98 <div class="label">
99 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
99 <span class="help-block">${_('Enable Largefiles extensions for all repositories.')}</span>
100 </div>
100 </div>
101 <div class="checkbox">
101 <div class="checkbox">
102 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
102 ${h.checkbox('phases_publish' + suffix, 'True', **kwargs)}
103 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
103 <label for="phases_publish${suffix}">${_('Set repositories as publishing') if display_globals else _('Set repository as publishing')}</label>
104 </div>
104 </div>
105 <div class="label">
105 <div class="label">
106 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
106 <span class="help-block">${_('When this is enabled all commits in the repository are seen as public commits by clients.')}</span>
107 </div>
107 </div>
108 % if display_globals:
108 % if display_globals:
109 <div class="checkbox">
109 <div class="checkbox">
110 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
110 ${h.checkbox('extensions_hgsubversion' + suffix,'True')}
111 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
111 <label for="extensions_hgsubversion${suffix}">${_('Enable hgsubversion extension')}</label>
112 </div>
112 </div>
113 <div class="label">
113 <div class="label">
114 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
114 <span class="help-block">${_('Requires hgsubversion library to be installed. Allows cloning remote SVN repositories and migrates them to Mercurial type.')}</span>
115 </div>
115 </div>
116 % endif
116 % endif
117 </div>
117 </div>
118 </div>
118 </div>
119 ## LABS for HG
119 ## LABS for HG
120 % if c.labs_active:
120 % if c.labs_active:
121 <div class="panel panel-danger">
121 <div class="panel panel-danger">
122 <div class="panel-heading">
122 <div class="panel-heading">
123 <h3 class="panel-title">${_('Mercurial Labs Settings')} (${_('These features are considered experimental and may not work as expected.')})</h3>
123 <h3 class="panel-title">${_('Mercurial Labs Settings')} (${_('These features are considered experimental and may not work as expected.')})</h3>
124 </div>
124 </div>
125 <div class="panel-body">
125 <div class="panel-body">
126
126
127 <div class="checkbox">
127 <div class="checkbox">
128 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
128 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
129 <label for="rhodecode_hg_use_rebase_for_merging{suffix}">${_('Use rebase as merge strategy')}</label>
129 <label for="rhodecode_hg_use_rebase_for_merging{suffix}">${_('Use rebase as merge strategy')}</label>
130 </div>
130 </div>
131 <div class="label">
131 <div class="label">
132 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
132 <span class="help-block">${_('Use rebase instead of creating a merge commit when merging via web interface.')}</span>
133 </div>
133 </div>
134
134
135 </div>
135 </div>
136 </div>
136 </div>
137 % endif
137 % endif
138
138
139 % endif
139 % endif
140
140
141 % if display_globals or repo_type in ['svn']:
141 % if display_globals:
142 <div class="panel panel-default">
142 <div class="panel panel-default">
143 <div class="panel-heading">
143 <div class="panel-heading">
144 <h3 class="panel-title">${_('Subversion Settings')}</h3>
144 <h3 class="panel-title">${_('Global Subversion Settings')}</h3>
145 </div>
145 </div>
146 <div class="panel-body">
146 <div class="panel-body">
147 <div class="field">
147 <div class="field">
148 <div class="checkbox">
148 <div class="checkbox">
149 ${h.checkbox('rhodecode_proxy_subversion_http_requests' + suffix, 'True', **kwargs)}
149 ${h.checkbox('vcs_svn_proxy_http_requests_enabled' + suffix, 'True', **kwargs)}
150 <label for="rhodecode_proxy_subversion_http_requests${suffix}">${_('Proxy subversion HTTP requests')}</label>
150 <label for="vcs_svn_proxy_http_requests_enabled{suffix}">${_('Proxy subversion HTTP requests')}</label>
151 </div>
151 </div>
152 <div class="label">
152 <div class="label">
153 <span class="help-block">${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}</span>
153 <span class="help-block">${_('Subversion HTTP Support. Enables communication with SVN over HTTP protocol.')}</span>
154 </div>
154 </div>
155 </div>
155 </div>
156 <div class="field">
156 <div class="field">
157 <div class="label">
157 <div class="label">
158 <label for="rhodecode_subversion_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
158 <label for="vcs_svn_proxy_http_server_url">${_('Subversion HTTP Server URL')}</label><br/>
159 </div>
159 </div>
160 <div class="input">
160 <div class="input">
161 ${h.text('rhodecode_subversion_http_server_url',size=59)}
161 ${h.text('vcs_svn_proxy_http_server_url',size=59)}
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
164 </div>
165 <div class="label">
165 </div>
166 <span class="help-block">${_('Url to Apache Proxy, e.g. http://localhost:8080/')}</span>
166 % endif
167 </div>
167
168 </div>
168 % if display_globals or repo_type in ['svn']:
169 <div class="panel panel-default">
170 <div class="panel-heading">
171 <h3 class="panel-title">${_('Subversion Settings')}</h3>
172 </div>
173 <div class="panel-body">
169 <div class="field">
174 <div class="field">
170 <div class="content" >
175 <div class="content" >
171 <label>${_('Repository patterns')}</label><br/>
176 <label>${_('Repository patterns')}</label><br/>
172 </div>
177 </div>
173 </div>
178 </div>
174 <div class="label">
179 <div class="label">
175 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
180 <span class="help-block">${_('Patterns for identifying SVN branches and tags. For recursive search, use "*". Eg.: "/branches/*"')}</span>
176 </div>
181 </div>
177
182
178 <div class="field branch_patterns">
183 <div class="field branch_patterns">
179 <div class="input" >
184 <div class="input" >
180 <label>${_('Branches')}:</label><br/>
185 <label>${_('Branches')}:</label><br/>
181 </div>
186 </div>
182 % if svn_branch_patterns:
187 % if svn_branch_patterns:
183 % for branch in svn_branch_patterns:
188 % for branch in svn_branch_patterns:
184 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
189 <div class="input adjacent" id="${'id%s' % branch.ui_id}">
185 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
190 ${h.hidden('branch_ui_key' + suffix, branch.ui_key)}
186 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
191 ${h.text('branch_value_%d' % branch.ui_id + suffix, branch.ui_value, size=59, readonly="readonly", class_='disabled')}
187 % if kwargs.get('disabled') != 'disabled':
192 % if kwargs.get('disabled') != 'disabled':
188 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
193 <span class="btn btn-x" onclick="ajaxDeletePattern(${branch.ui_id},'${'id%s' % branch.ui_id}')">
189 ${_('Delete')}
194 ${_('Delete')}
190 </span>
195 </span>
191 % endif
196 % endif
192 </div>
197 </div>
193 % endfor
198 % endfor
194 %endif
199 %endif
195 </div>
200 </div>
196 % if kwargs.get('disabled') != 'disabled':
201 % if kwargs.get('disabled') != 'disabled':
197 <div class="field branch_patterns">
202 <div class="field branch_patterns">
198 <div class="input" >
203 <div class="input" >
199 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
204 ${h.text('new_svn_branch',size=59,placeholder='New branch pattern')}
200 </div>
205 </div>
201 </div>
206 </div>
202 % endif
207 % endif
203 <div class="field tag_patterns">
208 <div class="field tag_patterns">
204 <div class="input" >
209 <div class="input" >
205 <label>${_('Tags')}:</label><br/>
210 <label>${_('Tags')}:</label><br/>
206 </div>
211 </div>
207 % if svn_tag_patterns:
212 % if svn_tag_patterns:
208 % for tag in svn_tag_patterns:
213 % for tag in svn_tag_patterns:
209 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
214 <div class="input" id="${'id%s' % tag.ui_id + suffix}">
210 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
215 ${h.hidden('tag_ui_key' + suffix, tag.ui_key)}
211 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
216 ${h.text('tag_ui_value_new_%d' % tag.ui_id + suffix, tag.ui_value, size=59, readonly="readonly", class_='disabled tag_input')}
212 % if kwargs.get('disabled') != 'disabled':
217 % if kwargs.get('disabled') != 'disabled':
213 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
218 <span class="btn btn-x" onclick="ajaxDeletePattern(${tag.ui_id},'${'id%s' % tag.ui_id}')">
214 ${_('Delete')}
219 ${_('Delete')}
215 </span>
220 </span>
216 %endif
221 %endif
217 </div>
222 </div>
218 % endfor
223 % endfor
219 % endif
224 % endif
220 </div>
225 </div>
221 % if kwargs.get('disabled') != 'disabled':
226 % if kwargs.get('disabled') != 'disabled':
222 <div class="field tag_patterns">
227 <div class="field tag_patterns">
223 <div class="input" >
228 <div class="input" >
224 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
229 ${h.text('new_svn_tag' + suffix, size=59, placeholder='New tag pattern')}
225 </div>
230 </div>
226 </div>
231 </div>
227 %endif
232 %endif
228 </div>
233 </div>
229 </div>
234 </div>
230 % else:
235 % else:
231 ${h.hidden('new_svn_branch' + suffix, '')}
236 ${h.hidden('new_svn_branch' + suffix, '')}
232 ${h.hidden('new_svn_tag' + suffix, '')}
237 ${h.hidden('new_svn_tag' + suffix, '')}
233 % endif
238 % endif
234
239
240
241
242
235 % if display_globals or repo_type in ['hg', 'git']:
243 % if display_globals or repo_type in ['hg', 'git']:
236 <div class="panel panel-default">
244 <div class="panel panel-default">
237 <div class="panel-heading">
245 <div class="panel-heading">
238 <h3 class="panel-title">${_('Pull Request Settings')}</h3>
246 <h3 class="panel-title">${_('Pull Request Settings')}</h3>
239 </div>
247 </div>
240 <div class="panel-body">
248 <div class="panel-body">
241 <div class="checkbox">
249 <div class="checkbox">
242 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
250 ${h.checkbox('rhodecode_pr_merge_enabled' + suffix, 'True', **kwargs)}
243 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
251 <label for="rhodecode_pr_merge_enabled${suffix}">${_('Enable server-side merge for pull requests')}</label>
244 </div>
252 </div>
245 <div class="label">
253 <div class="label">
246 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
254 <span class="help-block">${_('Note: when this feature is enabled, it only runs hooks defined in the rcextension package. Custom hooks added on the Admin -> Settings -> Hooks page will not be run when pull requests are automatically merged from the web interface.')}</span>
247 </div>
255 </div>
248 <div class="checkbox">
256 <div class="checkbox">
249 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
257 ${h.checkbox('rhodecode_use_outdated_comments' + suffix, 'True', **kwargs)}
250 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
258 <label for="rhodecode_use_outdated_comments${suffix}">${_('Invalidate and relocate inline comments during update')}</label>
251 </div>
259 </div>
252 <div class="label">
260 <div class="label">
253 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
261 <span class="help-block">${_('During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden.')}</span>
254 </div>
262 </div>
255 </div>
263 </div>
256 </div>
264 </div>
257 % endif
265 % endif
258
266
259 </%def>
267 </%def>
@@ -1,655 +1,611 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.config.routing import ADMIN_PREFIX
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import url, assert_session_flash
30 from rhodecode.tests import url, assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = (
34 UPDATE_DATA_QUALNAME = (
35 'rhodecode.controllers.admin.settings.SettingsController.get_update_data')
35 'rhodecode.controllers.admin.settings.SettingsController.get_update_data')
36
36
37
37
38 @pytest.mark.usefixtures('autologin_user', 'app')
38 @pytest.mark.usefixtures('autologin_user', 'app')
39 class TestAdminSettingsController:
39 class TestAdminSettingsController:
40
40
41 @pytest.mark.parametrize('urlname', [
41 @pytest.mark.parametrize('urlname', [
42 'admin_settings_vcs',
42 'admin_settings_vcs',
43 'admin_settings_mapping',
43 'admin_settings_mapping',
44 'admin_settings_global',
44 'admin_settings_global',
45 'admin_settings_visual',
45 'admin_settings_visual',
46 'admin_settings_email',
46 'admin_settings_email',
47 'admin_settings_hooks',
47 'admin_settings_hooks',
48 'admin_settings_search',
48 'admin_settings_search',
49 'admin_settings_system',
49 'admin_settings_system',
50 ])
50 ])
51 def test_simple_get(self, urlname, app):
51 def test_simple_get(self, urlname, app):
52 app.get(url(urlname))
52 app.get(url(urlname))
53
53
54 def test_create_custom_hook(self, csrf_token):
54 def test_create_custom_hook(self, csrf_token):
55 response = self.app.post(
55 response = self.app.post(
56 url('admin_settings_hooks'),
56 url('admin_settings_hooks'),
57 params={
57 params={
58 'new_hook_ui_key': 'test_hooks_1',
58 'new_hook_ui_key': 'test_hooks_1',
59 'new_hook_ui_value': 'cd /tmp',
59 'new_hook_ui_value': 'cd /tmp',
60 'csrf_token': csrf_token})
60 'csrf_token': csrf_token})
61
61
62 response = response.follow()
62 response = response.follow()
63 response.mustcontain('test_hooks_1')
63 response.mustcontain('test_hooks_1')
64 response.mustcontain('cd /tmp')
64 response.mustcontain('cd /tmp')
65
65
66 def test_create_custom_hook_delete(self, csrf_token):
66 def test_create_custom_hook_delete(self, csrf_token):
67 response = self.app.post(
67 response = self.app.post(
68 url('admin_settings_hooks'),
68 url('admin_settings_hooks'),
69 params={
69 params={
70 'new_hook_ui_key': 'test_hooks_2',
70 'new_hook_ui_key': 'test_hooks_2',
71 'new_hook_ui_value': 'cd /tmp2',
71 'new_hook_ui_value': 'cd /tmp2',
72 'csrf_token': csrf_token})
72 'csrf_token': csrf_token})
73
73
74 response = response.follow()
74 response = response.follow()
75 response.mustcontain('test_hooks_2')
75 response.mustcontain('test_hooks_2')
76 response.mustcontain('cd /tmp2')
76 response.mustcontain('cd /tmp2')
77
77
78 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
78 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
79
79
80 # delete
80 # delete
81 self.app.post(
81 self.app.post(
82 url('admin_settings_hooks'),
82 url('admin_settings_hooks'),
83 params={'hook_id': hook_id, 'csrf_token': csrf_token})
83 params={'hook_id': hook_id, 'csrf_token': csrf_token})
84 response = self.app.get(url('admin_settings_hooks'))
84 response = self.app.get(url('admin_settings_hooks'))
85 response.mustcontain(no=['test_hooks_2'])
85 response.mustcontain(no=['test_hooks_2'])
86 response.mustcontain(no=['cd /tmp2'])
86 response.mustcontain(no=['cd /tmp2'])
87
87
88 def test_system_update_new_version(self):
88 def test_system_update_new_version(self):
89 update_data = {
89 update_data = {
90 'versions': [
90 'versions': [
91 {
91 {
92 'version': '100.3.1415926535',
92 'version': '100.3.1415926535',
93 'general': 'The latest version we are ever going to ship'
93 'general': 'The latest version we are ever going to ship'
94 },
94 },
95 {
95 {
96 'version': '0.0.0',
96 'version': '0.0.0',
97 'general': 'The first version we ever shipped'
97 'general': 'The first version we ever shipped'
98 }
98 }
99 ]
99 ]
100 }
100 }
101 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
101 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
102 response = self.app.get(url('admin_settings_system_update'))
102 response = self.app.get(url('admin_settings_system_update'))
103 response.mustcontain('A <b>new version</b> is available')
103 response.mustcontain('A <b>new version</b> is available')
104
104
105 def test_system_update_nothing_new(self):
105 def test_system_update_nothing_new(self):
106 update_data = {
106 update_data = {
107 'versions': [
107 'versions': [
108 {
108 {
109 'version': '0.0.0',
109 'version': '0.0.0',
110 'general': 'The first version we ever shipped'
110 'general': 'The first version we ever shipped'
111 }
111 }
112 ]
112 ]
113 }
113 }
114 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
114 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
115 response = self.app.get(url('admin_settings_system_update'))
115 response = self.app.get(url('admin_settings_system_update'))
116 response.mustcontain(
116 response.mustcontain(
117 'You already have the <b>latest</b> stable version.')
117 'You already have the <b>latest</b> stable version.')
118
118
119 def test_system_update_bad_response(self):
119 def test_system_update_bad_response(self):
120 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
120 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
121 response = self.app.get(url('admin_settings_system_update'))
121 response = self.app.get(url('admin_settings_system_update'))
122 response.mustcontain(
122 response.mustcontain(
123 'Bad data sent from update server')
123 'Bad data sent from update server')
124
124
125
125
126 @pytest.mark.usefixtures('autologin_user', 'app')
126 @pytest.mark.usefixtures('autologin_user', 'app')
127 class TestAdminSettingsGlobal:
127 class TestAdminSettingsGlobal:
128
128
129 def test_pre_post_code_code_active(self, csrf_token):
129 def test_pre_post_code_code_active(self, csrf_token):
130 pre_code = 'rc-pre-code-187652122'
130 pre_code = 'rc-pre-code-187652122'
131 post_code = 'rc-postcode-98165231'
131 post_code = 'rc-postcode-98165231'
132
132
133 response = self.post_and_verify_settings({
133 response = self.post_and_verify_settings({
134 'rhodecode_pre_code': pre_code,
134 'rhodecode_pre_code': pre_code,
135 'rhodecode_post_code': post_code,
135 'rhodecode_post_code': post_code,
136 'csrf_token': csrf_token,
136 'csrf_token': csrf_token,
137 })
137 })
138
138
139 response = response.follow()
139 response = response.follow()
140 response.mustcontain(pre_code, post_code)
140 response.mustcontain(pre_code, post_code)
141
141
142 def test_pre_post_code_code_inactive(self, csrf_token):
142 def test_pre_post_code_code_inactive(self, csrf_token):
143 pre_code = 'rc-pre-code-187652122'
143 pre_code = 'rc-pre-code-187652122'
144 post_code = 'rc-postcode-98165231'
144 post_code = 'rc-postcode-98165231'
145 response = self.post_and_verify_settings({
145 response = self.post_and_verify_settings({
146 'rhodecode_pre_code': '',
146 'rhodecode_pre_code': '',
147 'rhodecode_post_code': '',
147 'rhodecode_post_code': '',
148 'csrf_token': csrf_token,
148 'csrf_token': csrf_token,
149 })
149 })
150
150
151 response = response.follow()
151 response = response.follow()
152 response.mustcontain(no=[pre_code, post_code])
152 response.mustcontain(no=[pre_code, post_code])
153
153
154 def test_captcha_activate(self, csrf_token):
154 def test_captcha_activate(self, csrf_token):
155 self.post_and_verify_settings({
155 self.post_and_verify_settings({
156 'rhodecode_captcha_private_key': '1234567890',
156 'rhodecode_captcha_private_key': '1234567890',
157 'rhodecode_captcha_public_key': '1234567890',
157 'rhodecode_captcha_public_key': '1234567890',
158 'csrf_token': csrf_token,
158 'csrf_token': csrf_token,
159 })
159 })
160
160
161 response = self.app.get(ADMIN_PREFIX + '/register')
161 response = self.app.get(ADMIN_PREFIX + '/register')
162 response.mustcontain('captcha')
162 response.mustcontain('captcha')
163
163
164 def test_captcha_deactivate(self, csrf_token):
164 def test_captcha_deactivate(self, csrf_token):
165 self.post_and_verify_settings({
165 self.post_and_verify_settings({
166 'rhodecode_captcha_private_key': '',
166 'rhodecode_captcha_private_key': '',
167 'rhodecode_captcha_public_key': '1234567890',
167 'rhodecode_captcha_public_key': '1234567890',
168 'csrf_token': csrf_token,
168 'csrf_token': csrf_token,
169 })
169 })
170
170
171 response = self.app.get(ADMIN_PREFIX + '/register')
171 response = self.app.get(ADMIN_PREFIX + '/register')
172 response.mustcontain(no=['captcha'])
172 response.mustcontain(no=['captcha'])
173
173
174 def test_title_change(self, csrf_token):
174 def test_title_change(self, csrf_token):
175 old_title = 'RhodeCode'
175 old_title = 'RhodeCode'
176 new_title = old_title + '_changed'
176 new_title = old_title + '_changed'
177
177
178 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
178 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
179 response = self.post_and_verify_settings({
179 response = self.post_and_verify_settings({
180 'rhodecode_title': new_title,
180 'rhodecode_title': new_title,
181 'csrf_token': csrf_token,
181 'csrf_token': csrf_token,
182 })
182 })
183
183
184 response = response.follow()
184 response = response.follow()
185 response.mustcontain(
185 response.mustcontain(
186 """<div class="branding">- %s</div>""" % new_title)
186 """<div class="branding">- %s</div>""" % new_title)
187
187
188 def post_and_verify_settings(self, settings):
188 def post_and_verify_settings(self, settings):
189 old_title = 'RhodeCode'
189 old_title = 'RhodeCode'
190 old_realm = 'RhodeCode authentication'
190 old_realm = 'RhodeCode authentication'
191 params = {
191 params = {
192 'rhodecode_title': old_title,
192 'rhodecode_title': old_title,
193 'rhodecode_realm': old_realm,
193 'rhodecode_realm': old_realm,
194 'rhodecode_pre_code': '',
194 'rhodecode_pre_code': '',
195 'rhodecode_post_code': '',
195 'rhodecode_post_code': '',
196 'rhodecode_captcha_private_key': '',
196 'rhodecode_captcha_private_key': '',
197 'rhodecode_captcha_public_key': '',
197 'rhodecode_captcha_public_key': '',
198 }
198 }
199 params.update(settings)
199 params.update(settings)
200 response = self.app.post(url('admin_settings_global'), params=params)
200 response = self.app.post(url('admin_settings_global'), params=params)
201
201
202 assert_session_flash(response, 'Updated application settings')
202 assert_session_flash(response, 'Updated application settings')
203 app_settings = SettingsModel().get_all_settings()
203 app_settings = SettingsModel().get_all_settings()
204 del settings['csrf_token']
204 del settings['csrf_token']
205 for key, value in settings.iteritems():
205 for key, value in settings.iteritems():
206 assert app_settings[key] == value.decode('utf-8')
206 assert app_settings[key] == value.decode('utf-8')
207
207
208 return response
208 return response
209
209
210
210
211 @pytest.mark.usefixtures('autologin_user', 'app')
211 @pytest.mark.usefixtures('autologin_user', 'app')
212 class TestAdminSettingsVcs:
212 class TestAdminSettingsVcs:
213
213
214 def test_contains_svn_default_patterns(self, app):
214 def test_contains_svn_default_patterns(self, app):
215 response = app.get(url('admin_settings_vcs'))
215 response = app.get(url('admin_settings_vcs'))
216 expected_patterns = [
216 expected_patterns = [
217 '/trunk',
217 '/trunk',
218 '/branches/*',
218 '/branches/*',
219 '/tags/*',
219 '/tags/*',
220 ]
220 ]
221 for pattern in expected_patterns:
221 for pattern in expected_patterns:
222 response.mustcontain(pattern)
222 response.mustcontain(pattern)
223
223
224 def test_add_new_svn_branch_and_tag_pattern(
224 def test_add_new_svn_branch_and_tag_pattern(
225 self, app, backend_svn, form_defaults, disable_sql_cache,
225 self, app, backend_svn, form_defaults, disable_sql_cache,
226 csrf_token):
226 csrf_token):
227 form_defaults.update({
227 form_defaults.update({
228 'new_svn_branch': '/exp/branches/*',
228 'new_svn_branch': '/exp/branches/*',
229 'new_svn_tag': '/important_tags/*',
229 'new_svn_tag': '/important_tags/*',
230 'csrf_token': csrf_token,
230 'csrf_token': csrf_token,
231 })
231 })
232
232
233 response = app.post(
233 response = app.post(
234 url('admin_settings_vcs'), params=form_defaults, status=302)
234 url('admin_settings_vcs'), params=form_defaults, status=302)
235 response = response.follow()
235 response = response.follow()
236
236
237 # Expect to find the new values on the page
237 # Expect to find the new values on the page
238 response.mustcontain('/exp/branches/*')
238 response.mustcontain('/exp/branches/*')
239 response.mustcontain('/important_tags/*')
239 response.mustcontain('/important_tags/*')
240
240
241 # Expect that those patterns are used to match branches and tags now
241 # Expect that those patterns are used to match branches and tags now
242 repo = backend_svn['svn-simple-layout'].scm_instance()
242 repo = backend_svn['svn-simple-layout'].scm_instance()
243 assert 'exp/branches/exp-sphinx-docs' in repo.branches
243 assert 'exp/branches/exp-sphinx-docs' in repo.branches
244 assert 'important_tags/v0.5' in repo.tags
244 assert 'important_tags/v0.5' in repo.tags
245
245
246 def test_add_same_svn_value_twice_shows_an_error_message(
246 def test_add_same_svn_value_twice_shows_an_error_message(
247 self, app, form_defaults, csrf_token, settings_util):
247 self, app, form_defaults, csrf_token, settings_util):
248 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
248 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
249 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
249 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
250
250
251 response = app.post(
251 response = app.post(
252 url('admin_settings_vcs'),
252 url('admin_settings_vcs'),
253 params={
253 params={
254 'paths_root_path': form_defaults['paths_root_path'],
254 'paths_root_path': form_defaults['paths_root_path'],
255 'new_svn_branch': '/test',
255 'new_svn_branch': '/test',
256 'new_svn_tag': '/test',
256 'new_svn_tag': '/test',
257 'csrf_token': csrf_token,
257 'csrf_token': csrf_token,
258 },
258 },
259 status=200)
259 status=200)
260
260
261 response.mustcontain("Pattern already exists")
261 response.mustcontain("Pattern already exists")
262 response.mustcontain("Some form inputs contain invalid data.")
262 response.mustcontain("Some form inputs contain invalid data.")
263
263
264 @pytest.mark.parametrize('section', [
264 @pytest.mark.parametrize('section', [
265 'vcs_svn_branch',
265 'vcs_svn_branch',
266 'vcs_svn_tag',
266 'vcs_svn_tag',
267 ])
267 ])
268 def test_delete_svn_patterns(
268 def test_delete_svn_patterns(
269 self, section, app, csrf_token, settings_util):
269 self, section, app, csrf_token, settings_util):
270 setting = settings_util.create_rhodecode_ui(
270 setting = settings_util.create_rhodecode_ui(
271 section, '/test_delete', cleanup=False)
271 section, '/test_delete', cleanup=False)
272
272
273 app.post(
273 app.post(
274 url('admin_settings_vcs'),
274 url('admin_settings_vcs'),
275 params={
275 params={
276 '_method': 'delete',
276 '_method': 'delete',
277 'delete_svn_pattern': setting.ui_id,
277 'delete_svn_pattern': setting.ui_id,
278 'csrf_token': csrf_token},
278 'csrf_token': csrf_token},
279 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
279 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
280
280
281 @pytest.mark.parametrize('section', [
281 @pytest.mark.parametrize('section', [
282 'vcs_svn_branch',
282 'vcs_svn_branch',
283 'vcs_svn_tag',
283 'vcs_svn_tag',
284 ])
284 ])
285 def test_delete_svn_patterns_raises_400_when_no_xhr(
285 def test_delete_svn_patterns_raises_400_when_no_xhr(
286 self, section, app, csrf_token, settings_util):
286 self, section, app, csrf_token, settings_util):
287 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
287 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
288
288
289 app.post(
289 app.post(
290 url('admin_settings_vcs'),
290 url('admin_settings_vcs'),
291 params={
291 params={
292 '_method': 'delete',
292 '_method': 'delete',
293 'delete_svn_pattern': setting.ui_id,
293 'delete_svn_pattern': setting.ui_id,
294 'csrf_token': csrf_token},
294 'csrf_token': csrf_token},
295 status=400)
295 status=400)
296
296
297 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
297 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
298 form_defaults.update({
298 form_defaults.update({
299 'csrf_token': csrf_token,
299 'csrf_token': csrf_token,
300 'extensions_hgsubversion': 'True',
300 'extensions_hgsubversion': 'True',
301 })
301 })
302 response = app.post(
302 response = app.post(
303 url('admin_settings_vcs'),
303 url('admin_settings_vcs'),
304 params=form_defaults,
304 params=form_defaults,
305 status=302)
305 status=302)
306
306
307 response = response.follow()
307 response = response.follow()
308 extensions_input = (
308 extensions_input = (
309 '<input id="extensions_hgsubversion" '
309 '<input id="extensions_hgsubversion" '
310 'name="extensions_hgsubversion" type="checkbox" '
310 'name="extensions_hgsubversion" type="checkbox" '
311 'value="True" checked="checked" />')
311 'value="True" checked="checked" />')
312 response.mustcontain(extensions_input)
312 response.mustcontain(extensions_input)
313
313
314 def test_has_a_section_for_pull_request_settings(self, app):
314 def test_has_a_section_for_pull_request_settings(self, app):
315 response = app.get(url('admin_settings_vcs'))
315 response = app.get(url('admin_settings_vcs'))
316 response.mustcontain('Pull Request Settings')
316 response.mustcontain('Pull Request Settings')
317
317
318 def test_has_an_input_for_invalidation_of_inline_comments(
318 def test_has_an_input_for_invalidation_of_inline_comments(
319 self, app):
319 self, app):
320 response = app.get(url('admin_settings_vcs'))
320 response = app.get(url('admin_settings_vcs'))
321 assert_response = AssertResponse(response)
321 assert_response = AssertResponse(response)
322 assert_response.one_element_exists(
322 assert_response.one_element_exists(
323 '[name=rhodecode_use_outdated_comments]')
323 '[name=rhodecode_use_outdated_comments]')
324
324
325 @pytest.mark.parametrize('new_value', [True, False])
325 @pytest.mark.parametrize('new_value', [True, False])
326 def test_allows_to_change_invalidation_of_inline_comments(
326 def test_allows_to_change_invalidation_of_inline_comments(
327 self, app, form_defaults, csrf_token, new_value):
327 self, app, form_defaults, csrf_token, new_value):
328 setting_key = 'use_outdated_comments'
328 setting_key = 'use_outdated_comments'
329 setting = SettingsModel().create_or_update_setting(
329 setting = SettingsModel().create_or_update_setting(
330 setting_key, not new_value, 'bool')
330 setting_key, not new_value, 'bool')
331 Session().add(setting)
331 Session().add(setting)
332 Session().commit()
332 Session().commit()
333
333
334 form_defaults.update({
334 form_defaults.update({
335 'csrf_token': csrf_token,
335 'csrf_token': csrf_token,
336 'rhodecode_use_outdated_comments': str(new_value),
336 'rhodecode_use_outdated_comments': str(new_value),
337 })
337 })
338 response = app.post(
338 response = app.post(
339 url('admin_settings_vcs'),
339 url('admin_settings_vcs'),
340 params=form_defaults,
340 params=form_defaults,
341 status=302)
341 status=302)
342 response = response.follow()
342 response = response.follow()
343 setting = SettingsModel().get_setting_by_name(setting_key)
343 setting = SettingsModel().get_setting_by_name(setting_key)
344 assert setting.app_settings_value is new_value
344 assert setting.app_settings_value is new_value
345
345
346 def test_has_a_section_for_labs_settings_if_enabled(self, app):
346 def test_has_a_section_for_labs_settings_if_enabled(self, app):
347 with mock.patch.dict(
347 with mock.patch.dict(
348 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
348 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
349 response = self.app.get(url('admin_settings_vcs'))
349 response = self.app.get(url('admin_settings_vcs'))
350 response.mustcontain('Labs settings:')
350 response.mustcontain('Labs Settings')
351
351
352 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
352 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
353 with mock.patch.dict(
353 with mock.patch.dict(
354 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
354 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
355 response = self.app.get(url('admin_settings_vcs'))
355 response = self.app.get(url('admin_settings_vcs'))
356 response.mustcontain(no='Labs settings:')
356 response.mustcontain(no='Labs Settings')
357
357
358 @pytest.mark.parametrize('new_value', [True, False])
358 @pytest.mark.parametrize('new_value', [True, False])
359 def test_allows_to_change_hg_rebase_merge_strategy(
359 def test_allows_to_change_hg_rebase_merge_strategy(
360 self, app, form_defaults, csrf_token, new_value):
360 self, app, form_defaults, csrf_token, new_value):
361 setting_key = 'hg_use_rebase_for_merging'
361 setting_key = 'hg_use_rebase_for_merging'
362
362
363 form_defaults.update({
363 form_defaults.update({
364 'csrf_token': csrf_token,
364 'csrf_token': csrf_token,
365 'rhodecode_' + setting_key: str(new_value),
365 'rhodecode_' + setting_key: str(new_value),
366 })
366 })
367
367
368 with mock.patch.dict(
368 with mock.patch.dict(
369 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
369 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
370 app.post(
370 app.post(
371 url('admin_settings_vcs'),
371 url('admin_settings_vcs'),
372 params=form_defaults,
372 params=form_defaults,
373 status=302)
373 status=302)
374
374
375 setting = SettingsModel().get_setting_by_name(setting_key)
375 setting = SettingsModel().get_setting_by_name(setting_key)
376 assert setting.app_settings_value is new_value
376 assert setting.app_settings_value is new_value
377
377
378 @pytest.fixture
378 @pytest.fixture
379 def disable_sql_cache(self, request):
379 def disable_sql_cache(self, request):
380 patcher = mock.patch(
380 patcher = mock.patch(
381 'rhodecode.lib.caching_query.FromCache.process_query')
381 'rhodecode.lib.caching_query.FromCache.process_query')
382 request.addfinalizer(patcher.stop)
382 request.addfinalizer(patcher.stop)
383 patcher.start()
383 patcher.start()
384
384
385 @pytest.fixture
385 @pytest.fixture
386 def form_defaults(self):
386 def form_defaults(self):
387 from rhodecode.controllers.admin.settings import SettingsController
387 from rhodecode.controllers.admin.settings import SettingsController
388 controller = SettingsController()
388 controller = SettingsController()
389 return controller._form_defaults()
389 return controller._form_defaults()
390
390
391 # TODO: johbo: What we really want is to checkpoint before a test run and
391 # TODO: johbo: What we really want is to checkpoint before a test run and
392 # reset the session afterwards.
392 # reset the session afterwards.
393 @pytest.fixture(scope='class', autouse=True)
393 @pytest.fixture(scope='class', autouse=True)
394 def cleanup_settings(self, request, pylonsapp):
394 def cleanup_settings(self, request, pylonsapp):
395 ui_id = RhodeCodeUi.ui_id
395 ui_id = RhodeCodeUi.ui_id
396 original_ids = list(
396 original_ids = list(
397 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
397 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
398
398
399 @request.addfinalizer
399 @request.addfinalizer
400 def cleanup():
400 def cleanup():
401 RhodeCodeUi.query().filter(
401 RhodeCodeUi.query().filter(
402 ui_id.notin_(original_ids)).delete(False)
402 ui_id.notin_(original_ids)).delete(False)
403
403
404
404
405 @pytest.mark.usefixtures('autologin_user', 'app')
405 @pytest.mark.usefixtures('autologin_user', 'app')
406 class TestLabsSettings(object):
406 class TestLabsSettings(object):
407 def test_get_settings_page_disabled(self):
407 def test_get_settings_page_disabled(self):
408 with mock.patch.dict(rhodecode.CONFIG,
408 with mock.patch.dict(rhodecode.CONFIG,
409 {'labs_settings_active': 'false'}):
409 {'labs_settings_active': 'false'}):
410 response = self.app.get(url('admin_settings_labs'), status=302)
410 response = self.app.get(url('admin_settings_labs'), status=302)
411
411
412 assert response.location.endswith(url('admin_settings'))
412 assert response.location.endswith(url('admin_settings'))
413
413
414 def test_get_settings_page_enabled(self):
414 def test_get_settings_page_enabled(self):
415 from rhodecode.controllers.admin import settings
415 from rhodecode.controllers.admin import settings
416 lab_settings = [
416 lab_settings = [
417 settings.LabSetting(
417 settings.LabSetting(
418 key='rhodecode_bool',
418 key='rhodecode_bool',
419 type='bool',
419 type='bool',
420 group='bool group',
420 group='bool group',
421 label='bool label',
421 label='bool label',
422 help='bool help'
422 help='bool help'
423 ),
423 ),
424 settings.LabSetting(
424 settings.LabSetting(
425 key='rhodecode_text',
425 key='rhodecode_text',
426 type='unicode',
426 type='unicode',
427 group='text group',
427 group='text group',
428 label='text label',
428 label='text label',
429 help='text help'
429 help='text help'
430 ),
430 ),
431 ]
431 ]
432 with mock.patch.dict(rhodecode.CONFIG,
432 with mock.patch.dict(rhodecode.CONFIG,
433 {'labs_settings_active': 'true'}):
433 {'labs_settings_active': 'true'}):
434 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
434 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
435 response = self.app.get(url('admin_settings_labs'))
435 response = self.app.get(url('admin_settings_labs'))
436
436
437 assert '<label>bool group:</label>' in response
437 assert '<label>bool group:</label>' in response
438 assert '<label for="rhodecode_bool">bool label</label>' in response
438 assert '<label for="rhodecode_bool">bool label</label>' in response
439 assert '<p class="help-block">bool help</p>' in response
439 assert '<p class="help-block">bool help</p>' in response
440 assert 'name="rhodecode_bool" type="checkbox"' in response
440 assert 'name="rhodecode_bool" type="checkbox"' in response
441
441
442 assert '<label>text group:</label>' in response
442 assert '<label>text group:</label>' in response
443 assert '<label for="rhodecode_text">text label</label>' in response
443 assert '<label for="rhodecode_text">text label</label>' in response
444 assert '<p class="help-block">text help</p>' in response
444 assert '<p class="help-block">text help</p>' in response
445 assert 'name="rhodecode_text" size="60" type="text"' in response
445 assert 'name="rhodecode_text" size="60" type="text"' in response
446
446
447 @pytest.mark.parametrize('setting_name', [
448 'proxy_subversion_http_requests',
449 ])
450 def test_update_boolean_settings(self, csrf_token, setting_name):
451 self.app.post(
452 url('admin_settings_labs'),
453 params={
454 'rhodecode_{}'.format(setting_name): 'true',
455 'csrf_token': csrf_token,
456 })
457 setting = SettingsModel().get_setting_by_name(setting_name)
458 assert setting.app_settings_value
459
460 self.app.post(
461 url('admin_settings_labs'),
462 params={
463 'rhodecode_{}'.format(setting_name): 'false',
464 'csrf_token': csrf_token,
465 })
466 setting = SettingsModel().get_setting_by_name(setting_name)
467 assert not setting.app_settings_value
468
469 @pytest.mark.parametrize('setting_name', [
470 'subversion_http_server_url',
471 ])
472 def test_update_string_settings(self, csrf_token, setting_name):
473 self.app.post(
474 url('admin_settings_labs'),
475 params={
476 'rhodecode_{}'.format(setting_name): 'Test 1',
477 'csrf_token': csrf_token,
478 })
479 setting = SettingsModel().get_setting_by_name(setting_name)
480 assert setting.app_settings_value == 'Test 1'
481
482 self.app.post(
483 url('admin_settings_labs'),
484 params={
485 'rhodecode_{}'.format(setting_name): ' Test 2 ',
486 'csrf_token': csrf_token,
487 })
488 setting = SettingsModel().get_setting_by_name(setting_name)
489 assert setting.app_settings_value == 'Test 2'
490
491
447
492 @pytest.mark.usefixtures('app')
448 @pytest.mark.usefixtures('app')
493 class TestOpenSourceLicenses(object):
449 class TestOpenSourceLicenses(object):
494
450
495 def _get_url(self):
451 def _get_url(self):
496 return ADMIN_PREFIX + '/settings/open_source'
452 return ADMIN_PREFIX + '/settings/open_source'
497
453
498 def test_records_are_displayed(self, autologin_user):
454 def test_records_are_displayed(self, autologin_user):
499 sample_licenses = {
455 sample_licenses = {
500 "python2.7-pytest-2.7.1": {
456 "python2.7-pytest-2.7.1": {
501 "UNKNOWN": None
457 "UNKNOWN": None
502 },
458 },
503 "python2.7-Markdown-2.6.2": {
459 "python2.7-Markdown-2.6.2": {
504 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
460 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
505 }
461 }
506 }
462 }
507 read_licenses_patch = mock.patch(
463 read_licenses_patch = mock.patch(
508 'rhodecode.admin.views.read_opensource_licenses',
464 'rhodecode.admin.views.read_opensource_licenses',
509 return_value=sample_licenses)
465 return_value=sample_licenses)
510 with read_licenses_patch:
466 with read_licenses_patch:
511 response = self.app.get(self._get_url(), status=200)
467 response = self.app.get(self._get_url(), status=200)
512
468
513 assert_response = AssertResponse(response)
469 assert_response = AssertResponse(response)
514 assert_response.element_contains(
470 assert_response.element_contains(
515 '.panel-heading', 'Licenses of Third Party Packages')
471 '.panel-heading', 'Licenses of Third Party Packages')
516 for name in sample_licenses:
472 for name in sample_licenses:
517 response.mustcontain(name)
473 response.mustcontain(name)
518 for license in sample_licenses[name]:
474 for license in sample_licenses[name]:
519 assert_response.element_contains('.panel-body', license)
475 assert_response.element_contains('.panel-body', license)
520
476
521 def test_records_can_be_read(self, autologin_user):
477 def test_records_can_be_read(self, autologin_user):
522 response = self.app.get(self._get_url(), status=200)
478 response = self.app.get(self._get_url(), status=200)
523 assert_response = AssertResponse(response)
479 assert_response = AssertResponse(response)
524 assert_response.element_contains(
480 assert_response.element_contains(
525 '.panel-heading', 'Licenses of Third Party Packages')
481 '.panel-heading', 'Licenses of Third Party Packages')
526
482
527 def test_forbidden_when_normal_user(self, autologin_regular_user):
483 def test_forbidden_when_normal_user(self, autologin_regular_user):
528 self.app.get(self._get_url(), status=403)
484 self.app.get(self._get_url(), status=403)
529
485
530
486
531 @pytest.mark.usefixtures("app")
487 @pytest.mark.usefixtures("app")
532 class TestAdminSettingsIssueTracker:
488 class TestAdminSettingsIssueTracker:
533 RC_PREFIX = 'rhodecode_'
489 RC_PREFIX = 'rhodecode_'
534 SHORT_PATTERN_KEY = 'issuetracker_pat_'
490 SHORT_PATTERN_KEY = 'issuetracker_pat_'
535 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
491 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
536
492
537 def test_issuetracker_index(self, autologin_user):
493 def test_issuetracker_index(self, autologin_user):
538 response = self.app.get(url('admin_settings_issuetracker'))
494 response = self.app.get(url('admin_settings_issuetracker'))
539 assert response.status_code == 200
495 assert response.status_code == 200
540
496
541 def test_add_issuetracker_pattern(
497 def test_add_issuetracker_pattern(
542 self, request, autologin_user, csrf_token):
498 self, request, autologin_user, csrf_token):
543 pattern = 'issuetracker_pat'
499 pattern = 'issuetracker_pat'
544 another_pattern = pattern+'1'
500 another_pattern = pattern+'1'
545 post_url = url('admin_settings_issuetracker_save')
501 post_url = url('admin_settings_issuetracker_save')
546 post_data = {
502 post_data = {
547 'new_pattern_pattern_0': pattern,
503 'new_pattern_pattern_0': pattern,
548 'new_pattern_url_0': 'url',
504 'new_pattern_url_0': 'url',
549 'new_pattern_prefix_0': 'prefix',
505 'new_pattern_prefix_0': 'prefix',
550 'new_pattern_description_0': 'description',
506 'new_pattern_description_0': 'description',
551 'new_pattern_pattern_1': another_pattern,
507 'new_pattern_pattern_1': another_pattern,
552 'new_pattern_url_1': 'url1',
508 'new_pattern_url_1': 'url1',
553 'new_pattern_prefix_1': 'prefix1',
509 'new_pattern_prefix_1': 'prefix1',
554 'new_pattern_description_1': 'description1',
510 'new_pattern_description_1': 'description1',
555 'csrf_token': csrf_token
511 'csrf_token': csrf_token
556 }
512 }
557 self.app.post(post_url, post_data, status=302)
513 self.app.post(post_url, post_data, status=302)
558 settings = SettingsModel().get_all_settings()
514 settings = SettingsModel().get_all_settings()
559 self.uid = md5(pattern)
515 self.uid = md5(pattern)
560 assert settings[self.PATTERN_KEY+self.uid] == pattern
516 assert settings[self.PATTERN_KEY+self.uid] == pattern
561 self.another_uid = md5(another_pattern)
517 self.another_uid = md5(another_pattern)
562 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
518 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
563
519
564 @request.addfinalizer
520 @request.addfinalizer
565 def cleanup():
521 def cleanup():
566 defaults = SettingsModel().get_all_settings()
522 defaults = SettingsModel().get_all_settings()
567
523
568 entries = [name for name in defaults if (
524 entries = [name for name in defaults if (
569 (self.uid in name) or (self.another_uid) in name)]
525 (self.uid in name) or (self.another_uid) in name)]
570 start = len(self.RC_PREFIX)
526 start = len(self.RC_PREFIX)
571 for del_key in entries:
527 for del_key in entries:
572 # TODO: anderson: get_by_name needs name without prefix
528 # TODO: anderson: get_by_name needs name without prefix
573 entry = SettingsModel().get_setting_by_name(del_key[start:])
529 entry = SettingsModel().get_setting_by_name(del_key[start:])
574 Session().delete(entry)
530 Session().delete(entry)
575
531
576 Session().commit()
532 Session().commit()
577
533
578 def test_edit_issuetracker_pattern(
534 def test_edit_issuetracker_pattern(
579 self, autologin_user, backend, csrf_token, request):
535 self, autologin_user, backend, csrf_token, request):
580 old_pattern = 'issuetracker_pat'
536 old_pattern = 'issuetracker_pat'
581 old_uid = md5(old_pattern)
537 old_uid = md5(old_pattern)
582 pattern = 'issuetracker_pat_new'
538 pattern = 'issuetracker_pat_new'
583 self.new_uid = md5(pattern)
539 self.new_uid = md5(pattern)
584
540
585 SettingsModel().create_or_update_setting(
541 SettingsModel().create_or_update_setting(
586 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
542 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
587
543
588 post_url = url('admin_settings_issuetracker_save')
544 post_url = url('admin_settings_issuetracker_save')
589 post_data = {
545 post_data = {
590 'new_pattern_pattern_0': pattern,
546 'new_pattern_pattern_0': pattern,
591 'new_pattern_url_0': 'url',
547 'new_pattern_url_0': 'url',
592 'new_pattern_prefix_0': 'prefix',
548 'new_pattern_prefix_0': 'prefix',
593 'new_pattern_description_0': 'description',
549 'new_pattern_description_0': 'description',
594 'uid': old_uid,
550 'uid': old_uid,
595 'csrf_token': csrf_token
551 'csrf_token': csrf_token
596 }
552 }
597 self.app.post(post_url, post_data, status=302)
553 self.app.post(post_url, post_data, status=302)
598 settings = SettingsModel().get_all_settings()
554 settings = SettingsModel().get_all_settings()
599 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
555 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
600 assert self.PATTERN_KEY+old_uid not in settings
556 assert self.PATTERN_KEY+old_uid not in settings
601
557
602 @request.addfinalizer
558 @request.addfinalizer
603 def cleanup():
559 def cleanup():
604 IssueTrackerSettingsModel().delete_entries(self.new_uid)
560 IssueTrackerSettingsModel().delete_entries(self.new_uid)
605
561
606 def test_replace_issuetracker_pattern_description(
562 def test_replace_issuetracker_pattern_description(
607 self, autologin_user, csrf_token, request, settings_util):
563 self, autologin_user, csrf_token, request, settings_util):
608 prefix = 'issuetracker'
564 prefix = 'issuetracker'
609 pattern = 'issuetracker_pat'
565 pattern = 'issuetracker_pat'
610 self.uid = md5(pattern)
566 self.uid = md5(pattern)
611 pattern_key = '_'.join([prefix, 'pat', self.uid])
567 pattern_key = '_'.join([prefix, 'pat', self.uid])
612 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
568 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
613 desc_key = '_'.join([prefix, 'desc', self.uid])
569 desc_key = '_'.join([prefix, 'desc', self.uid])
614 rc_desc_key = '_'.join(['rhodecode', desc_key])
570 rc_desc_key = '_'.join(['rhodecode', desc_key])
615 new_description = 'new_description'
571 new_description = 'new_description'
616
572
617 settings_util.create_rhodecode_setting(
573 settings_util.create_rhodecode_setting(
618 pattern_key, pattern, 'unicode', cleanup=False)
574 pattern_key, pattern, 'unicode', cleanup=False)
619 settings_util.create_rhodecode_setting(
575 settings_util.create_rhodecode_setting(
620 desc_key, 'old description', 'unicode', cleanup=False)
576 desc_key, 'old description', 'unicode', cleanup=False)
621
577
622 post_url = url('admin_settings_issuetracker_save')
578 post_url = url('admin_settings_issuetracker_save')
623 post_data = {
579 post_data = {
624 'new_pattern_pattern_0': pattern,
580 'new_pattern_pattern_0': pattern,
625 'new_pattern_url_0': 'url',
581 'new_pattern_url_0': 'url',
626 'new_pattern_prefix_0': 'prefix',
582 'new_pattern_prefix_0': 'prefix',
627 'new_pattern_description_0': new_description,
583 'new_pattern_description_0': new_description,
628 'uid': self.uid,
584 'uid': self.uid,
629 'csrf_token': csrf_token
585 'csrf_token': csrf_token
630 }
586 }
631 self.app.post(post_url, post_data, status=302)
587 self.app.post(post_url, post_data, status=302)
632 settings = SettingsModel().get_all_settings()
588 settings = SettingsModel().get_all_settings()
633 assert settings[rc_pattern_key] == pattern
589 assert settings[rc_pattern_key] == pattern
634 assert settings[rc_desc_key] == new_description
590 assert settings[rc_desc_key] == new_description
635
591
636 @request.addfinalizer
592 @request.addfinalizer
637 def cleanup():
593 def cleanup():
638 IssueTrackerSettingsModel().delete_entries(self.uid)
594 IssueTrackerSettingsModel().delete_entries(self.uid)
639
595
640 def test_delete_issuetracker_pattern(
596 def test_delete_issuetracker_pattern(
641 self, autologin_user, backend, csrf_token, settings_util):
597 self, autologin_user, backend, csrf_token, settings_util):
642 pattern = 'issuetracker_pat'
598 pattern = 'issuetracker_pat'
643 uid = md5(pattern)
599 uid = md5(pattern)
644 settings_util.create_rhodecode_setting(
600 settings_util.create_rhodecode_setting(
645 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
601 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
646
602
647 post_url = url('admin_issuetracker_delete')
603 post_url = url('admin_issuetracker_delete')
648 post_data = {
604 post_data = {
649 '_method': 'delete',
605 '_method': 'delete',
650 'uid': uid,
606 'uid': uid,
651 'csrf_token': csrf_token
607 'csrf_token': csrf_token
652 }
608 }
653 self.app.post(post_url, post_data, status=302)
609 self.app.post(post_url, post_data, status=302)
654 settings = SettingsModel().get_all_settings()
610 settings = SettingsModel().get_all_settings()
655 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
611 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,188 +1,203 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from StringIO import StringIO
21 from StringIO import StringIO
22
22
23 import pytest
23 import pytest
24 from mock import patch, Mock
24 from mock import patch, Mock
25
25
26 import rhodecode
26 import rhodecode
27 from rhodecode.lib.middleware.simplesvn import SimpleSvn, SimpleSvnApp
27 from rhodecode.lib.middleware.simplesvn import SimpleSvn, SimpleSvnApp
28
28
29
29
30 class TestSimpleSvn(object):
30 class TestSimpleSvn(object):
31 @pytest.fixture(autouse=True)
31 @pytest.fixture(autouse=True)
32 def simple_svn(self, pylonsapp):
32 def simple_svn(self, pylonsapp):
33 self.app = SimpleSvn(
33 self.app = SimpleSvn(
34 application='None',
34 application='None',
35 config={'auth_ret_code': '',
35 config={'auth_ret_code': '',
36 'base_path': rhodecode.CONFIG['base_path']},
36 'base_path': rhodecode.CONFIG['base_path']},
37 registry=None)
37 registry=None)
38
38
39 def test_get_config(self):
39 def test_get_config(self):
40 extras = {'foo': 'FOO', 'bar': 'BAR'}
40 extras = {'foo': 'FOO', 'bar': 'BAR'}
41 config = self.app._create_config(extras, repo_name='test-repo')
41 config = self.app._create_config(extras, repo_name='test-repo')
42 assert config == extras
42 assert config == extras
43
43
44 @pytest.mark.parametrize(
44 @pytest.mark.parametrize(
45 'method', ['OPTIONS', 'PROPFIND', 'GET', 'REPORT'])
45 'method', ['OPTIONS', 'PROPFIND', 'GET', 'REPORT'])
46 def test_get_action_returns_pull(self, method):
46 def test_get_action_returns_pull(self, method):
47 environment = {'REQUEST_METHOD': method}
47 environment = {'REQUEST_METHOD': method}
48 action = self.app._get_action(environment)
48 action = self.app._get_action(environment)
49 assert action == 'pull'
49 assert action == 'pull'
50
50
51 @pytest.mark.parametrize(
51 @pytest.mark.parametrize(
52 'method', [
52 'method', [
53 'MKACTIVITY', 'PROPPATCH', 'PUT', 'CHECKOUT', 'MKCOL', 'MOVE',
53 'MKACTIVITY', 'PROPPATCH', 'PUT', 'CHECKOUT', 'MKCOL', 'MOVE',
54 'COPY', 'DELETE', 'LOCK', 'UNLOCK', 'MERGE'
54 'COPY', 'DELETE', 'LOCK', 'UNLOCK', 'MERGE'
55 ])
55 ])
56 def test_get_action_returns_push(self, method):
56 def test_get_action_returns_push(self, method):
57 environment = {'REQUEST_METHOD': method}
57 environment = {'REQUEST_METHOD': method}
58 action = self.app._get_action(environment)
58 action = self.app._get_action(environment)
59 assert action == 'push'
59 assert action == 'push'
60
60
61 @pytest.mark.parametrize(
61 @pytest.mark.parametrize(
62 'path, expected_name', [
62 'path, expected_name', [
63 ('/hello-svn', 'hello-svn'),
63 ('/hello-svn', 'hello-svn'),
64 ('/hello-svn/', 'hello-svn'),
64 ('/hello-svn/', 'hello-svn'),
65 ('/group/hello-svn/', 'group/hello-svn'),
65 ('/group/hello-svn/', 'group/hello-svn'),
66 ('/group/hello-svn/!svn/vcc/default', 'group/hello-svn'),
66 ('/group/hello-svn/!svn/vcc/default', 'group/hello-svn'),
67 ])
67 ])
68 def test_get_repository_name(self, path, expected_name):
68 def test_get_repository_name(self, path, expected_name):
69 environment = {'PATH_INFO': path}
69 environment = {'PATH_INFO': path}
70 name = self.app._get_repository_name(environment)
70 name = self.app._get_repository_name(environment)
71 assert name == expected_name
71 assert name == expected_name
72
72
73 def test_get_repository_name_subfolder(self, backend_svn):
73 def test_get_repository_name_subfolder(self, backend_svn):
74 repo = backend_svn.repo
74 repo = backend_svn.repo
75 environment = {
75 environment = {
76 'PATH_INFO': '/{}/path/with/subfolders'.format(repo.repo_name)}
76 'PATH_INFO': '/{}/path/with/subfolders'.format(repo.repo_name)}
77 name = self.app._get_repository_name(environment)
77 name = self.app._get_repository_name(environment)
78 assert name == repo.repo_name
78 assert name == repo.repo_name
79
79
80 def test_create_wsgi_app(self):
80 def test_create_wsgi_app(self):
81 with patch('rhodecode.lib.middleware.simplesvn.SimpleSvnApp') as (
81 with patch.object(SimpleSvn, '_is_svn_enabled') as mock_method:
82 wsgi_app_mock):
82 mock_method.return_value = False
83 config = Mock()
83 with patch('rhodecode.lib.middleware.simplesvn.DisabledSimpleSvnApp') as (
84 wsgi_app = self.app._create_wsgi_app(
84 wsgi_app_mock):
85 repo_path='', repo_name='', config=config)
85 config = Mock()
86 wsgi_app = self.app._create_wsgi_app(
87 repo_path='', repo_name='', config=config)
88
89 wsgi_app_mock.assert_called_once_with(config)
90 assert wsgi_app == wsgi_app_mock()
86
91
87 wsgi_app_mock.assert_called_once_with(config)
92 def test_create_wsgi_app_when_enabled(self):
88 assert wsgi_app == wsgi_app_mock()
93 with patch.object(SimpleSvn, '_is_svn_enabled') as mock_method:
94 mock_method.return_value = True
95 with patch('rhodecode.lib.middleware.simplesvn.SimpleSvnApp') as (
96 wsgi_app_mock):
97 config = Mock()
98 wsgi_app = self.app._create_wsgi_app(
99 repo_path='', repo_name='', config=config)
100
101 wsgi_app_mock.assert_called_once_with(config)
102 assert wsgi_app == wsgi_app_mock()
103
89
104
90
105
91 class TestSimpleSvnApp(object):
106 class TestSimpleSvnApp(object):
92 data = '<xml></xml>'
107 data = '<xml></xml>'
93 path = '/group/my-repo'
108 path = '/group/my-repo'
94 wsgi_input = StringIO(data)
109 wsgi_input = StringIO(data)
95 environment = {
110 environment = {
96 'HTTP_DAV': (
111 'HTTP_DAV': (
97 'http://subversion.tigris.org/xmlns/dav/svn/depth,'
112 'http://subversion.tigris.org/xmlns/dav/svn/depth,'
98 ' http://subversion.tigris.org/xmlns/dav/svn/mergeinfo'),
113 ' http://subversion.tigris.org/xmlns/dav/svn/mergeinfo'),
99 'HTTP_USER_AGENT': 'SVN/1.8.11 (x86_64-linux) serf/1.3.8',
114 'HTTP_USER_AGENT': 'SVN/1.8.11 (x86_64-linux) serf/1.3.8',
100 'REQUEST_METHOD': 'OPTIONS',
115 'REQUEST_METHOD': 'OPTIONS',
101 'PATH_INFO': path,
116 'PATH_INFO': path,
102 'wsgi.input': wsgi_input,
117 'wsgi.input': wsgi_input,
103 'CONTENT_TYPE': 'text/xml',
118 'CONTENT_TYPE': 'text/xml',
104 'CONTENT_LENGTH': '130'
119 'CONTENT_LENGTH': '130'
105 }
120 }
106
121
107 def setup_method(self, method):
122 def setup_method(self, method):
108 self.host = 'http://localhost/'
123 self.host = 'http://localhost/'
109 self.app = SimpleSvnApp(
124 self.app = SimpleSvnApp(
110 config={'subversion_http_server_url': self.host})
125 config={'subversion_http_server_url': self.host})
111
126
112 def test_get_request_headers_with_content_type(self):
127 def test_get_request_headers_with_content_type(self):
113 expected_headers = {
128 expected_headers = {
114 'Dav': self.environment['HTTP_DAV'],
129 'Dav': self.environment['HTTP_DAV'],
115 'User-Agent': self.environment['HTTP_USER_AGENT'],
130 'User-Agent': self.environment['HTTP_USER_AGENT'],
116 'Content-Type': self.environment['CONTENT_TYPE'],
131 'Content-Type': self.environment['CONTENT_TYPE'],
117 'Content-Length': self.environment['CONTENT_LENGTH']
132 'Content-Length': self.environment['CONTENT_LENGTH']
118 }
133 }
119 headers = self.app._get_request_headers(self.environment)
134 headers = self.app._get_request_headers(self.environment)
120 assert headers == expected_headers
135 assert headers == expected_headers
121
136
122 def test_get_request_headers_without_content_type(self):
137 def test_get_request_headers_without_content_type(self):
123 environment = self.environment.copy()
138 environment = self.environment.copy()
124 environment.pop('CONTENT_TYPE')
139 environment.pop('CONTENT_TYPE')
125 expected_headers = {
140 expected_headers = {
126 'Dav': environment['HTTP_DAV'],
141 'Dav': environment['HTTP_DAV'],
127 'Content-Length': self.environment['CONTENT_LENGTH'],
142 'Content-Length': self.environment['CONTENT_LENGTH'],
128 'User-Agent': environment['HTTP_USER_AGENT'],
143 'User-Agent': environment['HTTP_USER_AGENT'],
129 }
144 }
130 request_headers = self.app._get_request_headers(environment)
145 request_headers = self.app._get_request_headers(environment)
131 assert request_headers == expected_headers
146 assert request_headers == expected_headers
132
147
133 def test_get_response_headers(self):
148 def test_get_response_headers(self):
134 headers = {
149 headers = {
135 'Connection': 'keep-alive',
150 'Connection': 'keep-alive',
136 'Keep-Alive': 'timeout=5, max=100',
151 'Keep-Alive': 'timeout=5, max=100',
137 'Transfer-Encoding': 'chunked',
152 'Transfer-Encoding': 'chunked',
138 'Content-Encoding': 'gzip',
153 'Content-Encoding': 'gzip',
139 'MS-Author-Via': 'DAV',
154 'MS-Author-Via': 'DAV',
140 'SVN-Supported-Posts': 'create-txn-with-props'
155 'SVN-Supported-Posts': 'create-txn-with-props'
141 }
156 }
142 expected_headers = [
157 expected_headers = [
143 ('MS-Author-Via', 'DAV'),
158 ('MS-Author-Via', 'DAV'),
144 ('SVN-Supported-Posts', 'create-txn-with-props'),
159 ('SVN-Supported-Posts', 'create-txn-with-props'),
145 ('X-RhodeCode-Backend', 'svn'),
160 ('X-RhodeCode-Backend', 'svn'),
146 ]
161 ]
147 response_headers = self.app._get_response_headers(headers)
162 response_headers = self.app._get_response_headers(headers)
148 assert sorted(response_headers) == sorted(expected_headers)
163 assert sorted(response_headers) == sorted(expected_headers)
149
164
150 def test_get_url(self):
165 def test_get_url(self):
151 url = self.app._get_url(self.path)
166 url = self.app._get_url(self.path)
152 expected_url = '{}{}'.format(self.host.strip('/'), self.path)
167 expected_url = '{}{}'.format(self.host.strip('/'), self.path)
153 assert url == expected_url
168 assert url == expected_url
154
169
155 def test_call(self):
170 def test_call(self):
156 start_response = Mock()
171 start_response = Mock()
157 response_mock = Mock()
172 response_mock = Mock()
158 response_mock.headers = {
173 response_mock.headers = {
159 'Content-Encoding': 'gzip',
174 'Content-Encoding': 'gzip',
160 'MS-Author-Via': 'DAV',
175 'MS-Author-Via': 'DAV',
161 'SVN-Supported-Posts': 'create-txn-with-props'
176 'SVN-Supported-Posts': 'create-txn-with-props'
162 }
177 }
163 response_mock.status_code = 200
178 response_mock.status_code = 200
164 response_mock.reason = 'OK'
179 response_mock.reason = 'OK'
165 with patch('rhodecode.lib.middleware.simplesvn.requests.request') as (
180 with patch('rhodecode.lib.middleware.simplesvn.requests.request') as (
166 request_mock):
181 request_mock):
167 request_mock.return_value = response_mock
182 request_mock.return_value = response_mock
168 self.app(self.environment, start_response)
183 self.app(self.environment, start_response)
169
184
170 expected_url = '{}{}'.format(self.host.strip('/'), self.path)
185 expected_url = '{}{}'.format(self.host.strip('/'), self.path)
171 expected_request_headers = {
186 expected_request_headers = {
172 'Dav': self.environment['HTTP_DAV'],
187 'Dav': self.environment['HTTP_DAV'],
173 'User-Agent': self.environment['HTTP_USER_AGENT'],
188 'User-Agent': self.environment['HTTP_USER_AGENT'],
174 'Content-Type': self.environment['CONTENT_TYPE'],
189 'Content-Type': self.environment['CONTENT_TYPE'],
175 'Content-Length': self.environment['CONTENT_LENGTH']
190 'Content-Length': self.environment['CONTENT_LENGTH']
176 }
191 }
177 expected_response_headers = [
192 expected_response_headers = [
178 ('SVN-Supported-Posts', 'create-txn-with-props'),
193 ('SVN-Supported-Posts', 'create-txn-with-props'),
179 ('MS-Author-Via', 'DAV'),
194 ('MS-Author-Via', 'DAV'),
180 ('X-RhodeCode-Backend', 'svn'),
195 ('X-RhodeCode-Backend', 'svn'),
181 ]
196 ]
182 request_mock.assert_called_once_with(
197 request_mock.assert_called_once_with(
183 self.environment['REQUEST_METHOD'], expected_url,
198 self.environment['REQUEST_METHOD'], expected_url,
184 data=self.data, headers=expected_request_headers)
199 data=self.data, headers=expected_request_headers)
185 response_mock.iter_content.assert_called_once_with(chunk_size=1024)
200 response_mock.iter_content.assert_called_once_with(chunk_size=1024)
186 args, _ = start_response.call_args
201 args, _ = start_response.call_args
187 assert args[0] == '200 OK'
202 assert args[0] == '200 OK'
188 assert sorted(args[1]) == sorted(expected_response_headers)
203 assert sorted(args[1]) == sorted(expected_response_headers)
@@ -1,136 +1,140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from mock import patch, Mock
21 from mock import patch, Mock
22
22
23 import rhodecode
23 import rhodecode
24 from rhodecode.lib.middleware import vcs
24 from rhodecode.lib.middleware import vcs
25 from rhodecode.lib.middleware.simplesvn import (
26 SimpleSvn, DisabledSimpleSvnApp, SimpleSvnApp)
27 from rhodecode.tests import SVN_REPO
25
28
29 svn_repo_path = '/'+ SVN_REPO
26
30
27 def test_is_hg():
31 def test_is_hg():
28 environ = {
32 environ = {
29 'PATH_INFO': '/rhodecode-dev',
33 'PATH_INFO': svn_repo_path,
30 'QUERY_STRING': 'cmd=changegroup',
34 'QUERY_STRING': 'cmd=changegroup',
31 'HTTP_ACCEPT': 'application/mercurial'
35 'HTTP_ACCEPT': 'application/mercurial'
32 }
36 }
33 assert vcs.is_hg(environ)
37 assert vcs.is_hg(environ)
34
38
35
39
36 def test_is_hg_no_cmd():
40 def test_is_hg_no_cmd():
37 environ = {
41 environ = {
38 'PATH_INFO': '/rhodecode-dev',
42 'PATH_INFO': svn_repo_path,
39 'QUERY_STRING': '',
43 'QUERY_STRING': '',
40 'HTTP_ACCEPT': 'application/mercurial'
44 'HTTP_ACCEPT': 'application/mercurial'
41 }
45 }
42 assert not vcs.is_hg(environ)
46 assert not vcs.is_hg(environ)
43
47
44
48
45 def test_is_hg_empty_cmd():
49 def test_is_hg_empty_cmd():
46 environ = {
50 environ = {
47 'PATH_INFO': '/rhodecode-dev',
51 'PATH_INFO': svn_repo_path,
48 'QUERY_STRING': 'cmd=',
52 'QUERY_STRING': 'cmd=',
49 'HTTP_ACCEPT': 'application/mercurial'
53 'HTTP_ACCEPT': 'application/mercurial'
50 }
54 }
51 assert not vcs.is_hg(environ)
55 assert not vcs.is_hg(environ)
52
56
53
57
54 def test_is_svn_returns_true_if_subversion_is_in_a_dav_header():
58 def test_is_svn_returns_true_if_subversion_is_in_a_dav_header():
55 environ = {
59 environ = {
56 'PATH_INFO': '/rhodecode-dev',
60 'PATH_INFO': svn_repo_path,
57 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log-revprops'
61 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log-revprops'
58 }
62 }
59 assert vcs.is_svn(environ) is True
63 assert vcs.is_svn(environ) is True
60
64
61
65
62 def test_is_svn_returns_false_if_subversion_is_not_in_a_dav_header():
66 def test_is_svn_returns_false_if_subversion_is_not_in_a_dav_header():
63 environ = {
67 environ = {
64 'PATH_INFO': '/rhodecode-dev',
68 'PATH_INFO': svn_repo_path,
65 'HTTP_DAV': 'http://stuff.tigris.org/xmlns/dav/svn/log-revprops'
69 'HTTP_DAV': 'http://stuff.tigris.org/xmlns/dav/svn/log-revprops'
66 }
70 }
67 assert vcs.is_svn(environ) is False
71 assert vcs.is_svn(environ) is False
68
72
69
73
70 def test_is_svn_returns_false_if_no_dav_header():
74 def test_is_svn_returns_false_if_no_dav_header():
71 environ = {
75 environ = {
72 'PATH_INFO': '/rhodecode-dev',
76 'PATH_INFO': svn_repo_path,
73 }
77 }
74 assert vcs.is_svn(environ) is False
78 assert vcs.is_svn(environ) is False
75
79
76
80
77 def test_is_svn_returns_true_if_magic_path_segment():
81 def test_is_svn_returns_true_if_magic_path_segment():
78 environ = {
82 environ = {
79 'PATH_INFO': '/stub-repository/!svn/rev/4',
83 'PATH_INFO': '/stub-repository/!svn/rev/4',
80 }
84 }
81 assert vcs.is_svn(environ)
85 assert vcs.is_svn(environ)
82
86
83
87
84 def test_is_svn_allows_to_configure_the_magic_path(monkeypatch):
88 def test_is_svn_allows_to_configure_the_magic_path(monkeypatch):
85 """
89 """
86 This is intended as a fallback in case someone has configured his
90 This is intended as a fallback in case someone has configured his
87 Subversion server with a different magic path segment.
91 Subversion server with a different magic path segment.
88 """
92 """
89 monkeypatch.setitem(
93 monkeypatch.setitem(
90 rhodecode.CONFIG, 'rhodecode_subversion_magic_path', '/!my-magic')
94 rhodecode.CONFIG, 'rhodecode_subversion_magic_path', '/!my-magic')
91 environ = {
95 environ = {
92 'PATH_INFO': '/stub-repository/!my-magic/rev/4',
96 'PATH_INFO': '/stub-repository/!my-magic/rev/4',
93 }
97 }
94 assert vcs.is_svn(environ)
98 assert vcs.is_svn(environ)
95
99
96
100
97 class TestVCSMiddleware(object):
101 class TestVCSMiddleware(object):
98 def test_get_handler_app_retuns_svn_app_when_proxy_enabled(self):
102 def test_get_handler_app_retuns_svn_app_when_proxy_enabled(self, app):
99 environ = {
103 environ = {
100 'PATH_INFO': 'rhodecode-dev',
104 'PATH_INFO': SVN_REPO,
101 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log'
105 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log'
102 }
106 }
103 app = Mock()
107 application = Mock()
104 config = Mock()
108 config = {'appenlight': False}
105 registry = Mock()
109 registry = Mock()
106 middleware = vcs.VCSMiddleware(
110 middleware = vcs.VCSMiddleware(
107 app, config=config, appenlight_client=None, registry=registry)
111 application, config=config,
108 snv_patch = patch('rhodecode.lib.middleware.vcs.SimpleSvn')
112 appenlight_client=None, registry=registry)
109 settings_patch = patch.dict(
113 middleware.use_gzip = False
110 rhodecode.CONFIG,
111 {'rhodecode_proxy_subversion_http_requests': True})
112 with snv_patch as svn_mock, settings_patch:
113 svn_mock.return_value = None
114 middleware._get_handler_app(environ)
115
114
116 svn_mock.assert_called_once_with(app, config, registry)
115 with patch.object(SimpleSvn, '_is_svn_enabled') as mock_method:
116 mock_method.return_value = True
117 application = middleware._get_handler_app(environ)
118 assert isinstance(application, SimpleSvn)
119 assert isinstance(application._create_wsgi_app(
120 Mock(), Mock(), Mock()), SimpleSvnApp)
117
121
118 def test_get_handler_app_retuns_no_svn_app_when_proxy_disabled(self):
122 def test_get_handler_app_retuns_dummy_svn_app_when_proxy_disabled(self, app):
119 environ = {
123 environ = {
120 'PATH_INFO': 'rhodecode-dev',
124 'PATH_INFO': SVN_REPO,
121 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log'
125 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log'
122 }
126 }
123 app = Mock()
127 application = Mock()
124 config = Mock()
128 config = {'appenlight': False}
125 registry = Mock()
129 registry = Mock()
126 middleware = vcs.VCSMiddleware(
130 middleware = vcs.VCSMiddleware(
127 app, config=config, appenlight_client=None, registry=registry)
131 application, config=config,
128 snv_patch = patch('rhodecode.lib.middleware.vcs.SimpleSvn')
132 appenlight_client=None, registry=registry)
129 settings_patch = patch.dict(
133 middleware.use_gzip = False
130 rhodecode.CONFIG,
131 {'rhodecode_proxy_subversion_http_requests': False})
132 with snv_patch as svn_mock, settings_patch:
133 app = middleware._get_handler_app(environ)
134
134
135 assert svn_mock.call_count == 0
135 with patch.object(SimpleSvn, '_is_svn_enabled') as mock_method:
136 assert app is None
136 mock_method.return_value = False
137 application = middleware._get_handler_app(environ)
138 assert isinstance(application, SimpleSvn)
139 assert isinstance(application._create_wsgi_app(
140 Mock(), Mock(), Mock()), DisabledSimpleSvnApp)
@@ -1,1038 +1,1029 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
27
27
28
28
29 HOOKS_FORM_DATA = {
29 HOOKS_FORM_DATA = {
30 'hooks_changegroup_repo_size': True,
30 'hooks_changegroup_repo_size': True,
31 'hooks_changegroup_push_logger': True,
31 'hooks_changegroup_push_logger': True,
32 'hooks_outgoing_pull_logger': True
32 'hooks_outgoing_pull_logger': True
33 }
33 }
34
34
35 SVN_FORM_DATA = {
35 SVN_FORM_DATA = {
36 'new_svn_branch': 'test-branch',
36 'new_svn_branch': 'test-branch',
37 'new_svn_tag': 'test-tag'
37 'new_svn_tag': 'test-tag'
38 }
38 }
39
39
40 GENERAL_FORM_DATA = {
40 GENERAL_FORM_DATA = {
41 'rhodecode_pr_merge_enabled': True,
41 'rhodecode_pr_merge_enabled': True,
42 'rhodecode_use_outdated_comments': True,
42 'rhodecode_use_outdated_comments': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
44 }
44 }
45
45
46
46
47 class TestInheritGlobalSettingsProperty(object):
47 class TestInheritGlobalSettingsProperty(object):
48 def test_get_raises_exception_when_repository_not_specified(self):
48 def test_get_raises_exception_when_repository_not_specified(self):
49 model = VcsSettingsModel()
49 model = VcsSettingsModel()
50 with pytest.raises(Exception) as exc_info:
50 with pytest.raises(Exception) as exc_info:
51 model.inherit_global_settings
51 model.inherit_global_settings
52 assert exc_info.value.message == 'Repository is not specified'
52 assert exc_info.value.message == 'Repository is not specified'
53
53
54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
54 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
55 model = VcsSettingsModel(repo=repo_stub.repo_name)
55 model = VcsSettingsModel(repo=repo_stub.repo_name)
56 assert model.inherit_global_settings is True
56 assert model.inherit_global_settings is True
57
57
58 def test_value_is_returned(self, repo_stub, settings_util):
58 def test_value_is_returned(self, repo_stub, settings_util):
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
60 settings_util.create_repo_rhodecode_setting(
60 settings_util.create_repo_rhodecode_setting(
61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
61 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
62 assert model.inherit_global_settings is False
62 assert model.inherit_global_settings is False
63
63
64 def test_value_is_set(self, repo_stub):
64 def test_value_is_set(self, repo_stub):
65 model = VcsSettingsModel(repo=repo_stub.repo_name)
65 model = VcsSettingsModel(repo=repo_stub.repo_name)
66 model.inherit_global_settings = False
66 model.inherit_global_settings = False
67 setting = model.repo_settings.get_setting_by_name(
67 setting = model.repo_settings.get_setting_by_name(
68 VcsSettingsModel.INHERIT_SETTINGS)
68 VcsSettingsModel.INHERIT_SETTINGS)
69 try:
69 try:
70 assert setting.app_settings_type == 'bool'
70 assert setting.app_settings_type == 'bool'
71 assert setting.app_settings_value is False
71 assert setting.app_settings_value is False
72 finally:
72 finally:
73 Session().delete(setting)
73 Session().delete(setting)
74 Session().commit()
74 Session().commit()
75
75
76 def test_set_raises_exception_when_repository_not_specified(self):
76 def test_set_raises_exception_when_repository_not_specified(self):
77 model = VcsSettingsModel()
77 model = VcsSettingsModel()
78 with pytest.raises(Exception) as exc_info:
78 with pytest.raises(Exception) as exc_info:
79 model.inherit_global_settings = False
79 model.inherit_global_settings = False
80 assert exc_info.value.message == 'Repository is not specified'
80 assert exc_info.value.message == 'Repository is not specified'
81
81
82
82
83 class TestVcsSettingsModel(object):
83 class TestVcsSettingsModel(object):
84 def test_global_svn_branch_patterns(self):
84 def test_global_svn_branch_patterns(self):
85 model = VcsSettingsModel()
85 model = VcsSettingsModel()
86 expected_result = {'test': 'test'}
86 expected_result = {'test': 'test'}
87 with mock.patch.object(model, 'global_settings') as settings_mock:
87 with mock.patch.object(model, 'global_settings') as settings_mock:
88 get_settings = settings_mock.get_ui_by_section
88 get_settings = settings_mock.get_ui_by_section
89 get_settings.return_value = expected_result
89 get_settings.return_value = expected_result
90 settings_mock.return_value = expected_result
90 settings_mock.return_value = expected_result
91 result = model.get_global_svn_branch_patterns()
91 result = model.get_global_svn_branch_patterns()
92
92
93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
93 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
94 assert expected_result == result
94 assert expected_result == result
95
95
96 def test_repo_svn_branch_patterns(self):
96 def test_repo_svn_branch_patterns(self):
97 model = VcsSettingsModel()
97 model = VcsSettingsModel()
98 expected_result = {'test': 'test'}
98 expected_result = {'test': 'test'}
99 with mock.patch.object(model, 'repo_settings') as settings_mock:
99 with mock.patch.object(model, 'repo_settings') as settings_mock:
100 get_settings = settings_mock.get_ui_by_section
100 get_settings = settings_mock.get_ui_by_section
101 get_settings.return_value = expected_result
101 get_settings.return_value = expected_result
102 settings_mock.return_value = expected_result
102 settings_mock.return_value = expected_result
103 result = model.get_repo_svn_branch_patterns()
103 result = model.get_repo_svn_branch_patterns()
104
104
105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
105 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
106 assert expected_result == result
106 assert expected_result == result
107
107
108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
108 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
109 self):
109 self):
110 model = VcsSettingsModel()
110 model = VcsSettingsModel()
111 with pytest.raises(Exception) as exc_info:
111 with pytest.raises(Exception) as exc_info:
112 model.get_repo_svn_branch_patterns()
112 model.get_repo_svn_branch_patterns()
113 assert exc_info.value.message == 'Repository is not specified'
113 assert exc_info.value.message == 'Repository is not specified'
114
114
115 def test_global_svn_tag_patterns(self):
115 def test_global_svn_tag_patterns(self):
116 model = VcsSettingsModel()
116 model = VcsSettingsModel()
117 expected_result = {'test': 'test'}
117 expected_result = {'test': 'test'}
118 with mock.patch.object(model, 'global_settings') as settings_mock:
118 with mock.patch.object(model, 'global_settings') as settings_mock:
119 get_settings = settings_mock.get_ui_by_section
119 get_settings = settings_mock.get_ui_by_section
120 get_settings.return_value = expected_result
120 get_settings.return_value = expected_result
121 settings_mock.return_value = expected_result
121 settings_mock.return_value = expected_result
122 result = model.get_global_svn_tag_patterns()
122 result = model.get_global_svn_tag_patterns()
123
123
124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
124 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
125 assert expected_result == result
125 assert expected_result == result
126
126
127 def test_repo_svn_tag_patterns(self):
127 def test_repo_svn_tag_patterns(self):
128 model = VcsSettingsModel()
128 model = VcsSettingsModel()
129 expected_result = {'test': 'test'}
129 expected_result = {'test': 'test'}
130 with mock.patch.object(model, 'repo_settings') as settings_mock:
130 with mock.patch.object(model, 'repo_settings') as settings_mock:
131 get_settings = settings_mock.get_ui_by_section
131 get_settings = settings_mock.get_ui_by_section
132 get_settings.return_value = expected_result
132 get_settings.return_value = expected_result
133 settings_mock.return_value = expected_result
133 settings_mock.return_value = expected_result
134 result = model.get_repo_svn_tag_patterns()
134 result = model.get_repo_svn_tag_patterns()
135
135
136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
136 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
137 assert expected_result == result
137 assert expected_result == result
138
138
139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
139 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
140 model = VcsSettingsModel()
140 model = VcsSettingsModel()
141 with pytest.raises(Exception) as exc_info:
141 with pytest.raises(Exception) as exc_info:
142 model.get_repo_svn_tag_patterns()
142 model.get_repo_svn_tag_patterns()
143 assert exc_info.value.message == 'Repository is not specified'
143 assert exc_info.value.message == 'Repository is not specified'
144
144
145 def test_get_global_settings(self):
145 def test_get_global_settings(self):
146 expected_result = {'test': 'test'}
146 expected_result = {'test': 'test'}
147 model = VcsSettingsModel()
147 model = VcsSettingsModel()
148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
148 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
149 collect_mock.return_value = expected_result
149 collect_mock.return_value = expected_result
150 result = model.get_global_settings()
150 result = model.get_global_settings()
151
151
152 collect_mock.assert_called_once_with(global_=True)
152 collect_mock.assert_called_once_with(global_=True)
153 assert result == expected_result
153 assert result == expected_result
154
154
155 def test_get_repo_settings(self, repo_stub):
155 def test_get_repo_settings(self, repo_stub):
156 model = VcsSettingsModel(repo=repo_stub.repo_name)
156 model = VcsSettingsModel(repo=repo_stub.repo_name)
157 expected_result = {'test': 'test'}
157 expected_result = {'test': 'test'}
158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
158 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
159 collect_mock.return_value = expected_result
159 collect_mock.return_value = expected_result
160 result = model.get_repo_settings()
160 result = model.get_repo_settings()
161
161
162 collect_mock.assert_called_once_with(global_=False)
162 collect_mock.assert_called_once_with(global_=False)
163 assert result == expected_result
163 assert result == expected_result
164
164
165 @pytest.mark.parametrize('settings, global_', [
165 @pytest.mark.parametrize('settings, global_', [
166 ('global_settings', True),
166 ('global_settings', True),
167 ('repo_settings', False)
167 ('repo_settings', False)
168 ])
168 ])
169 def test_collect_all_settings(self, settings, global_):
169 def test_collect_all_settings(self, settings, global_):
170 model = VcsSettingsModel()
170 model = VcsSettingsModel()
171 result_mock = self._mock_result()
171 result_mock = self._mock_result()
172
172
173 settings_patch = mock.patch.object(model, settings)
173 settings_patch = mock.patch.object(model, settings)
174 with settings_patch as settings_mock:
174 with settings_patch as settings_mock:
175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
175 settings_mock.get_ui_by_section_and_key.return_value = result_mock
176 settings_mock.get_setting_by_name.return_value = result_mock
176 settings_mock.get_setting_by_name.return_value = result_mock
177 result = model._collect_all_settings(global_=global_)
177 result = model._collect_all_settings(global_=global_)
178
178
179 ui_settings = model.HG_SETTINGS + model.HOOKS_SETTINGS
179 ui_settings = model.HG_SETTINGS + model.HOOKS_SETTINGS
180 self._assert_get_settings_calls(
180 self._assert_get_settings_calls(
181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
181 settings_mock, ui_settings, model.GENERAL_SETTINGS)
182 self._assert_collect_all_settings_result(
182 self._assert_collect_all_settings_result(
183 ui_settings, model.GENERAL_SETTINGS, result)
183 ui_settings, model.GENERAL_SETTINGS, result)
184
184
185 @pytest.mark.parametrize('settings, global_', [
185 @pytest.mark.parametrize('settings, global_', [
186 ('global_settings', True),
186 ('global_settings', True),
187 ('repo_settings', False)
187 ('repo_settings', False)
188 ])
188 ])
189 def test_collect_all_settings_without_empty_value(self, settings, global_):
189 def test_collect_all_settings_without_empty_value(self, settings, global_):
190 model = VcsSettingsModel()
190 model = VcsSettingsModel()
191
191
192 settings_patch = mock.patch.object(model, settings)
192 settings_patch = mock.patch.object(model, settings)
193 with settings_patch as settings_mock:
193 with settings_patch as settings_mock:
194 settings_mock.get_ui_by_section_and_key.return_value = None
194 settings_mock.get_ui_by_section_and_key.return_value = None
195 settings_mock.get_setting_by_name.return_value = None
195 settings_mock.get_setting_by_name.return_value = None
196 result = model._collect_all_settings(global_=global_)
196 result = model._collect_all_settings(global_=global_)
197
197
198 assert result == {}
198 assert result == {}
199
199
200 def _mock_result(self):
200 def _mock_result(self):
201 result_mock = mock.Mock()
201 result_mock = mock.Mock()
202 result_mock.ui_value = 'ui_value'
202 result_mock.ui_value = 'ui_value'
203 result_mock.ui_active = True
203 result_mock.ui_active = True
204 result_mock.app_settings_value = 'setting_value'
204 result_mock.app_settings_value = 'setting_value'
205 return result_mock
205 return result_mock
206
206
207 def _assert_get_settings_calls(
207 def _assert_get_settings_calls(
208 self, settings_mock, ui_settings, general_settings):
208 self, settings_mock, ui_settings, general_settings):
209 assert (
209 assert (
210 settings_mock.get_ui_by_section_and_key.call_count ==
210 settings_mock.get_ui_by_section_and_key.call_count ==
211 len(ui_settings))
211 len(ui_settings))
212 assert (
212 assert (
213 settings_mock.get_setting_by_name.call_count ==
213 settings_mock.get_setting_by_name.call_count ==
214 len(general_settings))
214 len(general_settings))
215
215
216 for section, key in ui_settings:
216 for section, key in ui_settings:
217 expected_call = mock.call(section, key)
217 expected_call = mock.call(section, key)
218 assert (
218 assert (
219 expected_call in
219 expected_call in
220 settings_mock.get_ui_by_section_and_key.call_args_list)
220 settings_mock.get_ui_by_section_and_key.call_args_list)
221
221
222 for name in general_settings:
222 for name in general_settings:
223 expected_call = mock.call(name)
223 expected_call = mock.call(name)
224 assert (
224 assert (
225 expected_call in
225 expected_call in
226 settings_mock.get_setting_by_name.call_args_list)
226 settings_mock.get_setting_by_name.call_args_list)
227
227
228 def _assert_collect_all_settings_result(
228 def _assert_collect_all_settings_result(
229 self, ui_settings, general_settings, result):
229 self, ui_settings, general_settings, result):
230 expected_result = {}
230 expected_result = {}
231 for section, key in ui_settings:
231 for section, key in ui_settings:
232 key = '{}_{}'.format(section, key.replace('.', '_'))
232 key = '{}_{}'.format(section, key.replace('.', '_'))
233 value = True if section in ('extensions', 'hooks') else 'ui_value'
233 value = True if section in ('extensions', 'hooks') else 'ui_value'
234 expected_result[key] = value
234 expected_result[key] = value
235
235
236 for name in general_settings:
236 for name in general_settings:
237 key = 'rhodecode_' + name
237 key = 'rhodecode_' + name
238 expected_result[key] = 'setting_value'
238 expected_result[key] = 'setting_value'
239
239
240 assert expected_result == result
240 assert expected_result == result
241
241
242
242
243 class TestCreateOrUpdateRepoHookSettings(object):
243 class TestCreateOrUpdateRepoHookSettings(object):
244 def test_create_when_no_repo_object_found(self, repo_stub):
244 def test_create_when_no_repo_object_found(self, repo_stub):
245 model = VcsSettingsModel(repo=repo_stub.repo_name)
245 model = VcsSettingsModel(repo=repo_stub.repo_name)
246
246
247 self._create_settings(model, HOOKS_FORM_DATA)
247 self._create_settings(model, HOOKS_FORM_DATA)
248
248
249 cleanup = []
249 cleanup = []
250 try:
250 try:
251 for section, key in model.HOOKS_SETTINGS:
251 for section, key in model.HOOKS_SETTINGS:
252 ui = model.repo_settings.get_ui_by_section_and_key(
252 ui = model.repo_settings.get_ui_by_section_and_key(
253 section, key)
253 section, key)
254 assert ui.ui_active is True
254 assert ui.ui_active is True
255 cleanup.append(ui)
255 cleanup.append(ui)
256 finally:
256 finally:
257 for ui in cleanup:
257 for ui in cleanup:
258 Session().delete(ui)
258 Session().delete(ui)
259 Session().commit()
259 Session().commit()
260
260
261 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
261 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
262 model = VcsSettingsModel(repo=repo_stub.repo_name)
262 model = VcsSettingsModel(repo=repo_stub.repo_name)
263
263
264 deleted_key = 'hooks_changegroup_repo_size'
264 deleted_key = 'hooks_changegroup_repo_size'
265 data = HOOKS_FORM_DATA.copy()
265 data = HOOKS_FORM_DATA.copy()
266 data.pop(deleted_key)
266 data.pop(deleted_key)
267
267
268 with pytest.raises(ValueError) as exc_info:
268 with pytest.raises(ValueError) as exc_info:
269 model.create_or_update_repo_hook_settings(data)
269 model.create_or_update_repo_hook_settings(data)
270 assert (
270 assert (
271 exc_info.value.message ==
271 exc_info.value.message ==
272 'The given data does not contain {} key'.format(deleted_key))
272 'The given data does not contain {} key'.format(deleted_key))
273
273
274 def test_update_when_repo_object_found(self, repo_stub, settings_util):
274 def test_update_when_repo_object_found(self, repo_stub, settings_util):
275 model = VcsSettingsModel(repo=repo_stub.repo_name)
275 model = VcsSettingsModel(repo=repo_stub.repo_name)
276 for section, key in model.HOOKS_SETTINGS:
276 for section, key in model.HOOKS_SETTINGS:
277 settings_util.create_repo_rhodecode_ui(
277 settings_util.create_repo_rhodecode_ui(
278 repo_stub, section, None, key=key, active=False)
278 repo_stub, section, None, key=key, active=False)
279 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
279 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
280 for section, key in model.HOOKS_SETTINGS:
280 for section, key in model.HOOKS_SETTINGS:
281 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
281 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
282 assert ui.ui_active is True
282 assert ui.ui_active is True
283
283
284 def _create_settings(self, model, data):
284 def _create_settings(self, model, data):
285 global_patch = mock.patch.object(model, 'global_settings')
285 global_patch = mock.patch.object(model, 'global_settings')
286 global_setting = mock.Mock()
286 global_setting = mock.Mock()
287 global_setting.ui_value = 'Test value'
287 global_setting.ui_value = 'Test value'
288 with global_patch as global_mock:
288 with global_patch as global_mock:
289 global_mock.get_ui_by_section_and_key.return_value = global_setting
289 global_mock.get_ui_by_section_and_key.return_value = global_setting
290 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
290 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
291
291
292
292
293 class TestUpdateGlobalHookSettings(object):
293 class TestUpdateGlobalHookSettings(object):
294 def test_update_raises_exception_when_data_incomplete(self):
294 def test_update_raises_exception_when_data_incomplete(self):
295 model = VcsSettingsModel()
295 model = VcsSettingsModel()
296
296
297 deleted_key = 'hooks_changegroup_repo_size'
297 deleted_key = 'hooks_changegroup_repo_size'
298 data = HOOKS_FORM_DATA.copy()
298 data = HOOKS_FORM_DATA.copy()
299 data.pop(deleted_key)
299 data.pop(deleted_key)
300
300
301 with pytest.raises(ValueError) as exc_info:
301 with pytest.raises(ValueError) as exc_info:
302 model.update_global_hook_settings(data)
302 model.update_global_hook_settings(data)
303 assert (
303 assert (
304 exc_info.value.message ==
304 exc_info.value.message ==
305 'The given data does not contain {} key'.format(deleted_key))
305 'The given data does not contain {} key'.format(deleted_key))
306
306
307 def test_update_global_hook_settings(self, settings_util):
307 def test_update_global_hook_settings(self, settings_util):
308 model = VcsSettingsModel()
308 model = VcsSettingsModel()
309 setting_mock = mock.MagicMock()
309 setting_mock = mock.MagicMock()
310 setting_mock.ui_active = False
310 setting_mock.ui_active = False
311 get_settings_patcher = mock.patch.object(
311 get_settings_patcher = mock.patch.object(
312 model.global_settings, 'get_ui_by_section_and_key',
312 model.global_settings, 'get_ui_by_section_and_key',
313 return_value=setting_mock)
313 return_value=setting_mock)
314 session_patcher = mock.patch('rhodecode.model.settings.Session')
314 session_patcher = mock.patch('rhodecode.model.settings.Session')
315 with get_settings_patcher as get_settings_mock, session_patcher:
315 with get_settings_patcher as get_settings_mock, session_patcher:
316 model.update_global_hook_settings(HOOKS_FORM_DATA)
316 model.update_global_hook_settings(HOOKS_FORM_DATA)
317 assert setting_mock.ui_active is True
317 assert setting_mock.ui_active is True
318 assert get_settings_mock.call_count == 3
318 assert get_settings_mock.call_count == 3
319
319
320
320
321 class TestCreateOrUpdateRepoGeneralSettings(object):
321 class TestCreateOrUpdateRepoGeneralSettings(object):
322 def test_calls_create_or_update_general_settings(self, repo_stub):
322 def test_calls_create_or_update_general_settings(self, repo_stub):
323 model = VcsSettingsModel(repo=repo_stub.repo_name)
323 model = VcsSettingsModel(repo=repo_stub.repo_name)
324 create_patch = mock.patch.object(
324 create_patch = mock.patch.object(
325 model, '_create_or_update_general_settings')
325 model, '_create_or_update_general_settings')
326 with create_patch as create_mock:
326 with create_patch as create_mock:
327 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
327 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
328 create_mock.assert_called_once_with(
328 create_mock.assert_called_once_with(
329 model.repo_settings, GENERAL_FORM_DATA)
329 model.repo_settings, GENERAL_FORM_DATA)
330
330
331 def test_raises_exception_when_repository_is_not_specified(self):
331 def test_raises_exception_when_repository_is_not_specified(self):
332 model = VcsSettingsModel()
332 model = VcsSettingsModel()
333 with pytest.raises(Exception) as exc_info:
333 with pytest.raises(Exception) as exc_info:
334 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
334 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
335 assert exc_info.value.message == 'Repository is not specified'
335 assert exc_info.value.message == 'Repository is not specified'
336
336
337
337
338 class TestCreateOrUpdatGlobalGeneralSettings(object):
338 class TestCreateOrUpdatGlobalGeneralSettings(object):
339 def test_calls_create_or_update_general_settings(self):
339 def test_calls_create_or_update_general_settings(self):
340 model = VcsSettingsModel()
340 model = VcsSettingsModel()
341 create_patch = mock.patch.object(
341 create_patch = mock.patch.object(
342 model, '_create_or_update_general_settings')
342 model, '_create_or_update_general_settings')
343 with create_patch as create_mock:
343 with create_patch as create_mock:
344 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
344 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
345 create_mock.assert_called_once_with(
345 create_mock.assert_called_once_with(
346 model.global_settings, GENERAL_FORM_DATA)
346 model.global_settings, GENERAL_FORM_DATA)
347
347
348
348
349 class TestCreateOrUpdateGeneralSettings(object):
349 class TestCreateOrUpdateGeneralSettings(object):
350 def test_create_when_no_repo_settings_found(self, repo_stub):
350 def test_create_when_no_repo_settings_found(self, repo_stub):
351 model = VcsSettingsModel(repo=repo_stub.repo_name)
351 model = VcsSettingsModel(repo=repo_stub.repo_name)
352 model._create_or_update_general_settings(
352 model._create_or_update_general_settings(
353 model.repo_settings, GENERAL_FORM_DATA)
353 model.repo_settings, GENERAL_FORM_DATA)
354
354
355 cleanup = []
355 cleanup = []
356 try:
356 try:
357 for name in model.GENERAL_SETTINGS:
357 for name in model.GENERAL_SETTINGS:
358 setting = model.repo_settings.get_setting_by_name(name)
358 setting = model.repo_settings.get_setting_by_name(name)
359 assert setting.app_settings_value is True
359 assert setting.app_settings_value is True
360 cleanup.append(setting)
360 cleanup.append(setting)
361 finally:
361 finally:
362 for setting in cleanup:
362 for setting in cleanup:
363 Session().delete(setting)
363 Session().delete(setting)
364 Session().commit()
364 Session().commit()
365
365
366 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
366 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
367 model = VcsSettingsModel(repo=repo_stub.repo_name)
367 model = VcsSettingsModel(repo=repo_stub.repo_name)
368
368
369 deleted_key = 'rhodecode_pr_merge_enabled'
369 deleted_key = 'rhodecode_pr_merge_enabled'
370 data = GENERAL_FORM_DATA.copy()
370 data = GENERAL_FORM_DATA.copy()
371 data.pop(deleted_key)
371 data.pop(deleted_key)
372
372
373 with pytest.raises(ValueError) as exc_info:
373 with pytest.raises(ValueError) as exc_info:
374 model._create_or_update_general_settings(model.repo_settings, data)
374 model._create_or_update_general_settings(model.repo_settings, data)
375 assert (
375 assert (
376 exc_info.value.message ==
376 exc_info.value.message ==
377 'The given data does not contain {} key'.format(deleted_key))
377 'The given data does not contain {} key'.format(deleted_key))
378
378
379 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
379 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
380 model = VcsSettingsModel(repo=repo_stub.repo_name)
380 model = VcsSettingsModel(repo=repo_stub.repo_name)
381 for name in model.GENERAL_SETTINGS:
381 for name in model.GENERAL_SETTINGS:
382 settings_util.create_repo_rhodecode_setting(
382 settings_util.create_repo_rhodecode_setting(
383 repo_stub, name, False, 'bool')
383 repo_stub, name, False, 'bool')
384
384
385 model._create_or_update_general_settings(
385 model._create_or_update_general_settings(
386 model.repo_settings, GENERAL_FORM_DATA)
386 model.repo_settings, GENERAL_FORM_DATA)
387
387
388 for name in model.GENERAL_SETTINGS:
388 for name in model.GENERAL_SETTINGS:
389 setting = model.repo_settings.get_setting_by_name(name)
389 setting = model.repo_settings.get_setting_by_name(name)
390 assert setting.app_settings_value is True
390 assert setting.app_settings_value is True
391
391
392
392
393 class TestCreateRepoSvnSettings(object):
393 class TestCreateRepoSvnSettings(object):
394 def test_calls_create_svn_settings(self, repo_stub):
394 def test_calls_create_svn_settings(self, repo_stub):
395 model = VcsSettingsModel(repo=repo_stub.repo_name)
395 model = VcsSettingsModel(repo=repo_stub.repo_name)
396 with mock.patch.object(model, '_create_svn_settings') as create_mock:
396 with mock.patch.object(model, '_create_svn_settings') as create_mock:
397 model.create_repo_svn_settings(SVN_FORM_DATA)
397 model.create_repo_svn_settings(SVN_FORM_DATA)
398 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
398 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
399
399
400 def test_raises_exception_when_repository_is_not_specified(self):
400 def test_raises_exception_when_repository_is_not_specified(self):
401 model = VcsSettingsModel()
401 model = VcsSettingsModel()
402 with pytest.raises(Exception) as exc_info:
402 with pytest.raises(Exception) as exc_info:
403 model.create_repo_svn_settings(SVN_FORM_DATA)
403 model.create_repo_svn_settings(SVN_FORM_DATA)
404 assert exc_info.value.message == 'Repository is not specified'
404 assert exc_info.value.message == 'Repository is not specified'
405
405
406
406
407 class TestCreateGlobalSvnSettings(object):
408 def test_calls_create_svn_settings(self):
409 model = VcsSettingsModel()
410 with mock.patch.object(model, '_create_svn_settings') as create_mock:
411 model.create_global_svn_settings(SVN_FORM_DATA)
412 create_mock.assert_called_once_with(
413 model.global_settings, SVN_FORM_DATA)
414
415
416 class TestCreateSvnSettings(object):
407 class TestCreateSvnSettings(object):
417 def test_create(self, repo_stub):
408 def test_create(self, repo_stub):
418 model = VcsSettingsModel(repo=repo_stub.repo_name)
409 model = VcsSettingsModel(repo=repo_stub.repo_name)
419 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
410 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
420 Session().commit()
411 Session().commit()
421
412
422 branch_ui = model.repo_settings.get_ui_by_section(
413 branch_ui = model.repo_settings.get_ui_by_section(
423 model.SVN_BRANCH_SECTION)
414 model.SVN_BRANCH_SECTION)
424 tag_ui = model.repo_settings.get_ui_by_section(
415 tag_ui = model.repo_settings.get_ui_by_section(
425 model.SVN_TAG_SECTION)
416 model.SVN_TAG_SECTION)
426
417
427 try:
418 try:
428 assert len(branch_ui) == 1
419 assert len(branch_ui) == 1
429 assert len(tag_ui) == 1
420 assert len(tag_ui) == 1
430 finally:
421 finally:
431 Session().delete(branch_ui[0])
422 Session().delete(branch_ui[0])
432 Session().delete(tag_ui[0])
423 Session().delete(tag_ui[0])
433 Session().commit()
424 Session().commit()
434
425
435 def test_create_tag(self, repo_stub):
426 def test_create_tag(self, repo_stub):
436 model = VcsSettingsModel(repo=repo_stub.repo_name)
427 model = VcsSettingsModel(repo=repo_stub.repo_name)
437 data = SVN_FORM_DATA.copy()
428 data = SVN_FORM_DATA.copy()
438 data.pop('new_svn_branch')
429 data.pop('new_svn_branch')
439 model._create_svn_settings(model.repo_settings, data)
430 model._create_svn_settings(model.repo_settings, data)
440 Session().commit()
431 Session().commit()
441
432
442 branch_ui = model.repo_settings.get_ui_by_section(
433 branch_ui = model.repo_settings.get_ui_by_section(
443 model.SVN_BRANCH_SECTION)
434 model.SVN_BRANCH_SECTION)
444 tag_ui = model.repo_settings.get_ui_by_section(
435 tag_ui = model.repo_settings.get_ui_by_section(
445 model.SVN_TAG_SECTION)
436 model.SVN_TAG_SECTION)
446
437
447 try:
438 try:
448 assert len(branch_ui) == 0
439 assert len(branch_ui) == 0
449 assert len(tag_ui) == 1
440 assert len(tag_ui) == 1
450 finally:
441 finally:
451 Session().delete(tag_ui[0])
442 Session().delete(tag_ui[0])
452 Session().commit()
443 Session().commit()
453
444
454 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
445 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
455 model = VcsSettingsModel(repo=repo_stub.repo_name)
446 model = VcsSettingsModel(repo=repo_stub.repo_name)
456 model._create_svn_settings(model.repo_settings, {})
447 model._create_svn_settings(model.repo_settings, {})
457 Session().commit()
448 Session().commit()
458
449
459 branch_ui = model.repo_settings.get_ui_by_section(
450 branch_ui = model.repo_settings.get_ui_by_section(
460 model.SVN_BRANCH_SECTION)
451 model.SVN_BRANCH_SECTION)
461 tag_ui = model.repo_settings.get_ui_by_section(
452 tag_ui = model.repo_settings.get_ui_by_section(
462 model.SVN_TAG_SECTION)
453 model.SVN_TAG_SECTION)
463
454
464 assert len(branch_ui) == 0
455 assert len(branch_ui) == 0
465 assert len(tag_ui) == 0
456 assert len(tag_ui) == 0
466
457
467 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
458 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
468 model = VcsSettingsModel(repo=repo_stub.repo_name)
459 model = VcsSettingsModel(repo=repo_stub.repo_name)
469 data = {
460 data = {
470 'new_svn_branch': '',
461 'new_svn_branch': '',
471 'new_svn_tag': ''
462 'new_svn_tag': ''
472 }
463 }
473 model._create_svn_settings(model.repo_settings, data)
464 model._create_svn_settings(model.repo_settings, data)
474 Session().commit()
465 Session().commit()
475
466
476 branch_ui = model.repo_settings.get_ui_by_section(
467 branch_ui = model.repo_settings.get_ui_by_section(
477 model.SVN_BRANCH_SECTION)
468 model.SVN_BRANCH_SECTION)
478 tag_ui = model.repo_settings.get_ui_by_section(
469 tag_ui = model.repo_settings.get_ui_by_section(
479 model.SVN_TAG_SECTION)
470 model.SVN_TAG_SECTION)
480
471
481 assert len(branch_ui) == 0
472 assert len(branch_ui) == 0
482 assert len(tag_ui) == 0
473 assert len(tag_ui) == 0
483
474
484
475
485 class TestCreateOrUpdateUi(object):
476 class TestCreateOrUpdateUi(object):
486 def test_create(self, repo_stub):
477 def test_create(self, repo_stub):
487 model = VcsSettingsModel(repo=repo_stub.repo_name)
478 model = VcsSettingsModel(repo=repo_stub.repo_name)
488 model._create_or_update_ui(
479 model._create_or_update_ui(
489 model.repo_settings, 'test-section', 'test-key', active=False,
480 model.repo_settings, 'test-section', 'test-key', active=False,
490 value='False')
481 value='False')
491 Session().commit()
482 Session().commit()
492
483
493 created_ui = model.repo_settings.get_ui_by_section_and_key(
484 created_ui = model.repo_settings.get_ui_by_section_and_key(
494 'test-section', 'test-key')
485 'test-section', 'test-key')
495
486
496 try:
487 try:
497 assert created_ui.ui_active is False
488 assert created_ui.ui_active is False
498 assert str2bool(created_ui.ui_value) is False
489 assert str2bool(created_ui.ui_value) is False
499 finally:
490 finally:
500 Session().delete(created_ui)
491 Session().delete(created_ui)
501 Session().commit()
492 Session().commit()
502
493
503 def test_update(self, repo_stub, settings_util):
494 def test_update(self, repo_stub, settings_util):
504 model = VcsSettingsModel(repo=repo_stub.repo_name)
495 model = VcsSettingsModel(repo=repo_stub.repo_name)
505
496
506 largefiles, phases = model.HG_SETTINGS
497 largefiles, phases = model.HG_SETTINGS
507 section = 'test-section'
498 section = 'test-section'
508 key = 'test-key'
499 key = 'test-key'
509 settings_util.create_repo_rhodecode_ui(
500 settings_util.create_repo_rhodecode_ui(
510 repo_stub, section, 'True', key=key, active=True)
501 repo_stub, section, 'True', key=key, active=True)
511
502
512 model._create_or_update_ui(
503 model._create_or_update_ui(
513 model.repo_settings, section, key, active=False, value='False')
504 model.repo_settings, section, key, active=False, value='False')
514 Session().commit()
505 Session().commit()
515
506
516 created_ui = model.repo_settings.get_ui_by_section_and_key(
507 created_ui = model.repo_settings.get_ui_by_section_and_key(
517 section, key)
508 section, key)
518 assert created_ui.ui_active is False
509 assert created_ui.ui_active is False
519 assert str2bool(created_ui.ui_value) is False
510 assert str2bool(created_ui.ui_value) is False
520
511
521
512
522 class TestCreateOrUpdateRepoHgSettings(object):
513 class TestCreateOrUpdateRepoHgSettings(object):
523 FORM_DATA = {
514 FORM_DATA = {
524 'extensions_largefiles': False,
515 'extensions_largefiles': False,
525 'phases_publish': False
516 'phases_publish': False
526 }
517 }
527
518
528 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
519 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
529 model = VcsSettingsModel(repo=repo_stub.repo_name)
520 model = VcsSettingsModel(repo=repo_stub.repo_name)
530 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
521 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
531 model.create_or_update_repo_hg_settings(self.FORM_DATA)
522 model.create_or_update_repo_hg_settings(self.FORM_DATA)
532 expected_calls = [
523 expected_calls = [
533 mock.call(model.repo_settings, 'extensions', 'largefiles',
524 mock.call(model.repo_settings, 'extensions', 'largefiles',
534 active=False, value=''),
525 active=False, value=''),
535 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
526 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
536 ]
527 ]
537 assert expected_calls == create_mock.call_args_list
528 assert expected_calls == create_mock.call_args_list
538
529
539 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
530 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
540 def test_key_is_not_found(self, repo_stub, field_to_remove):
531 def test_key_is_not_found(self, repo_stub, field_to_remove):
541 model = VcsSettingsModel(repo=repo_stub.repo_name)
532 model = VcsSettingsModel(repo=repo_stub.repo_name)
542 data = self.FORM_DATA.copy()
533 data = self.FORM_DATA.copy()
543 data.pop(field_to_remove)
534 data.pop(field_to_remove)
544 with pytest.raises(ValueError) as exc_info:
535 with pytest.raises(ValueError) as exc_info:
545 model.create_or_update_repo_hg_settings(data)
536 model.create_or_update_repo_hg_settings(data)
546 expected_message = 'The given data does not contain {} key'.format(
537 expected_message = 'The given data does not contain {} key'.format(
547 field_to_remove)
538 field_to_remove)
548 assert exc_info.value.message == expected_message
539 assert exc_info.value.message == expected_message
549
540
550 def test_create_raises_exception_when_repository_not_specified(self):
541 def test_create_raises_exception_when_repository_not_specified(self):
551 model = VcsSettingsModel()
542 model = VcsSettingsModel()
552 with pytest.raises(Exception) as exc_info:
543 with pytest.raises(Exception) as exc_info:
553 model.create_or_update_repo_hg_settings(self.FORM_DATA)
544 model.create_or_update_repo_hg_settings(self.FORM_DATA)
554 assert exc_info.value.message == 'Repository is not specified'
545 assert exc_info.value.message == 'Repository is not specified'
555
546
556
547
557 class TestUpdateGlobalSslSetting(object):
548 class TestUpdateGlobalSslSetting(object):
558 def test_updates_global_hg_settings(self):
549 def test_updates_global_hg_settings(self):
559 model = VcsSettingsModel()
550 model = VcsSettingsModel()
560 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
551 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
561 model.update_global_ssl_setting('False')
552 model.update_global_ssl_setting('False')
562 create_mock.assert_called_once_with(
553 create_mock.assert_called_once_with(
563 model.global_settings, 'web', 'push_ssl', value='False')
554 model.global_settings, 'web', 'push_ssl', value='False')
564
555
565
556
566 class TestUpdateGlobalPathSetting(object):
557 class TestUpdateGlobalPathSetting(object):
567 def test_updates_global_path_settings(self):
558 def test_updates_global_path_settings(self):
568 model = VcsSettingsModel()
559 model = VcsSettingsModel()
569 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
560 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
570 model.update_global_path_setting('False')
561 model.update_global_path_setting('False')
571 create_mock.assert_called_once_with(
562 create_mock.assert_called_once_with(
572 model.global_settings, 'paths', '/', value='False')
563 model.global_settings, 'paths', '/', value='False')
573
564
574
565
575 class TestCreateOrUpdateGlobalHgSettings(object):
566 class TestCreateOrUpdateGlobalHgSettings(object):
576 FORM_DATA = {
567 FORM_DATA = {
577 'extensions_largefiles': False,
568 'extensions_largefiles': False,
578 'phases_publish': False,
569 'phases_publish': False,
579 'extensions_hgsubversion': False
570 'extensions_hgsubversion': False
580 }
571 }
581
572
582 def test_creates_repo_hg_settings_when_data_is_correct(self):
573 def test_creates_repo_hg_settings_when_data_is_correct(self):
583 model = VcsSettingsModel()
574 model = VcsSettingsModel()
584 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
575 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
585 model.create_or_update_global_hg_settings(self.FORM_DATA)
576 model.create_or_update_global_hg_settings(self.FORM_DATA)
586 expected_calls = [
577 expected_calls = [
587 mock.call(model.global_settings, 'extensions', 'largefiles',
578 mock.call(model.global_settings, 'extensions', 'largefiles',
588 active=False, value=''),
579 active=False, value=''),
589 mock.call(model.global_settings, 'phases', 'publish',
580 mock.call(model.global_settings, 'phases', 'publish',
590 value='False'),
581 value='False'),
591 mock.call(model.global_settings, 'extensions', 'hgsubversion',
582 mock.call(model.global_settings, 'extensions', 'hgsubversion',
592 active=False)
583 active=False)
593 ]
584 ]
594 assert expected_calls == create_mock.call_args_list
585 assert expected_calls == create_mock.call_args_list
595
586
596 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
587 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
597 def test_key_is_not_found(self, repo_stub, field_to_remove):
588 def test_key_is_not_found(self, repo_stub, field_to_remove):
598 model = VcsSettingsModel(repo=repo_stub.repo_name)
589 model = VcsSettingsModel(repo=repo_stub.repo_name)
599 data = self.FORM_DATA.copy()
590 data = self.FORM_DATA.copy()
600 data.pop(field_to_remove)
591 data.pop(field_to_remove)
601 with pytest.raises(Exception) as exc_info:
592 with pytest.raises(Exception) as exc_info:
602 model.create_or_update_global_hg_settings(data)
593 model.create_or_update_global_hg_settings(data)
603 expected_message = 'The given data does not contain {} key'.format(
594 expected_message = 'The given data does not contain {} key'.format(
604 field_to_remove)
595 field_to_remove)
605 assert exc_info.value.message == expected_message
596 assert exc_info.value.message == expected_message
606
597
607
598
608 class TestDeleteRepoSvnPattern(object):
599 class TestDeleteRepoSvnPattern(object):
609 def test_success_when_repo_is_set(self, backend_svn):
600 def test_success_when_repo_is_set(self, backend_svn):
610 repo_name = backend_svn.repo_name
601 repo_name = backend_svn.repo_name
611 model = VcsSettingsModel(repo=repo_name)
602 model = VcsSettingsModel(repo=repo_name)
612 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
603 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
613 with delete_ui_patch as delete_ui_mock:
604 with delete_ui_patch as delete_ui_mock:
614 model.delete_repo_svn_pattern(123)
605 model.delete_repo_svn_pattern(123)
615 delete_ui_mock.assert_called_once_with(123)
606 delete_ui_mock.assert_called_once_with(123)
616
607
617 def test_raises_exception_when_repository_is_not_specified(self):
608 def test_raises_exception_when_repository_is_not_specified(self):
618 model = VcsSettingsModel()
609 model = VcsSettingsModel()
619 with pytest.raises(Exception) as exc_info:
610 with pytest.raises(Exception) as exc_info:
620 model.delete_repo_svn_pattern(123)
611 model.delete_repo_svn_pattern(123)
621 assert exc_info.value.message == 'Repository is not specified'
612 assert exc_info.value.message == 'Repository is not specified'
622
613
623
614
624 class TestDeleteGlobalSvnPattern(object):
615 class TestDeleteGlobalSvnPattern(object):
625 def test_delete_global_svn_pattern_calls_delete_ui(self):
616 def test_delete_global_svn_pattern_calls_delete_ui(self):
626 model = VcsSettingsModel()
617 model = VcsSettingsModel()
627 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
618 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
628 with delete_ui_patch as delete_ui_mock:
619 with delete_ui_patch as delete_ui_mock:
629 model.delete_global_svn_pattern(123)
620 model.delete_global_svn_pattern(123)
630 delete_ui_mock.assert_called_once_with(123)
621 delete_ui_mock.assert_called_once_with(123)
631
622
632
623
633 class TestFilterUiSettings(object):
624 class TestFilterUiSettings(object):
634 def test_settings_are_filtered(self):
625 def test_settings_are_filtered(self):
635 model = VcsSettingsModel()
626 model = VcsSettingsModel()
636 repo_settings = [
627 repo_settings = [
637 UiSetting('extensions', 'largefiles', '', True),
628 UiSetting('extensions', 'largefiles', '', True),
638 UiSetting('phases', 'publish', 'True', True),
629 UiSetting('phases', 'publish', 'True', True),
639 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
630 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
640 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
631 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
641 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
632 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
642 UiSetting(
633 UiSetting(
643 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
634 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
644 'test_branch', True),
635 'test_branch', True),
645 UiSetting(
636 UiSetting(
646 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
637 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
647 'test_tag', True),
638 'test_tag', True),
648 ]
639 ]
649 non_repo_settings = [
640 non_repo_settings = [
650 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
641 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
651 UiSetting('hooks', 'test2', 'hook', True),
642 UiSetting('hooks', 'test2', 'hook', True),
652 UiSetting(
643 UiSetting(
653 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
644 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
654 'test_tag', True),
645 'test_tag', True),
655 ]
646 ]
656 settings = repo_settings + non_repo_settings
647 settings = repo_settings + non_repo_settings
657 filtered_settings = model._filter_ui_settings(settings)
648 filtered_settings = model._filter_ui_settings(settings)
658 assert sorted(filtered_settings) == sorted(repo_settings)
649 assert sorted(filtered_settings) == sorted(repo_settings)
659
650
660
651
661 class TestFilterGeneralSettings(object):
652 class TestFilterGeneralSettings(object):
662 def test_settings_are_filtered(self):
653 def test_settings_are_filtered(self):
663 model = VcsSettingsModel()
654 model = VcsSettingsModel()
664 settings = {
655 settings = {
665 'rhodecode_abcde': 'value1',
656 'rhodecode_abcde': 'value1',
666 'rhodecode_vwxyz': 'value2',
657 'rhodecode_vwxyz': 'value2',
667 }
658 }
668 general_settings = {
659 general_settings = {
669 'rhodecode_{}'.format(key): 'value'
660 'rhodecode_{}'.format(key): 'value'
670 for key in VcsSettingsModel.GENERAL_SETTINGS
661 for key in VcsSettingsModel.GENERAL_SETTINGS
671 }
662 }
672 settings.update(general_settings)
663 settings.update(general_settings)
673
664
674 filtered_settings = model._filter_general_settings(general_settings)
665 filtered_settings = model._filter_general_settings(general_settings)
675 assert sorted(filtered_settings) == sorted(general_settings)
666 assert sorted(filtered_settings) == sorted(general_settings)
676
667
677
668
678 class TestGetRepoUiSettings(object):
669 class TestGetRepoUiSettings(object):
679 def test_global_uis_are_returned_when_no_repo_uis_found(
670 def test_global_uis_are_returned_when_no_repo_uis_found(
680 self, repo_stub):
671 self, repo_stub):
681 model = VcsSettingsModel(repo=repo_stub.repo_name)
672 model = VcsSettingsModel(repo=repo_stub.repo_name)
682 result = model.get_repo_ui_settings()
673 result = model.get_repo_ui_settings()
683 svn_sections = (
674 svn_sections = (
684 VcsSettingsModel.SVN_TAG_SECTION,
675 VcsSettingsModel.SVN_TAG_SECTION,
685 VcsSettingsModel.SVN_BRANCH_SECTION)
676 VcsSettingsModel.SVN_BRANCH_SECTION)
686 expected_result = [
677 expected_result = [
687 s for s in model.global_settings.get_ui()
678 s for s in model.global_settings.get_ui()
688 if s.section not in svn_sections]
679 if s.section not in svn_sections]
689 assert sorted(result) == sorted(expected_result)
680 assert sorted(result) == sorted(expected_result)
690
681
691 def test_repo_uis_are_overriding_global_uis(
682 def test_repo_uis_are_overriding_global_uis(
692 self, repo_stub, settings_util):
683 self, repo_stub, settings_util):
693 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
684 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
694 settings_util.create_repo_rhodecode_ui(
685 settings_util.create_repo_rhodecode_ui(
695 repo_stub, section, 'repo', key=key, active=False)
686 repo_stub, section, 'repo', key=key, active=False)
696 model = VcsSettingsModel(repo=repo_stub.repo_name)
687 model = VcsSettingsModel(repo=repo_stub.repo_name)
697 result = model.get_repo_ui_settings()
688 result = model.get_repo_ui_settings()
698 for setting in result:
689 for setting in result:
699 locator = (setting.section, setting.key)
690 locator = (setting.section, setting.key)
700 if locator in VcsSettingsModel.HOOKS_SETTINGS:
691 if locator in VcsSettingsModel.HOOKS_SETTINGS:
701 assert setting.value == 'repo'
692 assert setting.value == 'repo'
702
693
703 assert setting.active is False
694 assert setting.active is False
704
695
705 def test_global_svn_patterns_are_not_in_list(
696 def test_global_svn_patterns_are_not_in_list(
706 self, repo_stub, settings_util):
697 self, repo_stub, settings_util):
707 svn_sections = (
698 svn_sections = (
708 VcsSettingsModel.SVN_TAG_SECTION,
699 VcsSettingsModel.SVN_TAG_SECTION,
709 VcsSettingsModel.SVN_BRANCH_SECTION)
700 VcsSettingsModel.SVN_BRANCH_SECTION)
710 for section in svn_sections:
701 for section in svn_sections:
711 settings_util.create_rhodecode_ui(
702 settings_util.create_rhodecode_ui(
712 section, 'repo', key='deadbeef' + section, active=False)
703 section, 'repo', key='deadbeef' + section, active=False)
713 model = VcsSettingsModel(repo=repo_stub.repo_name)
704 model = VcsSettingsModel(repo=repo_stub.repo_name)
714 result = model.get_repo_ui_settings()
705 result = model.get_repo_ui_settings()
715 for setting in result:
706 for setting in result:
716 assert setting.section not in svn_sections
707 assert setting.section not in svn_sections
717
708
718 def test_repo_uis_filtered_by_section_are_returned(
709 def test_repo_uis_filtered_by_section_are_returned(
719 self, repo_stub, settings_util):
710 self, repo_stub, settings_util):
720 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
711 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
721 settings_util.create_repo_rhodecode_ui(
712 settings_util.create_repo_rhodecode_ui(
722 repo_stub, section, 'repo', key=key, active=False)
713 repo_stub, section, 'repo', key=key, active=False)
723 model = VcsSettingsModel(repo=repo_stub.repo_name)
714 model = VcsSettingsModel(repo=repo_stub.repo_name)
724 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
715 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
725 result = model.get_repo_ui_settings(section=section)
716 result = model.get_repo_ui_settings(section=section)
726 for setting in result:
717 for setting in result:
727 assert setting.section == section
718 assert setting.section == section
728
719
729 def test_repo_uis_filtered_by_key_are_returned(
720 def test_repo_uis_filtered_by_key_are_returned(
730 self, repo_stub, settings_util):
721 self, repo_stub, settings_util):
731 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
722 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
732 settings_util.create_repo_rhodecode_ui(
723 settings_util.create_repo_rhodecode_ui(
733 repo_stub, section, 'repo', key=key, active=False)
724 repo_stub, section, 'repo', key=key, active=False)
734 model = VcsSettingsModel(repo=repo_stub.repo_name)
725 model = VcsSettingsModel(repo=repo_stub.repo_name)
735 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
726 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
736 result = model.get_repo_ui_settings(key=key)
727 result = model.get_repo_ui_settings(key=key)
737 for setting in result:
728 for setting in result:
738 assert setting.key == key
729 assert setting.key == key
739
730
740 def test_raises_exception_when_repository_is_not_specified(self):
731 def test_raises_exception_when_repository_is_not_specified(self):
741 model = VcsSettingsModel()
732 model = VcsSettingsModel()
742 with pytest.raises(Exception) as exc_info:
733 with pytest.raises(Exception) as exc_info:
743 model.get_repo_ui_settings()
734 model.get_repo_ui_settings()
744 assert exc_info.value.message == 'Repository is not specified'
735 assert exc_info.value.message == 'Repository is not specified'
745
736
746
737
747 class TestGetRepoGeneralSettings(object):
738 class TestGetRepoGeneralSettings(object):
748 def test_global_settings_are_returned_when_no_repo_settings_found(
739 def test_global_settings_are_returned_when_no_repo_settings_found(
749 self, repo_stub):
740 self, repo_stub):
750 model = VcsSettingsModel(repo=repo_stub.repo_name)
741 model = VcsSettingsModel(repo=repo_stub.repo_name)
751 result = model.get_repo_general_settings()
742 result = model.get_repo_general_settings()
752 expected_result = model.global_settings.get_all_settings()
743 expected_result = model.global_settings.get_all_settings()
753 assert sorted(result) == sorted(expected_result)
744 assert sorted(result) == sorted(expected_result)
754
745
755 def test_repo_uis_are_overriding_global_uis(
746 def test_repo_uis_are_overriding_global_uis(
756 self, repo_stub, settings_util):
747 self, repo_stub, settings_util):
757 for key in VcsSettingsModel.GENERAL_SETTINGS:
748 for key in VcsSettingsModel.GENERAL_SETTINGS:
758 settings_util.create_repo_rhodecode_setting(
749 settings_util.create_repo_rhodecode_setting(
759 repo_stub, key, 'abcde', type_='unicode')
750 repo_stub, key, 'abcde', type_='unicode')
760 model = VcsSettingsModel(repo=repo_stub.repo_name)
751 model = VcsSettingsModel(repo=repo_stub.repo_name)
761 result = model.get_repo_ui_settings()
752 result = model.get_repo_ui_settings()
762 for key in result:
753 for key in result:
763 if key in VcsSettingsModel.GENERAL_SETTINGS:
754 if key in VcsSettingsModel.GENERAL_SETTINGS:
764 assert result[key] == 'abcde'
755 assert result[key] == 'abcde'
765
756
766 def test_raises_exception_when_repository_is_not_specified(self):
757 def test_raises_exception_when_repository_is_not_specified(self):
767 model = VcsSettingsModel()
758 model = VcsSettingsModel()
768 with pytest.raises(Exception) as exc_info:
759 with pytest.raises(Exception) as exc_info:
769 model.get_repo_general_settings()
760 model.get_repo_general_settings()
770 assert exc_info.value.message == 'Repository is not specified'
761 assert exc_info.value.message == 'Repository is not specified'
771
762
772
763
773 class TestGetGlobalGeneralSettings(object):
764 class TestGetGlobalGeneralSettings(object):
774 def test_global_settings_are_returned(self, repo_stub):
765 def test_global_settings_are_returned(self, repo_stub):
775 model = VcsSettingsModel()
766 model = VcsSettingsModel()
776 result = model.get_global_general_settings()
767 result = model.get_global_general_settings()
777 expected_result = model.global_settings.get_all_settings()
768 expected_result = model.global_settings.get_all_settings()
778 assert sorted(result) == sorted(expected_result)
769 assert sorted(result) == sorted(expected_result)
779
770
780 def test_repo_uis_are_not_overriding_global_uis(
771 def test_repo_uis_are_not_overriding_global_uis(
781 self, repo_stub, settings_util):
772 self, repo_stub, settings_util):
782 for key in VcsSettingsModel.GENERAL_SETTINGS:
773 for key in VcsSettingsModel.GENERAL_SETTINGS:
783 settings_util.create_repo_rhodecode_setting(
774 settings_util.create_repo_rhodecode_setting(
784 repo_stub, key, 'abcde', type_='unicode')
775 repo_stub, key, 'abcde', type_='unicode')
785 model = VcsSettingsModel(repo=repo_stub.repo_name)
776 model = VcsSettingsModel(repo=repo_stub.repo_name)
786 result = model.get_global_general_settings()
777 result = model.get_global_general_settings()
787 expected_result = model.global_settings.get_all_settings()
778 expected_result = model.global_settings.get_all_settings()
788 assert sorted(result) == sorted(expected_result)
779 assert sorted(result) == sorted(expected_result)
789
780
790
781
791 class TestGetGlobalUiSettings(object):
782 class TestGetGlobalUiSettings(object):
792 def test_global_uis_are_returned(self, repo_stub):
783 def test_global_uis_are_returned(self, repo_stub):
793 model = VcsSettingsModel()
784 model = VcsSettingsModel()
794 result = model.get_global_ui_settings()
785 result = model.get_global_ui_settings()
795 expected_result = model.global_settings.get_ui()
786 expected_result = model.global_settings.get_ui()
796 assert sorted(result) == sorted(expected_result)
787 assert sorted(result) == sorted(expected_result)
797
788
798 def test_repo_uis_are_not_overriding_global_uis(
789 def test_repo_uis_are_not_overriding_global_uis(
799 self, repo_stub, settings_util):
790 self, repo_stub, settings_util):
800 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
791 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
801 settings_util.create_repo_rhodecode_ui(
792 settings_util.create_repo_rhodecode_ui(
802 repo_stub, section, 'repo', key=key, active=False)
793 repo_stub, section, 'repo', key=key, active=False)
803 model = VcsSettingsModel(repo=repo_stub.repo_name)
794 model = VcsSettingsModel(repo=repo_stub.repo_name)
804 result = model.get_global_ui_settings()
795 result = model.get_global_ui_settings()
805 expected_result = model.global_settings.get_ui()
796 expected_result = model.global_settings.get_ui()
806 assert sorted(result) == sorted(expected_result)
797 assert sorted(result) == sorted(expected_result)
807
798
808 def test_ui_settings_filtered_by_section(
799 def test_ui_settings_filtered_by_section(
809 self, repo_stub, settings_util):
800 self, repo_stub, settings_util):
810 model = VcsSettingsModel(repo=repo_stub.repo_name)
801 model = VcsSettingsModel(repo=repo_stub.repo_name)
811 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
802 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
812 result = model.get_global_ui_settings(section=section)
803 result = model.get_global_ui_settings(section=section)
813 expected_result = model.global_settings.get_ui(section=section)
804 expected_result = model.global_settings.get_ui(section=section)
814 assert sorted(result) == sorted(expected_result)
805 assert sorted(result) == sorted(expected_result)
815
806
816 def test_ui_settings_filtered_by_key(
807 def test_ui_settings_filtered_by_key(
817 self, repo_stub, settings_util):
808 self, repo_stub, settings_util):
818 model = VcsSettingsModel(repo=repo_stub.repo_name)
809 model = VcsSettingsModel(repo=repo_stub.repo_name)
819 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
810 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
820 result = model.get_global_ui_settings(key=key)
811 result = model.get_global_ui_settings(key=key)
821 expected_result = model.global_settings.get_ui(key=key)
812 expected_result = model.global_settings.get_ui(key=key)
822 assert sorted(result) == sorted(expected_result)
813 assert sorted(result) == sorted(expected_result)
823
814
824
815
825 class TestGetGeneralSettings(object):
816 class TestGetGeneralSettings(object):
826 def test_global_settings_are_returned_when_inherited_is_true(
817 def test_global_settings_are_returned_when_inherited_is_true(
827 self, repo_stub, settings_util):
818 self, repo_stub, settings_util):
828 model = VcsSettingsModel(repo=repo_stub.repo_name)
819 model = VcsSettingsModel(repo=repo_stub.repo_name)
829 model.inherit_global_settings = True
820 model.inherit_global_settings = True
830 for key in VcsSettingsModel.GENERAL_SETTINGS:
821 for key in VcsSettingsModel.GENERAL_SETTINGS:
831 settings_util.create_repo_rhodecode_setting(
822 settings_util.create_repo_rhodecode_setting(
832 repo_stub, key, 'abcde', type_='unicode')
823 repo_stub, key, 'abcde', type_='unicode')
833 result = model.get_general_settings()
824 result = model.get_general_settings()
834 expected_result = model.get_global_general_settings()
825 expected_result = model.get_global_general_settings()
835 assert sorted(result) == sorted(expected_result)
826 assert sorted(result) == sorted(expected_result)
836
827
837 def test_repo_settings_are_returned_when_inherited_is_false(
828 def test_repo_settings_are_returned_when_inherited_is_false(
838 self, repo_stub, settings_util):
829 self, repo_stub, settings_util):
839 model = VcsSettingsModel(repo=repo_stub.repo_name)
830 model = VcsSettingsModel(repo=repo_stub.repo_name)
840 model.inherit_global_settings = False
831 model.inherit_global_settings = False
841 for key in VcsSettingsModel.GENERAL_SETTINGS:
832 for key in VcsSettingsModel.GENERAL_SETTINGS:
842 settings_util.create_repo_rhodecode_setting(
833 settings_util.create_repo_rhodecode_setting(
843 repo_stub, key, 'abcde', type_='unicode')
834 repo_stub, key, 'abcde', type_='unicode')
844 result = model.get_general_settings()
835 result = model.get_general_settings()
845 expected_result = model.get_repo_general_settings()
836 expected_result = model.get_repo_general_settings()
846 assert sorted(result) == sorted(expected_result)
837 assert sorted(result) == sorted(expected_result)
847
838
848 def test_global_settings_are_returned_when_no_repository_specified(self):
839 def test_global_settings_are_returned_when_no_repository_specified(self):
849 model = VcsSettingsModel()
840 model = VcsSettingsModel()
850 result = model.get_general_settings()
841 result = model.get_general_settings()
851 expected_result = model.get_global_general_settings()
842 expected_result = model.get_global_general_settings()
852 assert sorted(result) == sorted(expected_result)
843 assert sorted(result) == sorted(expected_result)
853
844
854
845
855 class TestGetUiSettings(object):
846 class TestGetUiSettings(object):
856 def test_global_settings_are_returned_when_inherited_is_true(
847 def test_global_settings_are_returned_when_inherited_is_true(
857 self, repo_stub, settings_util):
848 self, repo_stub, settings_util):
858 model = VcsSettingsModel(repo=repo_stub.repo_name)
849 model = VcsSettingsModel(repo=repo_stub.repo_name)
859 model.inherit_global_settings = True
850 model.inherit_global_settings = True
860 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
851 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
861 settings_util.create_repo_rhodecode_ui(
852 settings_util.create_repo_rhodecode_ui(
862 repo_stub, section, 'repo', key=key, active=True)
853 repo_stub, section, 'repo', key=key, active=True)
863 result = model.get_ui_settings()
854 result = model.get_ui_settings()
864 expected_result = model.get_global_ui_settings()
855 expected_result = model.get_global_ui_settings()
865 assert sorted(result) == sorted(expected_result)
856 assert sorted(result) == sorted(expected_result)
866
857
867 def test_repo_settings_are_returned_when_inherited_is_false(
858 def test_repo_settings_are_returned_when_inherited_is_false(
868 self, repo_stub, settings_util):
859 self, repo_stub, settings_util):
869 model = VcsSettingsModel(repo=repo_stub.repo_name)
860 model = VcsSettingsModel(repo=repo_stub.repo_name)
870 model.inherit_global_settings = False
861 model.inherit_global_settings = False
871 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
862 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
872 settings_util.create_repo_rhodecode_ui(
863 settings_util.create_repo_rhodecode_ui(
873 repo_stub, section, 'repo', key=key, active=True)
864 repo_stub, section, 'repo', key=key, active=True)
874 result = model.get_ui_settings()
865 result = model.get_ui_settings()
875 expected_result = model.get_repo_ui_settings()
866 expected_result = model.get_repo_ui_settings()
876 assert sorted(result) == sorted(expected_result)
867 assert sorted(result) == sorted(expected_result)
877
868
878 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
869 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
879 model = VcsSettingsModel(repo=repo_stub.repo_name)
870 model = VcsSettingsModel(repo=repo_stub.repo_name)
880 model.inherit_global_settings = False
871 model.inherit_global_settings = False
881 args = ('section', 'key')
872 args = ('section', 'key')
882 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
873 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
883 model.get_ui_settings(*args)
874 model.get_ui_settings(*args)
884 settings_mock.assert_called_once_with(*args)
875 settings_mock.assert_called_once_with(*args)
885
876
886 def test_global_settings_filtered_by_section_and_key(self):
877 def test_global_settings_filtered_by_section_and_key(self):
887 model = VcsSettingsModel()
878 model = VcsSettingsModel()
888 args = ('section', 'key')
879 args = ('section', 'key')
889 with mock.patch.object(model, 'get_global_ui_settings') as (
880 with mock.patch.object(model, 'get_global_ui_settings') as (
890 settings_mock):
881 settings_mock):
891 model.get_ui_settings(*args)
882 model.get_ui_settings(*args)
892 settings_mock.assert_called_once_with(*args)
883 settings_mock.assert_called_once_with(*args)
893
884
894 def test_global_settings_are_returned_when_no_repository_specified(self):
885 def test_global_settings_are_returned_when_no_repository_specified(self):
895 model = VcsSettingsModel()
886 model = VcsSettingsModel()
896 result = model.get_ui_settings()
887 result = model.get_ui_settings()
897 expected_result = model.get_global_ui_settings()
888 expected_result = model.get_global_ui_settings()
898 assert sorted(result) == sorted(expected_result)
889 assert sorted(result) == sorted(expected_result)
899
890
900
891
901 class TestGetSvnPatterns(object):
892 class TestGetSvnPatterns(object):
902 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
893 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
903 model = VcsSettingsModel(repo=repo_stub.repo_name)
894 model = VcsSettingsModel(repo=repo_stub.repo_name)
904 args = ('section', )
895 args = ('section', )
905 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
896 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
906 model.get_svn_patterns(*args)
897 model.get_svn_patterns(*args)
907 settings_mock.assert_called_once_with(*args)
898 settings_mock.assert_called_once_with(*args)
908
899
909 def test_global_settings_filtered_by_section_and_key(self):
900 def test_global_settings_filtered_by_section_and_key(self):
910 model = VcsSettingsModel()
901 model = VcsSettingsModel()
911 args = ('section', )
902 args = ('section', )
912 with mock.patch.object(model, 'get_global_ui_settings') as (
903 with mock.patch.object(model, 'get_global_ui_settings') as (
913 settings_mock):
904 settings_mock):
914 model.get_svn_patterns(*args)
905 model.get_svn_patterns(*args)
915 settings_mock.assert_called_once_with(*args)
906 settings_mock.assert_called_once_with(*args)
916
907
917
908
918 class TestGetReposLocation(object):
909 class TestGetReposLocation(object):
919 def test_returns_repos_location(self, repo_stub):
910 def test_returns_repos_location(self, repo_stub):
920 model = VcsSettingsModel()
911 model = VcsSettingsModel()
921
912
922 result_mock = mock.Mock()
913 result_mock = mock.Mock()
923 result_mock.ui_value = '/tmp'
914 result_mock.ui_value = '/tmp'
924
915
925 with mock.patch.object(model, 'global_settings') as settings_mock:
916 with mock.patch.object(model, 'global_settings') as settings_mock:
926 settings_mock.get_ui_by_key.return_value = result_mock
917 settings_mock.get_ui_by_key.return_value = result_mock
927 result = model.get_repos_location()
918 result = model.get_repos_location()
928
919
929 settings_mock.get_ui_by_key.assert_called_once_with('/')
920 settings_mock.get_ui_by_key.assert_called_once_with('/')
930 assert result == '/tmp'
921 assert result == '/tmp'
931
922
932
923
933 class TestCreateOrUpdateRepoSettings(object):
924 class TestCreateOrUpdateRepoSettings(object):
934 FORM_DATA = {
925 FORM_DATA = {
935 'inherit_global_settings': False,
926 'inherit_global_settings': False,
936 'hooks_changegroup_repo_size': False,
927 'hooks_changegroup_repo_size': False,
937 'hooks_changegroup_push_logger': False,
928 'hooks_changegroup_push_logger': False,
938 'hooks_outgoing_pull_logger': False,
929 'hooks_outgoing_pull_logger': False,
939 'extensions_largefiles': False,
930 'extensions_largefiles': False,
940 'phases_publish': 'false',
931 'phases_publish': 'false',
941 'rhodecode_pr_merge_enabled': False,
932 'rhodecode_pr_merge_enabled': False,
942 'rhodecode_use_outdated_comments': False,
933 'rhodecode_use_outdated_comments': False,
943 'new_svn_branch': '',
934 'new_svn_branch': '',
944 'new_svn_tag': ''
935 'new_svn_tag': ''
945 }
936 }
946
937
947 def test_get_raises_exception_when_repository_not_specified(self):
938 def test_get_raises_exception_when_repository_not_specified(self):
948 model = VcsSettingsModel()
939 model = VcsSettingsModel()
949 with pytest.raises(Exception) as exc_info:
940 with pytest.raises(Exception) as exc_info:
950 model.create_or_update_repo_settings(data=self.FORM_DATA)
941 model.create_or_update_repo_settings(data=self.FORM_DATA)
951 assert exc_info.value.message == 'Repository is not specified'
942 assert exc_info.value.message == 'Repository is not specified'
952
943
953 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
944 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
954 repo = backend_svn.create_repo()
945 repo = backend_svn.create_repo()
955 model = VcsSettingsModel(repo=repo)
946 model = VcsSettingsModel(repo=repo)
956 with self._patch_model(model) as mocks:
947 with self._patch_model(model) as mocks:
957 model.create_or_update_repo_settings(
948 model.create_or_update_repo_settings(
958 data=self.FORM_DATA, inherit_global_settings=False)
949 data=self.FORM_DATA, inherit_global_settings=False)
959 mocks['create_repo_svn_settings'].assert_called_once_with(
950 mocks['create_repo_svn_settings'].assert_called_once_with(
960 self.FORM_DATA)
951 self.FORM_DATA)
961 non_called_methods = (
952 non_called_methods = (
962 'create_or_update_repo_hook_settings',
953 'create_or_update_repo_hook_settings',
963 'create_or_update_repo_pr_settings',
954 'create_or_update_repo_pr_settings',
964 'create_or_update_repo_hg_settings')
955 'create_or_update_repo_hg_settings')
965 for method in non_called_methods:
956 for method in non_called_methods:
966 assert mocks[method].call_count == 0
957 assert mocks[method].call_count == 0
967
958
968 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
959 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
969 repo = backend_hg.create_repo()
960 repo = backend_hg.create_repo()
970 model = VcsSettingsModel(repo=repo)
961 model = VcsSettingsModel(repo=repo)
971 with self._patch_model(model) as mocks:
962 with self._patch_model(model) as mocks:
972 model.create_or_update_repo_settings(
963 model.create_or_update_repo_settings(
973 data=self.FORM_DATA, inherit_global_settings=False)
964 data=self.FORM_DATA, inherit_global_settings=False)
974
965
975 assert mocks['create_repo_svn_settings'].call_count == 0
966 assert mocks['create_repo_svn_settings'].call_count == 0
976 called_methods = (
967 called_methods = (
977 'create_or_update_repo_hook_settings',
968 'create_or_update_repo_hook_settings',
978 'create_or_update_repo_pr_settings',
969 'create_or_update_repo_pr_settings',
979 'create_or_update_repo_hg_settings')
970 'create_or_update_repo_hg_settings')
980 for method in called_methods:
971 for method in called_methods:
981 mocks[method].assert_called_once_with(self.FORM_DATA)
972 mocks[method].assert_called_once_with(self.FORM_DATA)
982
973
983 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
974 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
984 self, backend_git):
975 self, backend_git):
985 repo = backend_git.create_repo()
976 repo = backend_git.create_repo()
986 model = VcsSettingsModel(repo=repo)
977 model = VcsSettingsModel(repo=repo)
987 with self._patch_model(model) as mocks:
978 with self._patch_model(model) as mocks:
988 model.create_or_update_repo_settings(
979 model.create_or_update_repo_settings(
989 data=self.FORM_DATA, inherit_global_settings=False)
980 data=self.FORM_DATA, inherit_global_settings=False)
990
981
991 assert mocks['create_repo_svn_settings'].call_count == 0
982 assert mocks['create_repo_svn_settings'].call_count == 0
992 called_methods = (
983 called_methods = (
993 'create_or_update_repo_hook_settings',
984 'create_or_update_repo_hook_settings',
994 'create_or_update_repo_pr_settings')
985 'create_or_update_repo_pr_settings')
995 non_called_methods = (
986 non_called_methods = (
996 'create_repo_svn_settings',
987 'create_repo_svn_settings',
997 'create_or_update_repo_hg_settings'
988 'create_or_update_repo_hg_settings'
998 )
989 )
999 for method in called_methods:
990 for method in called_methods:
1000 mocks[method].assert_called_once_with(self.FORM_DATA)
991 mocks[method].assert_called_once_with(self.FORM_DATA)
1001 for method in non_called_methods:
992 for method in non_called_methods:
1002 assert mocks[method].call_count == 0
993 assert mocks[method].call_count == 0
1003
994
1004 def test_no_methods_are_called_when_settings_are_inherited(
995 def test_no_methods_are_called_when_settings_are_inherited(
1005 self, backend):
996 self, backend):
1006 repo = backend.create_repo()
997 repo = backend.create_repo()
1007 model = VcsSettingsModel(repo=repo)
998 model = VcsSettingsModel(repo=repo)
1008 with self._patch_model(model) as mocks:
999 with self._patch_model(model) as mocks:
1009 model.create_or_update_repo_settings(
1000 model.create_or_update_repo_settings(
1010 data=self.FORM_DATA, inherit_global_settings=True)
1001 data=self.FORM_DATA, inherit_global_settings=True)
1011 for method_name in mocks:
1002 for method_name in mocks:
1012 assert mocks[method_name].call_count == 0
1003 assert mocks[method_name].call_count == 0
1013
1004
1014 def test_cache_is_marked_for_invalidation(self, repo_stub):
1005 def test_cache_is_marked_for_invalidation(self, repo_stub):
1015 model = VcsSettingsModel(repo=repo_stub)
1006 model = VcsSettingsModel(repo=repo_stub)
1016 invalidation_patcher = mock.patch(
1007 invalidation_patcher = mock.patch(
1017 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1008 'rhodecode.controllers.admin.repos.ScmModel.mark_for_invalidation')
1018 with invalidation_patcher as invalidation_mock:
1009 with invalidation_patcher as invalidation_mock:
1019 model.create_or_update_repo_settings(
1010 model.create_or_update_repo_settings(
1020 data=self.FORM_DATA, inherit_global_settings=True)
1011 data=self.FORM_DATA, inherit_global_settings=True)
1021 invalidation_mock.assert_called_once_with(
1012 invalidation_mock.assert_called_once_with(
1022 repo_stub.repo_name, delete=True)
1013 repo_stub.repo_name, delete=True)
1023
1014
1024 def test_inherit_flag_is_saved(self, repo_stub):
1015 def test_inherit_flag_is_saved(self, repo_stub):
1025 model = VcsSettingsModel(repo=repo_stub)
1016 model = VcsSettingsModel(repo=repo_stub)
1026 model.inherit_global_settings = True
1017 model.inherit_global_settings = True
1027 with self._patch_model(model):
1018 with self._patch_model(model):
1028 model.create_or_update_repo_settings(
1019 model.create_or_update_repo_settings(
1029 data=self.FORM_DATA, inherit_global_settings=False)
1020 data=self.FORM_DATA, inherit_global_settings=False)
1030 assert model.inherit_global_settings is False
1021 assert model.inherit_global_settings is False
1031
1022
1032 def _patch_model(self, model):
1023 def _patch_model(self, model):
1033 return mock.patch.multiple(
1024 return mock.patch.multiple(
1034 model,
1025 model,
1035 create_repo_svn_settings=mock.DEFAULT,
1026 create_repo_svn_settings=mock.DEFAULT,
1036 create_or_update_repo_hook_settings=mock.DEFAULT,
1027 create_or_update_repo_hook_settings=mock.DEFAULT,
1037 create_or_update_repo_pr_settings=mock.DEFAULT,
1028 create_or_update_repo_pr_settings=mock.DEFAULT,
1038 create_or_update_repo_hg_settings=mock.DEFAULT)
1029 create_or_update_repo_hg_settings=mock.DEFAULT)
General Comments 0
You need to be logged in to leave comments. Login now