##// END OF EJS Templates
clone-urls: allow custom clone by id template.
milka -
r4629:35fb8d8f stable
parent child Browse files
Show More
@@ -1,719 +1,720 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import formencode.htmlfill
27 import formencode.htmlfill
28
28
29 import rhodecode
29 import rhodecode
30
30
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
31 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34
34
35 from rhodecode.apps._base import BaseAppView
35 from rhodecode.apps._base import BaseAppView
36 from rhodecode.apps._base.navigation import navigation_list
36 from rhodecode.apps._base.navigation import navigation_list
37 from rhodecode.apps.svn_support.config_keys import generate_config
37 from rhodecode.apps.svn_support.config_keys import generate_config
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
40 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
41 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.utils import repo2db_mapper
42 from rhodecode.lib.utils import repo2db_mapper
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
44 from rhodecode.lib.index import searcher_from_config
44 from rhodecode.lib.index import searcher_from_config
45
45
46 from rhodecode.model.db import RhodeCodeUi, Repository
46 from rhodecode.model.db import RhodeCodeUi, Repository
47 from rhodecode.model.forms import (ApplicationSettingsForm,
47 from rhodecode.model.forms import (ApplicationSettingsForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
48 ApplicationUiSettingsForm, ApplicationVisualisationForm,
49 LabsSettingsForm, IssueTrackerPatternsForm)
49 LabsSettingsForm, IssueTrackerPatternsForm)
50 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.repo_group import RepoGroupModel
51 from rhodecode.model.repo_group import RepoGroupModel
52
52
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.notification import EmailNotificationModel
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56 from rhodecode.model.settings import (
56 from rhodecode.model.settings import (
57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
58 SettingsModel)
58 SettingsModel)
59
59
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63
63
64 class AdminSettingsView(BaseAppView):
64 class AdminSettingsView(BaseAppView):
65
65
66 def load_default_context(self):
66 def load_default_context(self):
67 c = self._get_local_tmpl_context()
67 c = self._get_local_tmpl_context()
68 c.labs_active = str2bool(
68 c.labs_active = str2bool(
69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 rhodecode.CONFIG.get('labs_settings_active', 'true'))
70 c.navlist = navigation_list(self.request)
70 c.navlist = navigation_list(self.request)
71 return c
71 return c
72
72
73 @classmethod
73 @classmethod
74 def _get_ui_settings(cls):
74 def _get_ui_settings(cls):
75 ret = RhodeCodeUi.query().all()
75 ret = RhodeCodeUi.query().all()
76
76
77 if not ret:
77 if not ret:
78 raise Exception('Could not get application ui settings !')
78 raise Exception('Could not get application ui settings !')
79 settings = {}
79 settings = {}
80 for each in ret:
80 for each in ret:
81 k = each.ui_key
81 k = each.ui_key
82 v = each.ui_value
82 v = each.ui_value
83 if k == '/':
83 if k == '/':
84 k = 'root_path'
84 k = 'root_path'
85
85
86 if k in ['push_ssl', 'publish', 'enabled']:
86 if k in ['push_ssl', 'publish', 'enabled']:
87 v = str2bool(v)
87 v = str2bool(v)
88
88
89 if k.find('.') != -1:
89 if k.find('.') != -1:
90 k = k.replace('.', '_')
90 k = k.replace('.', '_')
91
91
92 if each.ui_section in ['hooks', 'extensions']:
92 if each.ui_section in ['hooks', 'extensions']:
93 v = each.ui_active
93 v = each.ui_active
94
94
95 settings[each.ui_section + '_' + k] = v
95 settings[each.ui_section + '_' + k] = v
96 return settings
96 return settings
97
97
98 @classmethod
98 @classmethod
99 def _form_defaults(cls):
99 def _form_defaults(cls):
100 defaults = SettingsModel().get_all_settings()
100 defaults = SettingsModel().get_all_settings()
101 defaults.update(cls._get_ui_settings())
101 defaults.update(cls._get_ui_settings())
102
102
103 defaults.update({
103 defaults.update({
104 'new_svn_branch': '',
104 'new_svn_branch': '',
105 'new_svn_tag': '',
105 'new_svn_tag': '',
106 })
106 })
107 return defaults
107 return defaults
108
108
109 @LoginRequired()
109 @LoginRequired()
110 @HasPermissionAllDecorator('hg.admin')
110 @HasPermissionAllDecorator('hg.admin')
111 def settings_vcs(self):
111 def settings_vcs(self):
112 c = self.load_default_context()
112 c = self.load_default_context()
113 c.active = 'vcs'
113 c.active = 'vcs'
114 model = VcsSettingsModel()
114 model = VcsSettingsModel()
115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
115 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
116 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
117
117
118 settings = self.request.registry.settings
118 settings = self.request.registry.settings
119 c.svn_proxy_generate_config = settings[generate_config]
119 c.svn_proxy_generate_config = settings[generate_config]
120
120
121 defaults = self._form_defaults()
121 defaults = self._form_defaults()
122
122
123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
123 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
124
124
125 data = render('rhodecode:templates/admin/settings/settings.mako',
125 data = render('rhodecode:templates/admin/settings/settings.mako',
126 self._get_template_context(c), self.request)
126 self._get_template_context(c), self.request)
127 html = formencode.htmlfill.render(
127 html = formencode.htmlfill.render(
128 data,
128 data,
129 defaults=defaults,
129 defaults=defaults,
130 encoding="UTF-8",
130 encoding="UTF-8",
131 force_defaults=False
131 force_defaults=False
132 )
132 )
133 return Response(html)
133 return Response(html)
134
134
135 @LoginRequired()
135 @LoginRequired()
136 @HasPermissionAllDecorator('hg.admin')
136 @HasPermissionAllDecorator('hg.admin')
137 @CSRFRequired()
137 @CSRFRequired()
138 def settings_vcs_update(self):
138 def settings_vcs_update(self):
139 _ = self.request.translate
139 _ = self.request.translate
140 c = self.load_default_context()
140 c = self.load_default_context()
141 c.active = 'vcs'
141 c.active = 'vcs'
142
142
143 model = VcsSettingsModel()
143 model = VcsSettingsModel()
144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
144 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
145 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
146
146
147 settings = self.request.registry.settings
147 settings = self.request.registry.settings
148 c.svn_proxy_generate_config = settings[generate_config]
148 c.svn_proxy_generate_config = settings[generate_config]
149
149
150 application_form = ApplicationUiSettingsForm(self.request.translate)()
150 application_form = ApplicationUiSettingsForm(self.request.translate)()
151
151
152 try:
152 try:
153 form_result = application_form.to_python(dict(self.request.POST))
153 form_result = application_form.to_python(dict(self.request.POST))
154 except formencode.Invalid as errors:
154 except formencode.Invalid as errors:
155 h.flash(
155 h.flash(
156 _("Some form inputs contain invalid data."),
156 _("Some form inputs contain invalid data."),
157 category='error')
157 category='error')
158 data = render('rhodecode:templates/admin/settings/settings.mako',
158 data = render('rhodecode:templates/admin/settings/settings.mako',
159 self._get_template_context(c), self.request)
159 self._get_template_context(c), self.request)
160 html = formencode.htmlfill.render(
160 html = formencode.htmlfill.render(
161 data,
161 data,
162 defaults=errors.value,
162 defaults=errors.value,
163 errors=errors.error_dict or {},
163 errors=errors.error_dict or {},
164 prefix_error=False,
164 prefix_error=False,
165 encoding="UTF-8",
165 encoding="UTF-8",
166 force_defaults=False
166 force_defaults=False
167 )
167 )
168 return Response(html)
168 return Response(html)
169
169
170 try:
170 try:
171 if c.visual.allow_repo_location_change:
171 if c.visual.allow_repo_location_change:
172 model.update_global_path_setting(form_result['paths_root_path'])
172 model.update_global_path_setting(form_result['paths_root_path'])
173
173
174 model.update_global_ssl_setting(form_result['web_push_ssl'])
174 model.update_global_ssl_setting(form_result['web_push_ssl'])
175 model.update_global_hook_settings(form_result)
175 model.update_global_hook_settings(form_result)
176
176
177 model.create_or_update_global_svn_settings(form_result)
177 model.create_or_update_global_svn_settings(form_result)
178 model.create_or_update_global_hg_settings(form_result)
178 model.create_or_update_global_hg_settings(form_result)
179 model.create_or_update_global_git_settings(form_result)
179 model.create_or_update_global_git_settings(form_result)
180 model.create_or_update_global_pr_settings(form_result)
180 model.create_or_update_global_pr_settings(form_result)
181 except Exception:
181 except Exception:
182 log.exception("Exception while updating settings")
182 log.exception("Exception while updating settings")
183 h.flash(_('Error occurred during updating '
183 h.flash(_('Error occurred during updating '
184 'application settings'), category='error')
184 'application settings'), category='error')
185 else:
185 else:
186 Session().commit()
186 Session().commit()
187 h.flash(_('Updated VCS settings'), category='success')
187 h.flash(_('Updated VCS settings'), category='success')
188 raise HTTPFound(h.route_path('admin_settings_vcs'))
188 raise HTTPFound(h.route_path('admin_settings_vcs'))
189
189
190 data = render('rhodecode:templates/admin/settings/settings.mako',
190 data = render('rhodecode:templates/admin/settings/settings.mako',
191 self._get_template_context(c), self.request)
191 self._get_template_context(c), self.request)
192 html = formencode.htmlfill.render(
192 html = formencode.htmlfill.render(
193 data,
193 data,
194 defaults=self._form_defaults(),
194 defaults=self._form_defaults(),
195 encoding="UTF-8",
195 encoding="UTF-8",
196 force_defaults=False
196 force_defaults=False
197 )
197 )
198 return Response(html)
198 return Response(html)
199
199
200 @LoginRequired()
200 @LoginRequired()
201 @HasPermissionAllDecorator('hg.admin')
201 @HasPermissionAllDecorator('hg.admin')
202 @CSRFRequired()
202 @CSRFRequired()
203 def settings_vcs_delete_svn_pattern(self):
203 def settings_vcs_delete_svn_pattern(self):
204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
204 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
205 model = VcsSettingsModel()
205 model = VcsSettingsModel()
206 try:
206 try:
207 model.delete_global_svn_pattern(delete_pattern_id)
207 model.delete_global_svn_pattern(delete_pattern_id)
208 except SettingNotFound:
208 except SettingNotFound:
209 log.exception(
209 log.exception(
210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
210 'Failed to delete svn_pattern with id %s', delete_pattern_id)
211 raise HTTPNotFound()
211 raise HTTPNotFound()
212
212
213 Session().commit()
213 Session().commit()
214 return True
214 return True
215
215
216 @LoginRequired()
216 @LoginRequired()
217 @HasPermissionAllDecorator('hg.admin')
217 @HasPermissionAllDecorator('hg.admin')
218 def settings_mapping(self):
218 def settings_mapping(self):
219 c = self.load_default_context()
219 c = self.load_default_context()
220 c.active = 'mapping'
220 c.active = 'mapping'
221
221
222 data = render('rhodecode:templates/admin/settings/settings.mako',
222 data = render('rhodecode:templates/admin/settings/settings.mako',
223 self._get_template_context(c), self.request)
223 self._get_template_context(c), self.request)
224 html = formencode.htmlfill.render(
224 html = formencode.htmlfill.render(
225 data,
225 data,
226 defaults=self._form_defaults(),
226 defaults=self._form_defaults(),
227 encoding="UTF-8",
227 encoding="UTF-8",
228 force_defaults=False
228 force_defaults=False
229 )
229 )
230 return Response(html)
230 return Response(html)
231
231
232 @LoginRequired()
232 @LoginRequired()
233 @HasPermissionAllDecorator('hg.admin')
233 @HasPermissionAllDecorator('hg.admin')
234 @CSRFRequired()
234 @CSRFRequired()
235 def settings_mapping_update(self):
235 def settings_mapping_update(self):
236 _ = self.request.translate
236 _ = self.request.translate
237 c = self.load_default_context()
237 c = self.load_default_context()
238 c.active = 'mapping'
238 c.active = 'mapping'
239 rm_obsolete = self.request.POST.get('destroy', False)
239 rm_obsolete = self.request.POST.get('destroy', False)
240 invalidate_cache = self.request.POST.get('invalidate', False)
240 invalidate_cache = self.request.POST.get('invalidate', False)
241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
241 log.debug('rescanning repo location with destroy obsolete=%s', rm_obsolete)
242
242
243 if invalidate_cache:
243 if invalidate_cache:
244 log.debug('invalidating all repositories cache')
244 log.debug('invalidating all repositories cache')
245 for repo in Repository.get_all():
245 for repo in Repository.get_all():
246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
246 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
247
247
248 filesystem_repos = ScmModel().repo_scan()
248 filesystem_repos = ScmModel().repo_scan()
249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
249 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
250 PermissionModel().trigger_permission_flush()
250 PermissionModel().trigger_permission_flush()
251
251
252 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
252 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
253 h.flash(_('Repositories successfully '
253 h.flash(_('Repositories successfully '
254 'rescanned added: %s ; removed: %s') %
254 'rescanned added: %s ; removed: %s') %
255 (_repr(added), _repr(removed)),
255 (_repr(added), _repr(removed)),
256 category='success')
256 category='success')
257 raise HTTPFound(h.route_path('admin_settings_mapping'))
257 raise HTTPFound(h.route_path('admin_settings_mapping'))
258
258
259 @LoginRequired()
259 @LoginRequired()
260 @HasPermissionAllDecorator('hg.admin')
260 @HasPermissionAllDecorator('hg.admin')
261 def settings_global(self):
261 def settings_global(self):
262 c = self.load_default_context()
262 c = self.load_default_context()
263 c.active = 'global'
263 c.active = 'global'
264 c.personal_repo_group_default_pattern = RepoGroupModel()\
264 c.personal_repo_group_default_pattern = RepoGroupModel()\
265 .get_personal_group_name_pattern()
265 .get_personal_group_name_pattern()
266
266
267 data = render('rhodecode:templates/admin/settings/settings.mako',
267 data = render('rhodecode:templates/admin/settings/settings.mako',
268 self._get_template_context(c), self.request)
268 self._get_template_context(c), self.request)
269 html = formencode.htmlfill.render(
269 html = formencode.htmlfill.render(
270 data,
270 data,
271 defaults=self._form_defaults(),
271 defaults=self._form_defaults(),
272 encoding="UTF-8",
272 encoding="UTF-8",
273 force_defaults=False
273 force_defaults=False
274 )
274 )
275 return Response(html)
275 return Response(html)
276
276
277 @LoginRequired()
277 @LoginRequired()
278 @HasPermissionAllDecorator('hg.admin')
278 @HasPermissionAllDecorator('hg.admin')
279 @CSRFRequired()
279 @CSRFRequired()
280 def settings_global_update(self):
280 def settings_global_update(self):
281 _ = self.request.translate
281 _ = self.request.translate
282 c = self.load_default_context()
282 c = self.load_default_context()
283 c.active = 'global'
283 c.active = 'global'
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
284 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 .get_personal_group_name_pattern()
285 .get_personal_group_name_pattern()
286 application_form = ApplicationSettingsForm(self.request.translate)()
286 application_form = ApplicationSettingsForm(self.request.translate)()
287 try:
287 try:
288 form_result = application_form.to_python(dict(self.request.POST))
288 form_result = application_form.to_python(dict(self.request.POST))
289 except formencode.Invalid as errors:
289 except formencode.Invalid as errors:
290 h.flash(
290 h.flash(
291 _("Some form inputs contain invalid data."),
291 _("Some form inputs contain invalid data."),
292 category='error')
292 category='error')
293 data = render('rhodecode:templates/admin/settings/settings.mako',
293 data = render('rhodecode:templates/admin/settings/settings.mako',
294 self._get_template_context(c), self.request)
294 self._get_template_context(c), self.request)
295 html = formencode.htmlfill.render(
295 html = formencode.htmlfill.render(
296 data,
296 data,
297 defaults=errors.value,
297 defaults=errors.value,
298 errors=errors.error_dict or {},
298 errors=errors.error_dict or {},
299 prefix_error=False,
299 prefix_error=False,
300 encoding="UTF-8",
300 encoding="UTF-8",
301 force_defaults=False
301 force_defaults=False
302 )
302 )
303 return Response(html)
303 return Response(html)
304
304
305 settings = [
305 settings = [
306 ('title', 'rhodecode_title', 'unicode'),
306 ('title', 'rhodecode_title', 'unicode'),
307 ('realm', 'rhodecode_realm', 'unicode'),
307 ('realm', 'rhodecode_realm', 'unicode'),
308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
308 ('pre_code', 'rhodecode_pre_code', 'unicode'),
309 ('post_code', 'rhodecode_post_code', 'unicode'),
309 ('post_code', 'rhodecode_post_code', 'unicode'),
310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
310 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
311 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
312 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
313 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
314 ]
314 ]
315 try:
315 try:
316 for setting, form_key, type_ in settings:
316 for setting, form_key, type_ in settings:
317 sett = SettingsModel().create_or_update_setting(
317 sett = SettingsModel().create_or_update_setting(
318 setting, form_result[form_key], type_)
318 setting, form_result[form_key], type_)
319 Session().add(sett)
319 Session().add(sett)
320
320
321 Session().commit()
321 Session().commit()
322 SettingsModel().invalidate_settings_cache()
322 SettingsModel().invalidate_settings_cache()
323 h.flash(_('Updated application settings'), category='success')
323 h.flash(_('Updated application settings'), category='success')
324 except Exception:
324 except Exception:
325 log.exception("Exception while updating application settings")
325 log.exception("Exception while updating application settings")
326 h.flash(
326 h.flash(
327 _('Error occurred during updating application settings'),
327 _('Error occurred during updating application settings'),
328 category='error')
328 category='error')
329
329
330 raise HTTPFound(h.route_path('admin_settings_global'))
330 raise HTTPFound(h.route_path('admin_settings_global'))
331
331
332 @LoginRequired()
332 @LoginRequired()
333 @HasPermissionAllDecorator('hg.admin')
333 @HasPermissionAllDecorator('hg.admin')
334 def settings_visual(self):
334 def settings_visual(self):
335 c = self.load_default_context()
335 c = self.load_default_context()
336 c.active = 'visual'
336 c.active = 'visual'
337
337
338 data = render('rhodecode:templates/admin/settings/settings.mako',
338 data = render('rhodecode:templates/admin/settings/settings.mako',
339 self._get_template_context(c), self.request)
339 self._get_template_context(c), self.request)
340 html = formencode.htmlfill.render(
340 html = formencode.htmlfill.render(
341 data,
341 data,
342 defaults=self._form_defaults(),
342 defaults=self._form_defaults(),
343 encoding="UTF-8",
343 encoding="UTF-8",
344 force_defaults=False
344 force_defaults=False
345 )
345 )
346 return Response(html)
346 return Response(html)
347
347
348 @LoginRequired()
348 @LoginRequired()
349 @HasPermissionAllDecorator('hg.admin')
349 @HasPermissionAllDecorator('hg.admin')
350 @CSRFRequired()
350 @CSRFRequired()
351 def settings_visual_update(self):
351 def settings_visual_update(self):
352 _ = self.request.translate
352 _ = self.request.translate
353 c = self.load_default_context()
353 c = self.load_default_context()
354 c.active = 'visual'
354 c.active = 'visual'
355 application_form = ApplicationVisualisationForm(self.request.translate)()
355 application_form = ApplicationVisualisationForm(self.request.translate)()
356 try:
356 try:
357 form_result = application_form.to_python(dict(self.request.POST))
357 form_result = application_form.to_python(dict(self.request.POST))
358 except formencode.Invalid as errors:
358 except formencode.Invalid as errors:
359 h.flash(
359 h.flash(
360 _("Some form inputs contain invalid data."),
360 _("Some form inputs contain invalid data."),
361 category='error')
361 category='error')
362 data = render('rhodecode:templates/admin/settings/settings.mako',
362 data = render('rhodecode:templates/admin/settings/settings.mako',
363 self._get_template_context(c), self.request)
363 self._get_template_context(c), self.request)
364 html = formencode.htmlfill.render(
364 html = formencode.htmlfill.render(
365 data,
365 data,
366 defaults=errors.value,
366 defaults=errors.value,
367 errors=errors.error_dict or {},
367 errors=errors.error_dict or {},
368 prefix_error=False,
368 prefix_error=False,
369 encoding="UTF-8",
369 encoding="UTF-8",
370 force_defaults=False
370 force_defaults=False
371 )
371 )
372 return Response(html)
372 return Response(html)
373
373
374 try:
374 try:
375 settings = [
375 settings = [
376 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
376 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
377 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
377 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
378 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
378 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
379 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
379 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
380 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
380 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
381 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
381 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
382 ('show_version', 'rhodecode_show_version', 'bool'),
382 ('show_version', 'rhodecode_show_version', 'bool'),
383 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
383 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
384 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
384 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
385 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
385 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
386 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
386 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
387 ('clone_uri_id_tmpl', 'rhodecode_clone_uri_id_tmpl', 'unicode'),
387 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
388 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
388 ('support_url', 'rhodecode_support_url', 'unicode'),
389 ('support_url', 'rhodecode_support_url', 'unicode'),
389 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
390 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
390 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
391 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
391 ]
392 ]
392 for setting, form_key, type_ in settings:
393 for setting, form_key, type_ in settings:
393 sett = SettingsModel().create_or_update_setting(
394 sett = SettingsModel().create_or_update_setting(
394 setting, form_result[form_key], type_)
395 setting, form_result[form_key], type_)
395 Session().add(sett)
396 Session().add(sett)
396
397
397 Session().commit()
398 Session().commit()
398 SettingsModel().invalidate_settings_cache()
399 SettingsModel().invalidate_settings_cache()
399 h.flash(_('Updated visualisation settings'), category='success')
400 h.flash(_('Updated visualisation settings'), category='success')
400 except Exception:
401 except Exception:
401 log.exception("Exception updating visualization settings")
402 log.exception("Exception updating visualization settings")
402 h.flash(_('Error occurred during updating '
403 h.flash(_('Error occurred during updating '
403 'visualisation settings'),
404 'visualisation settings'),
404 category='error')
405 category='error')
405
406
406 raise HTTPFound(h.route_path('admin_settings_visual'))
407 raise HTTPFound(h.route_path('admin_settings_visual'))
407
408
408 @LoginRequired()
409 @LoginRequired()
409 @HasPermissionAllDecorator('hg.admin')
410 @HasPermissionAllDecorator('hg.admin')
410 def settings_issuetracker(self):
411 def settings_issuetracker(self):
411 c = self.load_default_context()
412 c = self.load_default_context()
412 c.active = 'issuetracker'
413 c.active = 'issuetracker'
413 defaults = c.rc_config
414 defaults = c.rc_config
414
415
415 entry_key = 'rhodecode_issuetracker_pat_'
416 entry_key = 'rhodecode_issuetracker_pat_'
416
417
417 c.issuetracker_entries = {}
418 c.issuetracker_entries = {}
418 for k, v in defaults.items():
419 for k, v in defaults.items():
419 if k.startswith(entry_key):
420 if k.startswith(entry_key):
420 uid = k[len(entry_key):]
421 uid = k[len(entry_key):]
421 c.issuetracker_entries[uid] = None
422 c.issuetracker_entries[uid] = None
422
423
423 for uid in c.issuetracker_entries:
424 for uid in c.issuetracker_entries:
424 c.issuetracker_entries[uid] = AttributeDict({
425 c.issuetracker_entries[uid] = AttributeDict({
425 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
426 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
426 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
427 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
427 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
428 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
428 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
429 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
429 })
430 })
430
431
431 return self._get_template_context(c)
432 return self._get_template_context(c)
432
433
433 @LoginRequired()
434 @LoginRequired()
434 @HasPermissionAllDecorator('hg.admin')
435 @HasPermissionAllDecorator('hg.admin')
435 @CSRFRequired()
436 @CSRFRequired()
436 def settings_issuetracker_test(self):
437 def settings_issuetracker_test(self):
437 error_container = []
438 error_container = []
438
439
439 urlified_commit = h.urlify_commit_message(
440 urlified_commit = h.urlify_commit_message(
440 self.request.POST.get('test_text', ''),
441 self.request.POST.get('test_text', ''),
441 'repo_group/test_repo1', error_container=error_container)
442 'repo_group/test_repo1', error_container=error_container)
442 if error_container:
443 if error_container:
443 def converter(inp):
444 def converter(inp):
444 return h.html_escape(unicode(inp))
445 return h.html_escape(unicode(inp))
445
446
446 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
447 return 'ERRORS: ' + '\n'.join(map(converter, error_container))
447
448
448 return urlified_commit
449 return urlified_commit
449
450
450 @LoginRequired()
451 @LoginRequired()
451 @HasPermissionAllDecorator('hg.admin')
452 @HasPermissionAllDecorator('hg.admin')
452 @CSRFRequired()
453 @CSRFRequired()
453 def settings_issuetracker_update(self):
454 def settings_issuetracker_update(self):
454 _ = self.request.translate
455 _ = self.request.translate
455 self.load_default_context()
456 self.load_default_context()
456 settings_model = IssueTrackerSettingsModel()
457 settings_model = IssueTrackerSettingsModel()
457
458
458 try:
459 try:
459 form = IssueTrackerPatternsForm(self.request.translate)()
460 form = IssueTrackerPatternsForm(self.request.translate)()
460 data = form.to_python(self.request.POST)
461 data = form.to_python(self.request.POST)
461 except formencode.Invalid as errors:
462 except formencode.Invalid as errors:
462 log.exception('Failed to add new pattern')
463 log.exception('Failed to add new pattern')
463 error = errors
464 error = errors
464 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
465 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
465 category='error')
466 category='error')
466 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
467 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
467
468
468 if data:
469 if data:
469 for uid in data.get('delete_patterns', []):
470 for uid in data.get('delete_patterns', []):
470 settings_model.delete_entries(uid)
471 settings_model.delete_entries(uid)
471
472
472 for pattern in data.get('patterns', []):
473 for pattern in data.get('patterns', []):
473 for setting, value, type_ in pattern:
474 for setting, value, type_ in pattern:
474 sett = settings_model.create_or_update_setting(
475 sett = settings_model.create_or_update_setting(
475 setting, value, type_)
476 setting, value, type_)
476 Session().add(sett)
477 Session().add(sett)
477
478
478 Session().commit()
479 Session().commit()
479
480
480 SettingsModel().invalidate_settings_cache()
481 SettingsModel().invalidate_settings_cache()
481 h.flash(_('Updated issue tracker entries'), category='success')
482 h.flash(_('Updated issue tracker entries'), category='success')
482 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
483 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
483
484
484 @LoginRequired()
485 @LoginRequired()
485 @HasPermissionAllDecorator('hg.admin')
486 @HasPermissionAllDecorator('hg.admin')
486 @CSRFRequired()
487 @CSRFRequired()
487 def settings_issuetracker_delete(self):
488 def settings_issuetracker_delete(self):
488 _ = self.request.translate
489 _ = self.request.translate
489 self.load_default_context()
490 self.load_default_context()
490 uid = self.request.POST.get('uid')
491 uid = self.request.POST.get('uid')
491 try:
492 try:
492 IssueTrackerSettingsModel().delete_entries(uid)
493 IssueTrackerSettingsModel().delete_entries(uid)
493 except Exception:
494 except Exception:
494 log.exception('Failed to delete issue tracker setting %s', uid)
495 log.exception('Failed to delete issue tracker setting %s', uid)
495 raise HTTPNotFound()
496 raise HTTPNotFound()
496
497
497 SettingsModel().invalidate_settings_cache()
498 SettingsModel().invalidate_settings_cache()
498 h.flash(_('Removed issue tracker entry.'), category='success')
499 h.flash(_('Removed issue tracker entry.'), category='success')
499
500
500 return {'deleted': uid}
501 return {'deleted': uid}
501
502
502 @LoginRequired()
503 @LoginRequired()
503 @HasPermissionAllDecorator('hg.admin')
504 @HasPermissionAllDecorator('hg.admin')
504 def settings_email(self):
505 def settings_email(self):
505 c = self.load_default_context()
506 c = self.load_default_context()
506 c.active = 'email'
507 c.active = 'email'
507 c.rhodecode_ini = rhodecode.CONFIG
508 c.rhodecode_ini = rhodecode.CONFIG
508
509
509 data = render('rhodecode:templates/admin/settings/settings.mako',
510 data = render('rhodecode:templates/admin/settings/settings.mako',
510 self._get_template_context(c), self.request)
511 self._get_template_context(c), self.request)
511 html = formencode.htmlfill.render(
512 html = formencode.htmlfill.render(
512 data,
513 data,
513 defaults=self._form_defaults(),
514 defaults=self._form_defaults(),
514 encoding="UTF-8",
515 encoding="UTF-8",
515 force_defaults=False
516 force_defaults=False
516 )
517 )
517 return Response(html)
518 return Response(html)
518
519
519 @LoginRequired()
520 @LoginRequired()
520 @HasPermissionAllDecorator('hg.admin')
521 @HasPermissionAllDecorator('hg.admin')
521 @CSRFRequired()
522 @CSRFRequired()
522 def settings_email_update(self):
523 def settings_email_update(self):
523 _ = self.request.translate
524 _ = self.request.translate
524 c = self.load_default_context()
525 c = self.load_default_context()
525 c.active = 'email'
526 c.active = 'email'
526
527
527 test_email = self.request.POST.get('test_email')
528 test_email = self.request.POST.get('test_email')
528
529
529 if not test_email:
530 if not test_email:
530 h.flash(_('Please enter email address'), category='error')
531 h.flash(_('Please enter email address'), category='error')
531 raise HTTPFound(h.route_path('admin_settings_email'))
532 raise HTTPFound(h.route_path('admin_settings_email'))
532
533
533 email_kwargs = {
534 email_kwargs = {
534 'date': datetime.datetime.now(),
535 'date': datetime.datetime.now(),
535 'user': self._rhodecode_db_user
536 'user': self._rhodecode_db_user
536 }
537 }
537
538
538 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
539 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
539 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
540 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
540
541
541 recipients = [test_email] if test_email else None
542 recipients = [test_email] if test_email else None
542
543
543 run_task(tasks.send_email, recipients, subject,
544 run_task(tasks.send_email, recipients, subject,
544 email_body_plaintext, email_body)
545 email_body_plaintext, email_body)
545
546
546 h.flash(_('Send email task created'), category='success')
547 h.flash(_('Send email task created'), category='success')
547 raise HTTPFound(h.route_path('admin_settings_email'))
548 raise HTTPFound(h.route_path('admin_settings_email'))
548
549
549 @LoginRequired()
550 @LoginRequired()
550 @HasPermissionAllDecorator('hg.admin')
551 @HasPermissionAllDecorator('hg.admin')
551 def settings_hooks(self):
552 def settings_hooks(self):
552 c = self.load_default_context()
553 c = self.load_default_context()
553 c.active = 'hooks'
554 c.active = 'hooks'
554
555
555 model = SettingsModel()
556 model = SettingsModel()
556 c.hooks = model.get_builtin_hooks()
557 c.hooks = model.get_builtin_hooks()
557 c.custom_hooks = model.get_custom_hooks()
558 c.custom_hooks = model.get_custom_hooks()
558
559
559 data = render('rhodecode:templates/admin/settings/settings.mako',
560 data = render('rhodecode:templates/admin/settings/settings.mako',
560 self._get_template_context(c), self.request)
561 self._get_template_context(c), self.request)
561 html = formencode.htmlfill.render(
562 html = formencode.htmlfill.render(
562 data,
563 data,
563 defaults=self._form_defaults(),
564 defaults=self._form_defaults(),
564 encoding="UTF-8",
565 encoding="UTF-8",
565 force_defaults=False
566 force_defaults=False
566 )
567 )
567 return Response(html)
568 return Response(html)
568
569
569 @LoginRequired()
570 @LoginRequired()
570 @HasPermissionAllDecorator('hg.admin')
571 @HasPermissionAllDecorator('hg.admin')
571 @CSRFRequired()
572 @CSRFRequired()
572 def settings_hooks_update(self):
573 def settings_hooks_update(self):
573 _ = self.request.translate
574 _ = self.request.translate
574 c = self.load_default_context()
575 c = self.load_default_context()
575 c.active = 'hooks'
576 c.active = 'hooks'
576 if c.visual.allow_custom_hooks_settings:
577 if c.visual.allow_custom_hooks_settings:
577 ui_key = self.request.POST.get('new_hook_ui_key')
578 ui_key = self.request.POST.get('new_hook_ui_key')
578 ui_value = self.request.POST.get('new_hook_ui_value')
579 ui_value = self.request.POST.get('new_hook_ui_value')
579
580
580 hook_id = self.request.POST.get('hook_id')
581 hook_id = self.request.POST.get('hook_id')
581 new_hook = False
582 new_hook = False
582
583
583 model = SettingsModel()
584 model = SettingsModel()
584 try:
585 try:
585 if ui_value and ui_key:
586 if ui_value and ui_key:
586 model.create_or_update_hook(ui_key, ui_value)
587 model.create_or_update_hook(ui_key, ui_value)
587 h.flash(_('Added new hook'), category='success')
588 h.flash(_('Added new hook'), category='success')
588 new_hook = True
589 new_hook = True
589 elif hook_id:
590 elif hook_id:
590 RhodeCodeUi.delete(hook_id)
591 RhodeCodeUi.delete(hook_id)
591 Session().commit()
592 Session().commit()
592
593
593 # check for edits
594 # check for edits
594 update = False
595 update = False
595 _d = self.request.POST.dict_of_lists()
596 _d = self.request.POST.dict_of_lists()
596 for k, v in zip(_d.get('hook_ui_key', []),
597 for k, v in zip(_d.get('hook_ui_key', []),
597 _d.get('hook_ui_value_new', [])):
598 _d.get('hook_ui_value_new', [])):
598 model.create_or_update_hook(k, v)
599 model.create_or_update_hook(k, v)
599 update = True
600 update = True
600
601
601 if update and not new_hook:
602 if update and not new_hook:
602 h.flash(_('Updated hooks'), category='success')
603 h.flash(_('Updated hooks'), category='success')
603 Session().commit()
604 Session().commit()
604 except Exception:
605 except Exception:
605 log.exception("Exception during hook creation")
606 log.exception("Exception during hook creation")
606 h.flash(_('Error occurred during hook creation'),
607 h.flash(_('Error occurred during hook creation'),
607 category='error')
608 category='error')
608
609
609 raise HTTPFound(h.route_path('admin_settings_hooks'))
610 raise HTTPFound(h.route_path('admin_settings_hooks'))
610
611
611 @LoginRequired()
612 @LoginRequired()
612 @HasPermissionAllDecorator('hg.admin')
613 @HasPermissionAllDecorator('hg.admin')
613 def settings_search(self):
614 def settings_search(self):
614 c = self.load_default_context()
615 c = self.load_default_context()
615 c.active = 'search'
616 c.active = 'search'
616
617
617 c.searcher = searcher_from_config(self.request.registry.settings)
618 c.searcher = searcher_from_config(self.request.registry.settings)
618 c.statistics = c.searcher.statistics(self.request.translate)
619 c.statistics = c.searcher.statistics(self.request.translate)
619
620
620 return self._get_template_context(c)
621 return self._get_template_context(c)
621
622
622 @LoginRequired()
623 @LoginRequired()
623 @HasPermissionAllDecorator('hg.admin')
624 @HasPermissionAllDecorator('hg.admin')
624 def settings_automation(self):
625 def settings_automation(self):
625 c = self.load_default_context()
626 c = self.load_default_context()
626 c.active = 'automation'
627 c.active = 'automation'
627
628
628 return self._get_template_context(c)
629 return self._get_template_context(c)
629
630
630 @LoginRequired()
631 @LoginRequired()
631 @HasPermissionAllDecorator('hg.admin')
632 @HasPermissionAllDecorator('hg.admin')
632 def settings_labs(self):
633 def settings_labs(self):
633 c = self.load_default_context()
634 c = self.load_default_context()
634 if not c.labs_active:
635 if not c.labs_active:
635 raise HTTPFound(h.route_path('admin_settings'))
636 raise HTTPFound(h.route_path('admin_settings'))
636
637
637 c.active = 'labs'
638 c.active = 'labs'
638 c.lab_settings = _LAB_SETTINGS
639 c.lab_settings = _LAB_SETTINGS
639
640
640 data = render('rhodecode:templates/admin/settings/settings.mako',
641 data = render('rhodecode:templates/admin/settings/settings.mako',
641 self._get_template_context(c), self.request)
642 self._get_template_context(c), self.request)
642 html = formencode.htmlfill.render(
643 html = formencode.htmlfill.render(
643 data,
644 data,
644 defaults=self._form_defaults(),
645 defaults=self._form_defaults(),
645 encoding="UTF-8",
646 encoding="UTF-8",
646 force_defaults=False
647 force_defaults=False
647 )
648 )
648 return Response(html)
649 return Response(html)
649
650
650 @LoginRequired()
651 @LoginRequired()
651 @HasPermissionAllDecorator('hg.admin')
652 @HasPermissionAllDecorator('hg.admin')
652 @CSRFRequired()
653 @CSRFRequired()
653 def settings_labs_update(self):
654 def settings_labs_update(self):
654 _ = self.request.translate
655 _ = self.request.translate
655 c = self.load_default_context()
656 c = self.load_default_context()
656 c.active = 'labs'
657 c.active = 'labs'
657
658
658 application_form = LabsSettingsForm(self.request.translate)()
659 application_form = LabsSettingsForm(self.request.translate)()
659 try:
660 try:
660 form_result = application_form.to_python(dict(self.request.POST))
661 form_result = application_form.to_python(dict(self.request.POST))
661 except formencode.Invalid as errors:
662 except formencode.Invalid as errors:
662 h.flash(
663 h.flash(
663 _("Some form inputs contain invalid data."),
664 _("Some form inputs contain invalid data."),
664 category='error')
665 category='error')
665 data = render('rhodecode:templates/admin/settings/settings.mako',
666 data = render('rhodecode:templates/admin/settings/settings.mako',
666 self._get_template_context(c), self.request)
667 self._get_template_context(c), self.request)
667 html = formencode.htmlfill.render(
668 html = formencode.htmlfill.render(
668 data,
669 data,
669 defaults=errors.value,
670 defaults=errors.value,
670 errors=errors.error_dict or {},
671 errors=errors.error_dict or {},
671 prefix_error=False,
672 prefix_error=False,
672 encoding="UTF-8",
673 encoding="UTF-8",
673 force_defaults=False
674 force_defaults=False
674 )
675 )
675 return Response(html)
676 return Response(html)
676
677
677 try:
678 try:
678 session = Session()
679 session = Session()
679 for setting in _LAB_SETTINGS:
680 for setting in _LAB_SETTINGS:
680 setting_name = setting.key[len('rhodecode_'):]
681 setting_name = setting.key[len('rhodecode_'):]
681 sett = SettingsModel().create_or_update_setting(
682 sett = SettingsModel().create_or_update_setting(
682 setting_name, form_result[setting.key], setting.type)
683 setting_name, form_result[setting.key], setting.type)
683 session.add(sett)
684 session.add(sett)
684
685
685 except Exception:
686 except Exception:
686 log.exception('Exception while updating lab settings')
687 log.exception('Exception while updating lab settings')
687 h.flash(_('Error occurred during updating labs settings'),
688 h.flash(_('Error occurred during updating labs settings'),
688 category='error')
689 category='error')
689 else:
690 else:
690 Session().commit()
691 Session().commit()
691 SettingsModel().invalidate_settings_cache()
692 SettingsModel().invalidate_settings_cache()
692 h.flash(_('Updated Labs settings'), category='success')
693 h.flash(_('Updated Labs settings'), category='success')
693 raise HTTPFound(h.route_path('admin_settings_labs'))
694 raise HTTPFound(h.route_path('admin_settings_labs'))
694
695
695 data = render('rhodecode:templates/admin/settings/settings.mako',
696 data = render('rhodecode:templates/admin/settings/settings.mako',
696 self._get_template_context(c), self.request)
697 self._get_template_context(c), self.request)
697 html = formencode.htmlfill.render(
698 html = formencode.htmlfill.render(
698 data,
699 data,
699 defaults=self._form_defaults(),
700 defaults=self._form_defaults(),
700 encoding="UTF-8",
701 encoding="UTF-8",
701 force_defaults=False
702 force_defaults=False
702 )
703 )
703 return Response(html)
704 return Response(html)
704
705
705
706
706 # :param key: name of the setting including the 'rhodecode_' prefix
707 # :param key: name of the setting including the 'rhodecode_' prefix
707 # :param type: the RhodeCodeSetting type to use.
708 # :param type: the RhodeCodeSetting type to use.
708 # :param group: the i18ned group in which we should dispaly this setting
709 # :param group: the i18ned group in which we should dispaly this setting
709 # :param label: the i18ned label we should display for this setting
710 # :param label: the i18ned label we should display for this setting
710 # :param help: the i18ned help we should dispaly for this setting
711 # :param help: the i18ned help we should dispaly for this setting
711 LabSetting = collections.namedtuple(
712 LabSetting = collections.namedtuple(
712 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
713 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
713
714
714
715
715 # This list has to be kept in sync with the form
716 # This list has to be kept in sync with the form
716 # rhodecode.model.forms.LabsSettingsForm.
717 # rhodecode.model.forms.LabsSettingsForm.
717 _LAB_SETTINGS = [
718 _LAB_SETTINGS = [
718
719
719 ]
720 ]
@@ -1,293 +1,289 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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 import string
22 import string
23 import time
23 import time
24
24
25 import rhodecode
25 import rhodecode
26
26
27
27
28
28
29 from rhodecode.lib.view_utils import get_format_ref_id
29 from rhodecode.lib.view_utils import get_format_ref_id
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.lib import helpers as h, rc_cache
32 from rhodecode.lib import helpers as h, rc_cache
33 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.utils2 import safe_str, safe_int
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.db import Statistics, CacheKey, User
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
50 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
51 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
53 return c
54
54
55 def _load_commits_context(self, c):
55 def _load_commits_context(self, c):
56 p = safe_int(self.request.GET.get('page'), 1)
56 p = safe_int(self.request.GET.get('page'), 1)
57 size = safe_int(self.request.GET.get('size'), 10)
57 size = safe_int(self.request.GET.get('size'), 10)
58
58
59 def url_generator(page_num):
59 def url_generator(page_num):
60 query_params = {
60 query_params = {
61 'page': page_num,
61 'page': page_num,
62 'size': size
62 'size': size
63 }
63 }
64 return h.route_path(
64 return h.route_path(
65 'repo_summary_commits',
65 'repo_summary_commits',
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67
67
68 pre_load = ['author', 'branch', 'date', 'message']
68 pre_load = ['author', 'branch', 'date', 'message']
69 try:
69 try:
70 collection = self.rhodecode_vcs_repo.get_commits(
70 collection = self.rhodecode_vcs_repo.get_commits(
71 pre_load=pre_load, translate_tags=False)
71 pre_load=pre_load, translate_tags=False)
72 except EmptyRepositoryError:
72 except EmptyRepositoryError:
73 collection = self.rhodecode_vcs_repo
73 collection = self.rhodecode_vcs_repo
74
74
75 c.repo_commits = h.RepoPage(
75 c.repo_commits = h.RepoPage(
76 collection, page=p, items_per_page=size, url_maker=url_generator)
76 collection, page=p, items_per_page=size, url_maker=url_generator)
77 page_ids = [x.raw_id for x in c.repo_commits]
77 page_ids = [x.raw_id for x in c.repo_commits]
78 c.comments = self.db_repo.get_comments(page_ids)
78 c.comments = self.db_repo.get_comments(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
80
80
81 def _prepare_and_set_clone_url(self, c):
81 def _prepare_and_set_clone_url(self, c):
82 username = ''
82 username = ''
83 if self._rhodecode_user.username != User.DEFAULT_USER:
83 if self._rhodecode_user.username != User.DEFAULT_USER:
84 username = safe_str(self._rhodecode_user.username)
84 username = safe_str(self._rhodecode_user.username)
85
85
86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
86 _def_clone_uri = c.clone_uri_tmpl
87 _def_clone_uri_id = c.clone_uri_id_tmpl
87 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
88 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
88
89
89 if '{repo}' in _def_clone_uri:
90 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
91 elif '{repoid}' in _def_clone_uri:
92 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
93
94 c.clone_repo_url = self.db_repo.clone_url(
90 c.clone_repo_url = self.db_repo.clone_url(
95 user=username, uri_tmpl=_def_clone_uri)
91 user=username, uri_tmpl=_def_clone_uri)
96 c.clone_repo_url_id = self.db_repo.clone_url(
92 c.clone_repo_url_id = self.db_repo.clone_url(
97 user=username, uri_tmpl=_def_clone_uri_id)
93 user=username, uri_tmpl=_def_clone_uri_id)
98 c.clone_repo_url_ssh = self.db_repo.clone_url(
94 c.clone_repo_url_ssh = self.db_repo.clone_url(
99 uri_tmpl=_def_clone_uri_ssh, ssh=True)
95 uri_tmpl=_def_clone_uri_ssh, ssh=True)
100
96
101 @LoginRequired()
97 @LoginRequired()
102 @HasRepoPermissionAnyDecorator(
98 @HasRepoPermissionAnyDecorator(
103 'repository.read', 'repository.write', 'repository.admin')
99 'repository.read', 'repository.write', 'repository.admin')
104 def summary_commits(self):
100 def summary_commits(self):
105 c = self.load_default_context()
101 c = self.load_default_context()
106 self._prepare_and_set_clone_url(c)
102 self._prepare_and_set_clone_url(c)
107 self._load_commits_context(c)
103 self._load_commits_context(c)
108 return self._get_template_context(c)
104 return self._get_template_context(c)
109
105
110 @LoginRequired()
106 @LoginRequired()
111 @HasRepoPermissionAnyDecorator(
107 @HasRepoPermissionAnyDecorator(
112 'repository.read', 'repository.write', 'repository.admin')
108 'repository.read', 'repository.write', 'repository.admin')
113 def summary(self):
109 def summary(self):
114 c = self.load_default_context()
110 c = self.load_default_context()
115
111
116 # Prepare the clone URL
112 # Prepare the clone URL
117 self._prepare_and_set_clone_url(c)
113 self._prepare_and_set_clone_url(c)
118
114
119 # If enabled, get statistics data
115 # If enabled, get statistics data
120 c.show_stats = bool(self.db_repo.enable_statistics)
116 c.show_stats = bool(self.db_repo.enable_statistics)
121
117
122 stats = Session().query(Statistics) \
118 stats = Session().query(Statistics) \
123 .filter(Statistics.repository == self.db_repo) \
119 .filter(Statistics.repository == self.db_repo) \
124 .scalar()
120 .scalar()
125
121
126 c.stats_percentage = 0
122 c.stats_percentage = 0
127
123
128 if stats and stats.languages:
124 if stats and stats.languages:
129 c.no_data = False is self.db_repo.enable_statistics
125 c.no_data = False is self.db_repo.enable_statistics
130 lang_stats_d = json.loads(stats.languages)
126 lang_stats_d = json.loads(stats.languages)
131
127
132 # Sort first by decreasing count and second by the file extension,
128 # Sort first by decreasing count and second by the file extension,
133 # so we have a consistent output.
129 # so we have a consistent output.
134 lang_stats_items = sorted(lang_stats_d.iteritems(),
130 lang_stats_items = sorted(lang_stats_d.iteritems(),
135 key=lambda k: (-k[1], k[0]))[:10]
131 key=lambda k: (-k[1], k[0]))[:10]
136 lang_stats = [(x, {"count": y,
132 lang_stats = [(x, {"count": y,
137 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
133 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
138 for x, y in lang_stats_items]
134 for x, y in lang_stats_items]
139
135
140 c.trending_languages = json.dumps(lang_stats)
136 c.trending_languages = json.dumps(lang_stats)
141 else:
137 else:
142 c.no_data = True
138 c.no_data = True
143 c.trending_languages = json.dumps({})
139 c.trending_languages = json.dumps({})
144
140
145 scm_model = ScmModel()
141 scm_model = ScmModel()
146 c.enable_downloads = self.db_repo.enable_downloads
142 c.enable_downloads = self.db_repo.enable_downloads
147 c.repository_followers = scm_model.get_followers(self.db_repo)
143 c.repository_followers = scm_model.get_followers(self.db_repo)
148 c.repository_forks = scm_model.get_forks(self.db_repo)
144 c.repository_forks = scm_model.get_forks(self.db_repo)
149
145
150 # first interaction with the VCS instance after here...
146 # first interaction with the VCS instance after here...
151 if c.repository_requirements_missing:
147 if c.repository_requirements_missing:
152 self.request.override_renderer = \
148 self.request.override_renderer = \
153 'rhodecode:templates/summary/missing_requirements.mako'
149 'rhodecode:templates/summary/missing_requirements.mako'
154 return self._get_template_context(c)
150 return self._get_template_context(c)
155
151
156 c.readme_data, c.readme_file = \
152 c.readme_data, c.readme_file = \
157 self._get_readme_data(self.db_repo, c.visual.default_renderer)
153 self._get_readme_data(self.db_repo, c.visual.default_renderer)
158
154
159 # loads the summary commits template context
155 # loads the summary commits template context
160 self._load_commits_context(c)
156 self._load_commits_context(c)
161
157
162 return self._get_template_context(c)
158 return self._get_template_context(c)
163
159
164 @LoginRequired()
160 @LoginRequired()
165 @HasRepoPermissionAnyDecorator(
161 @HasRepoPermissionAnyDecorator(
166 'repository.read', 'repository.write', 'repository.admin')
162 'repository.read', 'repository.write', 'repository.admin')
167 def repo_stats(self):
163 def repo_stats(self):
168 show_stats = bool(self.db_repo.enable_statistics)
164 show_stats = bool(self.db_repo.enable_statistics)
169 repo_id = self.db_repo.repo_id
165 repo_id = self.db_repo.repo_id
170
166
171 landing_commit = self.db_repo.get_landing_commit()
167 landing_commit = self.db_repo.get_landing_commit()
172 if isinstance(landing_commit, EmptyCommit):
168 if isinstance(landing_commit, EmptyCommit):
173 return {'size': 0, 'code_stats': {}}
169 return {'size': 0, 'code_stats': {}}
174
170
175 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
171 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
176 cache_on = cache_seconds > 0
172 cache_on = cache_seconds > 0
177
173
178 log.debug(
174 log.debug(
179 'Computing REPO STATS for repo_id %s commit_id `%s` '
175 'Computing REPO STATS for repo_id %s commit_id `%s` '
180 'with caching: %s[TTL: %ss]' % (
176 'with caching: %s[TTL: %ss]' % (
181 repo_id, landing_commit, cache_on, cache_seconds or 0))
177 repo_id, landing_commit, cache_on, cache_seconds or 0))
182
178
183 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
179 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
184 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
180 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
185
181
186 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
182 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
187 condition=cache_on)
183 condition=cache_on)
188 def compute_stats(repo_id, commit_id, _show_stats):
184 def compute_stats(repo_id, commit_id, _show_stats):
189 code_stats = {}
185 code_stats = {}
190 size = 0
186 size = 0
191 try:
187 try:
192 commit = self.db_repo.get_commit(commit_id)
188 commit = self.db_repo.get_commit(commit_id)
193
189
194 for node in commit.get_filenodes_generator():
190 for node in commit.get_filenodes_generator():
195 size += node.size
191 size += node.size
196 if not _show_stats:
192 if not _show_stats:
197 continue
193 continue
198 ext = string.lower(node.extension)
194 ext = string.lower(node.extension)
199 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
195 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
200 if ext_info:
196 if ext_info:
201 if ext in code_stats:
197 if ext in code_stats:
202 code_stats[ext]['count'] += 1
198 code_stats[ext]['count'] += 1
203 else:
199 else:
204 code_stats[ext] = {"count": 1, "desc": ext_info}
200 code_stats[ext] = {"count": 1, "desc": ext_info}
205 except (EmptyRepositoryError, CommitDoesNotExistError):
201 except (EmptyRepositoryError, CommitDoesNotExistError):
206 pass
202 pass
207 return {'size': h.format_byte_size_binary(size),
203 return {'size': h.format_byte_size_binary(size),
208 'code_stats': code_stats}
204 'code_stats': code_stats}
209
205
210 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
206 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
211 return stats
207 return stats
212
208
213 @LoginRequired()
209 @LoginRequired()
214 @HasRepoPermissionAnyDecorator(
210 @HasRepoPermissionAnyDecorator(
215 'repository.read', 'repository.write', 'repository.admin')
211 'repository.read', 'repository.write', 'repository.admin')
216 def repo_refs_data(self):
212 def repo_refs_data(self):
217 _ = self.request.translate
213 _ = self.request.translate
218 self.load_default_context()
214 self.load_default_context()
219
215
220 repo = self.rhodecode_vcs_repo
216 repo = self.rhodecode_vcs_repo
221 refs_to_create = [
217 refs_to_create = [
222 (_("Branch"), repo.branches, 'branch'),
218 (_("Branch"), repo.branches, 'branch'),
223 (_("Tag"), repo.tags, 'tag'),
219 (_("Tag"), repo.tags, 'tag'),
224 (_("Bookmark"), repo.bookmarks, 'book'),
220 (_("Bookmark"), repo.bookmarks, 'book'),
225 ]
221 ]
226 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
222 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
227 data = {
223 data = {
228 'more': False,
224 'more': False,
229 'results': res
225 'results': res
230 }
226 }
231 return data
227 return data
232
228
233 @LoginRequired()
229 @LoginRequired()
234 @HasRepoPermissionAnyDecorator(
230 @HasRepoPermissionAnyDecorator(
235 'repository.read', 'repository.write', 'repository.admin')
231 'repository.read', 'repository.write', 'repository.admin')
236 def repo_refs_changelog_data(self):
232 def repo_refs_changelog_data(self):
237 _ = self.request.translate
233 _ = self.request.translate
238 self.load_default_context()
234 self.load_default_context()
239
235
240 repo = self.rhodecode_vcs_repo
236 repo = self.rhodecode_vcs_repo
241
237
242 refs_to_create = [
238 refs_to_create = [
243 (_("Branches"), repo.branches, 'branch'),
239 (_("Branches"), repo.branches, 'branch'),
244 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
240 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
245 # TODO: enable when vcs can handle bookmarks filters
241 # TODO: enable when vcs can handle bookmarks filters
246 # (_("Bookmarks"), repo.bookmarks, "book"),
242 # (_("Bookmarks"), repo.bookmarks, "book"),
247 ]
243 ]
248 res = self._create_reference_data(
244 res = self._create_reference_data(
249 repo, self.db_repo_name, refs_to_create)
245 repo, self.db_repo_name, refs_to_create)
250 data = {
246 data = {
251 'more': False,
247 'more': False,
252 'results': res
248 'results': res
253 }
249 }
254 return data
250 return data
255
251
256 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
252 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
257 format_ref_id = get_format_ref_id(repo)
253 format_ref_id = get_format_ref_id(repo)
258
254
259 result = []
255 result = []
260 for title, refs, ref_type in refs_to_create:
256 for title, refs, ref_type in refs_to_create:
261 if refs:
257 if refs:
262 result.append({
258 result.append({
263 'text': title,
259 'text': title,
264 'children': self._create_reference_items(
260 'children': self._create_reference_items(
265 repo, full_repo_name, refs, ref_type,
261 repo, full_repo_name, refs, ref_type,
266 format_ref_id),
262 format_ref_id),
267 })
263 })
268 return result
264 return result
269
265
270 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
266 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
271 result = []
267 result = []
272 is_svn = h.is_svn(repo)
268 is_svn = h.is_svn(repo)
273 for ref_name, raw_id in refs.iteritems():
269 for ref_name, raw_id in refs.iteritems():
274 files_url = self._create_files_url(
270 files_url = self._create_files_url(
275 repo, full_repo_name, ref_name, raw_id, is_svn)
271 repo, full_repo_name, ref_name, raw_id, is_svn)
276 result.append({
272 result.append({
277 'text': ref_name,
273 'text': ref_name,
278 'id': format_ref_id(ref_name, raw_id),
274 'id': format_ref_id(ref_name, raw_id),
279 'raw_id': raw_id,
275 'raw_id': raw_id,
280 'type': ref_type,
276 'type': ref_type,
281 'files_url': files_url,
277 'files_url': files_url,
282 'idx': 0,
278 'idx': 0,
283 })
279 })
284 return result
280 return result
285
281
286 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
282 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
287 use_commit_id = '/' in ref_name or is_svn
283 use_commit_id = '/' in ref_name or is_svn
288 return h.route_path(
284 return h.route_path(
289 'repo_files',
285 'repo_files',
290 repo_name=full_repo_name,
286 repo_name=full_repo_name,
291 f_path=ref_name if is_svn else '',
287 f_path=ref_name if is_svn else '',
292 commit_id=raw_id if use_commit_id else ref_name,
288 commit_id=raw_id if use_commit_id else ref_name,
293 _query=dict(at=ref_name))
289 _query=dict(at=ref_name))
@@ -1,626 +1,627 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _filter_proxy(ip):
54 def _filter_proxy(ip):
55 """
55 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
57 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
58 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
59 We only care about the first IP which came from the org. client.
60
60
61 :param ip: ip string from headers
61 :param ip: ip string from headers
62 """
62 """
63 if ',' in ip:
63 if ',' in ip:
64 _ips = ip.split(',')
64 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
65 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
67 return _first_ip
68 return ip
68 return ip
69
69
70
70
71 def _filter_port(ip):
71 def _filter_port(ip):
72 """
72 """
73 Removes a port from ip, there are 4 main cases to handle here.
73 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
74 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
75 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
76 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
77 - ipv6+port eg. [::1]:8080
78
78
79 :param ip:
79 :param ip:
80 """
80 """
81 def is_ipv6(ip_addr):
81 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
82 if hasattr(socket, 'inet_pton'):
83 try:
83 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
85 except socket.error:
86 return False
86 return False
87 else:
87 else:
88 # fallback to ipaddress
88 # fallback to ipaddress
89 try:
89 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
91 except Exception:
92 return False
92 return False
93 return True
93 return True
94
94
95 if ':' not in ip: # must be ipv4 pure ip
95 if ':' not in ip: # must be ipv4 pure ip
96 return ip
96 return ip
97
97
98 if '[' in ip and ']' in ip: # ipv6 with port
98 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
99 return ip.split(']')[0][1:].lower()
100
100
101 # must be ipv6 or ipv4 with port
101 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
102 if is_ipv6(ip):
103 return ip
103 return ip
104 else:
104 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
106 return ip
107
107
108
108
109 def get_ip_addr(environ):
109 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
112 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
113 _filters = lambda x: _filter_port(_filter_proxy(x))
114
114
115 ip = environ.get(proxy_key)
115 ip = environ.get(proxy_key)
116 if ip:
116 if ip:
117 return _filters(ip)
117 return _filters(ip)
118
118
119 ip = environ.get(proxy_key2)
119 ip = environ.get(proxy_key2)
120 if ip:
120 if ip:
121 return _filters(ip)
121 return _filters(ip)
122
122
123 ip = environ.get(def_key, '0.0.0.0')
123 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
124 return _filters(ip)
125
125
126
126
127 def get_server_ip_addr(environ, log_errors=True):
127 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
128 hostname = environ.get('SERVER_NAME')
129 try:
129 try:
130 return socket.gethostbyname(hostname)
130 return socket.gethostbyname(hostname)
131 except Exception as e:
131 except Exception as e:
132 if log_errors:
132 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
133 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
134 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
135 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
136 return hostname
137
137
138
138
139 def get_server_port(environ):
139 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
140 return environ.get('SERVER_PORT')
141
141
142
142
143 def get_access_path(environ):
143 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
144 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
145 org_req = environ.get('pylons.original_request')
146 if org_req:
146 if org_req:
147 path = org_req.environ.get('PATH_INFO')
147 path = org_req.environ.get('PATH_INFO')
148 return path
148 return path
149
149
150
150
151 def get_user_agent(environ):
151 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
152 return environ.get('HTTP_USER_AGENT')
153
153
154
154
155 def vcs_operation_context(
155 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
156 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
158 """
159 Generate the context for a vcs operation, e.g. push or pull.
159 Generate the context for a vcs operation, e.g. push or pull.
160
160
161 This context is passed over the layers so that hooks triggered by the
161 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
162 vcs operation know details like the user, the user's IP address etc.
163
163
164 :param check_locking: Allows to switch of the computation of the locking
164 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
165 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
166 able to disable this for certain operations.
167
167
168 """
168 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
169 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
170 make_lock = None
171 locked_by = [None, None, None]
171 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
172 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
173 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
174 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
175 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
177 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
178 action, user.user_id)
179 user_id = user.user_id
179 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
180 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
181 ui_settings = settings_model.get_ui_settings()
182
182
183 # NOTE(marcink): This should be also in sync with
183 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
185 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
186 repo_store = ''
187 if store:
187 if store:
188 repo_store = store[0].value
188 repo_store = store[0].value
189
189
190 scm_data = {
190 scm_data = {
191 'ip': get_ip_addr(environ),
191 'ip': get_ip_addr(environ),
192 'username': username,
192 'username': username,
193 'user_id': user_id,
193 'user_id': user_id,
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 'repo_store': repo_store,
198 'repo_store': repo_store,
199 'make_lock': make_lock,
199 'make_lock': make_lock,
200 'locked_by': locked_by,
200 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
201 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
202 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
205 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
206 'check_branch_perms': check_branch_perms,
207 }
207 }
208 return scm_data
208 return scm_data
209
209
210
210
211 class BasicAuth(AuthBasicAuthenticator):
211 class BasicAuth(AuthBasicAuthenticator):
212
212
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
215 self.realm = realm
215 self.realm = realm
216 self.rc_realm = rc_realm
216 self.rc_realm = rc_realm
217 self.initial_call = initial_call_detection
217 self.initial_call = initial_call_detection
218 self.authfunc = authfunc
218 self.authfunc = authfunc
219 self.registry = registry
219 self.registry = registry
220 self.acl_repo_name = acl_repo_name
220 self.acl_repo_name = acl_repo_name
221 self._rc_auth_http_code = auth_http_code
221 self._rc_auth_http_code = auth_http_code
222
222
223 def _get_response_from_code(self, http_code):
223 def _get_response_from_code(self, http_code):
224 try:
224 try:
225 return get_exception(safe_int(http_code))
225 return get_exception(safe_int(http_code))
226 except Exception:
226 except Exception:
227 log.exception('Failed to fetch response for code %s', http_code)
227 log.exception('Failed to fetch response for code %s', http_code)
228 return HTTPForbidden
228 return HTTPForbidden
229
229
230 def get_rc_realm(self):
230 def get_rc_realm(self):
231 return safe_str(self.rc_realm)
231 return safe_str(self.rc_realm)
232
232
233 def build_authentication(self):
233 def build_authentication(self):
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 if self._rc_auth_http_code and not self.initial_call:
235 if self._rc_auth_http_code and not self.initial_call:
236 # return alternative HTTP code if alternative http return code
236 # return alternative HTTP code if alternative http return code
237 # is specified in RhodeCode config, but ONLY if it's not the
237 # is specified in RhodeCode config, but ONLY if it's not the
238 # FIRST call
238 # FIRST call
239 custom_response_klass = self._get_response_from_code(
239 custom_response_klass = self._get_response_from_code(
240 self._rc_auth_http_code)
240 self._rc_auth_http_code)
241 return custom_response_klass(headers=head)
241 return custom_response_klass(headers=head)
242 return HTTPUnauthorized(headers=head)
242 return HTTPUnauthorized(headers=head)
243
243
244 def authenticate(self, environ):
244 def authenticate(self, environ):
245 authorization = AUTHORIZATION(environ)
245 authorization = AUTHORIZATION(environ)
246 if not authorization:
246 if not authorization:
247 return self.build_authentication()
247 return self.build_authentication()
248 (authmeth, auth) = authorization.split(' ', 1)
248 (authmeth, auth) = authorization.split(' ', 1)
249 if 'basic' != authmeth.lower():
249 if 'basic' != authmeth.lower():
250 return self.build_authentication()
250 return self.build_authentication()
251 auth = auth.strip().decode('base64')
251 auth = auth.strip().decode('base64')
252 _parts = auth.split(':', 1)
252 _parts = auth.split(':', 1)
253 if len(_parts) == 2:
253 if len(_parts) == 2:
254 username, password = _parts
254 username, password = _parts
255 auth_data = self.authfunc(
255 auth_data = self.authfunc(
256 username, password, environ, VCS_TYPE,
256 username, password, environ, VCS_TYPE,
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 if auth_data:
258 if auth_data:
259 return {'username': username, 'auth_data': auth_data}
259 return {'username': username, 'auth_data': auth_data}
260 if username and password:
260 if username and password:
261 # we mark that we actually executed authentication once, at
261 # we mark that we actually executed authentication once, at
262 # that point we can use the alternative auth code
262 # that point we can use the alternative auth code
263 self.initial_call = False
263 self.initial_call = False
264
264
265 return self.build_authentication()
265 return self.build_authentication()
266
266
267 __call__ = authenticate
267 __call__ = authenticate
268
268
269
269
270 def calculate_version_hash(config):
270 def calculate_version_hash(config):
271 return sha1(
271 return sha1(
272 config.get('beaker.session.secret', '') +
272 config.get('beaker.session.secret', '') +
273 rhodecode.__version__)[:8]
273 rhodecode.__version__)[:8]
274
274
275
275
276 def get_current_lang(request):
276 def get_current_lang(request):
277 # NOTE(marcink): remove after pyramid move
277 # NOTE(marcink): remove after pyramid move
278 try:
278 try:
279 return translation.get_lang()[0]
279 return translation.get_lang()[0]
280 except:
280 except:
281 pass
281 pass
282
282
283 return getattr(request, '_LOCALE_', request.locale_name)
283 return getattr(request, '_LOCALE_', request.locale_name)
284
284
285
285
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 """
287 """
288 Attach variables into template context called `c`.
288 Attach variables into template context called `c`.
289 """
289 """
290 config = request.registry.settings
290 config = request.registry.settings
291
291
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 context.rc_config = rc_config
293 context.rc_config = rc_config
294 context.rhodecode_version = rhodecode.__version__
294 context.rhodecode_version = rhodecode.__version__
295 context.rhodecode_edition = config.get('rhodecode.edition')
295 context.rhodecode_edition = config.get('rhodecode.edition')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
297 # unique secret + version does not leak the version but keep consistency
297 # unique secret + version does not leak the version but keep consistency
298 context.rhodecode_version_hash = calculate_version_hash(config)
298 context.rhodecode_version_hash = calculate_version_hash(config)
299
299
300 # Default language set for the incoming request
300 # Default language set for the incoming request
301 context.language = get_current_lang(request)
301 context.language = get_current_lang(request)
302
302
303 # Visual options
303 # Visual options
304 context.visual = AttributeDict({})
304 context.visual = AttributeDict({})
305
305
306 # DB stored Visual Items
306 # DB stored Visual Items
307 context.visual.show_public_icon = str2bool(
307 context.visual.show_public_icon = str2bool(
308 rc_config.get('rhodecode_show_public_icon'))
308 rc_config.get('rhodecode_show_public_icon'))
309 context.visual.show_private_icon = str2bool(
309 context.visual.show_private_icon = str2bool(
310 rc_config.get('rhodecode_show_private_icon'))
310 rc_config.get('rhodecode_show_private_icon'))
311 context.visual.stylify_metatags = str2bool(
311 context.visual.stylify_metatags = str2bool(
312 rc_config.get('rhodecode_stylify_metatags'))
312 rc_config.get('rhodecode_stylify_metatags'))
313 context.visual.dashboard_items = safe_int(
313 context.visual.dashboard_items = safe_int(
314 rc_config.get('rhodecode_dashboard_items', 100))
314 rc_config.get('rhodecode_dashboard_items', 100))
315 context.visual.admin_grid_items = safe_int(
315 context.visual.admin_grid_items = safe_int(
316 rc_config.get('rhodecode_admin_grid_items', 100))
316 rc_config.get('rhodecode_admin_grid_items', 100))
317 context.visual.show_revision_number = str2bool(
317 context.visual.show_revision_number = str2bool(
318 rc_config.get('rhodecode_show_revision_number', True))
318 rc_config.get('rhodecode_show_revision_number', True))
319 context.visual.show_sha_length = safe_int(
319 context.visual.show_sha_length = safe_int(
320 rc_config.get('rhodecode_show_sha_length', 100))
320 rc_config.get('rhodecode_show_sha_length', 100))
321 context.visual.repository_fields = str2bool(
321 context.visual.repository_fields = str2bool(
322 rc_config.get('rhodecode_repository_fields'))
322 rc_config.get('rhodecode_repository_fields'))
323 context.visual.show_version = str2bool(
323 context.visual.show_version = str2bool(
324 rc_config.get('rhodecode_show_version'))
324 rc_config.get('rhodecode_show_version'))
325 context.visual.use_gravatar = str2bool(
325 context.visual.use_gravatar = str2bool(
326 rc_config.get('rhodecode_use_gravatar'))
326 rc_config.get('rhodecode_use_gravatar'))
327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
328 context.visual.default_renderer = rc_config.get(
328 context.visual.default_renderer = rc_config.get(
329 'rhodecode_markup_renderer', 'rst')
329 'rhodecode_markup_renderer', 'rst')
330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
331 context.visual.rhodecode_support_url = \
331 context.visual.rhodecode_support_url = \
332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
333
333
334 context.visual.affected_files_cut_off = 60
334 context.visual.affected_files_cut_off = 60
335
335
336 context.pre_code = rc_config.get('rhodecode_pre_code')
336 context.pre_code = rc_config.get('rhodecode_pre_code')
337 context.post_code = rc_config.get('rhodecode_post_code')
337 context.post_code = rc_config.get('rhodecode_post_code')
338 context.rhodecode_name = rc_config.get('rhodecode_title')
338 context.rhodecode_name = rc_config.get('rhodecode_title')
339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
340 # if we have specified default_encoding in the request, it has more
340 # if we have specified default_encoding in the request, it has more
341 # priority
341 # priority
342 if request.GET.get('default_encoding'):
342 if request.GET.get('default_encoding'):
343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
345 context.clone_uri_id_tmpl = rc_config.get('rhodecode_clone_uri_id_tmpl')
345 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
346 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
346
347
347 # INI stored
348 # INI stored
348 context.labs_active = str2bool(
349 context.labs_active = str2bool(
349 config.get('labs_settings_active', 'false'))
350 config.get('labs_settings_active', 'false'))
350 context.ssh_enabled = str2bool(
351 context.ssh_enabled = str2bool(
351 config.get('ssh.generate_authorized_keyfile', 'false'))
352 config.get('ssh.generate_authorized_keyfile', 'false'))
352 context.ssh_key_generator_enabled = str2bool(
353 context.ssh_key_generator_enabled = str2bool(
353 config.get('ssh.enable_ui_key_generator', 'true'))
354 config.get('ssh.enable_ui_key_generator', 'true'))
354
355
355 context.visual.allow_repo_location_change = str2bool(
356 context.visual.allow_repo_location_change = str2bool(
356 config.get('allow_repo_location_change', True))
357 config.get('allow_repo_location_change', True))
357 context.visual.allow_custom_hooks_settings = str2bool(
358 context.visual.allow_custom_hooks_settings = str2bool(
358 config.get('allow_custom_hooks_settings', True))
359 config.get('allow_custom_hooks_settings', True))
359 context.debug_style = str2bool(config.get('debug_style', False))
360 context.debug_style = str2bool(config.get('debug_style', False))
360
361
361 context.rhodecode_instanceid = config.get('instance_id')
362 context.rhodecode_instanceid = config.get('instance_id')
362
363
363 context.visual.cut_off_limit_diff = safe_int(
364 context.visual.cut_off_limit_diff = safe_int(
364 config.get('cut_off_limit_diff'))
365 config.get('cut_off_limit_diff'))
365 context.visual.cut_off_limit_file = safe_int(
366 context.visual.cut_off_limit_file = safe_int(
366 config.get('cut_off_limit_file'))
367 config.get('cut_off_limit_file'))
367
368
368 context.license = AttributeDict({})
369 context.license = AttributeDict({})
369 context.license.hide_license_info = str2bool(
370 context.license.hide_license_info = str2bool(
370 config.get('license.hide_license_info', False))
371 config.get('license.hide_license_info', False))
371
372
372 # AppEnlight
373 # AppEnlight
373 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
374 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
374 context.appenlight_api_public_key = config.get(
375 context.appenlight_api_public_key = config.get(
375 'appenlight.api_public_key', '')
376 'appenlight.api_public_key', '')
376 context.appenlight_server_url = config.get('appenlight.server_url', '')
377 context.appenlight_server_url = config.get('appenlight.server_url', '')
377
378
378 diffmode = {
379 diffmode = {
379 "unified": "unified",
380 "unified": "unified",
380 "sideside": "sideside"
381 "sideside": "sideside"
381 }.get(request.GET.get('diffmode'))
382 }.get(request.GET.get('diffmode'))
382
383
383 if is_api is not None:
384 if is_api is not None:
384 is_api = hasattr(request, 'rpc_user')
385 is_api = hasattr(request, 'rpc_user')
385 session_attrs = {
386 session_attrs = {
386 # defaults
387 # defaults
387 "clone_url_format": "http",
388 "clone_url_format": "http",
388 "diffmode": "sideside",
389 "diffmode": "sideside",
389 "license_fingerprint": request.session.get('license_fingerprint')
390 "license_fingerprint": request.session.get('license_fingerprint')
390 }
391 }
391
392
392 if not is_api:
393 if not is_api:
393 # don't access pyramid session for API calls
394 # don't access pyramid session for API calls
394 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
395 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
395 request.session['rc_user_session_attr.diffmode'] = diffmode
396 request.session['rc_user_session_attr.diffmode'] = diffmode
396
397
397 # session settings per user
398 # session settings per user
398
399
399 for k, v in request.session.items():
400 for k, v in request.session.items():
400 pref = 'rc_user_session_attr.'
401 pref = 'rc_user_session_attr.'
401 if k and k.startswith(pref):
402 if k and k.startswith(pref):
402 k = k[len(pref):]
403 k = k[len(pref):]
403 session_attrs[k] = v
404 session_attrs[k] = v
404
405
405 context.user_session_attrs = session_attrs
406 context.user_session_attrs = session_attrs
406
407
407 # JS template context
408 # JS template context
408 context.template_context = {
409 context.template_context = {
409 'repo_name': None,
410 'repo_name': None,
410 'repo_type': None,
411 'repo_type': None,
411 'repo_landing_commit': None,
412 'repo_landing_commit': None,
412 'rhodecode_user': {
413 'rhodecode_user': {
413 'username': None,
414 'username': None,
414 'email': None,
415 'email': None,
415 'notification_status': False
416 'notification_status': False
416 },
417 },
417 'session_attrs': session_attrs,
418 'session_attrs': session_attrs,
418 'visual': {
419 'visual': {
419 'default_renderer': None
420 'default_renderer': None
420 },
421 },
421 'commit_data': {
422 'commit_data': {
422 'commit_id': None
423 'commit_id': None
423 },
424 },
424 'pull_request_data': {'pull_request_id': None},
425 'pull_request_data': {'pull_request_id': None},
425 'timeago': {
426 'timeago': {
426 'refresh_time': 120 * 1000,
427 'refresh_time': 120 * 1000,
427 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
428 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
428 },
429 },
429 'pyramid_dispatch': {
430 'pyramid_dispatch': {
430
431
431 },
432 },
432 'extra': {'plugins': {}}
433 'extra': {'plugins': {}}
433 }
434 }
434 # END CONFIG VARS
435 # END CONFIG VARS
435 if is_api:
436 if is_api:
436 csrf_token = None
437 csrf_token = None
437 else:
438 else:
438 csrf_token = auth.get_csrf_token(session=request.session)
439 csrf_token = auth.get_csrf_token(session=request.session)
439
440
440 context.csrf_token = csrf_token
441 context.csrf_token = csrf_token
441 context.backends = rhodecode.BACKENDS.keys()
442 context.backends = rhodecode.BACKENDS.keys()
442
443
443 unread_count = 0
444 unread_count = 0
444 user_bookmark_list = []
445 user_bookmark_list = []
445 if user_id:
446 if user_id:
446 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
447 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
447 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
448 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
448 context.unread_notifications = unread_count
449 context.unread_notifications = unread_count
449 context.bookmark_items = user_bookmark_list
450 context.bookmark_items = user_bookmark_list
450
451
451 # web case
452 # web case
452 if hasattr(request, 'user'):
453 if hasattr(request, 'user'):
453 context.auth_user = request.user
454 context.auth_user = request.user
454 context.rhodecode_user = request.user
455 context.rhodecode_user = request.user
455
456
456 # api case
457 # api case
457 if hasattr(request, 'rpc_user'):
458 if hasattr(request, 'rpc_user'):
458 context.auth_user = request.rpc_user
459 context.auth_user = request.rpc_user
459 context.rhodecode_user = request.rpc_user
460 context.rhodecode_user = request.rpc_user
460
461
461 # attach the whole call context to the request
462 # attach the whole call context to the request
462 request.call_context = context
463 request.call_context = context
463
464
464
465
465 def get_auth_user(request):
466 def get_auth_user(request):
466 environ = request.environ
467 environ = request.environ
467 session = request.session
468 session = request.session
468
469
469 ip_addr = get_ip_addr(environ)
470 ip_addr = get_ip_addr(environ)
470
471
471 # make sure that we update permissions each time we call controller
472 # make sure that we update permissions each time we call controller
472 _auth_token = (
473 _auth_token = (
473 # ?auth_token=XXX
474 # ?auth_token=XXX
474 request.GET.get('auth_token', '')
475 request.GET.get('auth_token', '')
475 # ?api_key=XXX !LEGACY
476 # ?api_key=XXX !LEGACY
476 or request.GET.get('api_key', '')
477 or request.GET.get('api_key', '')
477 # or headers....
478 # or headers....
478 or request.headers.get('X-Rc-Auth-Token', '')
479 or request.headers.get('X-Rc-Auth-Token', '')
479 )
480 )
480 if not _auth_token and request.matchdict:
481 if not _auth_token and request.matchdict:
481 url_auth_token = request.matchdict.get('_auth_token')
482 url_auth_token = request.matchdict.get('_auth_token')
482 _auth_token = url_auth_token
483 _auth_token = url_auth_token
483 if _auth_token:
484 if _auth_token:
484 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
485 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
485
486
486 if _auth_token:
487 if _auth_token:
487 # when using API_KEY we assume user exists, and
488 # when using API_KEY we assume user exists, and
488 # doesn't need auth based on cookies.
489 # doesn't need auth based on cookies.
489 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
490 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
490 authenticated = False
491 authenticated = False
491 else:
492 else:
492 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
493 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
493 try:
494 try:
494 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
495 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
495 ip_addr=ip_addr)
496 ip_addr=ip_addr)
496 except UserCreationError as e:
497 except UserCreationError as e:
497 h.flash(e, 'error')
498 h.flash(e, 'error')
498 # container auth or other auth functions that create users
499 # container auth or other auth functions that create users
499 # on the fly can throw this exception signaling that there's
500 # on the fly can throw this exception signaling that there's
500 # issue with user creation, explanation should be provided
501 # issue with user creation, explanation should be provided
501 # in Exception itself. We then create a simple blank
502 # in Exception itself. We then create a simple blank
502 # AuthUser
503 # AuthUser
503 auth_user = AuthUser(ip_addr=ip_addr)
504 auth_user = AuthUser(ip_addr=ip_addr)
504
505
505 # in case someone changes a password for user it triggers session
506 # in case someone changes a password for user it triggers session
506 # flush and forces a re-login
507 # flush and forces a re-login
507 if password_changed(auth_user, session):
508 if password_changed(auth_user, session):
508 session.invalidate()
509 session.invalidate()
509 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
510 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
510 auth_user = AuthUser(ip_addr=ip_addr)
511 auth_user = AuthUser(ip_addr=ip_addr)
511
512
512 authenticated = cookie_store.get('is_authenticated')
513 authenticated = cookie_store.get('is_authenticated')
513
514
514 if not auth_user.is_authenticated and auth_user.is_user_object:
515 if not auth_user.is_authenticated and auth_user.is_user_object:
515 # user is not authenticated and not empty
516 # user is not authenticated and not empty
516 auth_user.set_authenticated(authenticated)
517 auth_user.set_authenticated(authenticated)
517
518
518 return auth_user, _auth_token
519 return auth_user, _auth_token
519
520
520
521
521 def h_filter(s):
522 def h_filter(s):
522 """
523 """
523 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
524 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
524 we wrap this with additional functionality that converts None to empty
525 we wrap this with additional functionality that converts None to empty
525 strings
526 strings
526 """
527 """
527 if s is None:
528 if s is None:
528 return markupsafe.Markup()
529 return markupsafe.Markup()
529 return markupsafe.escape(s)
530 return markupsafe.escape(s)
530
531
531
532
532 def add_events_routes(config):
533 def add_events_routes(config):
533 """
534 """
534 Adds routing that can be used in events. Because some events are triggered
535 Adds routing that can be used in events. Because some events are triggered
535 outside of pyramid context, we need to bootstrap request with some
536 outside of pyramid context, we need to bootstrap request with some
536 routing registered
537 routing registered
537 """
538 """
538
539
539 from rhodecode.apps._base import ADMIN_PREFIX
540 from rhodecode.apps._base import ADMIN_PREFIX
540
541
541 config.add_route(name='home', pattern='/')
542 config.add_route(name='home', pattern='/')
542 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
543 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
543 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
544 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
544
545
545 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
546 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
546 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
547 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
547 config.add_route(name='repo_summary', pattern='/{repo_name}')
548 config.add_route(name='repo_summary', pattern='/{repo_name}')
548 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
549 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
549 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
550 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
550
551
551 config.add_route(name='pullrequest_show',
552 config.add_route(name='pullrequest_show',
552 pattern='/{repo_name}/pull-request/{pull_request_id}')
553 pattern='/{repo_name}/pull-request/{pull_request_id}')
553 config.add_route(name='pull_requests_global',
554 config.add_route(name='pull_requests_global',
554 pattern='/pull-request/{pull_request_id}')
555 pattern='/pull-request/{pull_request_id}')
555
556
556 config.add_route(name='repo_commit',
557 config.add_route(name='repo_commit',
557 pattern='/{repo_name}/changeset/{commit_id}')
558 pattern='/{repo_name}/changeset/{commit_id}')
558 config.add_route(name='repo_files',
559 config.add_route(name='repo_files',
559 pattern='/{repo_name}/files/{commit_id}/{f_path}')
560 pattern='/{repo_name}/files/{commit_id}/{f_path}')
560
561
561 config.add_route(name='hovercard_user',
562 config.add_route(name='hovercard_user',
562 pattern='/_hovercard/user/{user_id}')
563 pattern='/_hovercard/user/{user_id}')
563
564
564 config.add_route(name='hovercard_user_group',
565 config.add_route(name='hovercard_user_group',
565 pattern='/_hovercard/user_group/{user_group_id}')
566 pattern='/_hovercard/user_group/{user_group_id}')
566
567
567 config.add_route(name='hovercard_pull_request',
568 config.add_route(name='hovercard_pull_request',
568 pattern='/_hovercard/pull_request/{pull_request_id}')
569 pattern='/_hovercard/pull_request/{pull_request_id}')
569
570
570 config.add_route(name='hovercard_repo_commit',
571 config.add_route(name='hovercard_repo_commit',
571 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
572 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
572
573
573
574
574 def bootstrap_config(request):
575 def bootstrap_config(request):
575 import pyramid.testing
576 import pyramid.testing
576 registry = pyramid.testing.Registry('RcTestRegistry')
577 registry = pyramid.testing.Registry('RcTestRegistry')
577
578
578 config = pyramid.testing.setUp(registry=registry, request=request)
579 config = pyramid.testing.setUp(registry=registry, request=request)
579
580
580 # allow pyramid lookup in testing
581 # allow pyramid lookup in testing
581 config.include('pyramid_mako')
582 config.include('pyramid_mako')
582 config.include('rhodecode.lib.rc_beaker')
583 config.include('rhodecode.lib.rc_beaker')
583 config.include('rhodecode.lib.rc_cache')
584 config.include('rhodecode.lib.rc_cache')
584
585
585 add_events_routes(config)
586 add_events_routes(config)
586
587
587 return config
588 return config
588
589
589
590
590 def bootstrap_request(**kwargs):
591 def bootstrap_request(**kwargs):
591 import pyramid.testing
592 import pyramid.testing
592
593
593 class TestRequest(pyramid.testing.DummyRequest):
594 class TestRequest(pyramid.testing.DummyRequest):
594 application_url = kwargs.pop('application_url', 'http://example.com')
595 application_url = kwargs.pop('application_url', 'http://example.com')
595 host = kwargs.pop('host', 'example.com:80')
596 host = kwargs.pop('host', 'example.com:80')
596 domain = kwargs.pop('domain', 'example.com')
597 domain = kwargs.pop('domain', 'example.com')
597
598
598 def translate(self, msg):
599 def translate(self, msg):
599 return msg
600 return msg
600
601
601 def plularize(self, singular, plural, n):
602 def plularize(self, singular, plural, n):
602 return singular
603 return singular
603
604
604 def get_partial_renderer(self, tmpl_name):
605 def get_partial_renderer(self, tmpl_name):
605
606
606 from rhodecode.lib.partial_renderer import get_partial_renderer
607 from rhodecode.lib.partial_renderer import get_partial_renderer
607 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
608 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
608
609
609 _call_context = TemplateArgs()
610 _call_context = TemplateArgs()
610 _call_context.visual = TemplateArgs()
611 _call_context.visual = TemplateArgs()
611 _call_context.visual.show_sha_length = 12
612 _call_context.visual.show_sha_length = 12
612 _call_context.visual.show_revision_number = True
613 _call_context.visual.show_revision_number = True
613
614
614 @property
615 @property
615 def call_context(self):
616 def call_context(self):
616 return self._call_context
617 return self._call_context
617
618
618 class TestDummySession(pyramid.testing.DummySession):
619 class TestDummySession(pyramid.testing.DummySession):
619 def save(*arg, **kw):
620 def save(*arg, **kw):
620 pass
621 pass
621
622
622 request = TestRequest(**kwargs)
623 request = TestRequest(**kwargs)
623 request.session = TestDummySession()
624 request.session = TestDummySession()
624
625
625 return request
626 return request
626
627
@@ -1,678 +1,679 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 of database as well as for migration operations
23 of database as well as for migration operations
24 """
24 """
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import uuid
29 import uuid
30 import logging
30 import logging
31 import getpass
31 import getpass
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from sqlalchemy.engine import create_engine
34 from sqlalchemy.engine import create_engine
35
35
36 from rhodecode import __dbversion__
36 from rhodecode import __dbversion__
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 from rhodecode.model.meta import Session, Base
42 from rhodecode.model.meta import Session, Base
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def notify(msg):
52 def notify(msg):
53 """
53 """
54 Notification for migrations messages
54 Notification for migrations messages
55 """
55 """
56 ml = len(msg) + (4 * 2)
56 ml = len(msg) + (4 * 2)
57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
57 print(('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper())
58
58
59
59
60 class DbManage(object):
60 class DbManage(object):
61
61
62 def __init__(self, log_sql, dbconf, root, tests=False,
62 def __init__(self, log_sql, dbconf, root, tests=False,
63 SESSION=None, cli_args=None):
63 SESSION=None, cli_args=None):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.cli_args = cli_args or {}
69 self.cli_args = cli_args or {}
70 self.init_db(SESSION=SESSION)
70 self.init_db(SESSION=SESSION)
71 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
71 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72
72
73 def db_exists(self):
73 def db_exists(self):
74 if not self.sa:
74 if not self.sa:
75 self.init_db()
75 self.init_db()
76 try:
76 try:
77 self.sa.query(RhodeCodeUi)\
77 self.sa.query(RhodeCodeUi)\
78 .filter(RhodeCodeUi.ui_key == '/')\
78 .filter(RhodeCodeUi.ui_key == '/')\
79 .scalar()
79 .scalar()
80 return True
80 return True
81 except Exception:
81 except Exception:
82 return False
82 return False
83 finally:
83 finally:
84 self.sa.rollback()
84 self.sa.rollback()
85
85
86 def get_ask_ok_func(self, param):
86 def get_ask_ok_func(self, param):
87 if param not in [None]:
87 if param not in [None]:
88 # return a function lambda that has a default set to param
88 # return a function lambda that has a default set to param
89 return lambda *args, **kwargs: param
89 return lambda *args, **kwargs: param
90 else:
90 else:
91 from rhodecode.lib.utils import ask_ok
91 from rhodecode.lib.utils import ask_ok
92 return ask_ok
92 return ask_ok
93
93
94 def init_db(self, SESSION=None):
94 def init_db(self, SESSION=None):
95 if SESSION:
95 if SESSION:
96 self.sa = SESSION
96 self.sa = SESSION
97 else:
97 else:
98 # init new sessions
98 # init new sessions
99 engine = create_engine(self.dburi, echo=self.log_sql)
99 engine = create_engine(self.dburi, echo=self.log_sql)
100 init_model(engine)
100 init_model(engine)
101 self.sa = Session()
101 self.sa = Session()
102
102
103 def create_tables(self, override=False):
103 def create_tables(self, override=False):
104 """
104 """
105 Create a auth database
105 Create a auth database
106 """
106 """
107
107
108 log.info("Existing database with the same name is going to be destroyed.")
108 log.info("Existing database with the same name is going to be destroyed.")
109 log.info("Setup command will run DROP ALL command on that database.")
109 log.info("Setup command will run DROP ALL command on that database.")
110 if self.tests:
110 if self.tests:
111 destroy = True
111 destroy = True
112 else:
112 else:
113 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
113 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
114 if not destroy:
114 if not destroy:
115 log.info('Nothing done.')
115 log.info('Nothing done.')
116 sys.exit(0)
116 sys.exit(0)
117 if destroy:
117 if destroy:
118 Base.metadata.drop_all()
118 Base.metadata.drop_all()
119
119
120 checkfirst = not override
120 checkfirst = not override
121 Base.metadata.create_all(checkfirst=checkfirst)
121 Base.metadata.create_all(checkfirst=checkfirst)
122 log.info('Created tables for %s', self.dbname)
122 log.info('Created tables for %s', self.dbname)
123
123
124 def set_db_version(self):
124 def set_db_version(self):
125 ver = DbMigrateVersion()
125 ver = DbMigrateVersion()
126 ver.version = __dbversion__
126 ver.version = __dbversion__
127 ver.repository_id = 'rhodecode_db_migrations'
127 ver.repository_id = 'rhodecode_db_migrations'
128 ver.repository_path = 'versions'
128 ver.repository_path = 'versions'
129 self.sa.add(ver)
129 self.sa.add(ver)
130 log.info('db version set to: %s', __dbversion__)
130 log.info('db version set to: %s', __dbversion__)
131
131
132 def run_post_migration_tasks(self):
132 def run_post_migration_tasks(self):
133 """
133 """
134 Run various tasks before actually doing migrations
134 Run various tasks before actually doing migrations
135 """
135 """
136 # delete cache keys on each upgrade
136 # delete cache keys on each upgrade
137 total = CacheKey.query().count()
137 total = CacheKey.query().count()
138 log.info("Deleting (%s) cache keys now...", total)
138 log.info("Deleting (%s) cache keys now...", total)
139 CacheKey.delete_all_cache()
139 CacheKey.delete_all_cache()
140
140
141 def upgrade(self, version=None):
141 def upgrade(self, version=None):
142 """
142 """
143 Upgrades given database schema to given revision following
143 Upgrades given database schema to given revision following
144 all needed steps, to perform the upgrade
144 all needed steps, to perform the upgrade
145
145
146 """
146 """
147
147
148 from rhodecode.lib.dbmigrate.migrate.versioning import api
148 from rhodecode.lib.dbmigrate.migrate.versioning import api
149 from rhodecode.lib.dbmigrate.migrate.exceptions import \
149 from rhodecode.lib.dbmigrate.migrate.exceptions import \
150 DatabaseNotControlledError
150 DatabaseNotControlledError
151
151
152 if 'sqlite' in self.dburi:
152 if 'sqlite' in self.dburi:
153 print(
153 print(
154 '********************** WARNING **********************\n'
154 '********************** WARNING **********************\n'
155 'Make sure your version of sqlite is at least 3.7.X. \n'
155 'Make sure your version of sqlite is at least 3.7.X. \n'
156 'Earlier versions are known to fail on some migrations\n'
156 'Earlier versions are known to fail on some migrations\n'
157 '*****************************************************\n')
157 '*****************************************************\n')
158
158
159 upgrade = self.ask_ok(
159 upgrade = self.ask_ok(
160 'You are about to perform a database upgrade. Make '
160 'You are about to perform a database upgrade. Make '
161 'sure you have backed up your database. '
161 'sure you have backed up your database. '
162 'Continue ? [y/n]')
162 'Continue ? [y/n]')
163 if not upgrade:
163 if not upgrade:
164 log.info('No upgrade performed')
164 log.info('No upgrade performed')
165 sys.exit(0)
165 sys.exit(0)
166
166
167 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
167 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
168 'rhodecode/lib/dbmigrate')
168 'rhodecode/lib/dbmigrate')
169 db_uri = self.dburi
169 db_uri = self.dburi
170
170
171 if version:
171 if version:
172 DbMigrateVersion.set_version(version)
172 DbMigrateVersion.set_version(version)
173
173
174 try:
174 try:
175 curr_version = api.db_version(db_uri, repository_path)
175 curr_version = api.db_version(db_uri, repository_path)
176 msg = ('Found current database db_uri under version '
176 msg = ('Found current database db_uri under version '
177 'control with version {}'.format(curr_version))
177 'control with version {}'.format(curr_version))
178
178
179 except (RuntimeError, DatabaseNotControlledError):
179 except (RuntimeError, DatabaseNotControlledError):
180 curr_version = 1
180 curr_version = 1
181 msg = ('Current database is not under version control. Setting '
181 msg = ('Current database is not under version control. Setting '
182 'as version %s' % curr_version)
182 'as version %s' % curr_version)
183 api.version_control(db_uri, repository_path, curr_version)
183 api.version_control(db_uri, repository_path, curr_version)
184
184
185 notify(msg)
185 notify(msg)
186
186
187
187
188 if curr_version == __dbversion__:
188 if curr_version == __dbversion__:
189 log.info('This database is already at the newest version')
189 log.info('This database is already at the newest version')
190 sys.exit(0)
190 sys.exit(0)
191
191
192 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
192 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
193 notify('attempting to upgrade database from '
193 notify('attempting to upgrade database from '
194 'version %s to version %s' % (curr_version, __dbversion__))
194 'version %s to version %s' % (curr_version, __dbversion__))
195
195
196 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
196 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
197 _step = None
197 _step = None
198 for step in upgrade_steps:
198 for step in upgrade_steps:
199 notify('performing upgrade step %s' % step)
199 notify('performing upgrade step %s' % step)
200 time.sleep(0.5)
200 time.sleep(0.5)
201
201
202 api.upgrade(db_uri, repository_path, step)
202 api.upgrade(db_uri, repository_path, step)
203 self.sa.rollback()
203 self.sa.rollback()
204 notify('schema upgrade for step %s completed' % (step,))
204 notify('schema upgrade for step %s completed' % (step,))
205
205
206 _step = step
206 _step = step
207
207
208 self.run_post_migration_tasks()
208 self.run_post_migration_tasks()
209 notify('upgrade to version %s successful' % _step)
209 notify('upgrade to version %s successful' % _step)
210
210
211 def fix_repo_paths(self):
211 def fix_repo_paths(self):
212 """
212 """
213 Fixes an old RhodeCode version path into new one without a '*'
213 Fixes an old RhodeCode version path into new one without a '*'
214 """
214 """
215
215
216 paths = self.sa.query(RhodeCodeUi)\
216 paths = self.sa.query(RhodeCodeUi)\
217 .filter(RhodeCodeUi.ui_key == '/')\
217 .filter(RhodeCodeUi.ui_key == '/')\
218 .scalar()
218 .scalar()
219
219
220 paths.ui_value = paths.ui_value.replace('*', '')
220 paths.ui_value = paths.ui_value.replace('*', '')
221
221
222 try:
222 try:
223 self.sa.add(paths)
223 self.sa.add(paths)
224 self.sa.commit()
224 self.sa.commit()
225 except Exception:
225 except Exception:
226 self.sa.rollback()
226 self.sa.rollback()
227 raise
227 raise
228
228
229 def fix_default_user(self):
229 def fix_default_user(self):
230 """
230 """
231 Fixes an old default user with some 'nicer' default values,
231 Fixes an old default user with some 'nicer' default values,
232 used mostly for anonymous access
232 used mostly for anonymous access
233 """
233 """
234 def_user = self.sa.query(User)\
234 def_user = self.sa.query(User)\
235 .filter(User.username == User.DEFAULT_USER)\
235 .filter(User.username == User.DEFAULT_USER)\
236 .one()
236 .one()
237
237
238 def_user.name = 'Anonymous'
238 def_user.name = 'Anonymous'
239 def_user.lastname = 'User'
239 def_user.lastname = 'User'
240 def_user.email = User.DEFAULT_USER_EMAIL
240 def_user.email = User.DEFAULT_USER_EMAIL
241
241
242 try:
242 try:
243 self.sa.add(def_user)
243 self.sa.add(def_user)
244 self.sa.commit()
244 self.sa.commit()
245 except Exception:
245 except Exception:
246 self.sa.rollback()
246 self.sa.rollback()
247 raise
247 raise
248
248
249 def fix_settings(self):
249 def fix_settings(self):
250 """
250 """
251 Fixes rhodecode settings and adds ga_code key for google analytics
251 Fixes rhodecode settings and adds ga_code key for google analytics
252 """
252 """
253
253
254 hgsettings3 = RhodeCodeSetting('ga_code', '')
254 hgsettings3 = RhodeCodeSetting('ga_code', '')
255
255
256 try:
256 try:
257 self.sa.add(hgsettings3)
257 self.sa.add(hgsettings3)
258 self.sa.commit()
258 self.sa.commit()
259 except Exception:
259 except Exception:
260 self.sa.rollback()
260 self.sa.rollback()
261 raise
261 raise
262
262
263 def create_admin_and_prompt(self):
263 def create_admin_and_prompt(self):
264
264
265 # defaults
265 # defaults
266 defaults = self.cli_args
266 defaults = self.cli_args
267 username = defaults.get('username')
267 username = defaults.get('username')
268 password = defaults.get('password')
268 password = defaults.get('password')
269 email = defaults.get('email')
269 email = defaults.get('email')
270
270
271 if username is None:
271 if username is None:
272 username = raw_input('Specify admin username:')
272 username = raw_input('Specify admin username:')
273 if password is None:
273 if password is None:
274 password = self._get_admin_password()
274 password = self._get_admin_password()
275 if not password:
275 if not password:
276 # second try
276 # second try
277 password = self._get_admin_password()
277 password = self._get_admin_password()
278 if not password:
278 if not password:
279 sys.exit()
279 sys.exit()
280 if email is None:
280 if email is None:
281 email = raw_input('Specify admin email:')
281 email = raw_input('Specify admin email:')
282 api_key = self.cli_args.get('api_key')
282 api_key = self.cli_args.get('api_key')
283 self.create_user(username, password, email, True,
283 self.create_user(username, password, email, True,
284 strict_creation_check=False,
284 strict_creation_check=False,
285 api_key=api_key)
285 api_key=api_key)
286
286
287 def _get_admin_password(self):
287 def _get_admin_password(self):
288 password = getpass.getpass('Specify admin password '
288 password = getpass.getpass('Specify admin password '
289 '(min 6 chars):')
289 '(min 6 chars):')
290 confirm = getpass.getpass('Confirm password:')
290 confirm = getpass.getpass('Confirm password:')
291
291
292 if password != confirm:
292 if password != confirm:
293 log.error('passwords mismatch')
293 log.error('passwords mismatch')
294 return False
294 return False
295 if len(password) < 6:
295 if len(password) < 6:
296 log.error('password is too short - use at least 6 characters')
296 log.error('password is too short - use at least 6 characters')
297 return False
297 return False
298
298
299 return password
299 return password
300
300
301 def create_test_admin_and_users(self):
301 def create_test_admin_and_users(self):
302 log.info('creating admin and regular test users')
302 log.info('creating admin and regular test users')
303 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
303 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
304 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
304 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
305 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
305 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
306 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
306 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
307 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
307 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
308
308
309 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
309 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
310 TEST_USER_ADMIN_EMAIL, True, api_key=True)
310 TEST_USER_ADMIN_EMAIL, True, api_key=True)
311
311
312 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
312 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
313 TEST_USER_REGULAR_EMAIL, False, api_key=True)
313 TEST_USER_REGULAR_EMAIL, False, api_key=True)
314
314
315 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
315 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
316 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
316 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
317
317
318 def create_ui_settings(self, repo_store_path):
318 def create_ui_settings(self, repo_store_path):
319 """
319 """
320 Creates ui settings, fills out hooks
320 Creates ui settings, fills out hooks
321 and disables dotencode
321 and disables dotencode
322 """
322 """
323 settings_model = SettingsModel(sa=self.sa)
323 settings_model = SettingsModel(sa=self.sa)
324 from rhodecode.lib.vcs.backends.hg import largefiles_store
324 from rhodecode.lib.vcs.backends.hg import largefiles_store
325 from rhodecode.lib.vcs.backends.git import lfs_store
325 from rhodecode.lib.vcs.backends.git import lfs_store
326
326
327 # Build HOOKS
327 # Build HOOKS
328 hooks = [
328 hooks = [
329 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
329 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
330
330
331 # HG
331 # HG
332 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
332 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
333 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
333 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
334 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
334 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
335 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
335 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
336 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
336 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
337 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
337 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
338
338
339 ]
339 ]
340
340
341 for key, value in hooks:
341 for key, value in hooks:
342 hook_obj = settings_model.get_ui_by_key(key)
342 hook_obj = settings_model.get_ui_by_key(key)
343 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
343 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
344 hooks2.ui_section = 'hooks'
344 hooks2.ui_section = 'hooks'
345 hooks2.ui_key = key
345 hooks2.ui_key = key
346 hooks2.ui_value = value
346 hooks2.ui_value = value
347 self.sa.add(hooks2)
347 self.sa.add(hooks2)
348
348
349 # enable largefiles
349 # enable largefiles
350 largefiles = RhodeCodeUi()
350 largefiles = RhodeCodeUi()
351 largefiles.ui_section = 'extensions'
351 largefiles.ui_section = 'extensions'
352 largefiles.ui_key = 'largefiles'
352 largefiles.ui_key = 'largefiles'
353 largefiles.ui_value = ''
353 largefiles.ui_value = ''
354 self.sa.add(largefiles)
354 self.sa.add(largefiles)
355
355
356 # set default largefiles cache dir, defaults to
356 # set default largefiles cache dir, defaults to
357 # /repo_store_location/.cache/largefiles
357 # /repo_store_location/.cache/largefiles
358 largefiles = RhodeCodeUi()
358 largefiles = RhodeCodeUi()
359 largefiles.ui_section = 'largefiles'
359 largefiles.ui_section = 'largefiles'
360 largefiles.ui_key = 'usercache'
360 largefiles.ui_key = 'usercache'
361 largefiles.ui_value = largefiles_store(repo_store_path)
361 largefiles.ui_value = largefiles_store(repo_store_path)
362
362
363 self.sa.add(largefiles)
363 self.sa.add(largefiles)
364
364
365 # set default lfs cache dir, defaults to
365 # set default lfs cache dir, defaults to
366 # /repo_store_location/.cache/lfs_store
366 # /repo_store_location/.cache/lfs_store
367 lfsstore = RhodeCodeUi()
367 lfsstore = RhodeCodeUi()
368 lfsstore.ui_section = 'vcs_git_lfs'
368 lfsstore.ui_section = 'vcs_git_lfs'
369 lfsstore.ui_key = 'store_location'
369 lfsstore.ui_key = 'store_location'
370 lfsstore.ui_value = lfs_store(repo_store_path)
370 lfsstore.ui_value = lfs_store(repo_store_path)
371
371
372 self.sa.add(lfsstore)
372 self.sa.add(lfsstore)
373
373
374 # enable hgsubversion disabled by default
374 # enable hgsubversion disabled by default
375 hgsubversion = RhodeCodeUi()
375 hgsubversion = RhodeCodeUi()
376 hgsubversion.ui_section = 'extensions'
376 hgsubversion.ui_section = 'extensions'
377 hgsubversion.ui_key = 'hgsubversion'
377 hgsubversion.ui_key = 'hgsubversion'
378 hgsubversion.ui_value = ''
378 hgsubversion.ui_value = ''
379 hgsubversion.ui_active = False
379 hgsubversion.ui_active = False
380 self.sa.add(hgsubversion)
380 self.sa.add(hgsubversion)
381
381
382 # enable hgevolve disabled by default
382 # enable hgevolve disabled by default
383 hgevolve = RhodeCodeUi()
383 hgevolve = RhodeCodeUi()
384 hgevolve.ui_section = 'extensions'
384 hgevolve.ui_section = 'extensions'
385 hgevolve.ui_key = 'evolve'
385 hgevolve.ui_key = 'evolve'
386 hgevolve.ui_value = ''
386 hgevolve.ui_value = ''
387 hgevolve.ui_active = False
387 hgevolve.ui_active = False
388 self.sa.add(hgevolve)
388 self.sa.add(hgevolve)
389
389
390 hgevolve = RhodeCodeUi()
390 hgevolve = RhodeCodeUi()
391 hgevolve.ui_section = 'experimental'
391 hgevolve.ui_section = 'experimental'
392 hgevolve.ui_key = 'evolution'
392 hgevolve.ui_key = 'evolution'
393 hgevolve.ui_value = ''
393 hgevolve.ui_value = ''
394 hgevolve.ui_active = False
394 hgevolve.ui_active = False
395 self.sa.add(hgevolve)
395 self.sa.add(hgevolve)
396
396
397 hgevolve = RhodeCodeUi()
397 hgevolve = RhodeCodeUi()
398 hgevolve.ui_section = 'experimental'
398 hgevolve.ui_section = 'experimental'
399 hgevolve.ui_key = 'evolution.exchange'
399 hgevolve.ui_key = 'evolution.exchange'
400 hgevolve.ui_value = ''
400 hgevolve.ui_value = ''
401 hgevolve.ui_active = False
401 hgevolve.ui_active = False
402 self.sa.add(hgevolve)
402 self.sa.add(hgevolve)
403
403
404 hgevolve = RhodeCodeUi()
404 hgevolve = RhodeCodeUi()
405 hgevolve.ui_section = 'extensions'
405 hgevolve.ui_section = 'extensions'
406 hgevolve.ui_key = 'topic'
406 hgevolve.ui_key = 'topic'
407 hgevolve.ui_value = ''
407 hgevolve.ui_value = ''
408 hgevolve.ui_active = False
408 hgevolve.ui_active = False
409 self.sa.add(hgevolve)
409 self.sa.add(hgevolve)
410
410
411 # enable hggit disabled by default
411 # enable hggit disabled by default
412 hggit = RhodeCodeUi()
412 hggit = RhodeCodeUi()
413 hggit.ui_section = 'extensions'
413 hggit.ui_section = 'extensions'
414 hggit.ui_key = 'hggit'
414 hggit.ui_key = 'hggit'
415 hggit.ui_value = ''
415 hggit.ui_value = ''
416 hggit.ui_active = False
416 hggit.ui_active = False
417 self.sa.add(hggit)
417 self.sa.add(hggit)
418
418
419 # set svn branch defaults
419 # set svn branch defaults
420 branches = ["/branches/*", "/trunk"]
420 branches = ["/branches/*", "/trunk"]
421 tags = ["/tags/*"]
421 tags = ["/tags/*"]
422
422
423 for branch in branches:
423 for branch in branches:
424 settings_model.create_ui_section_value(
424 settings_model.create_ui_section_value(
425 RhodeCodeUi.SVN_BRANCH_ID, branch)
425 RhodeCodeUi.SVN_BRANCH_ID, branch)
426
426
427 for tag in tags:
427 for tag in tags:
428 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
428 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
429
429
430 def create_auth_plugin_options(self, skip_existing=False):
430 def create_auth_plugin_options(self, skip_existing=False):
431 """
431 """
432 Create default auth plugin settings, and make it active
432 Create default auth plugin settings, and make it active
433
433
434 :param skip_existing:
434 :param skip_existing:
435 """
435 """
436 defaults = [
436 defaults = [
437 ('auth_plugins',
437 ('auth_plugins',
438 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
438 'egg:rhodecode-enterprise-ce#token,egg:rhodecode-enterprise-ce#rhodecode',
439 'list'),
439 'list'),
440
440
441 ('auth_authtoken_enabled',
441 ('auth_authtoken_enabled',
442 'True',
442 'True',
443 'bool'),
443 'bool'),
444
444
445 ('auth_rhodecode_enabled',
445 ('auth_rhodecode_enabled',
446 'True',
446 'True',
447 'bool'),
447 'bool'),
448 ]
448 ]
449 for k, v, t in defaults:
449 for k, v, t in defaults:
450 if (skip_existing and
450 if (skip_existing and
451 SettingsModel().get_setting_by_name(k) is not None):
451 SettingsModel().get_setting_by_name(k) is not None):
452 log.debug('Skipping option %s', k)
452 log.debug('Skipping option %s', k)
453 continue
453 continue
454 setting = RhodeCodeSetting(k, v, t)
454 setting = RhodeCodeSetting(k, v, t)
455 self.sa.add(setting)
455 self.sa.add(setting)
456
456
457 def create_default_options(self, skip_existing=False):
457 def create_default_options(self, skip_existing=False):
458 """Creates default settings"""
458 """Creates default settings"""
459
459
460 for k, v, t in [
460 for k, v, t in [
461 ('default_repo_enable_locking', False, 'bool'),
461 ('default_repo_enable_locking', False, 'bool'),
462 ('default_repo_enable_downloads', False, 'bool'),
462 ('default_repo_enable_downloads', False, 'bool'),
463 ('default_repo_enable_statistics', False, 'bool'),
463 ('default_repo_enable_statistics', False, 'bool'),
464 ('default_repo_private', False, 'bool'),
464 ('default_repo_private', False, 'bool'),
465 ('default_repo_type', 'hg', 'unicode')]:
465 ('default_repo_type', 'hg', 'unicode')]:
466
466
467 if (skip_existing and
467 if (skip_existing and
468 SettingsModel().get_setting_by_name(k) is not None):
468 SettingsModel().get_setting_by_name(k) is not None):
469 log.debug('Skipping option %s', k)
469 log.debug('Skipping option %s', k)
470 continue
470 continue
471 setting = RhodeCodeSetting(k, v, t)
471 setting = RhodeCodeSetting(k, v, t)
472 self.sa.add(setting)
472 self.sa.add(setting)
473
473
474 def fixup_groups(self):
474 def fixup_groups(self):
475 def_usr = User.get_default_user()
475 def_usr = User.get_default_user()
476 for g in RepoGroup.query().all():
476 for g in RepoGroup.query().all():
477 g.group_name = g.get_new_name(g.name)
477 g.group_name = g.get_new_name(g.name)
478 self.sa.add(g)
478 self.sa.add(g)
479 # get default perm
479 # get default perm
480 default = UserRepoGroupToPerm.query()\
480 default = UserRepoGroupToPerm.query()\
481 .filter(UserRepoGroupToPerm.group == g)\
481 .filter(UserRepoGroupToPerm.group == g)\
482 .filter(UserRepoGroupToPerm.user == def_usr)\
482 .filter(UserRepoGroupToPerm.user == def_usr)\
483 .scalar()
483 .scalar()
484
484
485 if default is None:
485 if default is None:
486 log.debug('missing default permission for group %s adding', g)
486 log.debug('missing default permission for group %s adding', g)
487 perm_obj = RepoGroupModel()._create_default_perms(g)
487 perm_obj = RepoGroupModel()._create_default_perms(g)
488 self.sa.add(perm_obj)
488 self.sa.add(perm_obj)
489
489
490 def reset_permissions(self, username):
490 def reset_permissions(self, username):
491 """
491 """
492 Resets permissions to default state, useful when old systems had
492 Resets permissions to default state, useful when old systems had
493 bad permissions, we must clean them up
493 bad permissions, we must clean them up
494
494
495 :param username:
495 :param username:
496 """
496 """
497 default_user = User.get_by_username(username)
497 default_user = User.get_by_username(username)
498 if not default_user:
498 if not default_user:
499 return
499 return
500
500
501 u2p = UserToPerm.query()\
501 u2p = UserToPerm.query()\
502 .filter(UserToPerm.user == default_user).all()
502 .filter(UserToPerm.user == default_user).all()
503 fixed = False
503 fixed = False
504 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
504 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
505 for p in u2p:
505 for p in u2p:
506 Session().delete(p)
506 Session().delete(p)
507 fixed = True
507 fixed = True
508 self.populate_default_permissions()
508 self.populate_default_permissions()
509 return fixed
509 return fixed
510
510
511 def config_prompt(self, test_repo_path='', retries=3):
511 def config_prompt(self, test_repo_path='', retries=3):
512 defaults = self.cli_args
512 defaults = self.cli_args
513 _path = defaults.get('repos_location')
513 _path = defaults.get('repos_location')
514 if retries == 3:
514 if retries == 3:
515 log.info('Setting up repositories config')
515 log.info('Setting up repositories config')
516
516
517 if _path is not None:
517 if _path is not None:
518 path = _path
518 path = _path
519 elif not self.tests and not test_repo_path:
519 elif not self.tests and not test_repo_path:
520 path = raw_input(
520 path = raw_input(
521 'Enter a valid absolute path to store repositories. '
521 'Enter a valid absolute path to store repositories. '
522 'All repositories in that path will be added automatically:'
522 'All repositories in that path will be added automatically:'
523 )
523 )
524 else:
524 else:
525 path = test_repo_path
525 path = test_repo_path
526 path_ok = True
526 path_ok = True
527
527
528 # check proper dir
528 # check proper dir
529 if not os.path.isdir(path):
529 if not os.path.isdir(path):
530 path_ok = False
530 path_ok = False
531 log.error('Given path %s is not a valid directory', path)
531 log.error('Given path %s is not a valid directory', path)
532
532
533 elif not os.path.isabs(path):
533 elif not os.path.isabs(path):
534 path_ok = False
534 path_ok = False
535 log.error('Given path %s is not an absolute path', path)
535 log.error('Given path %s is not an absolute path', path)
536
536
537 # check if path is at least readable.
537 # check if path is at least readable.
538 if not os.access(path, os.R_OK):
538 if not os.access(path, os.R_OK):
539 path_ok = False
539 path_ok = False
540 log.error('Given path %s is not readable', path)
540 log.error('Given path %s is not readable', path)
541
541
542 # check write access, warn user about non writeable paths
542 # check write access, warn user about non writeable paths
543 elif not os.access(path, os.W_OK) and path_ok:
543 elif not os.access(path, os.W_OK) and path_ok:
544 log.warning('No write permission to given path %s', path)
544 log.warning('No write permission to given path %s', path)
545
545
546 q = ('Given path %s is not writeable, do you want to '
546 q = ('Given path %s is not writeable, do you want to '
547 'continue with read only mode ? [y/n]' % (path,))
547 'continue with read only mode ? [y/n]' % (path,))
548 if not self.ask_ok(q):
548 if not self.ask_ok(q):
549 log.error('Canceled by user')
549 log.error('Canceled by user')
550 sys.exit(-1)
550 sys.exit(-1)
551
551
552 if retries == 0:
552 if retries == 0:
553 sys.exit('max retries reached')
553 sys.exit('max retries reached')
554 if not path_ok:
554 if not path_ok:
555 retries -= 1
555 retries -= 1
556 return self.config_prompt(test_repo_path, retries)
556 return self.config_prompt(test_repo_path, retries)
557
557
558 real_path = os.path.normpath(os.path.realpath(path))
558 real_path = os.path.normpath(os.path.realpath(path))
559
559
560 if real_path != os.path.normpath(path):
560 if real_path != os.path.normpath(path):
561 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
561 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
562 'given path as %s ? [y/n]') % (real_path,)
562 'given path as %s ? [y/n]') % (real_path,)
563 if not self.ask_ok(q):
563 if not self.ask_ok(q):
564 log.error('Canceled by user')
564 log.error('Canceled by user')
565 sys.exit(-1)
565 sys.exit(-1)
566
566
567 return real_path
567 return real_path
568
568
569 def create_settings(self, path):
569 def create_settings(self, path):
570
570
571 self.create_ui_settings(path)
571 self.create_ui_settings(path)
572
572
573 ui_config = [
573 ui_config = [
574 ('web', 'push_ssl', 'False'),
574 ('web', 'push_ssl', 'False'),
575 ('web', 'allow_archive', 'gz zip bz2'),
575 ('web', 'allow_archive', 'gz zip bz2'),
576 ('web', 'allow_push', '*'),
576 ('web', 'allow_push', '*'),
577 ('web', 'baseurl', '/'),
577 ('web', 'baseurl', '/'),
578 ('paths', '/', path),
578 ('paths', '/', path),
579 ('phases', 'publish', 'True')
579 ('phases', 'publish', 'True')
580 ]
580 ]
581 for section, key, value in ui_config:
581 for section, key, value in ui_config:
582 ui_conf = RhodeCodeUi()
582 ui_conf = RhodeCodeUi()
583 setattr(ui_conf, 'ui_section', section)
583 setattr(ui_conf, 'ui_section', section)
584 setattr(ui_conf, 'ui_key', key)
584 setattr(ui_conf, 'ui_key', key)
585 setattr(ui_conf, 'ui_value', value)
585 setattr(ui_conf, 'ui_value', value)
586 self.sa.add(ui_conf)
586 self.sa.add(ui_conf)
587
587
588 # rhodecode app settings
588 # rhodecode app settings
589 settings = [
589 settings = [
590 ('realm', 'RhodeCode', 'unicode'),
590 ('realm', 'RhodeCode', 'unicode'),
591 ('title', '', 'unicode'),
591 ('title', '', 'unicode'),
592 ('pre_code', '', 'unicode'),
592 ('pre_code', '', 'unicode'),
593 ('post_code', '', 'unicode'),
593 ('post_code', '', 'unicode'),
594
594
595 # Visual
595 # Visual
596 ('show_public_icon', True, 'bool'),
596 ('show_public_icon', True, 'bool'),
597 ('show_private_icon', True, 'bool'),
597 ('show_private_icon', True, 'bool'),
598 ('stylify_metatags', False, 'bool'),
598 ('stylify_metatags', False, 'bool'),
599 ('dashboard_items', 100, 'int'),
599 ('dashboard_items', 100, 'int'),
600 ('admin_grid_items', 25, 'int'),
600 ('admin_grid_items', 25, 'int'),
601
601
602 ('markup_renderer', 'markdown', 'unicode'),
602 ('markup_renderer', 'markdown', 'unicode'),
603
603
604 ('show_version', True, 'bool'),
604 ('show_version', True, 'bool'),
605 ('show_revision_number', True, 'bool'),
605 ('show_revision_number', True, 'bool'),
606 ('show_sha_length', 12, 'int'),
606 ('show_sha_length', 12, 'int'),
607
607
608 ('use_gravatar', False, 'bool'),
608 ('use_gravatar', False, 'bool'),
609 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
609 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
610
610
611 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
611 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
612 ('clone_uri_id_tmpl', Repository.DEFAULT_CLONE_URI_ID, 'unicode'),
612 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
613 ('clone_uri_ssh_tmpl', Repository.DEFAULT_CLONE_URI_SSH, 'unicode'),
613 ('support_url', '', 'unicode'),
614 ('support_url', '', 'unicode'),
614 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
615 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
615
616
616 # VCS Settings
617 # VCS Settings
617 ('pr_merge_enabled', True, 'bool'),
618 ('pr_merge_enabled', True, 'bool'),
618 ('use_outdated_comments', True, 'bool'),
619 ('use_outdated_comments', True, 'bool'),
619 ('diff_cache', True, 'bool'),
620 ('diff_cache', True, 'bool'),
620 ]
621 ]
621
622
622 for key, val, type_ in settings:
623 for key, val, type_ in settings:
623 sett = RhodeCodeSetting(key, val, type_)
624 sett = RhodeCodeSetting(key, val, type_)
624 self.sa.add(sett)
625 self.sa.add(sett)
625
626
626 self.create_auth_plugin_options()
627 self.create_auth_plugin_options()
627 self.create_default_options()
628 self.create_default_options()
628
629
629 log.info('created ui config')
630 log.info('created ui config')
630
631
631 def create_user(self, username, password, email='', admin=False,
632 def create_user(self, username, password, email='', admin=False,
632 strict_creation_check=True, api_key=None):
633 strict_creation_check=True, api_key=None):
633 log.info('creating user `%s`', username)
634 log.info('creating user `%s`', username)
634 user = UserModel().create_or_update(
635 user = UserModel().create_or_update(
635 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
636 username, password, email, firstname=u'RhodeCode', lastname=u'Admin',
636 active=True, admin=admin, extern_type="rhodecode",
637 active=True, admin=admin, extern_type="rhodecode",
637 strict_creation_check=strict_creation_check)
638 strict_creation_check=strict_creation_check)
638
639
639 if api_key:
640 if api_key:
640 log.info('setting a new default auth token for user `%s`', username)
641 log.info('setting a new default auth token for user `%s`', username)
641 UserModel().add_auth_token(
642 UserModel().add_auth_token(
642 user=user, lifetime_minutes=-1,
643 user=user, lifetime_minutes=-1,
643 role=UserModel.auth_token_role.ROLE_ALL,
644 role=UserModel.auth_token_role.ROLE_ALL,
644 description=u'BUILTIN TOKEN')
645 description=u'BUILTIN TOKEN')
645
646
646 def create_default_user(self):
647 def create_default_user(self):
647 log.info('creating default user')
648 log.info('creating default user')
648 # create default user for handling default permissions.
649 # create default user for handling default permissions.
649 user = UserModel().create_or_update(username=User.DEFAULT_USER,
650 user = UserModel().create_or_update(username=User.DEFAULT_USER,
650 password=str(uuid.uuid1())[:20],
651 password=str(uuid.uuid1())[:20],
651 email=User.DEFAULT_USER_EMAIL,
652 email=User.DEFAULT_USER_EMAIL,
652 firstname=u'Anonymous',
653 firstname=u'Anonymous',
653 lastname=u'User',
654 lastname=u'User',
654 strict_creation_check=False)
655 strict_creation_check=False)
655 # based on configuration options activate/de-activate this user which
656 # based on configuration options activate/de-activate this user which
656 # controlls anonymous access
657 # controlls anonymous access
657 if self.cli_args.get('public_access') is False:
658 if self.cli_args.get('public_access') is False:
658 log.info('Public access disabled')
659 log.info('Public access disabled')
659 user.active = False
660 user.active = False
660 Session().add(user)
661 Session().add(user)
661 Session().commit()
662 Session().commit()
662
663
663 def create_permissions(self):
664 def create_permissions(self):
664 """
665 """
665 Creates all permissions defined in the system
666 Creates all permissions defined in the system
666 """
667 """
667 # module.(access|create|change|delete)_[name]
668 # module.(access|create|change|delete)_[name]
668 # module.(none|read|write|admin)
669 # module.(none|read|write|admin)
669 log.info('creating permissions')
670 log.info('creating permissions')
670 PermissionModel(self.sa).create_permissions()
671 PermissionModel(self.sa).create_permissions()
671
672
672 def populate_default_permissions(self):
673 def populate_default_permissions(self):
673 """
674 """
674 Populate default permissions. It will create only the default
675 Populate default permissions. It will create only the default
675 permissions that are missing, and not alter already defined ones
676 permissions that are missing, and not alter already defined ones
676 """
677 """
677 log.info('creating default user permissions')
678 log.info('creating default user permissions')
678 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
679 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,640 +1,641 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
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 kw['request'] = get_current_request()
69 kw['request'] = get_current_request()
70 return self.load(template_name)(**kw)
70 return self.load(template_name)(**kw)
71
71
72
72
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 deform.Form.set_default_renderer(form_renderer)
74 deform.Form.set_default_renderer(form_renderer)
75
75
76
76
77 def LoginForm(localizer):
77 def LoginForm(localizer):
78 _ = localizer
78 _ = localizer
79
79
80 class _LoginForm(formencode.Schema):
80 class _LoginForm(formencode.Schema):
81 allow_extra_fields = True
81 allow_extra_fields = True
82 filter_extra_fields = True
82 filter_extra_fields = True
83 username = v.UnicodeString(
83 username = v.UnicodeString(
84 strip=True,
84 strip=True,
85 min=1,
85 min=1,
86 not_empty=True,
86 not_empty=True,
87 messages={
87 messages={
88 'empty': _(u'Please enter a login'),
88 'empty': _(u'Please enter a login'),
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 }
90 }
91 )
91 )
92
92
93 password = v.UnicodeString(
93 password = v.UnicodeString(
94 strip=False,
94 strip=False,
95 min=3,
95 min=3,
96 max=72,
96 max=72,
97 not_empty=True,
97 not_empty=True,
98 messages={
98 messages={
99 'empty': _(u'Please enter a password'),
99 'empty': _(u'Please enter a password'),
100 'tooShort': _(u'Enter %(min)i characters or more')}
100 'tooShort': _(u'Enter %(min)i characters or more')}
101 )
101 )
102
102
103 remember = v.StringBoolean(if_missing=False)
103 remember = v.StringBoolean(if_missing=False)
104
104
105 chained_validators = [v.ValidAuth(localizer)]
105 chained_validators = [v.ValidAuth(localizer)]
106 return _LoginForm
106 return _LoginForm
107
107
108
108
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 old_data = old_data or {}
110 old_data = old_data or {}
111 available_languages = available_languages or []
111 available_languages = available_languages or []
112 _ = localizer
112 _ = localizer
113
113
114 class _UserForm(formencode.Schema):
114 class _UserForm(formencode.Schema):
115 allow_extra_fields = True
115 allow_extra_fields = True
116 filter_extra_fields = True
116 filter_extra_fields = True
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 v.ValidUsername(localizer, edit, old_data))
118 v.ValidUsername(localizer, edit, old_data))
119 if edit:
119 if edit:
120 new_password = All(
120 new_password = All(
121 v.ValidPassword(localizer),
121 v.ValidPassword(localizer),
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 )
123 )
124 password_confirmation = All(
124 password_confirmation = All(
125 v.ValidPassword(localizer),
125 v.ValidPassword(localizer),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 )
127 )
128 admin = v.StringBoolean(if_missing=False)
128 admin = v.StringBoolean(if_missing=False)
129 else:
129 else:
130 password = All(
130 password = All(
131 v.ValidPassword(localizer),
131 v.ValidPassword(localizer),
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 )
133 )
134 password_confirmation = All(
134 password_confirmation = All(
135 v.ValidPassword(localizer),
135 v.ValidPassword(localizer),
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 )
137 )
138
138
139 password_change = v.StringBoolean(if_missing=False)
139 password_change = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
141
141
142 active = v.StringBoolean(if_missing=False)
142 active = v.StringBoolean(if_missing=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
146 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
146 description = v.UnicodeString(strip=True, min=1, max=250, not_empty=False,
147 if_missing='')
147 if_missing='')
148 extern_name = v.UnicodeString(strip=True)
148 extern_name = v.UnicodeString(strip=True)
149 extern_type = v.UnicodeString(strip=True)
149 extern_type = v.UnicodeString(strip=True)
150 language = v.OneOf(available_languages, hideList=False,
150 language = v.OneOf(available_languages, hideList=False,
151 testValueList=True, if_missing=None)
151 testValueList=True, if_missing=None)
152 chained_validators = [v.ValidPasswordsMatch(localizer)]
152 chained_validators = [v.ValidPasswordsMatch(localizer)]
153 return _UserForm
153 return _UserForm
154
154
155
155
156 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
156 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
157 old_data = old_data or {}
157 old_data = old_data or {}
158 _ = localizer
158 _ = localizer
159
159
160 class _UserGroupForm(formencode.Schema):
160 class _UserGroupForm(formencode.Schema):
161 allow_extra_fields = True
161 allow_extra_fields = True
162 filter_extra_fields = True
162 filter_extra_fields = True
163
163
164 users_group_name = All(
164 users_group_name = All(
165 v.UnicodeString(strip=True, min=1, not_empty=True),
165 v.UnicodeString(strip=True, min=1, not_empty=True),
166 v.ValidUserGroup(localizer, edit, old_data)
166 v.ValidUserGroup(localizer, edit, old_data)
167 )
167 )
168 user_group_description = v.UnicodeString(strip=True, min=1,
168 user_group_description = v.UnicodeString(strip=True, min=1,
169 not_empty=False)
169 not_empty=False)
170
170
171 users_group_active = v.StringBoolean(if_missing=False)
171 users_group_active = v.StringBoolean(if_missing=False)
172
172
173 if edit:
173 if edit:
174 # this is user group owner
174 # this is user group owner
175 user = All(
175 user = All(
176 v.UnicodeString(not_empty=True),
176 v.UnicodeString(not_empty=True),
177 v.ValidRepoUser(localizer, allow_disabled))
177 v.ValidRepoUser(localizer, allow_disabled))
178 return _UserGroupForm
178 return _UserGroupForm
179
179
180
180
181 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
181 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
182 can_create_in_root=False, allow_disabled=False):
182 can_create_in_root=False, allow_disabled=False):
183 _ = localizer
183 _ = localizer
184 old_data = old_data or {}
184 old_data = old_data or {}
185 available_groups = available_groups or []
185 available_groups = available_groups or []
186
186
187 class _RepoGroupForm(formencode.Schema):
187 class _RepoGroupForm(formencode.Schema):
188 allow_extra_fields = True
188 allow_extra_fields = True
189 filter_extra_fields = False
189 filter_extra_fields = False
190
190
191 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
191 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
192 v.SlugifyName(localizer),)
192 v.SlugifyName(localizer),)
193 group_description = v.UnicodeString(strip=True, min=1,
193 group_description = v.UnicodeString(strip=True, min=1,
194 not_empty=False)
194 not_empty=False)
195 group_copy_permissions = v.StringBoolean(if_missing=False)
195 group_copy_permissions = v.StringBoolean(if_missing=False)
196
196
197 group_parent_id = v.OneOf(available_groups, hideList=False,
197 group_parent_id = v.OneOf(available_groups, hideList=False,
198 testValueList=True, not_empty=True)
198 testValueList=True, not_empty=True)
199 enable_locking = v.StringBoolean(if_missing=False)
199 enable_locking = v.StringBoolean(if_missing=False)
200 chained_validators = [
200 chained_validators = [
201 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
201 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
202
202
203 if edit:
203 if edit:
204 # this is repo group owner
204 # this is repo group owner
205 user = All(
205 user = All(
206 v.UnicodeString(not_empty=True),
206 v.UnicodeString(not_empty=True),
207 v.ValidRepoUser(localizer, allow_disabled))
207 v.ValidRepoUser(localizer, allow_disabled))
208 return _RepoGroupForm
208 return _RepoGroupForm
209
209
210
210
211 def RegisterForm(localizer, edit=False, old_data=None):
211 def RegisterForm(localizer, edit=False, old_data=None):
212 _ = localizer
212 _ = localizer
213 old_data = old_data or {}
213 old_data = old_data or {}
214
214
215 class _RegisterForm(formencode.Schema):
215 class _RegisterForm(formencode.Schema):
216 allow_extra_fields = True
216 allow_extra_fields = True
217 filter_extra_fields = True
217 filter_extra_fields = True
218 username = All(
218 username = All(
219 v.ValidUsername(localizer, edit, old_data),
219 v.ValidUsername(localizer, edit, old_data),
220 v.UnicodeString(strip=True, min=1, not_empty=True)
220 v.UnicodeString(strip=True, min=1, not_empty=True)
221 )
221 )
222 password = All(
222 password = All(
223 v.ValidPassword(localizer),
223 v.ValidPassword(localizer),
224 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
224 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
225 )
225 )
226 password_confirmation = All(
226 password_confirmation = All(
227 v.ValidPassword(localizer),
227 v.ValidPassword(localizer),
228 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
228 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
229 )
229 )
230 active = v.StringBoolean(if_missing=False)
230 active = v.StringBoolean(if_missing=False)
231 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
232 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
232 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
233 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
233 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
234
234
235 chained_validators = [v.ValidPasswordsMatch(localizer)]
235 chained_validators = [v.ValidPasswordsMatch(localizer)]
236 return _RegisterForm
236 return _RegisterForm
237
237
238
238
239 def PasswordResetForm(localizer):
239 def PasswordResetForm(localizer):
240 _ = localizer
240 _ = localizer
241
241
242 class _PasswordResetForm(formencode.Schema):
242 class _PasswordResetForm(formencode.Schema):
243 allow_extra_fields = True
243 allow_extra_fields = True
244 filter_extra_fields = True
244 filter_extra_fields = True
245 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
245 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
246 return _PasswordResetForm
246 return _PasswordResetForm
247
247
248
248
249 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
249 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
250 _ = localizer
250 _ = localizer
251 old_data = old_data or {}
251 old_data = old_data or {}
252 repo_groups = repo_groups or []
252 repo_groups = repo_groups or []
253 supported_backends = BACKENDS.keys()
253 supported_backends = BACKENDS.keys()
254
254
255 class _RepoForm(formencode.Schema):
255 class _RepoForm(formencode.Schema):
256 allow_extra_fields = True
256 allow_extra_fields = True
257 filter_extra_fields = False
257 filter_extra_fields = False
258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
260 repo_group = All(v.CanWriteGroup(localizer, old_data),
260 repo_group = All(v.CanWriteGroup(localizer, old_data),
261 v.OneOf(repo_groups, hideList=True))
261 v.OneOf(repo_groups, hideList=True))
262 repo_type = v.OneOf(supported_backends, required=False,
262 repo_type = v.OneOf(supported_backends, required=False,
263 if_missing=old_data.get('repo_type'))
263 if_missing=old_data.get('repo_type'))
264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
265 repo_private = v.StringBoolean(if_missing=False)
265 repo_private = v.StringBoolean(if_missing=False)
266 repo_copy_permissions = v.StringBoolean(if_missing=False)
266 repo_copy_permissions = v.StringBoolean(if_missing=False)
267 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
267 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
268
268
269 repo_enable_statistics = v.StringBoolean(if_missing=False)
269 repo_enable_statistics = v.StringBoolean(if_missing=False)
270 repo_enable_downloads = v.StringBoolean(if_missing=False)
270 repo_enable_downloads = v.StringBoolean(if_missing=False)
271 repo_enable_locking = v.StringBoolean(if_missing=False)
271 repo_enable_locking = v.StringBoolean(if_missing=False)
272
272
273 if edit:
273 if edit:
274 # this is repo owner
274 # this is repo owner
275 user = All(
275 user = All(
276 v.UnicodeString(not_empty=True),
276 v.UnicodeString(not_empty=True),
277 v.ValidRepoUser(localizer, allow_disabled))
277 v.ValidRepoUser(localizer, allow_disabled))
278 clone_uri_change = v.UnicodeString(
278 clone_uri_change = v.UnicodeString(
279 not_empty=False, if_missing=v.Missing)
279 not_empty=False, if_missing=v.Missing)
280
280
281 chained_validators = [v.ValidCloneUri(localizer),
281 chained_validators = [v.ValidCloneUri(localizer),
282 v.ValidRepoName(localizer, edit, old_data)]
282 v.ValidRepoName(localizer, edit, old_data)]
283 return _RepoForm
283 return _RepoForm
284
284
285
285
286 def RepoPermsForm(localizer):
286 def RepoPermsForm(localizer):
287 _ = localizer
287 _ = localizer
288
288
289 class _RepoPermsForm(formencode.Schema):
289 class _RepoPermsForm(formencode.Schema):
290 allow_extra_fields = True
290 allow_extra_fields = True
291 filter_extra_fields = False
291 filter_extra_fields = False
292 chained_validators = [v.ValidPerms(localizer, type_='repo')]
292 chained_validators = [v.ValidPerms(localizer, type_='repo')]
293 return _RepoPermsForm
293 return _RepoPermsForm
294
294
295
295
296 def RepoGroupPermsForm(localizer, valid_recursive_choices):
296 def RepoGroupPermsForm(localizer, valid_recursive_choices):
297 _ = localizer
297 _ = localizer
298
298
299 class _RepoGroupPermsForm(formencode.Schema):
299 class _RepoGroupPermsForm(formencode.Schema):
300 allow_extra_fields = True
300 allow_extra_fields = True
301 filter_extra_fields = False
301 filter_extra_fields = False
302 recursive = v.OneOf(valid_recursive_choices)
302 recursive = v.OneOf(valid_recursive_choices)
303 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
303 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
304 return _RepoGroupPermsForm
304 return _RepoGroupPermsForm
305
305
306
306
307 def UserGroupPermsForm(localizer):
307 def UserGroupPermsForm(localizer):
308 _ = localizer
308 _ = localizer
309
309
310 class _UserPermsForm(formencode.Schema):
310 class _UserPermsForm(formencode.Schema):
311 allow_extra_fields = True
311 allow_extra_fields = True
312 filter_extra_fields = False
312 filter_extra_fields = False
313 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
313 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
314 return _UserPermsForm
314 return _UserPermsForm
315
315
316
316
317 def RepoFieldForm(localizer):
317 def RepoFieldForm(localizer):
318 _ = localizer
318 _ = localizer
319
319
320 class _RepoFieldForm(formencode.Schema):
320 class _RepoFieldForm(formencode.Schema):
321 filter_extra_fields = True
321 filter_extra_fields = True
322 allow_extra_fields = True
322 allow_extra_fields = True
323
323
324 new_field_key = All(v.FieldKey(localizer),
324 new_field_key = All(v.FieldKey(localizer),
325 v.UnicodeString(strip=True, min=3, not_empty=True))
325 v.UnicodeString(strip=True, min=3, not_empty=True))
326 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
326 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
327 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
327 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
328 if_missing='str')
328 if_missing='str')
329 new_field_label = v.UnicodeString(not_empty=False)
329 new_field_label = v.UnicodeString(not_empty=False)
330 new_field_desc = v.UnicodeString(not_empty=False)
330 new_field_desc = v.UnicodeString(not_empty=False)
331 return _RepoFieldForm
331 return _RepoFieldForm
332
332
333
333
334 def RepoForkForm(localizer, edit=False, old_data=None,
334 def RepoForkForm(localizer, edit=False, old_data=None,
335 supported_backends=BACKENDS.keys(), repo_groups=None):
335 supported_backends=BACKENDS.keys(), repo_groups=None):
336 _ = localizer
336 _ = localizer
337 old_data = old_data or {}
337 old_data = old_data or {}
338 repo_groups = repo_groups or []
338 repo_groups = repo_groups or []
339
339
340 class _RepoForkForm(formencode.Schema):
340 class _RepoForkForm(formencode.Schema):
341 allow_extra_fields = True
341 allow_extra_fields = True
342 filter_extra_fields = False
342 filter_extra_fields = False
343 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
343 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
344 v.SlugifyName(localizer))
344 v.SlugifyName(localizer))
345 repo_group = All(v.CanWriteGroup(localizer, ),
345 repo_group = All(v.CanWriteGroup(localizer, ),
346 v.OneOf(repo_groups, hideList=True))
346 v.OneOf(repo_groups, hideList=True))
347 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
347 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
348 description = v.UnicodeString(strip=True, min=1, not_empty=True)
348 description = v.UnicodeString(strip=True, min=1, not_empty=True)
349 private = v.StringBoolean(if_missing=False)
349 private = v.StringBoolean(if_missing=False)
350 copy_permissions = v.StringBoolean(if_missing=False)
350 copy_permissions = v.StringBoolean(if_missing=False)
351 fork_parent_id = v.UnicodeString()
351 fork_parent_id = v.UnicodeString()
352 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
352 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
353 return _RepoForkForm
353 return _RepoForkForm
354
354
355
355
356 def ApplicationSettingsForm(localizer):
356 def ApplicationSettingsForm(localizer):
357 _ = localizer
357 _ = localizer
358
358
359 class _ApplicationSettingsForm(formencode.Schema):
359 class _ApplicationSettingsForm(formencode.Schema):
360 allow_extra_fields = True
360 allow_extra_fields = True
361 filter_extra_fields = False
361 filter_extra_fields = False
362 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
362 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
363 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
363 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
364 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
364 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
365 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
365 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
366 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
366 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
367 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
367 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
368 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
368 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
369 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
370 return _ApplicationSettingsForm
370 return _ApplicationSettingsForm
371
371
372
372
373 def ApplicationVisualisationForm(localizer):
373 def ApplicationVisualisationForm(localizer):
374 from rhodecode.model.db import Repository
374 from rhodecode.model.db import Repository
375 _ = localizer
375 _ = localizer
376
376
377 class _ApplicationVisualisationForm(formencode.Schema):
377 class _ApplicationVisualisationForm(formencode.Schema):
378 allow_extra_fields = True
378 allow_extra_fields = True
379 filter_extra_fields = False
379 filter_extra_fields = False
380 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
380 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
381 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
381 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
382 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
382 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
383
383
384 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
384 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
385 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
385 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
386 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
386 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
387 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
387 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
388 rhodecode_show_version = v.StringBoolean(if_missing=False)
388 rhodecode_show_version = v.StringBoolean(if_missing=False)
389 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
389 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
390 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
390 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
391 rhodecode_gravatar_url = v.UnicodeString(min=3)
391 rhodecode_gravatar_url = v.UnicodeString(min=3)
392 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
392 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
393 rhodecode_clone_uri_id_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_ID)
393 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
394 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
394 rhodecode_support_url = v.UnicodeString()
395 rhodecode_support_url = v.UnicodeString()
395 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
396 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
396 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
397 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
397 return _ApplicationVisualisationForm
398 return _ApplicationVisualisationForm
398
399
399
400
400 class _BaseVcsSettingsForm(formencode.Schema):
401 class _BaseVcsSettingsForm(formencode.Schema):
401
402
402 allow_extra_fields = True
403 allow_extra_fields = True
403 filter_extra_fields = False
404 filter_extra_fields = False
404 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
405 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
405 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
406 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
406 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
407 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
407
408
408 # PR/Code-review
409 # PR/Code-review
409 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
410 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
410 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
411 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
411
412
412 # hg
413 # hg
413 extensions_largefiles = v.StringBoolean(if_missing=False)
414 extensions_largefiles = v.StringBoolean(if_missing=False)
414 extensions_evolve = v.StringBoolean(if_missing=False)
415 extensions_evolve = v.StringBoolean(if_missing=False)
415 phases_publish = v.StringBoolean(if_missing=False)
416 phases_publish = v.StringBoolean(if_missing=False)
416
417
417 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
418 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
418 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
419 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
419
420
420 # git
421 # git
421 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
422 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
422 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
423 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
423 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
424 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
424
425
425 # svn
426 # svn
426 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
427 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
427 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
428 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
428
429
429 # cache
430 # cache
430 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
431 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
431
432
432
433
433 def ApplicationUiSettingsForm(localizer):
434 def ApplicationUiSettingsForm(localizer):
434 _ = localizer
435 _ = localizer
435
436
436 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
437 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
437 web_push_ssl = v.StringBoolean(if_missing=False)
438 web_push_ssl = v.StringBoolean(if_missing=False)
438 paths_root_path = All(
439 paths_root_path = All(
439 v.ValidPath(localizer),
440 v.ValidPath(localizer),
440 v.UnicodeString(strip=True, min=1, not_empty=True)
441 v.UnicodeString(strip=True, min=1, not_empty=True)
441 )
442 )
442 largefiles_usercache = All(
443 largefiles_usercache = All(
443 v.ValidPath(localizer),
444 v.ValidPath(localizer),
444 v.UnicodeString(strip=True, min=2, not_empty=True))
445 v.UnicodeString(strip=True, min=2, not_empty=True))
445 vcs_git_lfs_store_location = All(
446 vcs_git_lfs_store_location = All(
446 v.ValidPath(localizer),
447 v.ValidPath(localizer),
447 v.UnicodeString(strip=True, min=2, not_empty=True))
448 v.UnicodeString(strip=True, min=2, not_empty=True))
448 extensions_hgsubversion = v.StringBoolean(if_missing=False)
449 extensions_hgsubversion = v.StringBoolean(if_missing=False)
449 extensions_hggit = v.StringBoolean(if_missing=False)
450 extensions_hggit = v.StringBoolean(if_missing=False)
450 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
451 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
451 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
452 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
452 return _ApplicationUiSettingsForm
453 return _ApplicationUiSettingsForm
453
454
454
455
455 def RepoVcsSettingsForm(localizer, repo_name):
456 def RepoVcsSettingsForm(localizer, repo_name):
456 _ = localizer
457 _ = localizer
457
458
458 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
459 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
459 inherit_global_settings = v.StringBoolean(if_missing=False)
460 inherit_global_settings = v.StringBoolean(if_missing=False)
460 new_svn_branch = v.ValidSvnPattern(localizer,
461 new_svn_branch = v.ValidSvnPattern(localizer,
461 section='vcs_svn_branch', repo_name=repo_name)
462 section='vcs_svn_branch', repo_name=repo_name)
462 new_svn_tag = v.ValidSvnPattern(localizer,
463 new_svn_tag = v.ValidSvnPattern(localizer,
463 section='vcs_svn_tag', repo_name=repo_name)
464 section='vcs_svn_tag', repo_name=repo_name)
464 return _RepoVcsSettingsForm
465 return _RepoVcsSettingsForm
465
466
466
467
467 def LabsSettingsForm(localizer):
468 def LabsSettingsForm(localizer):
468 _ = localizer
469 _ = localizer
469
470
470 class _LabSettingsForm(formencode.Schema):
471 class _LabSettingsForm(formencode.Schema):
471 allow_extra_fields = True
472 allow_extra_fields = True
472 filter_extra_fields = False
473 filter_extra_fields = False
473 return _LabSettingsForm
474 return _LabSettingsForm
474
475
475
476
476 def ApplicationPermissionsForm(
477 def ApplicationPermissionsForm(
477 localizer, register_choices, password_reset_choices,
478 localizer, register_choices, password_reset_choices,
478 extern_activate_choices):
479 extern_activate_choices):
479 _ = localizer
480 _ = localizer
480
481
481 class _DefaultPermissionsForm(formencode.Schema):
482 class _DefaultPermissionsForm(formencode.Schema):
482 allow_extra_fields = True
483 allow_extra_fields = True
483 filter_extra_fields = True
484 filter_extra_fields = True
484
485
485 anonymous = v.StringBoolean(if_missing=False)
486 anonymous = v.StringBoolean(if_missing=False)
486 default_register = v.OneOf(register_choices)
487 default_register = v.OneOf(register_choices)
487 default_register_message = v.UnicodeString()
488 default_register_message = v.UnicodeString()
488 default_password_reset = v.OneOf(password_reset_choices)
489 default_password_reset = v.OneOf(password_reset_choices)
489 default_extern_activate = v.OneOf(extern_activate_choices)
490 default_extern_activate = v.OneOf(extern_activate_choices)
490 return _DefaultPermissionsForm
491 return _DefaultPermissionsForm
491
492
492
493
493 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
494 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
494 user_group_perms_choices):
495 user_group_perms_choices):
495 _ = localizer
496 _ = localizer
496
497
497 class _ObjectPermissionsForm(formencode.Schema):
498 class _ObjectPermissionsForm(formencode.Schema):
498 allow_extra_fields = True
499 allow_extra_fields = True
499 filter_extra_fields = True
500 filter_extra_fields = True
500 overwrite_default_repo = v.StringBoolean(if_missing=False)
501 overwrite_default_repo = v.StringBoolean(if_missing=False)
501 overwrite_default_group = v.StringBoolean(if_missing=False)
502 overwrite_default_group = v.StringBoolean(if_missing=False)
502 overwrite_default_user_group = v.StringBoolean(if_missing=False)
503 overwrite_default_user_group = v.StringBoolean(if_missing=False)
503
504
504 default_repo_perm = v.OneOf(repo_perms_choices)
505 default_repo_perm = v.OneOf(repo_perms_choices)
505 default_group_perm = v.OneOf(group_perms_choices)
506 default_group_perm = v.OneOf(group_perms_choices)
506 default_user_group_perm = v.OneOf(user_group_perms_choices)
507 default_user_group_perm = v.OneOf(user_group_perms_choices)
507
508
508 return _ObjectPermissionsForm
509 return _ObjectPermissionsForm
509
510
510
511
511 def BranchPermissionsForm(localizer, branch_perms_choices):
512 def BranchPermissionsForm(localizer, branch_perms_choices):
512 _ = localizer
513 _ = localizer
513
514
514 class _BranchPermissionsForm(formencode.Schema):
515 class _BranchPermissionsForm(formencode.Schema):
515 allow_extra_fields = True
516 allow_extra_fields = True
516 filter_extra_fields = True
517 filter_extra_fields = True
517 overwrite_default_branch = v.StringBoolean(if_missing=False)
518 overwrite_default_branch = v.StringBoolean(if_missing=False)
518 default_branch_perm = v.OneOf(branch_perms_choices)
519 default_branch_perm = v.OneOf(branch_perms_choices)
519
520
520 return _BranchPermissionsForm
521 return _BranchPermissionsForm
521
522
522
523
523 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
524 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
524 repo_group_create_choices, user_group_create_choices,
525 repo_group_create_choices, user_group_create_choices,
525 fork_choices, inherit_default_permissions_choices):
526 fork_choices, inherit_default_permissions_choices):
526 _ = localizer
527 _ = localizer
527
528
528 class _DefaultPermissionsForm(formencode.Schema):
529 class _DefaultPermissionsForm(formencode.Schema):
529 allow_extra_fields = True
530 allow_extra_fields = True
530 filter_extra_fields = True
531 filter_extra_fields = True
531
532
532 anonymous = v.StringBoolean(if_missing=False)
533 anonymous = v.StringBoolean(if_missing=False)
533
534
534 default_repo_create = v.OneOf(create_choices)
535 default_repo_create = v.OneOf(create_choices)
535 default_repo_create_on_write = v.OneOf(create_on_write_choices)
536 default_repo_create_on_write = v.OneOf(create_on_write_choices)
536 default_user_group_create = v.OneOf(user_group_create_choices)
537 default_user_group_create = v.OneOf(user_group_create_choices)
537 default_repo_group_create = v.OneOf(repo_group_create_choices)
538 default_repo_group_create = v.OneOf(repo_group_create_choices)
538 default_fork_create = v.OneOf(fork_choices)
539 default_fork_create = v.OneOf(fork_choices)
539 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
540 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
540 return _DefaultPermissionsForm
541 return _DefaultPermissionsForm
541
542
542
543
543 def UserIndividualPermissionsForm(localizer):
544 def UserIndividualPermissionsForm(localizer):
544 _ = localizer
545 _ = localizer
545
546
546 class _DefaultPermissionsForm(formencode.Schema):
547 class _DefaultPermissionsForm(formencode.Schema):
547 allow_extra_fields = True
548 allow_extra_fields = True
548 filter_extra_fields = True
549 filter_extra_fields = True
549
550
550 inherit_default_permissions = v.StringBoolean(if_missing=False)
551 inherit_default_permissions = v.StringBoolean(if_missing=False)
551 return _DefaultPermissionsForm
552 return _DefaultPermissionsForm
552
553
553
554
554 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
555 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
555 _ = localizer
556 _ = localizer
556 old_data = old_data or {}
557 old_data = old_data or {}
557
558
558 class _DefaultsForm(formencode.Schema):
559 class _DefaultsForm(formencode.Schema):
559 allow_extra_fields = True
560 allow_extra_fields = True
560 filter_extra_fields = True
561 filter_extra_fields = True
561 default_repo_type = v.OneOf(supported_backends)
562 default_repo_type = v.OneOf(supported_backends)
562 default_repo_private = v.StringBoolean(if_missing=False)
563 default_repo_private = v.StringBoolean(if_missing=False)
563 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
564 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
564 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
565 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
565 default_repo_enable_locking = v.StringBoolean(if_missing=False)
566 default_repo_enable_locking = v.StringBoolean(if_missing=False)
566 return _DefaultsForm
567 return _DefaultsForm
567
568
568
569
569 def AuthSettingsForm(localizer):
570 def AuthSettingsForm(localizer):
570 _ = localizer
571 _ = localizer
571
572
572 class _AuthSettingsForm(formencode.Schema):
573 class _AuthSettingsForm(formencode.Schema):
573 allow_extra_fields = True
574 allow_extra_fields = True
574 filter_extra_fields = True
575 filter_extra_fields = True
575 auth_plugins = All(v.ValidAuthPlugins(localizer),
576 auth_plugins = All(v.ValidAuthPlugins(localizer),
576 v.UniqueListFromString(localizer)(not_empty=True))
577 v.UniqueListFromString(localizer)(not_empty=True))
577 return _AuthSettingsForm
578 return _AuthSettingsForm
578
579
579
580
580 def UserExtraEmailForm(localizer):
581 def UserExtraEmailForm(localizer):
581 _ = localizer
582 _ = localizer
582
583
583 class _UserExtraEmailForm(formencode.Schema):
584 class _UserExtraEmailForm(formencode.Schema):
584 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
585 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
585 return _UserExtraEmailForm
586 return _UserExtraEmailForm
586
587
587
588
588 def UserExtraIpForm(localizer):
589 def UserExtraIpForm(localizer):
589 _ = localizer
590 _ = localizer
590
591
591 class _UserExtraIpForm(formencode.Schema):
592 class _UserExtraIpForm(formencode.Schema):
592 ip = v.ValidIp(localizer)(not_empty=True)
593 ip = v.ValidIp(localizer)(not_empty=True)
593 return _UserExtraIpForm
594 return _UserExtraIpForm
594
595
595
596
596 def PullRequestForm(localizer, repo_id):
597 def PullRequestForm(localizer, repo_id):
597 _ = localizer
598 _ = localizer
598
599
599 class ReviewerForm(formencode.Schema):
600 class ReviewerForm(formencode.Schema):
600 user_id = v.Int(not_empty=True)
601 user_id = v.Int(not_empty=True)
601 reasons = All()
602 reasons = All()
602 rules = All(v.UniqueList(localizer, convert=int)())
603 rules = All(v.UniqueList(localizer, convert=int)())
603 mandatory = v.StringBoolean()
604 mandatory = v.StringBoolean()
604 role = v.String(if_missing='reviewer')
605 role = v.String(if_missing='reviewer')
605
606
606 class ObserverForm(formencode.Schema):
607 class ObserverForm(formencode.Schema):
607 user_id = v.Int(not_empty=True)
608 user_id = v.Int(not_empty=True)
608 reasons = All()
609 reasons = All()
609 rules = All(v.UniqueList(localizer, convert=int)())
610 rules = All(v.UniqueList(localizer, convert=int)())
610 mandatory = v.StringBoolean()
611 mandatory = v.StringBoolean()
611 role = v.String(if_missing='observer')
612 role = v.String(if_missing='observer')
612
613
613 class _PullRequestForm(formencode.Schema):
614 class _PullRequestForm(formencode.Schema):
614 allow_extra_fields = True
615 allow_extra_fields = True
615 filter_extra_fields = True
616 filter_extra_fields = True
616
617
617 common_ancestor = v.UnicodeString(strip=True, required=True)
618 common_ancestor = v.UnicodeString(strip=True, required=True)
618 source_repo = v.UnicodeString(strip=True, required=True)
619 source_repo = v.UnicodeString(strip=True, required=True)
619 source_ref = v.UnicodeString(strip=True, required=True)
620 source_ref = v.UnicodeString(strip=True, required=True)
620 target_repo = v.UnicodeString(strip=True, required=True)
621 target_repo = v.UnicodeString(strip=True, required=True)
621 target_ref = v.UnicodeString(strip=True, required=True)
622 target_ref = v.UnicodeString(strip=True, required=True)
622 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
623 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
623 v.UniqueList(localizer)(not_empty=True))
624 v.UniqueList(localizer)(not_empty=True))
624 review_members = formencode.ForEach(ReviewerForm())
625 review_members = formencode.ForEach(ReviewerForm())
625 observer_members = formencode.ForEach(ObserverForm())
626 observer_members = formencode.ForEach(ObserverForm())
626 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
627 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
627 pullrequest_desc = v.UnicodeString(strip=True, required=False)
628 pullrequest_desc = v.UnicodeString(strip=True, required=False)
628 description_renderer = v.UnicodeString(strip=True, required=False)
629 description_renderer = v.UnicodeString(strip=True, required=False)
629
630
630 return _PullRequestForm
631 return _PullRequestForm
631
632
632
633
633 def IssueTrackerPatternsForm(localizer):
634 def IssueTrackerPatternsForm(localizer):
634 _ = localizer
635 _ = localizer
635
636
636 class _IssueTrackerPatternsForm(formencode.Schema):
637 class _IssueTrackerPatternsForm(formencode.Schema):
637 allow_extra_fields = True
638 allow_extra_fields = True
638 filter_extra_fields = False
639 filter_extra_fields = False
639 chained_validators = [v.ValidPattern(localizer)]
640 chained_validators = [v.ValidPattern(localizer)]
640 return _IssueTrackerPatternsForm
641 return _IssueTrackerPatternsForm
@@ -1,230 +1,233 b''
1 ${h.secure_form(h.route_path('admin_settings_visual_update'), request=request)}
1 ${h.secure_form(h.route_path('admin_settings_visual_update'), request=request)}
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading" id="general">
4 <div class="panel-heading" id="general">
5 <h3 class="panel-title">${_('General')}</h3>
5 <h3 class="panel-title">${_('General')}</h3>
6 </div>
6 </div>
7 <div class="panel-body">
7 <div class="panel-body">
8 <div class="checkbox">
8 <div class="checkbox">
9 ${h.checkbox('rhodecode_repository_fields','True')}
9 ${h.checkbox('rhodecode_repository_fields','True')}
10 <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
10 <label for="rhodecode_repository_fields">${_('Use repository extra fields')}</label>
11 </div>
11 </div>
12 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
12 <span class="help-block">${_('Allows storing additional customized fields per repository.')}</span>
13
13
14 <div></div>
14 <div></div>
15 <div class="checkbox">
15 <div class="checkbox">
16 ${h.checkbox('rhodecode_show_version','True')}
16 ${h.checkbox('rhodecode_show_version','True')}
17 <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
17 <label for="rhodecode_show_version">${_('Show RhodeCode version')}</label>
18 </div>
18 </div>
19 <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
19 <span class="help-block">${_('Shows or hides a version number of RhodeCode displayed in the footer.')}</span>
20 </div>
20 </div>
21 </div>
21 </div>
22
22
23
23
24 <div class="panel panel-default">
24 <div class="panel panel-default">
25 <div class="panel-heading" id="gravatars">
25 <div class="panel-heading" id="gravatars">
26 <h3 class="panel-title">${_('Gravatars')}</h3>
26 <h3 class="panel-title">${_('Gravatars')}</h3>
27 </div>
27 </div>
28 <div class="panel-body">
28 <div class="panel-body">
29 <div class="checkbox">
29 <div class="checkbox">
30 ${h.checkbox('rhodecode_use_gravatar','True')}
30 ${h.checkbox('rhodecode_use_gravatar','True')}
31 <label for="rhodecode_use_gravatar">${_('Use Gravatars based avatars')}</label>
31 <label for="rhodecode_use_gravatar">${_('Use Gravatars based avatars')}</label>
32 </div>
32 </div>
33 <span class="help-block">${_('Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email.')}</span>
33 <span class="help-block">${_('Use gravatar.com as avatar system for RhodeCode accounts. If this is disabled avatars are generated based on initials and email.')}</span>
34
34
35 <div class="label">
35 <div class="label">
36 <label for="rhodecode_gravatar_url">${_('Gravatar URL')}</label>
36 <label for="rhodecode_gravatar_url">${_('Gravatar URL')}</label>
37 </div>
37 </div>
38 <div class="input">
38 <div class="input">
39 <div class="field">
39 <div class="field">
40 ${h.text('rhodecode_gravatar_url', size='100%')}
40 ${h.text('rhodecode_gravatar_url', size='100%')}
41 </div>
41 </div>
42
42
43 <div class="field">
43 <div class="field">
44 <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
44 <span class="help-block">${_('''Gravatar url allows you to use other avatar server application.
45 Following variables of the URL will be replaced accordingly.
45 Following variables of the URL will be replaced accordingly.
46 {scheme} 'http' or 'https' sent from running RhodeCode server,
46 {scheme} 'http' or 'https' sent from running RhodeCode server,
47 {email} user email,
47 {email} user email,
48 {md5email} md5 hash of the user email (like at gravatar.com),
48 {md5email} md5 hash of the user email (like at gravatar.com),
49 {size} size of the image that is expected from the server application,
49 {size} size of the image that is expected from the server application,
50 {netloc} network location/server host of running RhodeCode server''')}</span>
50 {netloc} network location/server host of running RhodeCode server''')}</span>
51 </div>
51 </div>
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55
55
56
56
57 <div class="panel panel-default">
57 <div class="panel panel-default">
58 <div class="panel-heading" id="meta-tagging">
58 <div class="panel-heading" id="meta-tagging">
59 <h3 class="panel-title">${_('Meta-Tagging')}</h3>
59 <h3 class="panel-title">${_('Meta-Tagging')}</h3>
60 </div>
60 </div>
61 <div class="panel-body">
61 <div class="panel-body">
62 <div class="checkbox">
62 <div class="checkbox">
63 ${h.checkbox('rhodecode_stylify_metatags','True')}
63 ${h.checkbox('rhodecode_stylify_metatags','True')}
64 <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
64 <label for="rhodecode_stylify_metatags">${_('Stylify recognised meta tags')}</label>
65 </div>
65 </div>
66 <span class="help-block">${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}</span>
66 <span class="help-block">${_('Parses meta tags from repository or repository group description fields and turns them into colored tags.')}</span>
67 <div>
67 <div>
68 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
68 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
69 ${dt.metatags_help()}
69 ${dt.metatags_help()}
70 </div>
70 </div>
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74
74
75 <div class="panel panel-default">
75 <div class="panel panel-default">
76 <div class="panel-heading">
76 <div class="panel-heading">
77 <h3 class="panel-title">${_('Dashboard Items')}</h3>
77 <h3 class="panel-title">${_('Dashboard Items')}</h3>
78 </div>
78 </div>
79 <div class="panel-body">
79 <div class="panel-body">
80 <div class="label">
80 <div class="label">
81 <label for="rhodecode_dashboard_items">${_('Main page dashboard items')}</label>
81 <label for="rhodecode_dashboard_items">${_('Main page dashboard items')}</label>
82 </div>
82 </div>
83 <div class="field input">
83 <div class="field input">
84 ${h.text('rhodecode_dashboard_items',size=5)}
84 ${h.text('rhodecode_dashboard_items',size=5)}
85 </div>
85 </div>
86 <div class="field">
86 <div class="field">
87 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
87 <span class="help-block">${_('Number of items displayed in the main page dashboard before pagination is shown.')}</span>
88 </div>
88 </div>
89
89
90 <div class="label">
90 <div class="label">
91 <label for="rhodecode_admin_grid_items">${_('Admin pages items')}</label>
91 <label for="rhodecode_admin_grid_items">${_('Admin pages items')}</label>
92 </div>
92 </div>
93 <div class="field input">
93 <div class="field input">
94 ${h.text('rhodecode_admin_grid_items',size=5)}
94 ${h.text('rhodecode_admin_grid_items',size=5)}
95 </div>
95 </div>
96 <div class="field">
96 <div class="field">
97 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
97 <span class="help-block">${_('Number of items displayed in the admin pages grids before pagination is shown.')}</span>
98 </div>
98 </div>
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102
102
103
103
104 <div class="panel panel-default">
104 <div class="panel panel-default">
105 <div class="panel-heading" id="commit-id">
105 <div class="panel-heading" id="commit-id">
106 <h3 class="panel-title">${_('Commit ID Style')}</h3>
106 <h3 class="panel-title">${_('Commit ID Style')}</h3>
107 </div>
107 </div>
108 <div class="panel-body">
108 <div class="panel-body">
109 <div class="label">
109 <div class="label">
110 <label for="rhodecode_show_sha_length">${_('Commit sha length')}</label>
110 <label for="rhodecode_show_sha_length">${_('Commit sha length')}</label>
111 </div>
111 </div>
112 <div class="input">
112 <div class="input">
113 <div class="field">
113 <div class="field">
114 ${h.text('rhodecode_show_sha_length',size=5)}
114 ${h.text('rhodecode_show_sha_length',size=5)}
115 </div>
115 </div>
116 <div class="field">
116 <div class="field">
117 <span class="help-block">${_('''Number of chars to show in commit sha displayed in web interface.
117 <span class="help-block">${_('''Number of chars to show in commit sha displayed in web interface.
118 By default it's shown as r123:9043a6a4c226 this value defines the
118 By default it's shown as r123:9043a6a4c226 this value defines the
119 length of the sha after the `r123:` part.''')}</span>
119 length of the sha after the `r123:` part.''')}</span>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 <div class="checkbox">
123 <div class="checkbox">
124 ${h.checkbox('rhodecode_show_revision_number','True')}
124 ${h.checkbox('rhodecode_show_revision_number','True')}
125 <label for="rhodecode_show_revision_number">${_('Show commit ID numeric reference')} / ${_('Commit show revision number')}</label>
125 <label for="rhodecode_show_revision_number">${_('Show commit ID numeric reference')} / ${_('Commit show revision number')}</label>
126 </div>
126 </div>
127 <span class="help-block">${_('''Show revision number in commit sha displayed in web interface.
127 <span class="help-block">${_('''Show revision number in commit sha displayed in web interface.
128 By default it's shown as r123:9043a6a4c226 this value defines the
128 By default it's shown as r123:9043a6a4c226 this value defines the
129 if the `r123:` part is shown.''')}</span>
129 if the `r123:` part is shown.''')}</span>
130 </div>
130 </div>
131 </div>
131 </div>
132
132
133
133
134 <div class="panel panel-default">
134 <div class="panel panel-default">
135 <div class="panel-heading" id="icons">
135 <div class="panel-heading" id="icons">
136 <h3 class="panel-title">${_('Icons')}</h3>
136 <h3 class="panel-title">${_('Icons')}</h3>
137 </div>
137 </div>
138 <div class="panel-body">
138 <div class="panel-body">
139 <div class="checkbox">
139 <div class="checkbox">
140 ${h.checkbox('rhodecode_show_public_icon','True')}
140 ${h.checkbox('rhodecode_show_public_icon','True')}
141 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
141 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
142 </div>
142 </div>
143 <div></div>
143 <div></div>
144
144
145 <div class="checkbox">
145 <div class="checkbox">
146 ${h.checkbox('rhodecode_show_private_icon','True')}
146 ${h.checkbox('rhodecode_show_private_icon','True')}
147 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
147 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
148 </div>
148 </div>
149 <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
149 <span class="help-block">${_('Show public/private icons next to repositories names.')}</span>
150 </div>
150 </div>
151 </div>
151 </div>
152
152
153
153
154 <div class="panel panel-default">
154 <div class="panel panel-default">
155 <div class="panel-heading">
155 <div class="panel-heading">
156 <h3 class="panel-title">${_('Markup Renderer')}</h3>
156 <h3 class="panel-title">${_('Markup Renderer')}</h3>
157 </div>
157 </div>
158 <div class="panel-body">
158 <div class="panel-body">
159 <div class="field select">
159 <div class="field select">
160 ${h.select('rhodecode_markup_renderer', '', ['rst', 'markdown'])}
160 ${h.select('rhodecode_markup_renderer', '', ['rst', 'markdown'])}
161 </div>
161 </div>
162 <div class="field">
162 <div class="field">
163 <span class="help-block">${_('Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly.')}</span>
163 <span class="help-block">${_('Default renderer used to render comments, pull request descriptions and other description elements. After change old entries will still work correctly.')}</span>
164 </div>
164 </div>
165 </div>
165 </div>
166 </div>
166 </div>
167
167
168 <div class="panel panel-default">
168 <div class="panel panel-default">
169 <div class="panel-heading">
169 <div class="panel-heading">
170 <h3 class="panel-title">${_('Clone URL templates')}</h3>
170 <h3 class="panel-title">${_('Clone URL templates')}</h3>
171 </div>
171 </div>
172 <div class="panel-body">
172 <div class="panel-body">
173 <div class="field">
173 <div class="field">
174 ${h.text('rhodecode_clone_uri_tmpl', size=60)} HTTP[S]
174 ${h.text('rhodecode_clone_uri_tmpl', size=60)} HTTP[S]
175 </div>
175 </div>
176 <div class="field">
176 <div class="field">
177 ${h.text('rhodecode_clone_uri_id_tmpl', size=60)} HTTP UID
178 </div>
179 <div class="field">
177 ${h.text('rhodecode_clone_uri_ssh_tmpl', size=60)} SSH
180 ${h.text('rhodecode_clone_uri_ssh_tmpl', size=60)} SSH
178 </div>
181 </div>
179 <div class="field">
182 <div class="field">
180 <span class="help-block">
183 <span class="help-block">
181 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
184 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
182 {scheme} 'http' or 'https' sent from running RhodeCode server,
185 {scheme} 'http' or 'https' sent from running RhodeCode server,
183 {user} current user username,
186 {user} current user username,
184 {sys_user} current system user running this process, Useful for ssh,
187 {sys_user} current system user running this process, Useful for ssh,
185 {hostname} hostname of this server running RhodeCode,
188 {hostname} hostname of this server running RhodeCode,
186 {netloc} network location/server host of running RhodeCode server,
189 {netloc} network location/server host of running RhodeCode server,
187 {repo} full repository name,
190 {repo} full repository name,
188 {repoid} ID of repository, can be used to contruct clone-by-id''')}
191 {repoid} ID of repository, can be used to contruct clone-by-id''')}
189 </span>
192 </span>
190 </div>
193 </div>
191 </div>
194 </div>
192 </div>
195 </div>
193
196
194 <div class="panel panel-default">
197 <div class="panel panel-default">
195 <div class="panel-heading">
198 <div class="panel-heading">
196 <h3 class="panel-title">${_('Custom Support Link')}</h3>
199 <h3 class="panel-title">${_('Custom Support Link')}</h3>
197 </div>
200 </div>
198 <div class="panel-body">
201 <div class="panel-body">
199 <div class="field">
202 <div class="field">
200 ${h.text('rhodecode_support_url', size=60)}
203 ${h.text('rhodecode_support_url', size=60)}
201 </div>
204 </div>
202 <div class="field">
205 <div class="field">
203 <span class="help-block">
206 <span class="help-block">
204 ${_('''Custom url for the support link located at the bottom.
207 ${_('''Custom url for the support link located at the bottom.
205 The default is set to %(default_url)s. In case there's a need
208 The default is set to %(default_url)s. In case there's a need
206 to change the support link to internal issue tracker, it should be done here.
209 to change the support link to internal issue tracker, it should be done here.
207 ''') % {'default_url': h.route_url('rhodecode_support')}}
210 ''') % {'default_url': h.route_url('rhodecode_support')}}
208 </span>
211 </span>
209 </div>
212 </div>
210 </div>
213 </div>
211 </div>
214 </div>
212
215
213 <div class="buttons">
216 <div class="buttons">
214 ${h.submit('save',_('Save settings'),class_="btn")}
217 ${h.submit('save',_('Save settings'),class_="btn")}
215 ${h.reset('reset',_('Reset'),class_="btn")}
218 ${h.reset('reset',_('Reset'),class_="btn")}
216 </div>
219 </div>
217
220
218
221
219 ${h.end_form()}
222 ${h.end_form()}
220
223
221 <script>
224 <script>
222 $(document).ready(function() {
225 $(document).ready(function() {
223 $('#rhodecode_markup_renderer').select2({
226 $('#rhodecode_markup_renderer').select2({
224 containerCssClass: 'drop-menu',
227 containerCssClass: 'drop-menu',
225 dropdownCssClass: 'drop-menu-dropdown',
228 dropdownCssClass: 'drop-menu-dropdown',
226 dropdownAutoWidth: true,
229 dropdownAutoWidth: true,
227 minimumResultsForSearch: -1
230 minimumResultsForSearch: -1
228 });
231 });
229 });
232 });
230 </script>
233 </script>
General Comments 0
You need to be logged in to leave comments. Login now