##// END OF EJS Templates
ui: allow selecting and specifing ssh clone url....
marcink -
r2497:9a1b0044 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,763 +1,764 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 from pyramid.view import view_config
30 from pyramid.view import view_config
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.admin.navigation import navigation_list
36 from rhodecode.apps.admin.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.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
51
51
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.notification import EmailNotificationModel
54 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
55 from rhodecode.model.settings import (
55 from rhodecode.model.settings import (
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
56 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
57 SettingsModel)
57 SettingsModel)
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class AdminSettingsView(BaseAppView):
63 class AdminSettingsView(BaseAppView):
64
64
65 def load_default_context(self):
65 def load_default_context(self):
66 c = self._get_local_tmpl_context()
66 c = self._get_local_tmpl_context()
67 c.labs_active = str2bool(
67 c.labs_active = str2bool(
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
68 rhodecode.CONFIG.get('labs_settings_active', 'true'))
69 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
70
70
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 @view_config(
111 @view_config(
112 route_name='admin_settings_vcs', request_method='GET',
112 route_name='admin_settings_vcs', request_method='GET',
113 renderer='rhodecode:templates/admin/settings/settings.mako')
113 renderer='rhodecode:templates/admin/settings/settings.mako')
114 def settings_vcs(self):
114 def settings_vcs(self):
115 c = self.load_default_context()
115 c = self.load_default_context()
116 c.active = 'vcs'
116 c.active = 'vcs'
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
118 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
119 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
120
120
121 settings = self.request.registry.settings
121 settings = self.request.registry.settings
122 c.svn_proxy_generate_config = settings[generate_config]
122 c.svn_proxy_generate_config = settings[generate_config]
123
123
124 defaults = self._form_defaults()
124 defaults = self._form_defaults()
125
125
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
126 model.create_largeobjects_dirs_if_needed(defaults['paths_root_path'])
127
127
128 data = render('rhodecode:templates/admin/settings/settings.mako',
128 data = render('rhodecode:templates/admin/settings/settings.mako',
129 self._get_template_context(c), self.request)
129 self._get_template_context(c), self.request)
130 html = formencode.htmlfill.render(
130 html = formencode.htmlfill.render(
131 data,
131 data,
132 defaults=defaults,
132 defaults=defaults,
133 encoding="UTF-8",
133 encoding="UTF-8",
134 force_defaults=False
134 force_defaults=False
135 )
135 )
136 return Response(html)
136 return Response(html)
137
137
138 @LoginRequired()
138 @LoginRequired()
139 @HasPermissionAllDecorator('hg.admin')
139 @HasPermissionAllDecorator('hg.admin')
140 @CSRFRequired()
140 @CSRFRequired()
141 @view_config(
141 @view_config(
142 route_name='admin_settings_vcs_update', request_method='POST',
142 route_name='admin_settings_vcs_update', request_method='POST',
143 renderer='rhodecode:templates/admin/settings/settings.mako')
143 renderer='rhodecode:templates/admin/settings/settings.mako')
144 def settings_vcs_update(self):
144 def settings_vcs_update(self):
145 _ = self.request.translate
145 _ = self.request.translate
146 c = self.load_default_context()
146 c = self.load_default_context()
147 c.active = 'vcs'
147 c.active = 'vcs'
148
148
149 model = VcsSettingsModel()
149 model = VcsSettingsModel()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
150 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
151 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
152
152
153 settings = self.request.registry.settings
153 settings = self.request.registry.settings
154 c.svn_proxy_generate_config = settings[generate_config]
154 c.svn_proxy_generate_config = settings[generate_config]
155
155
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
156 application_form = ApplicationUiSettingsForm(self.request.translate)()
157
157
158 try:
158 try:
159 form_result = application_form.to_python(dict(self.request.POST))
159 form_result = application_form.to_python(dict(self.request.POST))
160 except formencode.Invalid as errors:
160 except formencode.Invalid as errors:
161 h.flash(
161 h.flash(
162 _("Some form inputs contain invalid data."),
162 _("Some form inputs contain invalid data."),
163 category='error')
163 category='error')
164 data = render('rhodecode:templates/admin/settings/settings.mako',
164 data = render('rhodecode:templates/admin/settings/settings.mako',
165 self._get_template_context(c), self.request)
165 self._get_template_context(c), self.request)
166 html = formencode.htmlfill.render(
166 html = formencode.htmlfill.render(
167 data,
167 data,
168 defaults=errors.value,
168 defaults=errors.value,
169 errors=errors.error_dict or {},
169 errors=errors.error_dict or {},
170 prefix_error=False,
170 prefix_error=False,
171 encoding="UTF-8",
171 encoding="UTF-8",
172 force_defaults=False
172 force_defaults=False
173 )
173 )
174 return Response(html)
174 return Response(html)
175
175
176 try:
176 try:
177 if c.visual.allow_repo_location_change:
177 if c.visual.allow_repo_location_change:
178 model.update_global_path_setting(
178 model.update_global_path_setting(
179 form_result['paths_root_path'])
179 form_result['paths_root_path'])
180
180
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
181 model.update_global_ssl_setting(form_result['web_push_ssl'])
182 model.update_global_hook_settings(form_result)
182 model.update_global_hook_settings(form_result)
183
183
184 model.create_or_update_global_svn_settings(form_result)
184 model.create_or_update_global_svn_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
185 model.create_or_update_global_hg_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
186 model.create_or_update_global_git_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
187 model.create_or_update_global_pr_settings(form_result)
188 except Exception:
188 except Exception:
189 log.exception("Exception while updating settings")
189 log.exception("Exception while updating settings")
190 h.flash(_('Error occurred during updating '
190 h.flash(_('Error occurred during updating '
191 'application settings'), category='error')
191 'application settings'), category='error')
192 else:
192 else:
193 Session().commit()
193 Session().commit()
194 h.flash(_('Updated VCS settings'), category='success')
194 h.flash(_('Updated VCS settings'), category='success')
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
195 raise HTTPFound(h.route_path('admin_settings_vcs'))
196
196
197 data = render('rhodecode:templates/admin/settings/settings.mako',
197 data = render('rhodecode:templates/admin/settings/settings.mako',
198 self._get_template_context(c), self.request)
198 self._get_template_context(c), self.request)
199 html = formencode.htmlfill.render(
199 html = formencode.htmlfill.render(
200 data,
200 data,
201 defaults=self._form_defaults(),
201 defaults=self._form_defaults(),
202 encoding="UTF-8",
202 encoding="UTF-8",
203 force_defaults=False
203 force_defaults=False
204 )
204 )
205 return Response(html)
205 return Response(html)
206
206
207 @LoginRequired()
207 @LoginRequired()
208 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
209 @CSRFRequired()
209 @CSRFRequired()
210 @view_config(
210 @view_config(
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
211 route_name='admin_settings_vcs_svn_pattern_delete', request_method='POST',
212 renderer='json_ext', xhr=True)
212 renderer='json_ext', xhr=True)
213 def settings_vcs_delete_svn_pattern(self):
213 def settings_vcs_delete_svn_pattern(self):
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
214 delete_pattern_id = self.request.POST.get('delete_svn_pattern')
215 model = VcsSettingsModel()
215 model = VcsSettingsModel()
216 try:
216 try:
217 model.delete_global_svn_pattern(delete_pattern_id)
217 model.delete_global_svn_pattern(delete_pattern_id)
218 except SettingNotFound:
218 except SettingNotFound:
219 log.exception(
219 log.exception(
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
220 'Failed to delete svn_pattern with id %s', delete_pattern_id)
221 raise HTTPNotFound()
221 raise HTTPNotFound()
222
222
223 Session().commit()
223 Session().commit()
224 return True
224 return True
225
225
226 @LoginRequired()
226 @LoginRequired()
227 @HasPermissionAllDecorator('hg.admin')
227 @HasPermissionAllDecorator('hg.admin')
228 @view_config(
228 @view_config(
229 route_name='admin_settings_mapping', request_method='GET',
229 route_name='admin_settings_mapping', request_method='GET',
230 renderer='rhodecode:templates/admin/settings/settings.mako')
230 renderer='rhodecode:templates/admin/settings/settings.mako')
231 def settings_mapping(self):
231 def settings_mapping(self):
232 c = self.load_default_context()
232 c = self.load_default_context()
233 c.active = 'mapping'
233 c.active = 'mapping'
234
234
235 data = render('rhodecode:templates/admin/settings/settings.mako',
235 data = render('rhodecode:templates/admin/settings/settings.mako',
236 self._get_template_context(c), self.request)
236 self._get_template_context(c), self.request)
237 html = formencode.htmlfill.render(
237 html = formencode.htmlfill.render(
238 data,
238 data,
239 defaults=self._form_defaults(),
239 defaults=self._form_defaults(),
240 encoding="UTF-8",
240 encoding="UTF-8",
241 force_defaults=False
241 force_defaults=False
242 )
242 )
243 return Response(html)
243 return Response(html)
244
244
245 @LoginRequired()
245 @LoginRequired()
246 @HasPermissionAllDecorator('hg.admin')
246 @HasPermissionAllDecorator('hg.admin')
247 @CSRFRequired()
247 @CSRFRequired()
248 @view_config(
248 @view_config(
249 route_name='admin_settings_mapping_update', request_method='POST',
249 route_name='admin_settings_mapping_update', request_method='POST',
250 renderer='rhodecode:templates/admin/settings/settings.mako')
250 renderer='rhodecode:templates/admin/settings/settings.mako')
251 def settings_mapping_update(self):
251 def settings_mapping_update(self):
252 _ = self.request.translate
252 _ = self.request.translate
253 c = self.load_default_context()
253 c = self.load_default_context()
254 c.active = 'mapping'
254 c.active = 'mapping'
255 rm_obsolete = self.request.POST.get('destroy', False)
255 rm_obsolete = self.request.POST.get('destroy', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
256 invalidate_cache = self.request.POST.get('invalidate', False)
257 log.debug(
257 log.debug(
258 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
258 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
259
259
260 if invalidate_cache:
260 if invalidate_cache:
261 log.debug('invalidating all repositories cache')
261 log.debug('invalidating all repositories cache')
262 for repo in Repository.get_all():
262 for repo in Repository.get_all():
263 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
263 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
264
264
265 filesystem_repos = ScmModel().repo_scan()
265 filesystem_repos = ScmModel().repo_scan()
266 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
266 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
267 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
267 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
268 h.flash(_('Repositories successfully '
268 h.flash(_('Repositories successfully '
269 'rescanned added: %s ; removed: %s') %
269 'rescanned added: %s ; removed: %s') %
270 (_repr(added), _repr(removed)),
270 (_repr(added), _repr(removed)),
271 category='success')
271 category='success')
272 raise HTTPFound(h.route_path('admin_settings_mapping'))
272 raise HTTPFound(h.route_path('admin_settings_mapping'))
273
273
274 @LoginRequired()
274 @LoginRequired()
275 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
276 @view_config(
276 @view_config(
277 route_name='admin_settings', request_method='GET',
277 route_name='admin_settings', request_method='GET',
278 renderer='rhodecode:templates/admin/settings/settings.mako')
278 renderer='rhodecode:templates/admin/settings/settings.mako')
279 @view_config(
279 @view_config(
280 route_name='admin_settings_global', request_method='GET',
280 route_name='admin_settings_global', request_method='GET',
281 renderer='rhodecode:templates/admin/settings/settings.mako')
281 renderer='rhodecode:templates/admin/settings/settings.mako')
282 def settings_global(self):
282 def settings_global(self):
283 c = self.load_default_context()
283 c = self.load_default_context()
284 c.active = 'global'
284 c.active = 'global'
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
285 c.personal_repo_group_default_pattern = RepoGroupModel()\
286 .get_personal_group_name_pattern()
286 .get_personal_group_name_pattern()
287
287
288 data = render('rhodecode:templates/admin/settings/settings.mako',
288 data = render('rhodecode:templates/admin/settings/settings.mako',
289 self._get_template_context(c), self.request)
289 self._get_template_context(c), self.request)
290 html = formencode.htmlfill.render(
290 html = formencode.htmlfill.render(
291 data,
291 data,
292 defaults=self._form_defaults(),
292 defaults=self._form_defaults(),
293 encoding="UTF-8",
293 encoding="UTF-8",
294 force_defaults=False
294 force_defaults=False
295 )
295 )
296 return Response(html)
296 return Response(html)
297
297
298 @LoginRequired()
298 @LoginRequired()
299 @HasPermissionAllDecorator('hg.admin')
299 @HasPermissionAllDecorator('hg.admin')
300 @CSRFRequired()
300 @CSRFRequired()
301 @view_config(
301 @view_config(
302 route_name='admin_settings_update', request_method='POST',
302 route_name='admin_settings_update', request_method='POST',
303 renderer='rhodecode:templates/admin/settings/settings.mako')
303 renderer='rhodecode:templates/admin/settings/settings.mako')
304 @view_config(
304 @view_config(
305 route_name='admin_settings_global_update', request_method='POST',
305 route_name='admin_settings_global_update', request_method='POST',
306 renderer='rhodecode:templates/admin/settings/settings.mako')
306 renderer='rhodecode:templates/admin/settings/settings.mako')
307 def settings_global_update(self):
307 def settings_global_update(self):
308 _ = self.request.translate
308 _ = self.request.translate
309 c = self.load_default_context()
309 c = self.load_default_context()
310 c.active = 'global'
310 c.active = 'global'
311 c.personal_repo_group_default_pattern = RepoGroupModel()\
311 c.personal_repo_group_default_pattern = RepoGroupModel()\
312 .get_personal_group_name_pattern()
312 .get_personal_group_name_pattern()
313 application_form = ApplicationSettingsForm(self.request.translate)()
313 application_form = ApplicationSettingsForm(self.request.translate)()
314 try:
314 try:
315 form_result = application_form.to_python(dict(self.request.POST))
315 form_result = application_form.to_python(dict(self.request.POST))
316 except formencode.Invalid as errors:
316 except formencode.Invalid as errors:
317 data = render('rhodecode:templates/admin/settings/settings.mako',
317 data = render('rhodecode:templates/admin/settings/settings.mako',
318 self._get_template_context(c), self.request)
318 self._get_template_context(c), self.request)
319 html = formencode.htmlfill.render(
319 html = formencode.htmlfill.render(
320 data,
320 data,
321 defaults=errors.value,
321 defaults=errors.value,
322 errors=errors.error_dict or {},
322 errors=errors.error_dict or {},
323 prefix_error=False,
323 prefix_error=False,
324 encoding="UTF-8",
324 encoding="UTF-8",
325 force_defaults=False
325 force_defaults=False
326 )
326 )
327 return Response(html)
327 return Response(html)
328
328
329 settings = [
329 settings = [
330 ('title', 'rhodecode_title', 'unicode'),
330 ('title', 'rhodecode_title', 'unicode'),
331 ('realm', 'rhodecode_realm', 'unicode'),
331 ('realm', 'rhodecode_realm', 'unicode'),
332 ('pre_code', 'rhodecode_pre_code', 'unicode'),
332 ('pre_code', 'rhodecode_pre_code', 'unicode'),
333 ('post_code', 'rhodecode_post_code', 'unicode'),
333 ('post_code', 'rhodecode_post_code', 'unicode'),
334 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
334 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
335 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
335 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
336 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
336 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
337 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
337 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
338 ]
338 ]
339 try:
339 try:
340 for setting, form_key, type_ in settings:
340 for setting, form_key, type_ in settings:
341 sett = SettingsModel().create_or_update_setting(
341 sett = SettingsModel().create_or_update_setting(
342 setting, form_result[form_key], type_)
342 setting, form_result[form_key], type_)
343 Session().add(sett)
343 Session().add(sett)
344
344
345 Session().commit()
345 Session().commit()
346 SettingsModel().invalidate_settings_cache()
346 SettingsModel().invalidate_settings_cache()
347 h.flash(_('Updated application settings'), category='success')
347 h.flash(_('Updated application settings'), category='success')
348 except Exception:
348 except Exception:
349 log.exception("Exception while updating application settings")
349 log.exception("Exception while updating application settings")
350 h.flash(
350 h.flash(
351 _('Error occurred during updating application settings'),
351 _('Error occurred during updating application settings'),
352 category='error')
352 category='error')
353
353
354 raise HTTPFound(h.route_path('admin_settings_global'))
354 raise HTTPFound(h.route_path('admin_settings_global'))
355
355
356 @LoginRequired()
356 @LoginRequired()
357 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
358 @view_config(
358 @view_config(
359 route_name='admin_settings_visual', request_method='GET',
359 route_name='admin_settings_visual', request_method='GET',
360 renderer='rhodecode:templates/admin/settings/settings.mako')
360 renderer='rhodecode:templates/admin/settings/settings.mako')
361 def settings_visual(self):
361 def settings_visual(self):
362 c = self.load_default_context()
362 c = self.load_default_context()
363 c.active = 'visual'
363 c.active = 'visual'
364
364
365 data = render('rhodecode:templates/admin/settings/settings.mako',
365 data = render('rhodecode:templates/admin/settings/settings.mako',
366 self._get_template_context(c), self.request)
366 self._get_template_context(c), self.request)
367 html = formencode.htmlfill.render(
367 html = formencode.htmlfill.render(
368 data,
368 data,
369 defaults=self._form_defaults(),
369 defaults=self._form_defaults(),
370 encoding="UTF-8",
370 encoding="UTF-8",
371 force_defaults=False
371 force_defaults=False
372 )
372 )
373 return Response(html)
373 return Response(html)
374
374
375 @LoginRequired()
375 @LoginRequired()
376 @HasPermissionAllDecorator('hg.admin')
376 @HasPermissionAllDecorator('hg.admin')
377 @CSRFRequired()
377 @CSRFRequired()
378 @view_config(
378 @view_config(
379 route_name='admin_settings_visual_update', request_method='POST',
379 route_name='admin_settings_visual_update', request_method='POST',
380 renderer='rhodecode:templates/admin/settings/settings.mako')
380 renderer='rhodecode:templates/admin/settings/settings.mako')
381 def settings_visual_update(self):
381 def settings_visual_update(self):
382 _ = self.request.translate
382 _ = self.request.translate
383 c = self.load_default_context()
383 c = self.load_default_context()
384 c.active = 'visual'
384 c.active = 'visual'
385 application_form = ApplicationVisualisationForm(self.request.translate)()
385 application_form = ApplicationVisualisationForm(self.request.translate)()
386 try:
386 try:
387 form_result = application_form.to_python(dict(self.request.POST))
387 form_result = application_form.to_python(dict(self.request.POST))
388 except formencode.Invalid as errors:
388 except formencode.Invalid as errors:
389 data = render('rhodecode:templates/admin/settings/settings.mako',
389 data = render('rhodecode:templates/admin/settings/settings.mako',
390 self._get_template_context(c), self.request)
390 self._get_template_context(c), self.request)
391 html = formencode.htmlfill.render(
391 html = formencode.htmlfill.render(
392 data,
392 data,
393 defaults=errors.value,
393 defaults=errors.value,
394 errors=errors.error_dict or {},
394 errors=errors.error_dict or {},
395 prefix_error=False,
395 prefix_error=False,
396 encoding="UTF-8",
396 encoding="UTF-8",
397 force_defaults=False
397 force_defaults=False
398 )
398 )
399 return Response(html)
399 return Response(html)
400
400
401 try:
401 try:
402 settings = [
402 settings = [
403 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
403 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
404 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
404 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
405 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
405 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
406 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
406 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
407 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
407 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
408 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
408 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
409 ('show_version', 'rhodecode_show_version', 'bool'),
409 ('show_version', 'rhodecode_show_version', 'bool'),
410 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
410 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
411 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
411 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
412 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
412 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
413 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
413 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
414 ('clone_uri_ssh_tmpl', 'rhodecode_clone_uri_ssh_tmpl', 'unicode'),
414 ('support_url', 'rhodecode_support_url', 'unicode'),
415 ('support_url', 'rhodecode_support_url', 'unicode'),
415 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
416 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
416 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
417 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
417 ]
418 ]
418 for setting, form_key, type_ in settings:
419 for setting, form_key, type_ in settings:
419 sett = SettingsModel().create_or_update_setting(
420 sett = SettingsModel().create_or_update_setting(
420 setting, form_result[form_key], type_)
421 setting, form_result[form_key], type_)
421 Session().add(sett)
422 Session().add(sett)
422
423
423 Session().commit()
424 Session().commit()
424 SettingsModel().invalidate_settings_cache()
425 SettingsModel().invalidate_settings_cache()
425 h.flash(_('Updated visualisation settings'), category='success')
426 h.flash(_('Updated visualisation settings'), category='success')
426 except Exception:
427 except Exception:
427 log.exception("Exception updating visualization settings")
428 log.exception("Exception updating visualization settings")
428 h.flash(_('Error occurred during updating '
429 h.flash(_('Error occurred during updating '
429 'visualisation settings'),
430 'visualisation settings'),
430 category='error')
431 category='error')
431
432
432 raise HTTPFound(h.route_path('admin_settings_visual'))
433 raise HTTPFound(h.route_path('admin_settings_visual'))
433
434
434 @LoginRequired()
435 @LoginRequired()
435 @HasPermissionAllDecorator('hg.admin')
436 @HasPermissionAllDecorator('hg.admin')
436 @view_config(
437 @view_config(
437 route_name='admin_settings_issuetracker', request_method='GET',
438 route_name='admin_settings_issuetracker', request_method='GET',
438 renderer='rhodecode:templates/admin/settings/settings.mako')
439 renderer='rhodecode:templates/admin/settings/settings.mako')
439 def settings_issuetracker(self):
440 def settings_issuetracker(self):
440 c = self.load_default_context()
441 c = self.load_default_context()
441 c.active = 'issuetracker'
442 c.active = 'issuetracker'
442 defaults = SettingsModel().get_all_settings()
443 defaults = SettingsModel().get_all_settings()
443
444
444 entry_key = 'rhodecode_issuetracker_pat_'
445 entry_key = 'rhodecode_issuetracker_pat_'
445
446
446 c.issuetracker_entries = {}
447 c.issuetracker_entries = {}
447 for k, v in defaults.items():
448 for k, v in defaults.items():
448 if k.startswith(entry_key):
449 if k.startswith(entry_key):
449 uid = k[len(entry_key):]
450 uid = k[len(entry_key):]
450 c.issuetracker_entries[uid] = None
451 c.issuetracker_entries[uid] = None
451
452
452 for uid in c.issuetracker_entries:
453 for uid in c.issuetracker_entries:
453 c.issuetracker_entries[uid] = AttributeDict({
454 c.issuetracker_entries[uid] = AttributeDict({
454 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
455 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
455 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
456 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
456 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
457 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
457 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
458 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
458 })
459 })
459
460
460 return self._get_template_context(c)
461 return self._get_template_context(c)
461
462
462 @LoginRequired()
463 @LoginRequired()
463 @HasPermissionAllDecorator('hg.admin')
464 @HasPermissionAllDecorator('hg.admin')
464 @CSRFRequired()
465 @CSRFRequired()
465 @view_config(
466 @view_config(
466 route_name='admin_settings_issuetracker_test', request_method='POST',
467 route_name='admin_settings_issuetracker_test', request_method='POST',
467 renderer='string', xhr=True)
468 renderer='string', xhr=True)
468 def settings_issuetracker_test(self):
469 def settings_issuetracker_test(self):
469 return h.urlify_commit_message(
470 return h.urlify_commit_message(
470 self.request.POST.get('test_text', ''),
471 self.request.POST.get('test_text', ''),
471 'repo_group/test_repo1')
472 'repo_group/test_repo1')
472
473
473 @LoginRequired()
474 @LoginRequired()
474 @HasPermissionAllDecorator('hg.admin')
475 @HasPermissionAllDecorator('hg.admin')
475 @CSRFRequired()
476 @CSRFRequired()
476 @view_config(
477 @view_config(
477 route_name='admin_settings_issuetracker_update', request_method='POST',
478 route_name='admin_settings_issuetracker_update', request_method='POST',
478 renderer='rhodecode:templates/admin/settings/settings.mako')
479 renderer='rhodecode:templates/admin/settings/settings.mako')
479 def settings_issuetracker_update(self):
480 def settings_issuetracker_update(self):
480 _ = self.request.translate
481 _ = self.request.translate
481 self.load_default_context()
482 self.load_default_context()
482 settings_model = IssueTrackerSettingsModel()
483 settings_model = IssueTrackerSettingsModel()
483
484
484 try:
485 try:
485 form = IssueTrackerPatternsForm(self.request.translate)()
486 form = IssueTrackerPatternsForm(self.request.translate)()
486 data = form.to_python(self.request.POST)
487 data = form.to_python(self.request.POST)
487 except formencode.Invalid as errors:
488 except formencode.Invalid as errors:
488 log.exception('Failed to add new pattern')
489 log.exception('Failed to add new pattern')
489 error = errors
490 error = errors
490 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
491 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
491 category='error')
492 category='error')
492 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
493 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
493
494
494 if data:
495 if data:
495 for uid in data.get('delete_patterns', []):
496 for uid in data.get('delete_patterns', []):
496 settings_model.delete_entries(uid)
497 settings_model.delete_entries(uid)
497
498
498 for pattern in data.get('patterns', []):
499 for pattern in data.get('patterns', []):
499 for setting, value, type_ in pattern:
500 for setting, value, type_ in pattern:
500 sett = settings_model.create_or_update_setting(
501 sett = settings_model.create_or_update_setting(
501 setting, value, type_)
502 setting, value, type_)
502 Session().add(sett)
503 Session().add(sett)
503
504
504 Session().commit()
505 Session().commit()
505
506
506 SettingsModel().invalidate_settings_cache()
507 SettingsModel().invalidate_settings_cache()
507 h.flash(_('Updated issue tracker entries'), category='success')
508 h.flash(_('Updated issue tracker entries'), category='success')
508 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
509 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
509
510
510 @LoginRequired()
511 @LoginRequired()
511 @HasPermissionAllDecorator('hg.admin')
512 @HasPermissionAllDecorator('hg.admin')
512 @CSRFRequired()
513 @CSRFRequired()
513 @view_config(
514 @view_config(
514 route_name='admin_settings_issuetracker_delete', request_method='POST',
515 route_name='admin_settings_issuetracker_delete', request_method='POST',
515 renderer='rhodecode:templates/admin/settings/settings.mako')
516 renderer='rhodecode:templates/admin/settings/settings.mako')
516 def settings_issuetracker_delete(self):
517 def settings_issuetracker_delete(self):
517 _ = self.request.translate
518 _ = self.request.translate
518 self.load_default_context()
519 self.load_default_context()
519 uid = self.request.POST.get('uid')
520 uid = self.request.POST.get('uid')
520 try:
521 try:
521 IssueTrackerSettingsModel().delete_entries(uid)
522 IssueTrackerSettingsModel().delete_entries(uid)
522 except Exception:
523 except Exception:
523 log.exception('Failed to delete issue tracker setting %s', uid)
524 log.exception('Failed to delete issue tracker setting %s', uid)
524 raise HTTPNotFound()
525 raise HTTPNotFound()
525 h.flash(_('Removed issue tracker entry'), category='success')
526 h.flash(_('Removed issue tracker entry'), category='success')
526 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
527 raise HTTPFound(h.route_path('admin_settings_issuetracker'))
527
528
528 @LoginRequired()
529 @LoginRequired()
529 @HasPermissionAllDecorator('hg.admin')
530 @HasPermissionAllDecorator('hg.admin')
530 @view_config(
531 @view_config(
531 route_name='admin_settings_email', request_method='GET',
532 route_name='admin_settings_email', request_method='GET',
532 renderer='rhodecode:templates/admin/settings/settings.mako')
533 renderer='rhodecode:templates/admin/settings/settings.mako')
533 def settings_email(self):
534 def settings_email(self):
534 c = self.load_default_context()
535 c = self.load_default_context()
535 c.active = 'email'
536 c.active = 'email'
536 c.rhodecode_ini = rhodecode.CONFIG
537 c.rhodecode_ini = rhodecode.CONFIG
537
538
538 data = render('rhodecode:templates/admin/settings/settings.mako',
539 data = render('rhodecode:templates/admin/settings/settings.mako',
539 self._get_template_context(c), self.request)
540 self._get_template_context(c), self.request)
540 html = formencode.htmlfill.render(
541 html = formencode.htmlfill.render(
541 data,
542 data,
542 defaults=self._form_defaults(),
543 defaults=self._form_defaults(),
543 encoding="UTF-8",
544 encoding="UTF-8",
544 force_defaults=False
545 force_defaults=False
545 )
546 )
546 return Response(html)
547 return Response(html)
547
548
548 @LoginRequired()
549 @LoginRequired()
549 @HasPermissionAllDecorator('hg.admin')
550 @HasPermissionAllDecorator('hg.admin')
550 @CSRFRequired()
551 @CSRFRequired()
551 @view_config(
552 @view_config(
552 route_name='admin_settings_email_update', request_method='POST',
553 route_name='admin_settings_email_update', request_method='POST',
553 renderer='rhodecode:templates/admin/settings/settings.mako')
554 renderer='rhodecode:templates/admin/settings/settings.mako')
554 def settings_email_update(self):
555 def settings_email_update(self):
555 _ = self.request.translate
556 _ = self.request.translate
556 c = self.load_default_context()
557 c = self.load_default_context()
557 c.active = 'email'
558 c.active = 'email'
558
559
559 test_email = self.request.POST.get('test_email')
560 test_email = self.request.POST.get('test_email')
560
561
561 if not test_email:
562 if not test_email:
562 h.flash(_('Please enter email address'), category='error')
563 h.flash(_('Please enter email address'), category='error')
563 raise HTTPFound(h.route_path('admin_settings_email'))
564 raise HTTPFound(h.route_path('admin_settings_email'))
564
565
565 email_kwargs = {
566 email_kwargs = {
566 'date': datetime.datetime.now(),
567 'date': datetime.datetime.now(),
567 'user': c.rhodecode_user,
568 'user': c.rhodecode_user,
568 'rhodecode_version': c.rhodecode_version
569 'rhodecode_version': c.rhodecode_version
569 }
570 }
570
571
571 (subject, headers, email_body,
572 (subject, headers, email_body,
572 email_body_plaintext) = EmailNotificationModel().render_email(
573 email_body_plaintext) = EmailNotificationModel().render_email(
573 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
574 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
574
575
575 recipients = [test_email] if test_email else None
576 recipients = [test_email] if test_email else None
576
577
577 run_task(tasks.send_email, recipients, subject,
578 run_task(tasks.send_email, recipients, subject,
578 email_body_plaintext, email_body)
579 email_body_plaintext, email_body)
579
580
580 h.flash(_('Send email task created'), category='success')
581 h.flash(_('Send email task created'), category='success')
581 raise HTTPFound(h.route_path('admin_settings_email'))
582 raise HTTPFound(h.route_path('admin_settings_email'))
582
583
583 @LoginRequired()
584 @LoginRequired()
584 @HasPermissionAllDecorator('hg.admin')
585 @HasPermissionAllDecorator('hg.admin')
585 @view_config(
586 @view_config(
586 route_name='admin_settings_hooks', request_method='GET',
587 route_name='admin_settings_hooks', request_method='GET',
587 renderer='rhodecode:templates/admin/settings/settings.mako')
588 renderer='rhodecode:templates/admin/settings/settings.mako')
588 def settings_hooks(self):
589 def settings_hooks(self):
589 c = self.load_default_context()
590 c = self.load_default_context()
590 c.active = 'hooks'
591 c.active = 'hooks'
591
592
592 model = SettingsModel()
593 model = SettingsModel()
593 c.hooks = model.get_builtin_hooks()
594 c.hooks = model.get_builtin_hooks()
594 c.custom_hooks = model.get_custom_hooks()
595 c.custom_hooks = model.get_custom_hooks()
595
596
596 data = render('rhodecode:templates/admin/settings/settings.mako',
597 data = render('rhodecode:templates/admin/settings/settings.mako',
597 self._get_template_context(c), self.request)
598 self._get_template_context(c), self.request)
598 html = formencode.htmlfill.render(
599 html = formencode.htmlfill.render(
599 data,
600 data,
600 defaults=self._form_defaults(),
601 defaults=self._form_defaults(),
601 encoding="UTF-8",
602 encoding="UTF-8",
602 force_defaults=False
603 force_defaults=False
603 )
604 )
604 return Response(html)
605 return Response(html)
605
606
606 @LoginRequired()
607 @LoginRequired()
607 @HasPermissionAllDecorator('hg.admin')
608 @HasPermissionAllDecorator('hg.admin')
608 @CSRFRequired()
609 @CSRFRequired()
609 @view_config(
610 @view_config(
610 route_name='admin_settings_hooks_update', request_method='POST',
611 route_name='admin_settings_hooks_update', request_method='POST',
611 renderer='rhodecode:templates/admin/settings/settings.mako')
612 renderer='rhodecode:templates/admin/settings/settings.mako')
612 @view_config(
613 @view_config(
613 route_name='admin_settings_hooks_delete', request_method='POST',
614 route_name='admin_settings_hooks_delete', request_method='POST',
614 renderer='rhodecode:templates/admin/settings/settings.mako')
615 renderer='rhodecode:templates/admin/settings/settings.mako')
615 def settings_hooks_update(self):
616 def settings_hooks_update(self):
616 _ = self.request.translate
617 _ = self.request.translate
617 c = self.load_default_context()
618 c = self.load_default_context()
618 c.active = 'hooks'
619 c.active = 'hooks'
619 if c.visual.allow_custom_hooks_settings:
620 if c.visual.allow_custom_hooks_settings:
620 ui_key = self.request.POST.get('new_hook_ui_key')
621 ui_key = self.request.POST.get('new_hook_ui_key')
621 ui_value = self.request.POST.get('new_hook_ui_value')
622 ui_value = self.request.POST.get('new_hook_ui_value')
622
623
623 hook_id = self.request.POST.get('hook_id')
624 hook_id = self.request.POST.get('hook_id')
624 new_hook = False
625 new_hook = False
625
626
626 model = SettingsModel()
627 model = SettingsModel()
627 try:
628 try:
628 if ui_value and ui_key:
629 if ui_value and ui_key:
629 model.create_or_update_hook(ui_key, ui_value)
630 model.create_or_update_hook(ui_key, ui_value)
630 h.flash(_('Added new hook'), category='success')
631 h.flash(_('Added new hook'), category='success')
631 new_hook = True
632 new_hook = True
632 elif hook_id:
633 elif hook_id:
633 RhodeCodeUi.delete(hook_id)
634 RhodeCodeUi.delete(hook_id)
634 Session().commit()
635 Session().commit()
635
636
636 # check for edits
637 # check for edits
637 update = False
638 update = False
638 _d = self.request.POST.dict_of_lists()
639 _d = self.request.POST.dict_of_lists()
639 for k, v in zip(_d.get('hook_ui_key', []),
640 for k, v in zip(_d.get('hook_ui_key', []),
640 _d.get('hook_ui_value_new', [])):
641 _d.get('hook_ui_value_new', [])):
641 model.create_or_update_hook(k, v)
642 model.create_or_update_hook(k, v)
642 update = True
643 update = True
643
644
644 if update and not new_hook:
645 if update and not new_hook:
645 h.flash(_('Updated hooks'), category='success')
646 h.flash(_('Updated hooks'), category='success')
646 Session().commit()
647 Session().commit()
647 except Exception:
648 except Exception:
648 log.exception("Exception during hook creation")
649 log.exception("Exception during hook creation")
649 h.flash(_('Error occurred during hook creation'),
650 h.flash(_('Error occurred during hook creation'),
650 category='error')
651 category='error')
651
652
652 raise HTTPFound(h.route_path('admin_settings_hooks'))
653 raise HTTPFound(h.route_path('admin_settings_hooks'))
653
654
654 @LoginRequired()
655 @LoginRequired()
655 @HasPermissionAllDecorator('hg.admin')
656 @HasPermissionAllDecorator('hg.admin')
656 @view_config(
657 @view_config(
657 route_name='admin_settings_search', request_method='GET',
658 route_name='admin_settings_search', request_method='GET',
658 renderer='rhodecode:templates/admin/settings/settings.mako')
659 renderer='rhodecode:templates/admin/settings/settings.mako')
659 def settings_search(self):
660 def settings_search(self):
660 c = self.load_default_context()
661 c = self.load_default_context()
661 c.active = 'search'
662 c.active = 'search'
662
663
663 searcher = searcher_from_config(self.request.registry.settings)
664 searcher = searcher_from_config(self.request.registry.settings)
664 c.statistics = searcher.statistics(self.request.translate)
665 c.statistics = searcher.statistics(self.request.translate)
665
666
666 return self._get_template_context(c)
667 return self._get_template_context(c)
667
668
668 @LoginRequired()
669 @LoginRequired()
669 @HasPermissionAllDecorator('hg.admin')
670 @HasPermissionAllDecorator('hg.admin')
670 @view_config(
671 @view_config(
671 route_name='admin_settings_labs', request_method='GET',
672 route_name='admin_settings_labs', request_method='GET',
672 renderer='rhodecode:templates/admin/settings/settings.mako')
673 renderer='rhodecode:templates/admin/settings/settings.mako')
673 def settings_labs(self):
674 def settings_labs(self):
674 c = self.load_default_context()
675 c = self.load_default_context()
675 if not c.labs_active:
676 if not c.labs_active:
676 raise HTTPFound(h.route_path('admin_settings'))
677 raise HTTPFound(h.route_path('admin_settings'))
677
678
678 c.active = 'labs'
679 c.active = 'labs'
679 c.lab_settings = _LAB_SETTINGS
680 c.lab_settings = _LAB_SETTINGS
680
681
681 data = render('rhodecode:templates/admin/settings/settings.mako',
682 data = render('rhodecode:templates/admin/settings/settings.mako',
682 self._get_template_context(c), self.request)
683 self._get_template_context(c), self.request)
683 html = formencode.htmlfill.render(
684 html = formencode.htmlfill.render(
684 data,
685 data,
685 defaults=self._form_defaults(),
686 defaults=self._form_defaults(),
686 encoding="UTF-8",
687 encoding="UTF-8",
687 force_defaults=False
688 force_defaults=False
688 )
689 )
689 return Response(html)
690 return Response(html)
690
691
691 @LoginRequired()
692 @LoginRequired()
692 @HasPermissionAllDecorator('hg.admin')
693 @HasPermissionAllDecorator('hg.admin')
693 @CSRFRequired()
694 @CSRFRequired()
694 @view_config(
695 @view_config(
695 route_name='admin_settings_labs_update', request_method='POST',
696 route_name='admin_settings_labs_update', request_method='POST',
696 renderer='rhodecode:templates/admin/settings/settings.mako')
697 renderer='rhodecode:templates/admin/settings/settings.mako')
697 def settings_labs_update(self):
698 def settings_labs_update(self):
698 _ = self.request.translate
699 _ = self.request.translate
699 c = self.load_default_context()
700 c = self.load_default_context()
700 c.active = 'labs'
701 c.active = 'labs'
701
702
702 application_form = LabsSettingsForm(self.request.translate)()
703 application_form = LabsSettingsForm(self.request.translate)()
703 try:
704 try:
704 form_result = application_form.to_python(dict(self.request.POST))
705 form_result = application_form.to_python(dict(self.request.POST))
705 except formencode.Invalid as errors:
706 except formencode.Invalid as errors:
706 h.flash(
707 h.flash(
707 _('Some form inputs contain invalid data.'),
708 _('Some form inputs contain invalid data.'),
708 category='error')
709 category='error')
709 data = render('rhodecode:templates/admin/settings/settings.mako',
710 data = render('rhodecode:templates/admin/settings/settings.mako',
710 self._get_template_context(c), self.request)
711 self._get_template_context(c), self.request)
711 html = formencode.htmlfill.render(
712 html = formencode.htmlfill.render(
712 data,
713 data,
713 defaults=errors.value,
714 defaults=errors.value,
714 errors=errors.error_dict or {},
715 errors=errors.error_dict or {},
715 prefix_error=False,
716 prefix_error=False,
716 encoding="UTF-8",
717 encoding="UTF-8",
717 force_defaults=False
718 force_defaults=False
718 )
719 )
719 return Response(html)
720 return Response(html)
720
721
721 try:
722 try:
722 session = Session()
723 session = Session()
723 for setting in _LAB_SETTINGS:
724 for setting in _LAB_SETTINGS:
724 setting_name = setting.key[len('rhodecode_'):]
725 setting_name = setting.key[len('rhodecode_'):]
725 sett = SettingsModel().create_or_update_setting(
726 sett = SettingsModel().create_or_update_setting(
726 setting_name, form_result[setting.key], setting.type)
727 setting_name, form_result[setting.key], setting.type)
727 session.add(sett)
728 session.add(sett)
728
729
729 except Exception:
730 except Exception:
730 log.exception('Exception while updating lab settings')
731 log.exception('Exception while updating lab settings')
731 h.flash(_('Error occurred during updating labs settings'),
732 h.flash(_('Error occurred during updating labs settings'),
732 category='error')
733 category='error')
733 else:
734 else:
734 Session().commit()
735 Session().commit()
735 SettingsModel().invalidate_settings_cache()
736 SettingsModel().invalidate_settings_cache()
736 h.flash(_('Updated Labs settings'), category='success')
737 h.flash(_('Updated Labs settings'), category='success')
737 raise HTTPFound(h.route_path('admin_settings_labs'))
738 raise HTTPFound(h.route_path('admin_settings_labs'))
738
739
739 data = render('rhodecode:templates/admin/settings/settings.mako',
740 data = render('rhodecode:templates/admin/settings/settings.mako',
740 self._get_template_context(c), self.request)
741 self._get_template_context(c), self.request)
741 html = formencode.htmlfill.render(
742 html = formencode.htmlfill.render(
742 data,
743 data,
743 defaults=self._form_defaults(),
744 defaults=self._form_defaults(),
744 encoding="UTF-8",
745 encoding="UTF-8",
745 force_defaults=False
746 force_defaults=False
746 )
747 )
747 return Response(html)
748 return Response(html)
748
749
749
750
750 # :param key: name of the setting including the 'rhodecode_' prefix
751 # :param key: name of the setting including the 'rhodecode_' prefix
751 # :param type: the RhodeCodeSetting type to use.
752 # :param type: the RhodeCodeSetting type to use.
752 # :param group: the i18ned group in which we should dispaly this setting
753 # :param group: the i18ned group in which we should dispaly this setting
753 # :param label: the i18ned label we should display for this setting
754 # :param label: the i18ned label we should display for this setting
754 # :param help: the i18ned help we should dispaly for this setting
755 # :param help: the i18ned help we should dispaly for this setting
755 LabSetting = collections.namedtuple(
756 LabSetting = collections.namedtuple(
756 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
757 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
757
758
758
759
759 # This list has to be kept in sync with the form
760 # This list has to be kept in sync with the form
760 # rhodecode.model.forms.LabsSettingsForm.
761 # rhodecode.model.forms.LabsSettingsForm.
761 _LAB_SETTINGS = [
762 _LAB_SETTINGS = [
762
763
763 ]
764 ]
@@ -1,371 +1,375 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 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
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from beaker.cache import cache_region
25 from beaker.cache import cache_region
26
26
27 from rhodecode.controllers import utils
27 from rhodecode.controllers import utils
28 from rhodecode.apps._base import RepoAppView
28 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import caches, helpers as h
30 from rhodecode.lib import caches, helpers as h
31 from rhodecode.lib.helpers import RepoPage
31 from rhodecode.lib.helpers import RepoPage
32 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
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 CommitError, EmptyRepositoryError, \
37 from rhodecode.lib.vcs.exceptions import CommitError, EmptyRepositoryError, \
38 CommitDoesNotExistError
38 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.repo import ReadmeFinder
41 from rhodecode.model.repo import ReadmeFinder
42 from rhodecode.model.scm import ScmModel
42 from rhodecode.model.scm import ScmModel
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoSummaryView(RepoAppView):
47 class RepoSummaryView(RepoAppView):
48
48
49 def load_default_context(self):
49 def load_default_context(self):
50 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c = self._get_local_tmpl_context(include_app_defaults=True)
51
51
52 c.rhodecode_repo = None
52 c.rhodecode_repo = None
53 if not c.repository_requirements_missing:
53 if not c.repository_requirements_missing:
54 c.rhodecode_repo = self.rhodecode_vcs_repo
54 c.rhodecode_repo = self.rhodecode_vcs_repo
55
55
56
56
57 return c
57 return c
58
58
59 def _get_readme_data(self, db_repo, default_renderer):
59 def _get_readme_data(self, db_repo, default_renderer):
60 repo_name = db_repo.repo_name
60 repo_name = db_repo.repo_name
61 log.debug('Looking for README file')
61 log.debug('Looking for README file')
62
62
63 @cache_region('long_term')
63 @cache_region('long_term')
64 def _generate_readme(cache_key):
64 def _generate_readme(cache_key):
65 readme_data = None
65 readme_data = None
66 readme_node = None
66 readme_node = None
67 readme_filename = None
67 readme_filename = None
68 commit = self._get_landing_commit_or_none(db_repo)
68 commit = self._get_landing_commit_or_none(db_repo)
69 if commit:
69 if commit:
70 log.debug("Searching for a README file.")
70 log.debug("Searching for a README file.")
71 readme_node = ReadmeFinder(default_renderer).search(commit)
71 readme_node = ReadmeFinder(default_renderer).search(commit)
72 if readme_node:
72 if readme_node:
73 relative_urls = {
73 relative_urls = {
74 'raw': h.route_path(
74 'raw': h.route_path(
75 'repo_file_raw', repo_name=repo_name,
75 'repo_file_raw', repo_name=repo_name,
76 commit_id=commit.raw_id, f_path=readme_node.path),
76 commit_id=commit.raw_id, f_path=readme_node.path),
77 'standard': h.route_path(
77 'standard': h.route_path(
78 'repo_files', repo_name=repo_name,
78 'repo_files', repo_name=repo_name,
79 commit_id=commit.raw_id, f_path=readme_node.path),
79 commit_id=commit.raw_id, f_path=readme_node.path),
80 }
80 }
81 readme_data = self._render_readme_or_none(
81 readme_data = self._render_readme_or_none(
82 commit, readme_node, relative_urls)
82 commit, readme_node, relative_urls)
83 readme_filename = readme_node.path
83 readme_filename = readme_node.path
84 return readme_data, readme_filename
84 return readme_data, readme_filename
85
85
86 invalidator_context = CacheKey.repo_context_cache(
86 invalidator_context = CacheKey.repo_context_cache(
87 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
87 _generate_readme, repo_name, CacheKey.CACHE_TYPE_README)
88
88
89 with invalidator_context as context:
89 with invalidator_context as context:
90 context.invalidate()
90 context.invalidate()
91 computed = context.compute()
91 computed = context.compute()
92
92
93 return computed
93 return computed
94
94
95 def _get_landing_commit_or_none(self, db_repo):
95 def _get_landing_commit_or_none(self, db_repo):
96 log.debug("Getting the landing commit.")
96 log.debug("Getting the landing commit.")
97 try:
97 try:
98 commit = db_repo.get_landing_commit()
98 commit = db_repo.get_landing_commit()
99 if not isinstance(commit, EmptyCommit):
99 if not isinstance(commit, EmptyCommit):
100 return commit
100 return commit
101 else:
101 else:
102 log.debug("Repository is empty, no README to render.")
102 log.debug("Repository is empty, no README to render.")
103 except CommitError:
103 except CommitError:
104 log.exception(
104 log.exception(
105 "Problem getting commit when trying to render the README.")
105 "Problem getting commit when trying to render the README.")
106
106
107 def _render_readme_or_none(self, commit, readme_node, relative_urls):
107 def _render_readme_or_none(self, commit, readme_node, relative_urls):
108 log.debug(
108 log.debug(
109 'Found README file `%s` rendering...', readme_node.path)
109 'Found README file `%s` rendering...', readme_node.path)
110 renderer = MarkupRenderer()
110 renderer = MarkupRenderer()
111 try:
111 try:
112 html_source = renderer.render(
112 html_source = renderer.render(
113 readme_node.content, filename=readme_node.path)
113 readme_node.content, filename=readme_node.path)
114 if relative_urls:
114 if relative_urls:
115 return relative_links(html_source, relative_urls)
115 return relative_links(html_source, relative_urls)
116 return html_source
116 return html_source
117 except Exception:
117 except Exception:
118 log.exception(
118 log.exception(
119 "Exception while trying to render the README")
119 "Exception while trying to render the README")
120
120
121 def _load_commits_context(self, c):
121 def _load_commits_context(self, c):
122 p = safe_int(self.request.GET.get('page'), 1)
122 p = safe_int(self.request.GET.get('page'), 1)
123 size = safe_int(self.request.GET.get('size'), 10)
123 size = safe_int(self.request.GET.get('size'), 10)
124
124
125 def url_generator(**kw):
125 def url_generator(**kw):
126 query_params = {
126 query_params = {
127 'size': size
127 'size': size
128 }
128 }
129 query_params.update(kw)
129 query_params.update(kw)
130 return h.route_path(
130 return h.route_path(
131 'repo_summary_commits',
131 'repo_summary_commits',
132 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
132 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
133
133
134 pre_load = ['author', 'branch', 'date', 'message']
134 pre_load = ['author', 'branch', 'date', 'message']
135 try:
135 try:
136 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
136 collection = self.rhodecode_vcs_repo.get_commits(pre_load=pre_load)
137 except EmptyRepositoryError:
137 except EmptyRepositoryError:
138 collection = self.rhodecode_vcs_repo
138 collection = self.rhodecode_vcs_repo
139
139
140 c.repo_commits = RepoPage(
140 c.repo_commits = RepoPage(
141 collection, page=p, items_per_page=size, url=url_generator)
141 collection, page=p, items_per_page=size, url=url_generator)
142 page_ids = [x.raw_id for x in c.repo_commits]
142 page_ids = [x.raw_id for x in c.repo_commits]
143 c.comments = self.db_repo.get_comments(page_ids)
143 c.comments = self.db_repo.get_comments(page_ids)
144 c.statuses = self.db_repo.statuses(page_ids)
144 c.statuses = self.db_repo.statuses(page_ids)
145
145
146 @LoginRequired()
146 @LoginRequired()
147 @HasRepoPermissionAnyDecorator(
147 @HasRepoPermissionAnyDecorator(
148 'repository.read', 'repository.write', 'repository.admin')
148 'repository.read', 'repository.write', 'repository.admin')
149 @view_config(
149 @view_config(
150 route_name='repo_summary_commits', request_method='GET',
150 route_name='repo_summary_commits', request_method='GET',
151 renderer='rhodecode:templates/summary/summary_commits.mako')
151 renderer='rhodecode:templates/summary/summary_commits.mako')
152 def summary_commits(self):
152 def summary_commits(self):
153 c = self.load_default_context()
153 c = self.load_default_context()
154 self._load_commits_context(c)
154 self._load_commits_context(c)
155 return self._get_template_context(c)
155 return self._get_template_context(c)
156
156
157 @LoginRequired()
157 @LoginRequired()
158 @HasRepoPermissionAnyDecorator(
158 @HasRepoPermissionAnyDecorator(
159 'repository.read', 'repository.write', 'repository.admin')
159 'repository.read', 'repository.write', 'repository.admin')
160 @view_config(
160 @view_config(
161 route_name='repo_summary', request_method='GET',
161 route_name='repo_summary', request_method='GET',
162 renderer='rhodecode:templates/summary/summary.mako')
162 renderer='rhodecode:templates/summary/summary.mako')
163 @view_config(
163 @view_config(
164 route_name='repo_summary_slash', request_method='GET',
164 route_name='repo_summary_slash', request_method='GET',
165 renderer='rhodecode:templates/summary/summary.mako')
165 renderer='rhodecode:templates/summary/summary.mako')
166 @view_config(
166 @view_config(
167 route_name='repo_summary_explicit', request_method='GET',
167 route_name='repo_summary_explicit', request_method='GET',
168 renderer='rhodecode:templates/summary/summary.mako')
168 renderer='rhodecode:templates/summary/summary.mako')
169 def summary(self):
169 def summary(self):
170 c = self.load_default_context()
170 c = self.load_default_context()
171
171
172 # Prepare the clone URL
172 # Prepare the clone URL
173 username = ''
173 username = ''
174 if self._rhodecode_user.username != User.DEFAULT_USER:
174 if self._rhodecode_user.username != User.DEFAULT_USER:
175 username = safe_str(self._rhodecode_user.username)
175 username = safe_str(self._rhodecode_user.username)
176
176
177 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
177 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
178 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
179
178 if '{repo}' in _def_clone_uri:
180 if '{repo}' in _def_clone_uri:
179 _def_clone_uri_by_id = _def_clone_uri.replace(
181 _def_clone_uri_id = _def_clone_uri.replace(
180 '{repo}', '_{repoid}')
182 '{repo}', '_{repoid}')
181 elif '{repoid}' in _def_clone_uri:
183 elif '{repoid}' in _def_clone_uri:
182 _def_clone_uri_by_id = _def_clone_uri.replace(
184 _def_clone_uri_id = _def_clone_uri.replace(
183 '_{repoid}', '{repo}')
185 '_{repoid}', '{repo}')
184
186
185 c.clone_repo_url = self.db_repo.clone_url(
187 c.clone_repo_url = self.db_repo.clone_url(
186 user=username, uri_tmpl=_def_clone_uri)
188 user=username, uri_tmpl=_def_clone_uri)
187 c.clone_repo_url_id = self.db_repo.clone_url(
189 c.clone_repo_url_id = self.db_repo.clone_url(
188 user=username, uri_tmpl=_def_clone_uri_by_id)
190 user=username, uri_tmpl=_def_clone_uri_id)
191 c.clone_repo_url_ssh = self.db_repo.clone_url(
192 uri_tmpl=_def_clone_uri_ssh, ssh=True)
189
193
190 # If enabled, get statistics data
194 # If enabled, get statistics data
191
195
192 c.show_stats = bool(self.db_repo.enable_statistics)
196 c.show_stats = bool(self.db_repo.enable_statistics)
193
197
194 stats = Session().query(Statistics) \
198 stats = Session().query(Statistics) \
195 .filter(Statistics.repository == self.db_repo) \
199 .filter(Statistics.repository == self.db_repo) \
196 .scalar()
200 .scalar()
197
201
198 c.stats_percentage = 0
202 c.stats_percentage = 0
199
203
200 if stats and stats.languages:
204 if stats and stats.languages:
201 c.no_data = False is self.db_repo.enable_statistics
205 c.no_data = False is self.db_repo.enable_statistics
202 lang_stats_d = json.loads(stats.languages)
206 lang_stats_d = json.loads(stats.languages)
203
207
204 # Sort first by decreasing count and second by the file extension,
208 # Sort first by decreasing count and second by the file extension,
205 # so we have a consistent output.
209 # so we have a consistent output.
206 lang_stats_items = sorted(lang_stats_d.iteritems(),
210 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 key=lambda k: (-k[1], k[0]))[:10]
211 key=lambda k: (-k[1], k[0]))[:10]
208 lang_stats = [(x, {"count": y,
212 lang_stats = [(x, {"count": y,
209 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
213 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 for x, y in lang_stats_items]
214 for x, y in lang_stats_items]
211
215
212 c.trending_languages = json.dumps(lang_stats)
216 c.trending_languages = json.dumps(lang_stats)
213 else:
217 else:
214 c.no_data = True
218 c.no_data = True
215 c.trending_languages = json.dumps({})
219 c.trending_languages = json.dumps({})
216
220
217 scm_model = ScmModel()
221 scm_model = ScmModel()
218 c.enable_downloads = self.db_repo.enable_downloads
222 c.enable_downloads = self.db_repo.enable_downloads
219 c.repository_followers = scm_model.get_followers(self.db_repo)
223 c.repository_followers = scm_model.get_followers(self.db_repo)
220 c.repository_forks = scm_model.get_forks(self.db_repo)
224 c.repository_forks = scm_model.get_forks(self.db_repo)
221 c.repository_is_user_following = scm_model.is_following_repo(
225 c.repository_is_user_following = scm_model.is_following_repo(
222 self.db_repo_name, self._rhodecode_user.user_id)
226 self.db_repo_name, self._rhodecode_user.user_id)
223
227
224 # first interaction with the VCS instance after here...
228 # first interaction with the VCS instance after here...
225 if c.repository_requirements_missing:
229 if c.repository_requirements_missing:
226 self.request.override_renderer = \
230 self.request.override_renderer = \
227 'rhodecode:templates/summary/missing_requirements.mako'
231 'rhodecode:templates/summary/missing_requirements.mako'
228 return self._get_template_context(c)
232 return self._get_template_context(c)
229
233
230 c.readme_data, c.readme_file = \
234 c.readme_data, c.readme_file = \
231 self._get_readme_data(self.db_repo, c.visual.default_renderer)
235 self._get_readme_data(self.db_repo, c.visual.default_renderer)
232
236
233 # loads the summary commits template context
237 # loads the summary commits template context
234 self._load_commits_context(c)
238 self._load_commits_context(c)
235
239
236 return self._get_template_context(c)
240 return self._get_template_context(c)
237
241
238 def get_request_commit_id(self):
242 def get_request_commit_id(self):
239 return self.request.matchdict['commit_id']
243 return self.request.matchdict['commit_id']
240
244
241 @LoginRequired()
245 @LoginRequired()
242 @HasRepoPermissionAnyDecorator(
246 @HasRepoPermissionAnyDecorator(
243 'repository.read', 'repository.write', 'repository.admin')
247 'repository.read', 'repository.write', 'repository.admin')
244 @view_config(
248 @view_config(
245 route_name='repo_stats', request_method='GET',
249 route_name='repo_stats', request_method='GET',
246 renderer='json_ext')
250 renderer='json_ext')
247 def repo_stats(self):
251 def repo_stats(self):
248 commit_id = self.get_request_commit_id()
252 commit_id = self.get_request_commit_id()
249
253
250 _namespace = caches.get_repo_namespace_key(
254 _namespace = caches.get_repo_namespace_key(
251 caches.SUMMARY_STATS, self.db_repo_name)
255 caches.SUMMARY_STATS, self.db_repo_name)
252 show_stats = bool(self.db_repo.enable_statistics)
256 show_stats = bool(self.db_repo.enable_statistics)
253 cache_manager = caches.get_cache_manager(
257 cache_manager = caches.get_cache_manager(
254 'repo_cache_long', _namespace)
258 'repo_cache_long', _namespace)
255 _cache_key = caches.compute_key_from_params(
259 _cache_key = caches.compute_key_from_params(
256 self.db_repo_name, commit_id, show_stats)
260 self.db_repo_name, commit_id, show_stats)
257
261
258 def compute_stats():
262 def compute_stats():
259 code_stats = {}
263 code_stats = {}
260 size = 0
264 size = 0
261 try:
265 try:
262 scm_instance = self.db_repo.scm_instance()
266 scm_instance = self.db_repo.scm_instance()
263 commit = scm_instance.get_commit(commit_id)
267 commit = scm_instance.get_commit(commit_id)
264
268
265 for node in commit.get_filenodes_generator():
269 for node in commit.get_filenodes_generator():
266 size += node.size
270 size += node.size
267 if not show_stats:
271 if not show_stats:
268 continue
272 continue
269 ext = string.lower(node.extension)
273 ext = string.lower(node.extension)
270 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
274 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
271 if ext_info:
275 if ext_info:
272 if ext in code_stats:
276 if ext in code_stats:
273 code_stats[ext]['count'] += 1
277 code_stats[ext]['count'] += 1
274 else:
278 else:
275 code_stats[ext] = {"count": 1, "desc": ext_info}
279 code_stats[ext] = {"count": 1, "desc": ext_info}
276 except (EmptyRepositoryError, CommitDoesNotExistError):
280 except (EmptyRepositoryError, CommitDoesNotExistError):
277 pass
281 pass
278 return {'size': h.format_byte_size_binary(size),
282 return {'size': h.format_byte_size_binary(size),
279 'code_stats': code_stats}
283 'code_stats': code_stats}
280
284
281 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
285 stats = cache_manager.get(_cache_key, createfunc=compute_stats)
282 return stats
286 return stats
283
287
284 @LoginRequired()
288 @LoginRequired()
285 @HasRepoPermissionAnyDecorator(
289 @HasRepoPermissionAnyDecorator(
286 'repository.read', 'repository.write', 'repository.admin')
290 'repository.read', 'repository.write', 'repository.admin')
287 @view_config(
291 @view_config(
288 route_name='repo_refs_data', request_method='GET',
292 route_name='repo_refs_data', request_method='GET',
289 renderer='json_ext')
293 renderer='json_ext')
290 def repo_refs_data(self):
294 def repo_refs_data(self):
291 _ = self.request.translate
295 _ = self.request.translate
292 self.load_default_context()
296 self.load_default_context()
293
297
294 repo = self.rhodecode_vcs_repo
298 repo = self.rhodecode_vcs_repo
295 refs_to_create = [
299 refs_to_create = [
296 (_("Branch"), repo.branches, 'branch'),
300 (_("Branch"), repo.branches, 'branch'),
297 (_("Tag"), repo.tags, 'tag'),
301 (_("Tag"), repo.tags, 'tag'),
298 (_("Bookmark"), repo.bookmarks, 'book'),
302 (_("Bookmark"), repo.bookmarks, 'book'),
299 ]
303 ]
300 res = self._create_reference_data(
304 res = self._create_reference_data(
301 repo, self.db_repo_name, refs_to_create)
305 repo, self.db_repo_name, refs_to_create)
302 data = {
306 data = {
303 'more': False,
307 'more': False,
304 'results': res
308 'results': res
305 }
309 }
306 return data
310 return data
307
311
308 @LoginRequired()
312 @LoginRequired()
309 @HasRepoPermissionAnyDecorator(
313 @HasRepoPermissionAnyDecorator(
310 'repository.read', 'repository.write', 'repository.admin')
314 'repository.read', 'repository.write', 'repository.admin')
311 @view_config(
315 @view_config(
312 route_name='repo_refs_changelog_data', request_method='GET',
316 route_name='repo_refs_changelog_data', request_method='GET',
313 renderer='json_ext')
317 renderer='json_ext')
314 def repo_refs_changelog_data(self):
318 def repo_refs_changelog_data(self):
315 _ = self.request.translate
319 _ = self.request.translate
316 self.load_default_context()
320 self.load_default_context()
317
321
318 repo = self.rhodecode_vcs_repo
322 repo = self.rhodecode_vcs_repo
319
323
320 refs_to_create = [
324 refs_to_create = [
321 (_("Branches"), repo.branches, 'branch'),
325 (_("Branches"), repo.branches, 'branch'),
322 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
326 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
323 # TODO: enable when vcs can handle bookmarks filters
327 # TODO: enable when vcs can handle bookmarks filters
324 # (_("Bookmarks"), repo.bookmarks, "book"),
328 # (_("Bookmarks"), repo.bookmarks, "book"),
325 ]
329 ]
326 res = self._create_reference_data(
330 res = self._create_reference_data(
327 repo, self.db_repo_name, refs_to_create)
331 repo, self.db_repo_name, refs_to_create)
328 data = {
332 data = {
329 'more': False,
333 'more': False,
330 'results': res
334 'results': res
331 }
335 }
332 return data
336 return data
333
337
334 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
338 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
335 format_ref_id = utils.get_format_ref_id(repo)
339 format_ref_id = utils.get_format_ref_id(repo)
336
340
337 result = []
341 result = []
338 for title, refs, ref_type in refs_to_create:
342 for title, refs, ref_type in refs_to_create:
339 if refs:
343 if refs:
340 result.append({
344 result.append({
341 'text': title,
345 'text': title,
342 'children': self._create_reference_items(
346 'children': self._create_reference_items(
343 repo, full_repo_name, refs, ref_type,
347 repo, full_repo_name, refs, ref_type,
344 format_ref_id),
348 format_ref_id),
345 })
349 })
346 return result
350 return result
347
351
348 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
352 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
349 format_ref_id):
353 format_ref_id):
350 result = []
354 result = []
351 is_svn = h.is_svn(repo)
355 is_svn = h.is_svn(repo)
352 for ref_name, raw_id in refs.iteritems():
356 for ref_name, raw_id in refs.iteritems():
353 files_url = self._create_files_url(
357 files_url = self._create_files_url(
354 repo, full_repo_name, ref_name, raw_id, is_svn)
358 repo, full_repo_name, ref_name, raw_id, is_svn)
355 result.append({
359 result.append({
356 'text': ref_name,
360 'text': ref_name,
357 'id': format_ref_id(ref_name, raw_id),
361 'id': format_ref_id(ref_name, raw_id),
358 'raw_id': raw_id,
362 'raw_id': raw_id,
359 'type': ref_type,
363 'type': ref_type,
360 'files_url': files_url,
364 'files_url': files_url,
361 })
365 })
362 return result
366 return result
363
367
364 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
368 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
365 use_commit_id = '/' in ref_name or is_svn
369 use_commit_id = '/' in ref_name or is_svn
366 return h.route_path(
370 return h.route_path(
367 'repo_files',
371 'repo_files',
368 repo_name=full_repo_name,
372 repo_name=full_repo_name,
369 f_path=ref_name if is_svn else '',
373 f_path=ref_name if is_svn else '',
370 commit_id=raw_id if use_commit_id else ref_name,
374 commit_id=raw_id if use_commit_id else ref_name,
371 _query=dict(at=ref_name))
375 _query=dict(at=ref_name))
@@ -1,542 +1,543 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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.authentication.base import VCS_TYPE
38 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.lib import auth, utils2
39 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
45 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist, safe_str)
45 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist, safe_str)
46 from rhodecode.model.db import Repository, User, ChangesetComment
46 from rhodecode.model.db import Repository, User, ChangesetComment
47 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def _filter_proxy(ip):
53 def _filter_proxy(ip):
54 """
54 """
55 Passed in IP addresses in HEADERS can be in a special format of multiple
55 Passed in IP addresses in HEADERS can be in a special format of multiple
56 ips. Those comma separated IPs are passed from various proxies in the
56 ips. Those comma separated IPs are passed from various proxies in the
57 chain of request processing. The left-most being the original client.
57 chain of request processing. The left-most being the original client.
58 We only care about the first IP which came from the org. client.
58 We only care about the first IP which came from the org. client.
59
59
60 :param ip: ip string from headers
60 :param ip: ip string from headers
61 """
61 """
62 if ',' in ip:
62 if ',' in ip:
63 _ips = ip.split(',')
63 _ips = ip.split(',')
64 _first_ip = _ips[0].strip()
64 _first_ip = _ips[0].strip()
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 return _first_ip
66 return _first_ip
67 return ip
67 return ip
68
68
69
69
70 def _filter_port(ip):
70 def _filter_port(ip):
71 """
71 """
72 Removes a port from ip, there are 4 main cases to handle here.
72 Removes a port from ip, there are 4 main cases to handle here.
73 - ipv4 eg. 127.0.0.1
73 - ipv4 eg. 127.0.0.1
74 - ipv6 eg. ::1
74 - ipv6 eg. ::1
75 - ipv4+port eg. 127.0.0.1:8080
75 - ipv4+port eg. 127.0.0.1:8080
76 - ipv6+port eg. [::1]:8080
76 - ipv6+port eg. [::1]:8080
77
77
78 :param ip:
78 :param ip:
79 """
79 """
80 def is_ipv6(ip_addr):
80 def is_ipv6(ip_addr):
81 if hasattr(socket, 'inet_pton'):
81 if hasattr(socket, 'inet_pton'):
82 try:
82 try:
83 socket.inet_pton(socket.AF_INET6, ip_addr)
83 socket.inet_pton(socket.AF_INET6, ip_addr)
84 except socket.error:
84 except socket.error:
85 return False
85 return False
86 else:
86 else:
87 # fallback to ipaddress
87 # fallback to ipaddress
88 try:
88 try:
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 except Exception:
90 except Exception:
91 return False
91 return False
92 return True
92 return True
93
93
94 if ':' not in ip: # must be ipv4 pure ip
94 if ':' not in ip: # must be ipv4 pure ip
95 return ip
95 return ip
96
96
97 if '[' in ip and ']' in ip: # ipv6 with port
97 if '[' in ip and ']' in ip: # ipv6 with port
98 return ip.split(']')[0][1:].lower()
98 return ip.split(']')[0][1:].lower()
99
99
100 # must be ipv6 or ipv4 with port
100 # must be ipv6 or ipv4 with port
101 if is_ipv6(ip):
101 if is_ipv6(ip):
102 return ip
102 return ip
103 else:
103 else:
104 ip, _port = ip.split(':')[:2] # means ipv4+port
104 ip, _port = ip.split(':')[:2] # means ipv4+port
105 return ip
105 return ip
106
106
107
107
108 def get_ip_addr(environ):
108 def get_ip_addr(environ):
109 proxy_key = 'HTTP_X_REAL_IP'
109 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
112 _filters = lambda x: _filter_port(_filter_proxy(x))
112 _filters = lambda x: _filter_port(_filter_proxy(x))
113
113
114 ip = environ.get(proxy_key)
114 ip = environ.get(proxy_key)
115 if ip:
115 if ip:
116 return _filters(ip)
116 return _filters(ip)
117
117
118 ip = environ.get(proxy_key2)
118 ip = environ.get(proxy_key2)
119 if ip:
119 if ip:
120 return _filters(ip)
120 return _filters(ip)
121
121
122 ip = environ.get(def_key, '0.0.0.0')
122 ip = environ.get(def_key, '0.0.0.0')
123 return _filters(ip)
123 return _filters(ip)
124
124
125
125
126 def get_server_ip_addr(environ, log_errors=True):
126 def get_server_ip_addr(environ, log_errors=True):
127 hostname = environ.get('SERVER_NAME')
127 hostname = environ.get('SERVER_NAME')
128 try:
128 try:
129 return socket.gethostbyname(hostname)
129 return socket.gethostbyname(hostname)
130 except Exception as e:
130 except Exception as e:
131 if log_errors:
131 if log_errors:
132 # in some cases this lookup is not possible, and we don't want to
132 # in some cases this lookup is not possible, and we don't want to
133 # make it an exception in logs
133 # make it an exception in logs
134 log.exception('Could not retrieve server ip address: %s', e)
134 log.exception('Could not retrieve server ip address: %s', e)
135 return hostname
135 return hostname
136
136
137
137
138 def get_server_port(environ):
138 def get_server_port(environ):
139 return environ.get('SERVER_PORT')
139 return environ.get('SERVER_PORT')
140
140
141
141
142 def get_access_path(environ):
142 def get_access_path(environ):
143 path = environ.get('PATH_INFO')
143 path = environ.get('PATH_INFO')
144 org_req = environ.get('pylons.original_request')
144 org_req = environ.get('pylons.original_request')
145 if org_req:
145 if org_req:
146 path = org_req.environ.get('PATH_INFO')
146 path = org_req.environ.get('PATH_INFO')
147 return path
147 return path
148
148
149
149
150 def get_user_agent(environ):
150 def get_user_agent(environ):
151 return environ.get('HTTP_USER_AGENT')
151 return environ.get('HTTP_USER_AGENT')
152
152
153
153
154 def vcs_operation_context(
154 def vcs_operation_context(
155 environ, repo_name, username, action, scm, check_locking=True,
155 environ, repo_name, username, action, scm, check_locking=True,
156 is_shadow_repo=False):
156 is_shadow_repo=False):
157 """
157 """
158 Generate the context for a vcs operation, e.g. push or pull.
158 Generate the context for a vcs operation, e.g. push or pull.
159
159
160 This context is passed over the layers so that hooks triggered by the
160 This context is passed over the layers so that hooks triggered by the
161 vcs operation know details like the user, the user's IP address etc.
161 vcs operation know details like the user, the user's IP address etc.
162
162
163 :param check_locking: Allows to switch of the computation of the locking
163 :param check_locking: Allows to switch of the computation of the locking
164 data. This serves mainly the need of the simplevcs middleware to be
164 data. This serves mainly the need of the simplevcs middleware to be
165 able to disable this for certain operations.
165 able to disable this for certain operations.
166
166
167 """
167 """
168 # Tri-state value: False: unlock, None: nothing, True: lock
168 # Tri-state value: False: unlock, None: nothing, True: lock
169 make_lock = None
169 make_lock = None
170 locked_by = [None, None, None]
170 locked_by = [None, None, None]
171 is_anonymous = username == User.DEFAULT_USER
171 is_anonymous = username == User.DEFAULT_USER
172 user = User.get_by_username(username)
172 user = User.get_by_username(username)
173 if not is_anonymous and check_locking:
173 if not is_anonymous and check_locking:
174 log.debug('Checking locking on repository "%s"', repo_name)
174 log.debug('Checking locking on repository "%s"', repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
176 make_lock, __, locked_by = repo.get_locking_state(
176 make_lock, __, locked_by = repo.get_locking_state(
177 action, user.user_id)
177 action, user.user_id)
178 user_id = user.user_id
178 user_id = user.user_id
179 settings_model = VcsSettingsModel(repo=repo_name)
179 settings_model = VcsSettingsModel(repo=repo_name)
180 ui_settings = settings_model.get_ui_settings()
180 ui_settings = settings_model.get_ui_settings()
181
181
182 extras = {
182 extras = {
183 'ip': get_ip_addr(environ),
183 'ip': get_ip_addr(environ),
184 'username': username,
184 'username': username,
185 'user_id': user_id,
185 'user_id': user_id,
186 'action': action,
186 'action': action,
187 'repository': repo_name,
187 'repository': repo_name,
188 'scm': scm,
188 'scm': scm,
189 'config': rhodecode.CONFIG['__file__'],
189 'config': rhodecode.CONFIG['__file__'],
190 'make_lock': make_lock,
190 'make_lock': make_lock,
191 'locked_by': locked_by,
191 'locked_by': locked_by,
192 'server_url': utils2.get_server_url(environ),
192 'server_url': utils2.get_server_url(environ),
193 'user_agent': get_user_agent(environ),
193 'user_agent': get_user_agent(environ),
194 'hooks': get_enabled_hook_classes(ui_settings),
194 'hooks': get_enabled_hook_classes(ui_settings),
195 'is_shadow_repo': is_shadow_repo,
195 'is_shadow_repo': is_shadow_repo,
196 }
196 }
197 return extras
197 return extras
198
198
199
199
200 class BasicAuth(AuthBasicAuthenticator):
200 class BasicAuth(AuthBasicAuthenticator):
201
201
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
203 initial_call_detection=False, acl_repo_name=None):
203 initial_call_detection=False, acl_repo_name=None):
204 self.realm = realm
204 self.realm = realm
205 self.initial_call = initial_call_detection
205 self.initial_call = initial_call_detection
206 self.authfunc = authfunc
206 self.authfunc = authfunc
207 self.registry = registry
207 self.registry = registry
208 self.acl_repo_name = acl_repo_name
208 self.acl_repo_name = acl_repo_name
209 self._rc_auth_http_code = auth_http_code
209 self._rc_auth_http_code = auth_http_code
210
210
211 def _get_response_from_code(self, http_code):
211 def _get_response_from_code(self, http_code):
212 try:
212 try:
213 return get_exception(safe_int(http_code))
213 return get_exception(safe_int(http_code))
214 except Exception:
214 except Exception:
215 log.exception('Failed to fetch response for code %s' % http_code)
215 log.exception('Failed to fetch response for code %s' % http_code)
216 return HTTPForbidden
216 return HTTPForbidden
217
217
218 def get_rc_realm(self):
218 def get_rc_realm(self):
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
220
220
221 def build_authentication(self):
221 def build_authentication(self):
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 if self._rc_auth_http_code and not self.initial_call:
223 if self._rc_auth_http_code and not self.initial_call:
224 # return alternative HTTP code if alternative http return code
224 # return alternative HTTP code if alternative http return code
225 # is specified in RhodeCode config, but ONLY if it's not the
225 # is specified in RhodeCode config, but ONLY if it's not the
226 # FIRST call
226 # FIRST call
227 custom_response_klass = self._get_response_from_code(
227 custom_response_klass = self._get_response_from_code(
228 self._rc_auth_http_code)
228 self._rc_auth_http_code)
229 return custom_response_klass(headers=head)
229 return custom_response_klass(headers=head)
230 return HTTPUnauthorized(headers=head)
230 return HTTPUnauthorized(headers=head)
231
231
232 def authenticate(self, environ):
232 def authenticate(self, environ):
233 authorization = AUTHORIZATION(environ)
233 authorization = AUTHORIZATION(environ)
234 if not authorization:
234 if not authorization:
235 return self.build_authentication()
235 return self.build_authentication()
236 (authmeth, auth) = authorization.split(' ', 1)
236 (authmeth, auth) = authorization.split(' ', 1)
237 if 'basic' != authmeth.lower():
237 if 'basic' != authmeth.lower():
238 return self.build_authentication()
238 return self.build_authentication()
239 auth = auth.strip().decode('base64')
239 auth = auth.strip().decode('base64')
240 _parts = auth.split(':', 1)
240 _parts = auth.split(':', 1)
241 if len(_parts) == 2:
241 if len(_parts) == 2:
242 username, password = _parts
242 username, password = _parts
243 auth_data = self.authfunc(
243 auth_data = self.authfunc(
244 username, password, environ, VCS_TYPE,
244 username, password, environ, VCS_TYPE,
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
246 if auth_data:
246 if auth_data:
247 return {'username': username, 'auth_data': auth_data}
247 return {'username': username, 'auth_data': auth_data}
248 if username and password:
248 if username and password:
249 # we mark that we actually executed authentication once, at
249 # we mark that we actually executed authentication once, at
250 # that point we can use the alternative auth code
250 # that point we can use the alternative auth code
251 self.initial_call = False
251 self.initial_call = False
252
252
253 return self.build_authentication()
253 return self.build_authentication()
254
254
255 __call__ = authenticate
255 __call__ = authenticate
256
256
257
257
258 def calculate_version_hash(config):
258 def calculate_version_hash(config):
259 return md5(
259 return md5(
260 config.get('beaker.session.secret', '') +
260 config.get('beaker.session.secret', '') +
261 rhodecode.__version__)[:8]
261 rhodecode.__version__)[:8]
262
262
263
263
264 def get_current_lang(request):
264 def get_current_lang(request):
265 # NOTE(marcink): remove after pyramid move
265 # NOTE(marcink): remove after pyramid move
266 try:
266 try:
267 return translation.get_lang()[0]
267 return translation.get_lang()[0]
268 except:
268 except:
269 pass
269 pass
270
270
271 return getattr(request, '_LOCALE_', request.locale_name)
271 return getattr(request, '_LOCALE_', request.locale_name)
272
272
273
273
274 def attach_context_attributes(context, request, user_id):
274 def attach_context_attributes(context, request, user_id):
275 """
275 """
276 Attach variables into template context called `c`.
276 Attach variables into template context called `c`.
277 """
277 """
278 config = request.registry.settings
278 config = request.registry.settings
279
279
280
280
281 rc_config = SettingsModel().get_all_settings(cache=True)
281 rc_config = SettingsModel().get_all_settings(cache=True)
282
282
283 context.rhodecode_version = rhodecode.__version__
283 context.rhodecode_version = rhodecode.__version__
284 context.rhodecode_edition = config.get('rhodecode.edition')
284 context.rhodecode_edition = config.get('rhodecode.edition')
285 # unique secret + version does not leak the version but keep consistency
285 # unique secret + version does not leak the version but keep consistency
286 context.rhodecode_version_hash = calculate_version_hash(config)
286 context.rhodecode_version_hash = calculate_version_hash(config)
287
287
288 # Default language set for the incoming request
288 # Default language set for the incoming request
289 context.language = get_current_lang(request)
289 context.language = get_current_lang(request)
290
290
291 # Visual options
291 # Visual options
292 context.visual = AttributeDict({})
292 context.visual = AttributeDict({})
293
293
294 # DB stored Visual Items
294 # DB stored Visual Items
295 context.visual.show_public_icon = str2bool(
295 context.visual.show_public_icon = str2bool(
296 rc_config.get('rhodecode_show_public_icon'))
296 rc_config.get('rhodecode_show_public_icon'))
297 context.visual.show_private_icon = str2bool(
297 context.visual.show_private_icon = str2bool(
298 rc_config.get('rhodecode_show_private_icon'))
298 rc_config.get('rhodecode_show_private_icon'))
299 context.visual.stylify_metatags = str2bool(
299 context.visual.stylify_metatags = str2bool(
300 rc_config.get('rhodecode_stylify_metatags'))
300 rc_config.get('rhodecode_stylify_metatags'))
301 context.visual.dashboard_items = safe_int(
301 context.visual.dashboard_items = safe_int(
302 rc_config.get('rhodecode_dashboard_items', 100))
302 rc_config.get('rhodecode_dashboard_items', 100))
303 context.visual.admin_grid_items = safe_int(
303 context.visual.admin_grid_items = safe_int(
304 rc_config.get('rhodecode_admin_grid_items', 100))
304 rc_config.get('rhodecode_admin_grid_items', 100))
305 context.visual.repository_fields = str2bool(
305 context.visual.repository_fields = str2bool(
306 rc_config.get('rhodecode_repository_fields'))
306 rc_config.get('rhodecode_repository_fields'))
307 context.visual.show_version = str2bool(
307 context.visual.show_version = str2bool(
308 rc_config.get('rhodecode_show_version'))
308 rc_config.get('rhodecode_show_version'))
309 context.visual.use_gravatar = str2bool(
309 context.visual.use_gravatar = str2bool(
310 rc_config.get('rhodecode_use_gravatar'))
310 rc_config.get('rhodecode_use_gravatar'))
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
312 context.visual.default_renderer = rc_config.get(
312 context.visual.default_renderer = rc_config.get(
313 'rhodecode_markup_renderer', 'rst')
313 'rhodecode_markup_renderer', 'rst')
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
315 context.visual.rhodecode_support_url = \
315 context.visual.rhodecode_support_url = \
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
317
317
318 context.visual.affected_files_cut_off = 60
318 context.visual.affected_files_cut_off = 60
319
319
320 context.pre_code = rc_config.get('rhodecode_pre_code')
320 context.pre_code = rc_config.get('rhodecode_pre_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
324 # if we have specified default_encoding in the request, it has more
324 # if we have specified default_encoding in the request, it has more
325 # priority
325 # priority
326 if request.GET.get('default_encoding'):
326 if request.GET.get('default_encoding'):
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
329
330
330 # INI stored
331 # INI stored
331 context.labs_active = str2bool(
332 context.labs_active = str2bool(
332 config.get('labs_settings_active', 'false'))
333 config.get('labs_settings_active', 'false'))
333 context.visual.allow_repo_location_change = str2bool(
334 context.visual.allow_repo_location_change = str2bool(
334 config.get('allow_repo_location_change', True))
335 config.get('allow_repo_location_change', True))
335 context.visual.allow_custom_hooks_settings = str2bool(
336 context.visual.allow_custom_hooks_settings = str2bool(
336 config.get('allow_custom_hooks_settings', True))
337 config.get('allow_custom_hooks_settings', True))
337 context.debug_style = str2bool(config.get('debug_style', False))
338 context.debug_style = str2bool(config.get('debug_style', False))
338
339
339 context.rhodecode_instanceid = config.get('instance_id')
340 context.rhodecode_instanceid = config.get('instance_id')
340
341
341 context.visual.cut_off_limit_diff = safe_int(
342 context.visual.cut_off_limit_diff = safe_int(
342 config.get('cut_off_limit_diff'))
343 config.get('cut_off_limit_diff'))
343 context.visual.cut_off_limit_file = safe_int(
344 context.visual.cut_off_limit_file = safe_int(
344 config.get('cut_off_limit_file'))
345 config.get('cut_off_limit_file'))
345
346
346 # AppEnlight
347 # AppEnlight
347 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
348 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
348 context.appenlight_api_public_key = config.get(
349 context.appenlight_api_public_key = config.get(
349 'appenlight.api_public_key', '')
350 'appenlight.api_public_key', '')
350 context.appenlight_server_url = config.get('appenlight.server_url', '')
351 context.appenlight_server_url = config.get('appenlight.server_url', '')
351
352
352 # JS template context
353 # JS template context
353 context.template_context = {
354 context.template_context = {
354 'repo_name': None,
355 'repo_name': None,
355 'repo_type': None,
356 'repo_type': None,
356 'repo_landing_commit': None,
357 'repo_landing_commit': None,
357 'rhodecode_user': {
358 'rhodecode_user': {
358 'username': None,
359 'username': None,
359 'email': None,
360 'email': None,
360 'notification_status': False
361 'notification_status': False
361 },
362 },
362 'visual': {
363 'visual': {
363 'default_renderer': None
364 'default_renderer': None
364 },
365 },
365 'commit_data': {
366 'commit_data': {
366 'commit_id': None
367 'commit_id': None
367 },
368 },
368 'pull_request_data': {'pull_request_id': None},
369 'pull_request_data': {'pull_request_id': None},
369 'timeago': {
370 'timeago': {
370 'refresh_time': 120 * 1000,
371 'refresh_time': 120 * 1000,
371 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
372 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
372 },
373 },
373 'pyramid_dispatch': {
374 'pyramid_dispatch': {
374
375
375 },
376 },
376 'extra': {'plugins': {}}
377 'extra': {'plugins': {}}
377 }
378 }
378 # END CONFIG VARS
379 # END CONFIG VARS
379
380
380 diffmode = 'sideside'
381 diffmode = 'sideside'
381 if request.GET.get('diffmode'):
382 if request.GET.get('diffmode'):
382 if request.GET['diffmode'] == 'unified':
383 if request.GET['diffmode'] == 'unified':
383 diffmode = 'unified'
384 diffmode = 'unified'
384 elif request.session.get('diffmode'):
385 elif request.session.get('diffmode'):
385 diffmode = request.session['diffmode']
386 diffmode = request.session['diffmode']
386
387
387 context.diffmode = diffmode
388 context.diffmode = diffmode
388
389
389 if request.session.get('diffmode') != diffmode:
390 if request.session.get('diffmode') != diffmode:
390 request.session['diffmode'] = diffmode
391 request.session['diffmode'] = diffmode
391
392
392 context.csrf_token = auth.get_csrf_token(session=request.session)
393 context.csrf_token = auth.get_csrf_token(session=request.session)
393 context.backends = rhodecode.BACKENDS.keys()
394 context.backends = rhodecode.BACKENDS.keys()
394 context.backends.sort()
395 context.backends.sort()
395 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
396 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
396
397
397 # web case
398 # web case
398 if hasattr(request, 'user'):
399 if hasattr(request, 'user'):
399 context.auth_user = request.user
400 context.auth_user = request.user
400 context.rhodecode_user = request.user
401 context.rhodecode_user = request.user
401
402
402 # api case
403 # api case
403 if hasattr(request, 'rpc_user'):
404 if hasattr(request, 'rpc_user'):
404 context.auth_user = request.rpc_user
405 context.auth_user = request.rpc_user
405 context.rhodecode_user = request.rpc_user
406 context.rhodecode_user = request.rpc_user
406
407
407 # attach the whole call context to the request
408 # attach the whole call context to the request
408 request.call_context = context
409 request.call_context = context
409
410
410
411
411 def get_auth_user(request):
412 def get_auth_user(request):
412 environ = request.environ
413 environ = request.environ
413 session = request.session
414 session = request.session
414
415
415 ip_addr = get_ip_addr(environ)
416 ip_addr = get_ip_addr(environ)
416 # make sure that we update permissions each time we call controller
417 # make sure that we update permissions each time we call controller
417 _auth_token = (request.GET.get('auth_token', '') or
418 _auth_token = (request.GET.get('auth_token', '') or
418 request.GET.get('api_key', ''))
419 request.GET.get('api_key', ''))
419
420
420 if _auth_token:
421 if _auth_token:
421 # when using API_KEY we assume user exists, and
422 # when using API_KEY we assume user exists, and
422 # doesn't need auth based on cookies.
423 # doesn't need auth based on cookies.
423 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
424 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
424 authenticated = False
425 authenticated = False
425 else:
426 else:
426 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
427 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
427 try:
428 try:
428 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
429 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
429 ip_addr=ip_addr)
430 ip_addr=ip_addr)
430 except UserCreationError as e:
431 except UserCreationError as e:
431 h.flash(e, 'error')
432 h.flash(e, 'error')
432 # container auth or other auth functions that create users
433 # container auth or other auth functions that create users
433 # on the fly can throw this exception signaling that there's
434 # on the fly can throw this exception signaling that there's
434 # issue with user creation, explanation should be provided
435 # issue with user creation, explanation should be provided
435 # in Exception itself. We then create a simple blank
436 # in Exception itself. We then create a simple blank
436 # AuthUser
437 # AuthUser
437 auth_user = AuthUser(ip_addr=ip_addr)
438 auth_user = AuthUser(ip_addr=ip_addr)
438
439
439 # in case someone changes a password for user it triggers session
440 # in case someone changes a password for user it triggers session
440 # flush and forces a re-login
441 # flush and forces a re-login
441 if password_changed(auth_user, session):
442 if password_changed(auth_user, session):
442 session.invalidate()
443 session.invalidate()
443 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
444 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
444 auth_user = AuthUser(ip_addr=ip_addr)
445 auth_user = AuthUser(ip_addr=ip_addr)
445
446
446 authenticated = cookie_store.get('is_authenticated')
447 authenticated = cookie_store.get('is_authenticated')
447
448
448 if not auth_user.is_authenticated and auth_user.is_user_object:
449 if not auth_user.is_authenticated and auth_user.is_user_object:
449 # user is not authenticated and not empty
450 # user is not authenticated and not empty
450 auth_user.set_authenticated(authenticated)
451 auth_user.set_authenticated(authenticated)
451
452
452 return auth_user
453 return auth_user
453
454
454
455
455 def h_filter(s):
456 def h_filter(s):
456 """
457 """
457 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
458 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
458 we wrap this with additional functionality that converts None to empty
459 we wrap this with additional functionality that converts None to empty
459 strings
460 strings
460 """
461 """
461 if s is None:
462 if s is None:
462 return markupsafe.Markup()
463 return markupsafe.Markup()
463 return markupsafe.escape(s)
464 return markupsafe.escape(s)
464
465
465
466
466 def add_events_routes(config):
467 def add_events_routes(config):
467 """
468 """
468 Adds routing that can be used in events. Because some events are triggered
469 Adds routing that can be used in events. Because some events are triggered
469 outside of pyramid context, we need to bootstrap request with some
470 outside of pyramid context, we need to bootstrap request with some
470 routing registered
471 routing registered
471 """
472 """
472
473
473 from rhodecode.apps._base import ADMIN_PREFIX
474 from rhodecode.apps._base import ADMIN_PREFIX
474
475
475 config.add_route(name='home', pattern='/')
476 config.add_route(name='home', pattern='/')
476
477
477 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
478 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
478 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
479 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
479 config.add_route(name='repo_summary', pattern='/{repo_name}')
480 config.add_route(name='repo_summary', pattern='/{repo_name}')
480 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
481 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
481 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
482 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
482
483
483 config.add_route(name='pullrequest_show',
484 config.add_route(name='pullrequest_show',
484 pattern='/{repo_name}/pull-request/{pull_request_id}')
485 pattern='/{repo_name}/pull-request/{pull_request_id}')
485 config.add_route(name='pull_requests_global',
486 config.add_route(name='pull_requests_global',
486 pattern='/pull-request/{pull_request_id}')
487 pattern='/pull-request/{pull_request_id}')
487 config.add_route(name='repo_commit',
488 config.add_route(name='repo_commit',
488 pattern='/{repo_name}/changeset/{commit_id}')
489 pattern='/{repo_name}/changeset/{commit_id}')
489
490
490 config.add_route(name='repo_files',
491 config.add_route(name='repo_files',
491 pattern='/{repo_name}/files/{commit_id}/{f_path}')
492 pattern='/{repo_name}/files/{commit_id}/{f_path}')
492
493
493
494
494 def bootstrap_config(request):
495 def bootstrap_config(request):
495 import pyramid.testing
496 import pyramid.testing
496 registry = pyramid.testing.Registry('RcTestRegistry')
497 registry = pyramid.testing.Registry('RcTestRegistry')
497
498
498 config = pyramid.testing.setUp(registry=registry, request=request)
499 config = pyramid.testing.setUp(registry=registry, request=request)
499
500
500 # allow pyramid lookup in testing
501 # allow pyramid lookup in testing
501 config.include('pyramid_mako')
502 config.include('pyramid_mako')
502 config.include('pyramid_beaker')
503 config.include('pyramid_beaker')
503 config.include('rhodecode.lib.caches')
504 config.include('rhodecode.lib.caches')
504
505
505 add_events_routes(config)
506 add_events_routes(config)
506
507
507 return config
508 return config
508
509
509
510
510 def bootstrap_request(**kwargs):
511 def bootstrap_request(**kwargs):
511 import pyramid.testing
512 import pyramid.testing
512
513
513 class TestRequest(pyramid.testing.DummyRequest):
514 class TestRequest(pyramid.testing.DummyRequest):
514 application_url = kwargs.pop('application_url', 'http://example.com')
515 application_url = kwargs.pop('application_url', 'http://example.com')
515 host = kwargs.pop('host', 'example.com:80')
516 host = kwargs.pop('host', 'example.com:80')
516 domain = kwargs.pop('domain', 'example.com')
517 domain = kwargs.pop('domain', 'example.com')
517
518
518 def translate(self, msg):
519 def translate(self, msg):
519 return msg
520 return msg
520
521
521 def plularize(self, singular, plural, n):
522 def plularize(self, singular, plural, n):
522 return singular
523 return singular
523
524
524 def get_partial_renderer(self, tmpl_name):
525 def get_partial_renderer(self, tmpl_name):
525
526
526 from rhodecode.lib.partial_renderer import get_partial_renderer
527 from rhodecode.lib.partial_renderer import get_partial_renderer
527 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
528 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
528
529
529 _call_context = {}
530 _call_context = {}
530 @property
531 @property
531 def call_context(self):
532 def call_context(self):
532 return self._call_context
533 return self._call_context
533
534
534 class TestDummySession(pyramid.testing.DummySession):
535 class TestDummySession(pyramid.testing.DummySession):
535 def save(*arg, **kw):
536 def save(*arg, **kw):
536 pass
537 pass
537
538
538 request = TestRequest(**kwargs)
539 request = TestRequest(**kwargs)
539 request.session = TestDummySession()
540 request.session = TestDummySession()
540
541
541 return request
542 return request
542
543
@@ -1,980 +1,984 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26 import collections
26 import collections
27 import datetime
27 import datetime
28 import dateutil.relativedelta
28 import dateutil.relativedelta
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import re
31 import re
32 import sys
32 import sys
33 import time
33 import time
34 import urllib
34 import urllib
35 import urlobject
35 import urlobject
36 import uuid
36 import uuid
37 import getpass
37
38
38 import pygments.lexers
39 import pygments.lexers
39 import sqlalchemy
40 import sqlalchemy
40 import sqlalchemy.engine.url
41 import sqlalchemy.engine.url
41 import sqlalchemy.exc
42 import sqlalchemy.exc
42 import sqlalchemy.sql
43 import sqlalchemy.sql
43 import webob
44 import webob
44 import pyramid.threadlocal
45 import pyramid.threadlocal
45
46
46 import rhodecode
47 import rhodecode
47 from rhodecode.translation import _, _pluralize
48 from rhodecode.translation import _, _pluralize
48
49
49
50
50 def md5(s):
51 def md5(s):
51 return hashlib.md5(s).hexdigest()
52 return hashlib.md5(s).hexdigest()
52
53
53
54
54 def md5_safe(s):
55 def md5_safe(s):
55 return md5(safe_str(s))
56 return md5(safe_str(s))
56
57
57
58
58 def __get_lem(extra_mapping=None):
59 def __get_lem(extra_mapping=None):
59 """
60 """
60 Get language extension map based on what's inside pygments lexers
61 Get language extension map based on what's inside pygments lexers
61 """
62 """
62 d = collections.defaultdict(lambda: [])
63 d = collections.defaultdict(lambda: [])
63
64
64 def __clean(s):
65 def __clean(s):
65 s = s.lstrip('*')
66 s = s.lstrip('*')
66 s = s.lstrip('.')
67 s = s.lstrip('.')
67
68
68 if s.find('[') != -1:
69 if s.find('[') != -1:
69 exts = []
70 exts = []
70 start, stop = s.find('['), s.find(']')
71 start, stop = s.find('['), s.find(']')
71
72
72 for suffix in s[start + 1:stop]:
73 for suffix in s[start + 1:stop]:
73 exts.append(s[:s.find('[')] + suffix)
74 exts.append(s[:s.find('[')] + suffix)
74 return [e.lower() for e in exts]
75 return [e.lower() for e in exts]
75 else:
76 else:
76 return [s.lower()]
77 return [s.lower()]
77
78
78 for lx, t in sorted(pygments.lexers.LEXERS.items()):
79 for lx, t in sorted(pygments.lexers.LEXERS.items()):
79 m = map(__clean, t[-2])
80 m = map(__clean, t[-2])
80 if m:
81 if m:
81 m = reduce(lambda x, y: x + y, m)
82 m = reduce(lambda x, y: x + y, m)
82 for ext in m:
83 for ext in m:
83 desc = lx.replace('Lexer', '')
84 desc = lx.replace('Lexer', '')
84 d[ext].append(desc)
85 d[ext].append(desc)
85
86
86 data = dict(d)
87 data = dict(d)
87
88
88 extra_mapping = extra_mapping or {}
89 extra_mapping = extra_mapping or {}
89 if extra_mapping:
90 if extra_mapping:
90 for k, v in extra_mapping.items():
91 for k, v in extra_mapping.items():
91 if k not in data:
92 if k not in data:
92 # register new mapping2lexer
93 # register new mapping2lexer
93 data[k] = [v]
94 data[k] = [v]
94
95
95 return data
96 return data
96
97
97
98
98 def str2bool(_str):
99 def str2bool(_str):
99 """
100 """
100 returns True/False value from given string, it tries to translate the
101 returns True/False value from given string, it tries to translate the
101 string into boolean
102 string into boolean
102
103
103 :param _str: string value to translate into boolean
104 :param _str: string value to translate into boolean
104 :rtype: boolean
105 :rtype: boolean
105 :returns: boolean from given string
106 :returns: boolean from given string
106 """
107 """
107 if _str is None:
108 if _str is None:
108 return False
109 return False
109 if _str in (True, False):
110 if _str in (True, False):
110 return _str
111 return _str
111 _str = str(_str).strip().lower()
112 _str = str(_str).strip().lower()
112 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
113 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
113
114
114
115
115 def aslist(obj, sep=None, strip=True):
116 def aslist(obj, sep=None, strip=True):
116 """
117 """
117 Returns given string separated by sep as list
118 Returns given string separated by sep as list
118
119
119 :param obj:
120 :param obj:
120 :param sep:
121 :param sep:
121 :param strip:
122 :param strip:
122 """
123 """
123 if isinstance(obj, (basestring,)):
124 if isinstance(obj, (basestring,)):
124 lst = obj.split(sep)
125 lst = obj.split(sep)
125 if strip:
126 if strip:
126 lst = [v.strip() for v in lst]
127 lst = [v.strip() for v in lst]
127 return lst
128 return lst
128 elif isinstance(obj, (list, tuple)):
129 elif isinstance(obj, (list, tuple)):
129 return obj
130 return obj
130 elif obj is None:
131 elif obj is None:
131 return []
132 return []
132 else:
133 else:
133 return [obj]
134 return [obj]
134
135
135
136
136 def convert_line_endings(line, mode):
137 def convert_line_endings(line, mode):
137 """
138 """
138 Converts a given line "line end" accordingly to given mode
139 Converts a given line "line end" accordingly to given mode
139
140
140 Available modes are::
141 Available modes are::
141 0 - Unix
142 0 - Unix
142 1 - Mac
143 1 - Mac
143 2 - DOS
144 2 - DOS
144
145
145 :param line: given line to convert
146 :param line: given line to convert
146 :param mode: mode to convert to
147 :param mode: mode to convert to
147 :rtype: str
148 :rtype: str
148 :return: converted line according to mode
149 :return: converted line according to mode
149 """
150 """
150 if mode == 0:
151 if mode == 0:
151 line = line.replace('\r\n', '\n')
152 line = line.replace('\r\n', '\n')
152 line = line.replace('\r', '\n')
153 line = line.replace('\r', '\n')
153 elif mode == 1:
154 elif mode == 1:
154 line = line.replace('\r\n', '\r')
155 line = line.replace('\r\n', '\r')
155 line = line.replace('\n', '\r')
156 line = line.replace('\n', '\r')
156 elif mode == 2:
157 elif mode == 2:
157 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
158 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
158 return line
159 return line
159
160
160
161
161 def detect_mode(line, default):
162 def detect_mode(line, default):
162 """
163 """
163 Detects line break for given line, if line break couldn't be found
164 Detects line break for given line, if line break couldn't be found
164 given default value is returned
165 given default value is returned
165
166
166 :param line: str line
167 :param line: str line
167 :param default: default
168 :param default: default
168 :rtype: int
169 :rtype: int
169 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
170 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
170 """
171 """
171 if line.endswith('\r\n'):
172 if line.endswith('\r\n'):
172 return 2
173 return 2
173 elif line.endswith('\n'):
174 elif line.endswith('\n'):
174 return 0
175 return 0
175 elif line.endswith('\r'):
176 elif line.endswith('\r'):
176 return 1
177 return 1
177 else:
178 else:
178 return default
179 return default
179
180
180
181
181 def safe_int(val, default=None):
182 def safe_int(val, default=None):
182 """
183 """
183 Returns int() of val if val is not convertable to int use default
184 Returns int() of val if val is not convertable to int use default
184 instead
185 instead
185
186
186 :param val:
187 :param val:
187 :param default:
188 :param default:
188 """
189 """
189
190
190 try:
191 try:
191 val = int(val)
192 val = int(val)
192 except (ValueError, TypeError):
193 except (ValueError, TypeError):
193 val = default
194 val = default
194
195
195 return val
196 return val
196
197
197
198
198 def safe_unicode(str_, from_encoding=None):
199 def safe_unicode(str_, from_encoding=None):
199 """
200 """
200 safe unicode function. Does few trick to turn str_ into unicode
201 safe unicode function. Does few trick to turn str_ into unicode
201
202
202 In case of UnicodeDecode error, we try to return it with encoding detected
203 In case of UnicodeDecode error, we try to return it with encoding detected
203 by chardet library if it fails fallback to unicode with errors replaced
204 by chardet library if it fails fallback to unicode with errors replaced
204
205
205 :param str_: string to decode
206 :param str_: string to decode
206 :rtype: unicode
207 :rtype: unicode
207 :returns: unicode object
208 :returns: unicode object
208 """
209 """
209 if isinstance(str_, unicode):
210 if isinstance(str_, unicode):
210 return str_
211 return str_
211
212
212 if not from_encoding:
213 if not from_encoding:
213 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
214 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
214 'utf8'), sep=',')
215 'utf8'), sep=',')
215 from_encoding = DEFAULT_ENCODINGS
216 from_encoding = DEFAULT_ENCODINGS
216
217
217 if not isinstance(from_encoding, (list, tuple)):
218 if not isinstance(from_encoding, (list, tuple)):
218 from_encoding = [from_encoding]
219 from_encoding = [from_encoding]
219
220
220 try:
221 try:
221 return unicode(str_)
222 return unicode(str_)
222 except UnicodeDecodeError:
223 except UnicodeDecodeError:
223 pass
224 pass
224
225
225 for enc in from_encoding:
226 for enc in from_encoding:
226 try:
227 try:
227 return unicode(str_, enc)
228 return unicode(str_, enc)
228 except UnicodeDecodeError:
229 except UnicodeDecodeError:
229 pass
230 pass
230
231
231 try:
232 try:
232 import chardet
233 import chardet
233 encoding = chardet.detect(str_)['encoding']
234 encoding = chardet.detect(str_)['encoding']
234 if encoding is None:
235 if encoding is None:
235 raise Exception()
236 raise Exception()
236 return str_.decode(encoding)
237 return str_.decode(encoding)
237 except (ImportError, UnicodeDecodeError, Exception):
238 except (ImportError, UnicodeDecodeError, Exception):
238 return unicode(str_, from_encoding[0], 'replace')
239 return unicode(str_, from_encoding[0], 'replace')
239
240
240
241
241 def safe_str(unicode_, to_encoding=None):
242 def safe_str(unicode_, to_encoding=None):
242 """
243 """
243 safe str function. Does few trick to turn unicode_ into string
244 safe str function. Does few trick to turn unicode_ into string
244
245
245 In case of UnicodeEncodeError, we try to return it with encoding detected
246 In case of UnicodeEncodeError, we try to return it with encoding detected
246 by chardet library if it fails fallback to string with errors replaced
247 by chardet library if it fails fallback to string with errors replaced
247
248
248 :param unicode_: unicode to encode
249 :param unicode_: unicode to encode
249 :rtype: str
250 :rtype: str
250 :returns: str object
251 :returns: str object
251 """
252 """
252
253
253 # if it's not basestr cast to str
254 # if it's not basestr cast to str
254 if not isinstance(unicode_, basestring):
255 if not isinstance(unicode_, basestring):
255 return str(unicode_)
256 return str(unicode_)
256
257
257 if isinstance(unicode_, str):
258 if isinstance(unicode_, str):
258 return unicode_
259 return unicode_
259
260
260 if not to_encoding:
261 if not to_encoding:
261 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
262 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
262 'utf8'), sep=',')
263 'utf8'), sep=',')
263 to_encoding = DEFAULT_ENCODINGS
264 to_encoding = DEFAULT_ENCODINGS
264
265
265 if not isinstance(to_encoding, (list, tuple)):
266 if not isinstance(to_encoding, (list, tuple)):
266 to_encoding = [to_encoding]
267 to_encoding = [to_encoding]
267
268
268 for enc in to_encoding:
269 for enc in to_encoding:
269 try:
270 try:
270 return unicode_.encode(enc)
271 return unicode_.encode(enc)
271 except UnicodeEncodeError:
272 except UnicodeEncodeError:
272 pass
273 pass
273
274
274 try:
275 try:
275 import chardet
276 import chardet
276 encoding = chardet.detect(unicode_)['encoding']
277 encoding = chardet.detect(unicode_)['encoding']
277 if encoding is None:
278 if encoding is None:
278 raise UnicodeEncodeError()
279 raise UnicodeEncodeError()
279
280
280 return unicode_.encode(encoding)
281 return unicode_.encode(encoding)
281 except (ImportError, UnicodeEncodeError):
282 except (ImportError, UnicodeEncodeError):
282 return unicode_.encode(to_encoding[0], 'replace')
283 return unicode_.encode(to_encoding[0], 'replace')
283
284
284
285
285 def remove_suffix(s, suffix):
286 def remove_suffix(s, suffix):
286 if s.endswith(suffix):
287 if s.endswith(suffix):
287 s = s[:-1 * len(suffix)]
288 s = s[:-1 * len(suffix)]
288 return s
289 return s
289
290
290
291
291 def remove_prefix(s, prefix):
292 def remove_prefix(s, prefix):
292 if s.startswith(prefix):
293 if s.startswith(prefix):
293 s = s[len(prefix):]
294 s = s[len(prefix):]
294 return s
295 return s
295
296
296
297
297 def find_calling_context(ignore_modules=None):
298 def find_calling_context(ignore_modules=None):
298 """
299 """
299 Look through the calling stack and return the frame which called
300 Look through the calling stack and return the frame which called
300 this function and is part of core module ( ie. rhodecode.* )
301 this function and is part of core module ( ie. rhodecode.* )
301
302
302 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
303 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
303 """
304 """
304
305
305 ignore_modules = ignore_modules or []
306 ignore_modules = ignore_modules or []
306
307
307 f = sys._getframe(2)
308 f = sys._getframe(2)
308 while f.f_back is not None:
309 while f.f_back is not None:
309 name = f.f_globals.get('__name__')
310 name = f.f_globals.get('__name__')
310 if name and name.startswith(__name__.split('.')[0]):
311 if name and name.startswith(__name__.split('.')[0]):
311 if name not in ignore_modules:
312 if name not in ignore_modules:
312 return f
313 return f
313 f = f.f_back
314 f = f.f_back
314 return None
315 return None
315
316
316
317
317 def ping_connection(connection, branch):
318 def ping_connection(connection, branch):
318 if branch:
319 if branch:
319 # "branch" refers to a sub-connection of a connection,
320 # "branch" refers to a sub-connection of a connection,
320 # we don't want to bother pinging on these.
321 # we don't want to bother pinging on these.
321 return
322 return
322
323
323 # turn off "close with result". This flag is only used with
324 # turn off "close with result". This flag is only used with
324 # "connectionless" execution, otherwise will be False in any case
325 # "connectionless" execution, otherwise will be False in any case
325 save_should_close_with_result = connection.should_close_with_result
326 save_should_close_with_result = connection.should_close_with_result
326 connection.should_close_with_result = False
327 connection.should_close_with_result = False
327
328
328 try:
329 try:
329 # run a SELECT 1. use a core select() so that
330 # run a SELECT 1. use a core select() so that
330 # the SELECT of a scalar value without a table is
331 # the SELECT of a scalar value without a table is
331 # appropriately formatted for the backend
332 # appropriately formatted for the backend
332 connection.scalar(sqlalchemy.sql.select([1]))
333 connection.scalar(sqlalchemy.sql.select([1]))
333 except sqlalchemy.exc.DBAPIError as err:
334 except sqlalchemy.exc.DBAPIError as err:
334 # catch SQLAlchemy's DBAPIError, which is a wrapper
335 # catch SQLAlchemy's DBAPIError, which is a wrapper
335 # for the DBAPI's exception. It includes a .connection_invalidated
336 # for the DBAPI's exception. It includes a .connection_invalidated
336 # attribute which specifies if this connection is a "disconnect"
337 # attribute which specifies if this connection is a "disconnect"
337 # condition, which is based on inspection of the original exception
338 # condition, which is based on inspection of the original exception
338 # by the dialect in use.
339 # by the dialect in use.
339 if err.connection_invalidated:
340 if err.connection_invalidated:
340 # run the same SELECT again - the connection will re-validate
341 # run the same SELECT again - the connection will re-validate
341 # itself and establish a new connection. The disconnect detection
342 # itself and establish a new connection. The disconnect detection
342 # here also causes the whole connection pool to be invalidated
343 # here also causes the whole connection pool to be invalidated
343 # so that all stale connections are discarded.
344 # so that all stale connections are discarded.
344 connection.scalar(sqlalchemy.sql.select([1]))
345 connection.scalar(sqlalchemy.sql.select([1]))
345 else:
346 else:
346 raise
347 raise
347 finally:
348 finally:
348 # restore "close with result"
349 # restore "close with result"
349 connection.should_close_with_result = save_should_close_with_result
350 connection.should_close_with_result = save_should_close_with_result
350
351
351
352
352 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
353 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
353 """Custom engine_from_config functions."""
354 """Custom engine_from_config functions."""
354 log = logging.getLogger('sqlalchemy.engine')
355 log = logging.getLogger('sqlalchemy.engine')
355 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
356 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
356
357
357 def color_sql(sql):
358 def color_sql(sql):
358 color_seq = '\033[1;33m' # This is yellow: code 33
359 color_seq = '\033[1;33m' # This is yellow: code 33
359 normal = '\x1b[0m'
360 normal = '\x1b[0m'
360 return ''.join([color_seq, sql, normal])
361 return ''.join([color_seq, sql, normal])
361
362
362 if configuration['debug']:
363 if configuration['debug']:
363 # attach events only for debug configuration
364 # attach events only for debug configuration
364
365
365 def before_cursor_execute(conn, cursor, statement,
366 def before_cursor_execute(conn, cursor, statement,
366 parameters, context, executemany):
367 parameters, context, executemany):
367 setattr(conn, 'query_start_time', time.time())
368 setattr(conn, 'query_start_time', time.time())
368 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
369 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
369 calling_context = find_calling_context(ignore_modules=[
370 calling_context = find_calling_context(ignore_modules=[
370 'rhodecode.lib.caching_query',
371 'rhodecode.lib.caching_query',
371 'rhodecode.model.settings',
372 'rhodecode.model.settings',
372 ])
373 ])
373 if calling_context:
374 if calling_context:
374 log.info(color_sql('call context %s:%s' % (
375 log.info(color_sql('call context %s:%s' % (
375 calling_context.f_code.co_filename,
376 calling_context.f_code.co_filename,
376 calling_context.f_lineno,
377 calling_context.f_lineno,
377 )))
378 )))
378
379
379 def after_cursor_execute(conn, cursor, statement,
380 def after_cursor_execute(conn, cursor, statement,
380 parameters, context, executemany):
381 parameters, context, executemany):
381 delattr(conn, 'query_start_time')
382 delattr(conn, 'query_start_time')
382
383
383 sqlalchemy.event.listen(engine, "engine_connect",
384 sqlalchemy.event.listen(engine, "engine_connect",
384 ping_connection)
385 ping_connection)
385 sqlalchemy.event.listen(engine, "before_cursor_execute",
386 sqlalchemy.event.listen(engine, "before_cursor_execute",
386 before_cursor_execute)
387 before_cursor_execute)
387 sqlalchemy.event.listen(engine, "after_cursor_execute",
388 sqlalchemy.event.listen(engine, "after_cursor_execute",
388 after_cursor_execute)
389 after_cursor_execute)
389
390
390 return engine
391 return engine
391
392
392
393
393 def get_encryption_key(config):
394 def get_encryption_key(config):
394 secret = config.get('rhodecode.encrypted_values.secret')
395 secret = config.get('rhodecode.encrypted_values.secret')
395 default = config['beaker.session.secret']
396 default = config['beaker.session.secret']
396 return secret or default
397 return secret or default
397
398
398
399
399 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
400 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
400 short_format=False):
401 short_format=False):
401 """
402 """
402 Turns a datetime into an age string.
403 Turns a datetime into an age string.
403 If show_short_version is True, this generates a shorter string with
404 If show_short_version is True, this generates a shorter string with
404 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
405 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
405
406
406 * IMPORTANT*
407 * IMPORTANT*
407 Code of this function is written in special way so it's easier to
408 Code of this function is written in special way so it's easier to
408 backport it to javascript. If you mean to update it, please also update
409 backport it to javascript. If you mean to update it, please also update
409 `jquery.timeago-extension.js` file
410 `jquery.timeago-extension.js` file
410
411
411 :param prevdate: datetime object
412 :param prevdate: datetime object
412 :param now: get current time, if not define we use
413 :param now: get current time, if not define we use
413 `datetime.datetime.now()`
414 `datetime.datetime.now()`
414 :param show_short_version: if it should approximate the date and
415 :param show_short_version: if it should approximate the date and
415 return a shorter string
416 return a shorter string
416 :param show_suffix:
417 :param show_suffix:
417 :param short_format: show short format, eg 2D instead of 2 days
418 :param short_format: show short format, eg 2D instead of 2 days
418 :rtype: unicode
419 :rtype: unicode
419 :returns: unicode words describing age
420 :returns: unicode words describing age
420 """
421 """
421
422
422 def _get_relative_delta(now, prevdate):
423 def _get_relative_delta(now, prevdate):
423 base = dateutil.relativedelta.relativedelta(now, prevdate)
424 base = dateutil.relativedelta.relativedelta(now, prevdate)
424 return {
425 return {
425 'year': base.years,
426 'year': base.years,
426 'month': base.months,
427 'month': base.months,
427 'day': base.days,
428 'day': base.days,
428 'hour': base.hours,
429 'hour': base.hours,
429 'minute': base.minutes,
430 'minute': base.minutes,
430 'second': base.seconds,
431 'second': base.seconds,
431 }
432 }
432
433
433 def _is_leap_year(year):
434 def _is_leap_year(year):
434 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
435 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
435
436
436 def get_month(prevdate):
437 def get_month(prevdate):
437 return prevdate.month
438 return prevdate.month
438
439
439 def get_year(prevdate):
440 def get_year(prevdate):
440 return prevdate.year
441 return prevdate.year
441
442
442 now = now or datetime.datetime.now()
443 now = now or datetime.datetime.now()
443 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
444 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
444 deltas = {}
445 deltas = {}
445 future = False
446 future = False
446
447
447 if prevdate > now:
448 if prevdate > now:
448 now_old = now
449 now_old = now
449 now = prevdate
450 now = prevdate
450 prevdate = now_old
451 prevdate = now_old
451 future = True
452 future = True
452 if future:
453 if future:
453 prevdate = prevdate.replace(microsecond=0)
454 prevdate = prevdate.replace(microsecond=0)
454 # Get date parts deltas
455 # Get date parts deltas
455 for part in order:
456 for part in order:
456 rel_delta = _get_relative_delta(now, prevdate)
457 rel_delta = _get_relative_delta(now, prevdate)
457 deltas[part] = rel_delta[part]
458 deltas[part] = rel_delta[part]
458
459
459 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
460 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
460 # not 1 hour, -59 minutes and -59 seconds)
461 # not 1 hour, -59 minutes and -59 seconds)
461 offsets = [[5, 60], [4, 60], [3, 24]]
462 offsets = [[5, 60], [4, 60], [3, 24]]
462 for element in offsets: # seconds, minutes, hours
463 for element in offsets: # seconds, minutes, hours
463 num = element[0]
464 num = element[0]
464 length = element[1]
465 length = element[1]
465
466
466 part = order[num]
467 part = order[num]
467 carry_part = order[num - 1]
468 carry_part = order[num - 1]
468
469
469 if deltas[part] < 0:
470 if deltas[part] < 0:
470 deltas[part] += length
471 deltas[part] += length
471 deltas[carry_part] -= 1
472 deltas[carry_part] -= 1
472
473
473 # Same thing for days except that the increment depends on the (variable)
474 # Same thing for days except that the increment depends on the (variable)
474 # number of days in the month
475 # number of days in the month
475 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
476 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
476 if deltas['day'] < 0:
477 if deltas['day'] < 0:
477 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
478 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
478 deltas['day'] += 29
479 deltas['day'] += 29
479 else:
480 else:
480 deltas['day'] += month_lengths[get_month(prevdate) - 1]
481 deltas['day'] += month_lengths[get_month(prevdate) - 1]
481
482
482 deltas['month'] -= 1
483 deltas['month'] -= 1
483
484
484 if deltas['month'] < 0:
485 if deltas['month'] < 0:
485 deltas['month'] += 12
486 deltas['month'] += 12
486 deltas['year'] -= 1
487 deltas['year'] -= 1
487
488
488 # Format the result
489 # Format the result
489 if short_format:
490 if short_format:
490 fmt_funcs = {
491 fmt_funcs = {
491 'year': lambda d: u'%dy' % d,
492 'year': lambda d: u'%dy' % d,
492 'month': lambda d: u'%dm' % d,
493 'month': lambda d: u'%dm' % d,
493 'day': lambda d: u'%dd' % d,
494 'day': lambda d: u'%dd' % d,
494 'hour': lambda d: u'%dh' % d,
495 'hour': lambda d: u'%dh' % d,
495 'minute': lambda d: u'%dmin' % d,
496 'minute': lambda d: u'%dmin' % d,
496 'second': lambda d: u'%dsec' % d,
497 'second': lambda d: u'%dsec' % d,
497 }
498 }
498 else:
499 else:
499 fmt_funcs = {
500 fmt_funcs = {
500 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
501 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
501 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
502 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
502 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
503 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
503 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
504 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
504 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
505 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
505 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
506 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
506 }
507 }
507
508
508 i = 0
509 i = 0
509 for part in order:
510 for part in order:
510 value = deltas[part]
511 value = deltas[part]
511 if value != 0:
512 if value != 0:
512
513
513 if i < 5:
514 if i < 5:
514 sub_part = order[i + 1]
515 sub_part = order[i + 1]
515 sub_value = deltas[sub_part]
516 sub_value = deltas[sub_part]
516 else:
517 else:
517 sub_value = 0
518 sub_value = 0
518
519
519 if sub_value == 0 or show_short_version:
520 if sub_value == 0 or show_short_version:
520 _val = fmt_funcs[part](value)
521 _val = fmt_funcs[part](value)
521 if future:
522 if future:
522 if show_suffix:
523 if show_suffix:
523 return _(u'in ${ago}', mapping={'ago': _val})
524 return _(u'in ${ago}', mapping={'ago': _val})
524 else:
525 else:
525 return _(_val)
526 return _(_val)
526
527
527 else:
528 else:
528 if show_suffix:
529 if show_suffix:
529 return _(u'${ago} ago', mapping={'ago': _val})
530 return _(u'${ago} ago', mapping={'ago': _val})
530 else:
531 else:
531 return _(_val)
532 return _(_val)
532
533
533 val = fmt_funcs[part](value)
534 val = fmt_funcs[part](value)
534 val_detail = fmt_funcs[sub_part](sub_value)
535 val_detail = fmt_funcs[sub_part](sub_value)
535 mapping = {'val': val, 'detail': val_detail}
536 mapping = {'val': val, 'detail': val_detail}
536
537
537 if short_format:
538 if short_format:
538 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
539 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
539 if show_suffix:
540 if show_suffix:
540 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
541 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
541 if future:
542 if future:
542 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
543 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
543 else:
544 else:
544 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
545 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
545 if show_suffix:
546 if show_suffix:
546 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
547 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
547 if future:
548 if future:
548 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
549 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
549
550
550 return datetime_tmpl
551 return datetime_tmpl
551 i += 1
552 i += 1
552 return _(u'just now')
553 return _(u'just now')
553
554
554
555
555 def cleaned_uri(uri):
556 def cleaned_uri(uri):
556 """
557 """
557 Quotes '[' and ']' from uri if there is only one of them.
558 Quotes '[' and ']' from uri if there is only one of them.
558 according to RFC3986 we cannot use such chars in uri
559 according to RFC3986 we cannot use such chars in uri
559 :param uri:
560 :param uri:
560 :return: uri without this chars
561 :return: uri without this chars
561 """
562 """
562 return urllib.quote(uri, safe='@$:/')
563 return urllib.quote(uri, safe='@$:/')
563
564
564
565
565 def uri_filter(uri):
566 def uri_filter(uri):
566 """
567 """
567 Removes user:password from given url string
568 Removes user:password from given url string
568
569
569 :param uri:
570 :param uri:
570 :rtype: unicode
571 :rtype: unicode
571 :returns: filtered list of strings
572 :returns: filtered list of strings
572 """
573 """
573 if not uri:
574 if not uri:
574 return ''
575 return ''
575
576
576 proto = ''
577 proto = ''
577
578
578 for pat in ('https://', 'http://'):
579 for pat in ('https://', 'http://'):
579 if uri.startswith(pat):
580 if uri.startswith(pat):
580 uri = uri[len(pat):]
581 uri = uri[len(pat):]
581 proto = pat
582 proto = pat
582 break
583 break
583
584
584 # remove passwords and username
585 # remove passwords and username
585 uri = uri[uri.find('@') + 1:]
586 uri = uri[uri.find('@') + 1:]
586
587
587 # get the port
588 # get the port
588 cred_pos = uri.find(':')
589 cred_pos = uri.find(':')
589 if cred_pos == -1:
590 if cred_pos == -1:
590 host, port = uri, None
591 host, port = uri, None
591 else:
592 else:
592 host, port = uri[:cred_pos], uri[cred_pos + 1:]
593 host, port = uri[:cred_pos], uri[cred_pos + 1:]
593
594
594 return filter(None, [proto, host, port])
595 return filter(None, [proto, host, port])
595
596
596
597
597 def credentials_filter(uri):
598 def credentials_filter(uri):
598 """
599 """
599 Returns a url with removed credentials
600 Returns a url with removed credentials
600
601
601 :param uri:
602 :param uri:
602 """
603 """
603
604
604 uri = uri_filter(uri)
605 uri = uri_filter(uri)
605 # check if we have port
606 # check if we have port
606 if len(uri) > 2 and uri[2]:
607 if len(uri) > 2 and uri[2]:
607 uri[2] = ':' + uri[2]
608 uri[2] = ':' + uri[2]
608
609
609 return ''.join(uri)
610 return ''.join(uri)
610
611
611
612
612 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
613 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
613 qualifed_home_url = request.route_url('home')
614 qualifed_home_url = request.route_url('home')
614 parsed_url = urlobject.URLObject(qualifed_home_url)
615 parsed_url = urlobject.URLObject(qualifed_home_url)
615 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
616 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
617
616 args = {
618 args = {
617 'scheme': parsed_url.scheme,
619 'scheme': parsed_url.scheme,
618 'user': '',
620 'user': '',
621 'sys_user': getpass.getuser(),
619 # path if we use proxy-prefix
622 # path if we use proxy-prefix
620 'netloc': parsed_url.netloc+decoded_path,
623 'netloc': parsed_url.netloc+decoded_path,
624 'hostname': parsed_url.hostname,
621 'prefix': decoded_path,
625 'prefix': decoded_path,
622 'repo': repo_name,
626 'repo': repo_name,
623 'repoid': str(repo_id)
627 'repoid': str(repo_id)
624 }
628 }
625 args.update(override)
629 args.update(override)
626 args['user'] = urllib.quote(safe_str(args['user']))
630 args['user'] = urllib.quote(safe_str(args['user']))
627
631
628 for k, v in args.items():
632 for k, v in args.items():
629 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
633 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
630
634
631 # remove leading @ sign if it's present. Case of empty user
635 # remove leading @ sign if it's present. Case of empty user
632 url_obj = urlobject.URLObject(uri_tmpl)
636 url_obj = urlobject.URLObject(uri_tmpl)
633 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
637 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
634
638
635 return safe_unicode(url)
639 return safe_unicode(url)
636
640
637
641
638 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
642 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
639 """
643 """
640 Safe version of get_commit if this commit doesn't exists for a
644 Safe version of get_commit if this commit doesn't exists for a
641 repository it returns a Dummy one instead
645 repository it returns a Dummy one instead
642
646
643 :param repo: repository instance
647 :param repo: repository instance
644 :param commit_id: commit id as str
648 :param commit_id: commit id as str
645 :param pre_load: optional list of commit attributes to load
649 :param pre_load: optional list of commit attributes to load
646 """
650 """
647 # TODO(skreft): remove these circular imports
651 # TODO(skreft): remove these circular imports
648 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
652 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
649 from rhodecode.lib.vcs.exceptions import RepositoryError
653 from rhodecode.lib.vcs.exceptions import RepositoryError
650 if not isinstance(repo, BaseRepository):
654 if not isinstance(repo, BaseRepository):
651 raise Exception('You must pass an Repository '
655 raise Exception('You must pass an Repository '
652 'object as first argument got %s', type(repo))
656 'object as first argument got %s', type(repo))
653
657
654 try:
658 try:
655 commit = repo.get_commit(
659 commit = repo.get_commit(
656 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
660 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
657 except (RepositoryError, LookupError):
661 except (RepositoryError, LookupError):
658 commit = EmptyCommit()
662 commit = EmptyCommit()
659 return commit
663 return commit
660
664
661
665
662 def datetime_to_time(dt):
666 def datetime_to_time(dt):
663 if dt:
667 if dt:
664 return time.mktime(dt.timetuple())
668 return time.mktime(dt.timetuple())
665
669
666
670
667 def time_to_datetime(tm):
671 def time_to_datetime(tm):
668 if tm:
672 if tm:
669 if isinstance(tm, basestring):
673 if isinstance(tm, basestring):
670 try:
674 try:
671 tm = float(tm)
675 tm = float(tm)
672 except ValueError:
676 except ValueError:
673 return
677 return
674 return datetime.datetime.fromtimestamp(tm)
678 return datetime.datetime.fromtimestamp(tm)
675
679
676
680
677 def time_to_utcdatetime(tm):
681 def time_to_utcdatetime(tm):
678 if tm:
682 if tm:
679 if isinstance(tm, basestring):
683 if isinstance(tm, basestring):
680 try:
684 try:
681 tm = float(tm)
685 tm = float(tm)
682 except ValueError:
686 except ValueError:
683 return
687 return
684 return datetime.datetime.utcfromtimestamp(tm)
688 return datetime.datetime.utcfromtimestamp(tm)
685
689
686
690
687 MENTIONS_REGEX = re.compile(
691 MENTIONS_REGEX = re.compile(
688 # ^@ or @ without any special chars in front
692 # ^@ or @ without any special chars in front
689 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
693 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
690 # main body starts with letter, then can be . - _
694 # main body starts with letter, then can be . - _
691 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
695 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
692 re.VERBOSE | re.MULTILINE)
696 re.VERBOSE | re.MULTILINE)
693
697
694
698
695 def extract_mentioned_users(s):
699 def extract_mentioned_users(s):
696 """
700 """
697 Returns unique usernames from given string s that have @mention
701 Returns unique usernames from given string s that have @mention
698
702
699 :param s: string to get mentions
703 :param s: string to get mentions
700 """
704 """
701 usrs = set()
705 usrs = set()
702 for username in MENTIONS_REGEX.findall(s):
706 for username in MENTIONS_REGEX.findall(s):
703 usrs.add(username)
707 usrs.add(username)
704
708
705 return sorted(list(usrs), key=lambda k: k.lower())
709 return sorted(list(usrs), key=lambda k: k.lower())
706
710
707
711
708 class StrictAttributeDict(dict):
712 class StrictAttributeDict(dict):
709 """
713 """
710 Strict Version of Attribute dict which raises an Attribute error when
714 Strict Version of Attribute dict which raises an Attribute error when
711 requested attribute is not set
715 requested attribute is not set
712 """
716 """
713 def __getattr__(self, attr):
717 def __getattr__(self, attr):
714 try:
718 try:
715 return self[attr]
719 return self[attr]
716 except KeyError:
720 except KeyError:
717 raise AttributeError('%s object has no attribute %s' % (
721 raise AttributeError('%s object has no attribute %s' % (
718 self.__class__, attr))
722 self.__class__, attr))
719 __setattr__ = dict.__setitem__
723 __setattr__ = dict.__setitem__
720 __delattr__ = dict.__delitem__
724 __delattr__ = dict.__delitem__
721
725
722
726
723 class AttributeDict(dict):
727 class AttributeDict(dict):
724 def __getattr__(self, attr):
728 def __getattr__(self, attr):
725 return self.get(attr, None)
729 return self.get(attr, None)
726 __setattr__ = dict.__setitem__
730 __setattr__ = dict.__setitem__
727 __delattr__ = dict.__delitem__
731 __delattr__ = dict.__delitem__
728
732
729
733
730 def fix_PATH(os_=None):
734 def fix_PATH(os_=None):
731 """
735 """
732 Get current active python path, and append it to PATH variable to fix
736 Get current active python path, and append it to PATH variable to fix
733 issues of subprocess calls and different python versions
737 issues of subprocess calls and different python versions
734 """
738 """
735 if os_ is None:
739 if os_ is None:
736 import os
740 import os
737 else:
741 else:
738 os = os_
742 os = os_
739
743
740 cur_path = os.path.split(sys.executable)[0]
744 cur_path = os.path.split(sys.executable)[0]
741 if not os.environ['PATH'].startswith(cur_path):
745 if not os.environ['PATH'].startswith(cur_path):
742 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
746 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
743
747
744
748
745 def obfuscate_url_pw(engine):
749 def obfuscate_url_pw(engine):
746 _url = engine or ''
750 _url = engine or ''
747 try:
751 try:
748 _url = sqlalchemy.engine.url.make_url(engine)
752 _url = sqlalchemy.engine.url.make_url(engine)
749 if _url.password:
753 if _url.password:
750 _url.password = 'XXXXX'
754 _url.password = 'XXXXX'
751 except Exception:
755 except Exception:
752 pass
756 pass
753 return unicode(_url)
757 return unicode(_url)
754
758
755
759
756 def get_server_url(environ):
760 def get_server_url(environ):
757 req = webob.Request(environ)
761 req = webob.Request(environ)
758 return req.host_url + req.script_name
762 return req.host_url + req.script_name
759
763
760
764
761 def unique_id(hexlen=32):
765 def unique_id(hexlen=32):
762 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
766 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
763 return suuid(truncate_to=hexlen, alphabet=alphabet)
767 return suuid(truncate_to=hexlen, alphabet=alphabet)
764
768
765
769
766 def suuid(url=None, truncate_to=22, alphabet=None):
770 def suuid(url=None, truncate_to=22, alphabet=None):
767 """
771 """
768 Generate and return a short URL safe UUID.
772 Generate and return a short URL safe UUID.
769
773
770 If the url parameter is provided, set the namespace to the provided
774 If the url parameter is provided, set the namespace to the provided
771 URL and generate a UUID.
775 URL and generate a UUID.
772
776
773 :param url to get the uuid for
777 :param url to get the uuid for
774 :truncate_to: truncate the basic 22 UUID to shorter version
778 :truncate_to: truncate the basic 22 UUID to shorter version
775
779
776 The IDs won't be universally unique any longer, but the probability of
780 The IDs won't be universally unique any longer, but the probability of
777 a collision will still be very low.
781 a collision will still be very low.
778 """
782 """
779 # Define our alphabet.
783 # Define our alphabet.
780 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
784 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
781
785
782 # If no URL is given, generate a random UUID.
786 # If no URL is given, generate a random UUID.
783 if url is None:
787 if url is None:
784 unique_id = uuid.uuid4().int
788 unique_id = uuid.uuid4().int
785 else:
789 else:
786 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
790 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
787
791
788 alphabet_length = len(_ALPHABET)
792 alphabet_length = len(_ALPHABET)
789 output = []
793 output = []
790 while unique_id > 0:
794 while unique_id > 0:
791 digit = unique_id % alphabet_length
795 digit = unique_id % alphabet_length
792 output.append(_ALPHABET[digit])
796 output.append(_ALPHABET[digit])
793 unique_id = int(unique_id / alphabet_length)
797 unique_id = int(unique_id / alphabet_length)
794 return "".join(output)[:truncate_to]
798 return "".join(output)[:truncate_to]
795
799
796
800
797 def get_current_rhodecode_user(request=None):
801 def get_current_rhodecode_user(request=None):
798 """
802 """
799 Gets rhodecode user from request
803 Gets rhodecode user from request
800 """
804 """
801 pyramid_request = request or pyramid.threadlocal.get_current_request()
805 pyramid_request = request or pyramid.threadlocal.get_current_request()
802
806
803 # web case
807 # web case
804 if pyramid_request and hasattr(pyramid_request, 'user'):
808 if pyramid_request and hasattr(pyramid_request, 'user'):
805 return pyramid_request.user
809 return pyramid_request.user
806
810
807 # api case
811 # api case
808 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
812 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
809 return pyramid_request.rpc_user
813 return pyramid_request.rpc_user
810
814
811 return None
815 return None
812
816
813
817
814 def action_logger_generic(action, namespace=''):
818 def action_logger_generic(action, namespace=''):
815 """
819 """
816 A generic logger for actions useful to the system overview, tries to find
820 A generic logger for actions useful to the system overview, tries to find
817 an acting user for the context of the call otherwise reports unknown user
821 an acting user for the context of the call otherwise reports unknown user
818
822
819 :param action: logging message eg 'comment 5 deleted'
823 :param action: logging message eg 'comment 5 deleted'
820 :param type: string
824 :param type: string
821
825
822 :param namespace: namespace of the logging message eg. 'repo.comments'
826 :param namespace: namespace of the logging message eg. 'repo.comments'
823 :param type: string
827 :param type: string
824
828
825 """
829 """
826
830
827 logger_name = 'rhodecode.actions'
831 logger_name = 'rhodecode.actions'
828
832
829 if namespace:
833 if namespace:
830 logger_name += '.' + namespace
834 logger_name += '.' + namespace
831
835
832 log = logging.getLogger(logger_name)
836 log = logging.getLogger(logger_name)
833
837
834 # get a user if we can
838 # get a user if we can
835 user = get_current_rhodecode_user()
839 user = get_current_rhodecode_user()
836
840
837 logfunc = log.info
841 logfunc = log.info
838
842
839 if not user:
843 if not user:
840 user = '<unknown user>'
844 user = '<unknown user>'
841 logfunc = log.warning
845 logfunc = log.warning
842
846
843 logfunc('Logging action by {}: {}'.format(user, action))
847 logfunc('Logging action by {}: {}'.format(user, action))
844
848
845
849
846 def escape_split(text, sep=',', maxsplit=-1):
850 def escape_split(text, sep=',', maxsplit=-1):
847 r"""
851 r"""
848 Allows for escaping of the separator: e.g. arg='foo\, bar'
852 Allows for escaping of the separator: e.g. arg='foo\, bar'
849
853
850 It should be noted that the way bash et. al. do command line parsing, those
854 It should be noted that the way bash et. al. do command line parsing, those
851 single quotes are required.
855 single quotes are required.
852 """
856 """
853 escaped_sep = r'\%s' % sep
857 escaped_sep = r'\%s' % sep
854
858
855 if escaped_sep not in text:
859 if escaped_sep not in text:
856 return text.split(sep, maxsplit)
860 return text.split(sep, maxsplit)
857
861
858 before, _mid, after = text.partition(escaped_sep)
862 before, _mid, after = text.partition(escaped_sep)
859 startlist = before.split(sep, maxsplit) # a regular split is fine here
863 startlist = before.split(sep, maxsplit) # a regular split is fine here
860 unfinished = startlist[-1]
864 unfinished = startlist[-1]
861 startlist = startlist[:-1]
865 startlist = startlist[:-1]
862
866
863 # recurse because there may be more escaped separators
867 # recurse because there may be more escaped separators
864 endlist = escape_split(after, sep, maxsplit)
868 endlist = escape_split(after, sep, maxsplit)
865
869
866 # finish building the escaped value. we use endlist[0] becaue the first
870 # finish building the escaped value. we use endlist[0] becaue the first
867 # part of the string sent in recursion is the rest of the escaped value.
871 # part of the string sent in recursion is the rest of the escaped value.
868 unfinished += sep + endlist[0]
872 unfinished += sep + endlist[0]
869
873
870 return startlist + [unfinished] + endlist[1:] # put together all the parts
874 return startlist + [unfinished] + endlist[1:] # put together all the parts
871
875
872
876
873 class OptionalAttr(object):
877 class OptionalAttr(object):
874 """
878 """
875 Special Optional Option that defines other attribute. Example::
879 Special Optional Option that defines other attribute. Example::
876
880
877 def test(apiuser, userid=Optional(OAttr('apiuser')):
881 def test(apiuser, userid=Optional(OAttr('apiuser')):
878 user = Optional.extract(userid)
882 user = Optional.extract(userid)
879 # calls
883 # calls
880
884
881 """
885 """
882
886
883 def __init__(self, attr_name):
887 def __init__(self, attr_name):
884 self.attr_name = attr_name
888 self.attr_name = attr_name
885
889
886 def __repr__(self):
890 def __repr__(self):
887 return '<OptionalAttr:%s>' % self.attr_name
891 return '<OptionalAttr:%s>' % self.attr_name
888
892
889 def __call__(self):
893 def __call__(self):
890 return self
894 return self
891
895
892
896
893 # alias
897 # alias
894 OAttr = OptionalAttr
898 OAttr = OptionalAttr
895
899
896
900
897 class Optional(object):
901 class Optional(object):
898 """
902 """
899 Defines an optional parameter::
903 Defines an optional parameter::
900
904
901 param = param.getval() if isinstance(param, Optional) else param
905 param = param.getval() if isinstance(param, Optional) else param
902 param = param() if isinstance(param, Optional) else param
906 param = param() if isinstance(param, Optional) else param
903
907
904 is equivalent of::
908 is equivalent of::
905
909
906 param = Optional.extract(param)
910 param = Optional.extract(param)
907
911
908 """
912 """
909
913
910 def __init__(self, type_):
914 def __init__(self, type_):
911 self.type_ = type_
915 self.type_ = type_
912
916
913 def __repr__(self):
917 def __repr__(self):
914 return '<Optional:%s>' % self.type_.__repr__()
918 return '<Optional:%s>' % self.type_.__repr__()
915
919
916 def __call__(self):
920 def __call__(self):
917 return self.getval()
921 return self.getval()
918
922
919 def getval(self):
923 def getval(self):
920 """
924 """
921 returns value from this Optional instance
925 returns value from this Optional instance
922 """
926 """
923 if isinstance(self.type_, OAttr):
927 if isinstance(self.type_, OAttr):
924 # use params name
928 # use params name
925 return self.type_.attr_name
929 return self.type_.attr_name
926 return self.type_
930 return self.type_
927
931
928 @classmethod
932 @classmethod
929 def extract(cls, val):
933 def extract(cls, val):
930 """
934 """
931 Extracts value from Optional() instance
935 Extracts value from Optional() instance
932
936
933 :param val:
937 :param val:
934 :return: original value if it's not Optional instance else
938 :return: original value if it's not Optional instance else
935 value of instance
939 value of instance
936 """
940 """
937 if isinstance(val, cls):
941 if isinstance(val, cls):
938 return val.getval()
942 return val.getval()
939 return val
943 return val
940
944
941
945
942 def glob2re(pat):
946 def glob2re(pat):
943 """
947 """
944 Translate a shell PATTERN to a regular expression.
948 Translate a shell PATTERN to a regular expression.
945
949
946 There is no way to quote meta-characters.
950 There is no way to quote meta-characters.
947 """
951 """
948
952
949 i, n = 0, len(pat)
953 i, n = 0, len(pat)
950 res = ''
954 res = ''
951 while i < n:
955 while i < n:
952 c = pat[i]
956 c = pat[i]
953 i = i+1
957 i = i+1
954 if c == '*':
958 if c == '*':
955 #res = res + '.*'
959 #res = res + '.*'
956 res = res + '[^/]*'
960 res = res + '[^/]*'
957 elif c == '?':
961 elif c == '?':
958 #res = res + '.'
962 #res = res + '.'
959 res = res + '[^/]'
963 res = res + '[^/]'
960 elif c == '[':
964 elif c == '[':
961 j = i
965 j = i
962 if j < n and pat[j] == '!':
966 if j < n and pat[j] == '!':
963 j = j+1
967 j = j+1
964 if j < n and pat[j] == ']':
968 if j < n and pat[j] == ']':
965 j = j+1
969 j = j+1
966 while j < n and pat[j] != ']':
970 while j < n and pat[j] != ']':
967 j = j+1
971 j = j+1
968 if j >= n:
972 if j >= n:
969 res = res + '\\['
973 res = res + '\\['
970 else:
974 else:
971 stuff = pat[i:j].replace('\\','\\\\')
975 stuff = pat[i:j].replace('\\','\\\\')
972 i = j+1
976 i = j+1
973 if stuff[0] == '!':
977 if stuff[0] == '!':
974 stuff = '^' + stuff[1:]
978 stuff = '^' + stuff[1:]
975 elif stuff[0] == '^':
979 elif stuff[0] == '^':
976 stuff = '\\' + stuff
980 stuff = '\\' + stuff
977 res = '%s[%s]' % (res, stuff)
981 res = '%s[%s]' % (res, stuff)
978 else:
982 else:
979 res = res + re.escape(c)
983 res = res + re.escape(c)
980 return res + '\Z(?ms)'
984 return res + '\Z(?ms)'
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,615 +1,616 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 extern_name = v.UnicodeString(strip=True)
146 extern_name = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
148 language = v.OneOf(available_languages, hideList=False,
148 language = v.OneOf(available_languages, hideList=False,
149 testValueList=True, if_missing=None)
149 testValueList=True, if_missing=None)
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
151 return _UserForm
151 return _UserForm
152
152
153
153
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
155 old_data = old_data or {}
155 old_data = old_data or {}
156 _ = localizer
156 _ = localizer
157
157
158 class _UserGroupForm(formencode.Schema):
158 class _UserGroupForm(formencode.Schema):
159 allow_extra_fields = True
159 allow_extra_fields = True
160 filter_extra_fields = True
160 filter_extra_fields = True
161
161
162 users_group_name = All(
162 users_group_name = All(
163 v.UnicodeString(strip=True, min=1, not_empty=True),
163 v.UnicodeString(strip=True, min=1, not_empty=True),
164 v.ValidUserGroup(localizer, edit, old_data)
164 v.ValidUserGroup(localizer, edit, old_data)
165 )
165 )
166 user_group_description = v.UnicodeString(strip=True, min=1,
166 user_group_description = v.UnicodeString(strip=True, min=1,
167 not_empty=False)
167 not_empty=False)
168
168
169 users_group_active = v.StringBoolean(if_missing=False)
169 users_group_active = v.StringBoolean(if_missing=False)
170
170
171 if edit:
171 if edit:
172 # this is user group owner
172 # this is user group owner
173 user = All(
173 user = All(
174 v.UnicodeString(not_empty=True),
174 v.UnicodeString(not_empty=True),
175 v.ValidRepoUser(localizer, allow_disabled))
175 v.ValidRepoUser(localizer, allow_disabled))
176 return _UserGroupForm
176 return _UserGroupForm
177
177
178
178
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
180 can_create_in_root=False, allow_disabled=False):
180 can_create_in_root=False, allow_disabled=False):
181 _ = localizer
181 _ = localizer
182 old_data = old_data or {}
182 old_data = old_data or {}
183 available_groups = available_groups or []
183 available_groups = available_groups or []
184
184
185 class _RepoGroupForm(formencode.Schema):
185 class _RepoGroupForm(formencode.Schema):
186 allow_extra_fields = True
186 allow_extra_fields = True
187 filter_extra_fields = False
187 filter_extra_fields = False
188
188
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
190 v.SlugifyName(localizer),)
190 v.SlugifyName(localizer),)
191 group_description = v.UnicodeString(strip=True, min=1,
191 group_description = v.UnicodeString(strip=True, min=1,
192 not_empty=False)
192 not_empty=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
194
194
195 group_parent_id = v.OneOf(available_groups, hideList=False,
195 group_parent_id = v.OneOf(available_groups, hideList=False,
196 testValueList=True, not_empty=True)
196 testValueList=True, not_empty=True)
197 enable_locking = v.StringBoolean(if_missing=False)
197 enable_locking = v.StringBoolean(if_missing=False)
198 chained_validators = [
198 chained_validators = [
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
200
200
201 if edit:
201 if edit:
202 # this is repo group owner
202 # this is repo group owner
203 user = All(
203 user = All(
204 v.UnicodeString(not_empty=True),
204 v.UnicodeString(not_empty=True),
205 v.ValidRepoUser(localizer, allow_disabled))
205 v.ValidRepoUser(localizer, allow_disabled))
206 return _RepoGroupForm
206 return _RepoGroupForm
207
207
208
208
209 def RegisterForm(localizer, edit=False, old_data=None):
209 def RegisterForm(localizer, edit=False, old_data=None):
210 _ = localizer
210 _ = localizer
211 old_data = old_data or {}
211 old_data = old_data or {}
212
212
213 class _RegisterForm(formencode.Schema):
213 class _RegisterForm(formencode.Schema):
214 allow_extra_fields = True
214 allow_extra_fields = True
215 filter_extra_fields = True
215 filter_extra_fields = True
216 username = All(
216 username = All(
217 v.ValidUsername(localizer, edit, old_data),
217 v.ValidUsername(localizer, edit, old_data),
218 v.UnicodeString(strip=True, min=1, not_empty=True)
218 v.UnicodeString(strip=True, min=1, not_empty=True)
219 )
219 )
220 password = All(
220 password = All(
221 v.ValidPassword(localizer),
221 v.ValidPassword(localizer),
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
223 )
223 )
224 password_confirmation = All(
224 password_confirmation = All(
225 v.ValidPassword(localizer),
225 v.ValidPassword(localizer),
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
227 )
227 )
228 active = v.StringBoolean(if_missing=False)
228 active = v.StringBoolean(if_missing=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
232
232
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
234 return _RegisterForm
234 return _RegisterForm
235
235
236
236
237 def PasswordResetForm(localizer):
237 def PasswordResetForm(localizer):
238 _ = localizer
238 _ = localizer
239
239
240 class _PasswordResetForm(formencode.Schema):
240 class _PasswordResetForm(formencode.Schema):
241 allow_extra_fields = True
241 allow_extra_fields = True
242 filter_extra_fields = True
242 filter_extra_fields = True
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
244 return _PasswordResetForm
244 return _PasswordResetForm
245
245
246
246
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
248 landing_revs=None, allow_disabled=False):
248 landing_revs=None, allow_disabled=False):
249 _ = localizer
249 _ = localizer
250 old_data = old_data or {}
250 old_data = old_data or {}
251 repo_groups = repo_groups or []
251 repo_groups = repo_groups or []
252 landing_revs = landing_revs or []
252 landing_revs = landing_revs 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_landing_rev = v.OneOf(landing_revs, hideList=True)
266 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
267 repo_copy_permissions = v.StringBoolean(if_missing=False)
267 repo_copy_permissions = v.StringBoolean(if_missing=False)
268 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
268 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
269
269
270 repo_enable_statistics = v.StringBoolean(if_missing=False)
270 repo_enable_statistics = v.StringBoolean(if_missing=False)
271 repo_enable_downloads = v.StringBoolean(if_missing=False)
271 repo_enable_downloads = v.StringBoolean(if_missing=False)
272 repo_enable_locking = v.StringBoolean(if_missing=False)
272 repo_enable_locking = v.StringBoolean(if_missing=False)
273
273
274 if edit:
274 if edit:
275 # this is repo owner
275 # this is repo owner
276 user = All(
276 user = All(
277 v.UnicodeString(not_empty=True),
277 v.UnicodeString(not_empty=True),
278 v.ValidRepoUser(localizer, allow_disabled))
278 v.ValidRepoUser(localizer, allow_disabled))
279 clone_uri_change = v.UnicodeString(
279 clone_uri_change = v.UnicodeString(
280 not_empty=False, if_missing=v.Missing)
280 not_empty=False, if_missing=v.Missing)
281
281
282 chained_validators = [v.ValidCloneUri(localizer),
282 chained_validators = [v.ValidCloneUri(localizer),
283 v.ValidRepoName(localizer, edit, old_data)]
283 v.ValidRepoName(localizer, edit, old_data)]
284 return _RepoForm
284 return _RepoForm
285
285
286
286
287 def RepoPermsForm(localizer):
287 def RepoPermsForm(localizer):
288 _ = localizer
288 _ = localizer
289
289
290 class _RepoPermsForm(formencode.Schema):
290 class _RepoPermsForm(formencode.Schema):
291 allow_extra_fields = True
291 allow_extra_fields = True
292 filter_extra_fields = False
292 filter_extra_fields = False
293 chained_validators = [v.ValidPerms(localizer, type_='repo')]
293 chained_validators = [v.ValidPerms(localizer, type_='repo')]
294 return _RepoPermsForm
294 return _RepoPermsForm
295
295
296
296
297 def RepoGroupPermsForm(localizer, valid_recursive_choices):
297 def RepoGroupPermsForm(localizer, valid_recursive_choices):
298 _ = localizer
298 _ = localizer
299
299
300 class _RepoGroupPermsForm(formencode.Schema):
300 class _RepoGroupPermsForm(formencode.Schema):
301 allow_extra_fields = True
301 allow_extra_fields = True
302 filter_extra_fields = False
302 filter_extra_fields = False
303 recursive = v.OneOf(valid_recursive_choices)
303 recursive = v.OneOf(valid_recursive_choices)
304 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
304 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
305 return _RepoGroupPermsForm
305 return _RepoGroupPermsForm
306
306
307
307
308 def UserGroupPermsForm(localizer):
308 def UserGroupPermsForm(localizer):
309 _ = localizer
309 _ = localizer
310
310
311 class _UserPermsForm(formencode.Schema):
311 class _UserPermsForm(formencode.Schema):
312 allow_extra_fields = True
312 allow_extra_fields = True
313 filter_extra_fields = False
313 filter_extra_fields = False
314 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
314 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
315 return _UserPermsForm
315 return _UserPermsForm
316
316
317
317
318 def RepoFieldForm(localizer):
318 def RepoFieldForm(localizer):
319 _ = localizer
319 _ = localizer
320
320
321 class _RepoFieldForm(formencode.Schema):
321 class _RepoFieldForm(formencode.Schema):
322 filter_extra_fields = True
322 filter_extra_fields = True
323 allow_extra_fields = True
323 allow_extra_fields = True
324
324
325 new_field_key = All(v.FieldKey(localizer),
325 new_field_key = All(v.FieldKey(localizer),
326 v.UnicodeString(strip=True, min=3, not_empty=True))
326 v.UnicodeString(strip=True, min=3, not_empty=True))
327 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
327 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
328 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
328 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
329 if_missing='str')
329 if_missing='str')
330 new_field_label = v.UnicodeString(not_empty=False)
330 new_field_label = v.UnicodeString(not_empty=False)
331 new_field_desc = v.UnicodeString(not_empty=False)
331 new_field_desc = v.UnicodeString(not_empty=False)
332 return _RepoFieldForm
332 return _RepoFieldForm
333
333
334
334
335 def RepoForkForm(localizer, edit=False, old_data=None,
335 def RepoForkForm(localizer, edit=False, old_data=None,
336 supported_backends=BACKENDS.keys(), repo_groups=None,
336 supported_backends=BACKENDS.keys(), repo_groups=None,
337 landing_revs=None):
337 landing_revs=None):
338 _ = localizer
338 _ = localizer
339 old_data = old_data or {}
339 old_data = old_data or {}
340 repo_groups = repo_groups or []
340 repo_groups = repo_groups or []
341 landing_revs = landing_revs or []
341 landing_revs = landing_revs or []
342
342
343 class _RepoForkForm(formencode.Schema):
343 class _RepoForkForm(formencode.Schema):
344 allow_extra_fields = True
344 allow_extra_fields = True
345 filter_extra_fields = False
345 filter_extra_fields = False
346 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
346 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
347 v.SlugifyName(localizer))
347 v.SlugifyName(localizer))
348 repo_group = All(v.CanWriteGroup(localizer, ),
348 repo_group = All(v.CanWriteGroup(localizer, ),
349 v.OneOf(repo_groups, hideList=True))
349 v.OneOf(repo_groups, hideList=True))
350 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
350 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
351 description = v.UnicodeString(strip=True, min=1, not_empty=True)
351 description = v.UnicodeString(strip=True, min=1, not_empty=True)
352 private = v.StringBoolean(if_missing=False)
352 private = v.StringBoolean(if_missing=False)
353 copy_permissions = v.StringBoolean(if_missing=False)
353 copy_permissions = v.StringBoolean(if_missing=False)
354 fork_parent_id = v.UnicodeString()
354 fork_parent_id = v.UnicodeString()
355 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
355 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
356 landing_rev = v.OneOf(landing_revs, hideList=True)
356 landing_rev = v.OneOf(landing_revs, hideList=True)
357 return _RepoForkForm
357 return _RepoForkForm
358
358
359
359
360 def ApplicationSettingsForm(localizer):
360 def ApplicationSettingsForm(localizer):
361 _ = localizer
361 _ = localizer
362
362
363 class _ApplicationSettingsForm(formencode.Schema):
363 class _ApplicationSettingsForm(formencode.Schema):
364 allow_extra_fields = True
364 allow_extra_fields = True
365 filter_extra_fields = False
365 filter_extra_fields = False
366 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
366 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
367 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
367 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
368 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
368 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
370 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
370 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
372 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
372 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
373 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
373 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
374 return _ApplicationSettingsForm
374 return _ApplicationSettingsForm
375
375
376
376
377 def ApplicationVisualisationForm(localizer):
377 def ApplicationVisualisationForm(localizer):
378 _ = localizer
378 _ = localizer
379
379
380 class _ApplicationVisualisationForm(formencode.Schema):
380 class _ApplicationVisualisationForm(formencode.Schema):
381 allow_extra_fields = True
381 allow_extra_fields = True
382 filter_extra_fields = False
382 filter_extra_fields = False
383 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
383 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
384 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
384 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
385 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
385 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
386
386
387 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
387 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
388 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
388 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
389 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
389 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
390 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
390 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
391 rhodecode_show_version = v.StringBoolean(if_missing=False)
391 rhodecode_show_version = v.StringBoolean(if_missing=False)
392 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
392 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
393 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
393 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
394 rhodecode_gravatar_url = v.UnicodeString(min=3)
394 rhodecode_gravatar_url = v.UnicodeString(min=3)
395 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
395 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
396 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(min=3)
396 rhodecode_support_url = v.UnicodeString()
397 rhodecode_support_url = v.UnicodeString()
397 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
398 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
398 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
399 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
399 return _ApplicationVisualisationForm
400 return _ApplicationVisualisationForm
400
401
401
402
402 class _BaseVcsSettingsForm(formencode.Schema):
403 class _BaseVcsSettingsForm(formencode.Schema):
403
404
404 allow_extra_fields = True
405 allow_extra_fields = True
405 filter_extra_fields = False
406 filter_extra_fields = False
406 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
407 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
407 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
408 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
408 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
409 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
409
410
410 # PR/Code-review
411 # PR/Code-review
411 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
412 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
412 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
413 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
413
414
414 # hg
415 # hg
415 extensions_largefiles = v.StringBoolean(if_missing=False)
416 extensions_largefiles = v.StringBoolean(if_missing=False)
416 extensions_evolve = v.StringBoolean(if_missing=False)
417 extensions_evolve = v.StringBoolean(if_missing=False)
417 phases_publish = v.StringBoolean(if_missing=False)
418 phases_publish = v.StringBoolean(if_missing=False)
418
419
419 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
420 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
420 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
421 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
421
422
422 # git
423 # git
423 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
424 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
424 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
425 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
425 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
426 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
426
427
427 # svn
428 # svn
428 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
429 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
429 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
430 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
430
431
431
432
432 def ApplicationUiSettingsForm(localizer):
433 def ApplicationUiSettingsForm(localizer):
433 _ = localizer
434 _ = localizer
434
435
435 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
436 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
436 web_push_ssl = v.StringBoolean(if_missing=False)
437 web_push_ssl = v.StringBoolean(if_missing=False)
437 paths_root_path = All(
438 paths_root_path = All(
438 v.ValidPath(localizer),
439 v.ValidPath(localizer),
439 v.UnicodeString(strip=True, min=1, not_empty=True)
440 v.UnicodeString(strip=True, min=1, not_empty=True)
440 )
441 )
441 largefiles_usercache = All(
442 largefiles_usercache = All(
442 v.ValidPath(localizer),
443 v.ValidPath(localizer),
443 v.UnicodeString(strip=True, min=2, not_empty=True))
444 v.UnicodeString(strip=True, min=2, not_empty=True))
444 vcs_git_lfs_store_location = All(
445 vcs_git_lfs_store_location = All(
445 v.ValidPath(localizer),
446 v.ValidPath(localizer),
446 v.UnicodeString(strip=True, min=2, not_empty=True))
447 v.UnicodeString(strip=True, min=2, not_empty=True))
447 extensions_hgsubversion = v.StringBoolean(if_missing=False)
448 extensions_hgsubversion = v.StringBoolean(if_missing=False)
448 extensions_hggit = v.StringBoolean(if_missing=False)
449 extensions_hggit = v.StringBoolean(if_missing=False)
449 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
450 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
450 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
451 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
451 return _ApplicationUiSettingsForm
452 return _ApplicationUiSettingsForm
452
453
453
454
454 def RepoVcsSettingsForm(localizer, repo_name):
455 def RepoVcsSettingsForm(localizer, repo_name):
455 _ = localizer
456 _ = localizer
456
457
457 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
458 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
458 inherit_global_settings = v.StringBoolean(if_missing=False)
459 inherit_global_settings = v.StringBoolean(if_missing=False)
459 new_svn_branch = v.ValidSvnPattern(localizer,
460 new_svn_branch = v.ValidSvnPattern(localizer,
460 section='vcs_svn_branch', repo_name=repo_name)
461 section='vcs_svn_branch', repo_name=repo_name)
461 new_svn_tag = v.ValidSvnPattern(localizer,
462 new_svn_tag = v.ValidSvnPattern(localizer,
462 section='vcs_svn_tag', repo_name=repo_name)
463 section='vcs_svn_tag', repo_name=repo_name)
463 return _RepoVcsSettingsForm
464 return _RepoVcsSettingsForm
464
465
465
466
466 def LabsSettingsForm(localizer):
467 def LabsSettingsForm(localizer):
467 _ = localizer
468 _ = localizer
468
469
469 class _LabSettingsForm(formencode.Schema):
470 class _LabSettingsForm(formencode.Schema):
470 allow_extra_fields = True
471 allow_extra_fields = True
471 filter_extra_fields = False
472 filter_extra_fields = False
472 return _LabSettingsForm
473 return _LabSettingsForm
473
474
474
475
475 def ApplicationPermissionsForm(
476 def ApplicationPermissionsForm(
476 localizer, register_choices, password_reset_choices,
477 localizer, register_choices, password_reset_choices,
477 extern_activate_choices):
478 extern_activate_choices):
478 _ = localizer
479 _ = localizer
479
480
480 class _DefaultPermissionsForm(formencode.Schema):
481 class _DefaultPermissionsForm(formencode.Schema):
481 allow_extra_fields = True
482 allow_extra_fields = True
482 filter_extra_fields = True
483 filter_extra_fields = True
483
484
484 anonymous = v.StringBoolean(if_missing=False)
485 anonymous = v.StringBoolean(if_missing=False)
485 default_register = v.OneOf(register_choices)
486 default_register = v.OneOf(register_choices)
486 default_register_message = v.UnicodeString()
487 default_register_message = v.UnicodeString()
487 default_password_reset = v.OneOf(password_reset_choices)
488 default_password_reset = v.OneOf(password_reset_choices)
488 default_extern_activate = v.OneOf(extern_activate_choices)
489 default_extern_activate = v.OneOf(extern_activate_choices)
489 return _DefaultPermissionsForm
490 return _DefaultPermissionsForm
490
491
491
492
492 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
493 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
493 user_group_perms_choices):
494 user_group_perms_choices):
494 _ = localizer
495 _ = localizer
495
496
496 class _ObjectPermissionsForm(formencode.Schema):
497 class _ObjectPermissionsForm(formencode.Schema):
497 allow_extra_fields = True
498 allow_extra_fields = True
498 filter_extra_fields = True
499 filter_extra_fields = True
499 overwrite_default_repo = v.StringBoolean(if_missing=False)
500 overwrite_default_repo = v.StringBoolean(if_missing=False)
500 overwrite_default_group = v.StringBoolean(if_missing=False)
501 overwrite_default_group = v.StringBoolean(if_missing=False)
501 overwrite_default_user_group = v.StringBoolean(if_missing=False)
502 overwrite_default_user_group = v.StringBoolean(if_missing=False)
502 default_repo_perm = v.OneOf(repo_perms_choices)
503 default_repo_perm = v.OneOf(repo_perms_choices)
503 default_group_perm = v.OneOf(group_perms_choices)
504 default_group_perm = v.OneOf(group_perms_choices)
504 default_user_group_perm = v.OneOf(user_group_perms_choices)
505 default_user_group_perm = v.OneOf(user_group_perms_choices)
505 return _ObjectPermissionsForm
506 return _ObjectPermissionsForm
506
507
507
508
508 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
509 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
509 repo_group_create_choices, user_group_create_choices,
510 repo_group_create_choices, user_group_create_choices,
510 fork_choices, inherit_default_permissions_choices):
511 fork_choices, inherit_default_permissions_choices):
511 _ = localizer
512 _ = localizer
512
513
513 class _DefaultPermissionsForm(formencode.Schema):
514 class _DefaultPermissionsForm(formencode.Schema):
514 allow_extra_fields = True
515 allow_extra_fields = True
515 filter_extra_fields = True
516 filter_extra_fields = True
516
517
517 anonymous = v.StringBoolean(if_missing=False)
518 anonymous = v.StringBoolean(if_missing=False)
518
519
519 default_repo_create = v.OneOf(create_choices)
520 default_repo_create = v.OneOf(create_choices)
520 default_repo_create_on_write = v.OneOf(create_on_write_choices)
521 default_repo_create_on_write = v.OneOf(create_on_write_choices)
521 default_user_group_create = v.OneOf(user_group_create_choices)
522 default_user_group_create = v.OneOf(user_group_create_choices)
522 default_repo_group_create = v.OneOf(repo_group_create_choices)
523 default_repo_group_create = v.OneOf(repo_group_create_choices)
523 default_fork_create = v.OneOf(fork_choices)
524 default_fork_create = v.OneOf(fork_choices)
524 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
525 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
525 return _DefaultPermissionsForm
526 return _DefaultPermissionsForm
526
527
527
528
528 def UserIndividualPermissionsForm(localizer):
529 def UserIndividualPermissionsForm(localizer):
529 _ = localizer
530 _ = localizer
530
531
531 class _DefaultPermissionsForm(formencode.Schema):
532 class _DefaultPermissionsForm(formencode.Schema):
532 allow_extra_fields = True
533 allow_extra_fields = True
533 filter_extra_fields = True
534 filter_extra_fields = True
534
535
535 inherit_default_permissions = v.StringBoolean(if_missing=False)
536 inherit_default_permissions = v.StringBoolean(if_missing=False)
536 return _DefaultPermissionsForm
537 return _DefaultPermissionsForm
537
538
538
539
539 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
540 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
540 _ = localizer
541 _ = localizer
541 old_data = old_data or {}
542 old_data = old_data or {}
542
543
543 class _DefaultsForm(formencode.Schema):
544 class _DefaultsForm(formencode.Schema):
544 allow_extra_fields = True
545 allow_extra_fields = True
545 filter_extra_fields = True
546 filter_extra_fields = True
546 default_repo_type = v.OneOf(supported_backends)
547 default_repo_type = v.OneOf(supported_backends)
547 default_repo_private = v.StringBoolean(if_missing=False)
548 default_repo_private = v.StringBoolean(if_missing=False)
548 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
549 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
549 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
550 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
550 default_repo_enable_locking = v.StringBoolean(if_missing=False)
551 default_repo_enable_locking = v.StringBoolean(if_missing=False)
551 return _DefaultsForm
552 return _DefaultsForm
552
553
553
554
554 def AuthSettingsForm(localizer):
555 def AuthSettingsForm(localizer):
555 _ = localizer
556 _ = localizer
556
557
557 class _AuthSettingsForm(formencode.Schema):
558 class _AuthSettingsForm(formencode.Schema):
558 allow_extra_fields = True
559 allow_extra_fields = True
559 filter_extra_fields = True
560 filter_extra_fields = True
560 auth_plugins = All(v.ValidAuthPlugins(localizer),
561 auth_plugins = All(v.ValidAuthPlugins(localizer),
561 v.UniqueListFromString(localizer)(not_empty=True))
562 v.UniqueListFromString(localizer)(not_empty=True))
562 return _AuthSettingsForm
563 return _AuthSettingsForm
563
564
564
565
565 def UserExtraEmailForm(localizer):
566 def UserExtraEmailForm(localizer):
566 _ = localizer
567 _ = localizer
567
568
568 class _UserExtraEmailForm(formencode.Schema):
569 class _UserExtraEmailForm(formencode.Schema):
569 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
570 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
570 return _UserExtraEmailForm
571 return _UserExtraEmailForm
571
572
572
573
573 def UserExtraIpForm(localizer):
574 def UserExtraIpForm(localizer):
574 _ = localizer
575 _ = localizer
575
576
576 class _UserExtraIpForm(formencode.Schema):
577 class _UserExtraIpForm(formencode.Schema):
577 ip = v.ValidIp(localizer)(not_empty=True)
578 ip = v.ValidIp(localizer)(not_empty=True)
578 return _UserExtraIpForm
579 return _UserExtraIpForm
579
580
580
581
581 def PullRequestForm(localizer, repo_id):
582 def PullRequestForm(localizer, repo_id):
582 _ = localizer
583 _ = localizer
583
584
584 class ReviewerForm(formencode.Schema):
585 class ReviewerForm(formencode.Schema):
585 user_id = v.Int(not_empty=True)
586 user_id = v.Int(not_empty=True)
586 reasons = All()
587 reasons = All()
587 rules = All(v.UniqueList(localizer, convert=int)())
588 rules = All(v.UniqueList(localizer, convert=int)())
588 mandatory = v.StringBoolean()
589 mandatory = v.StringBoolean()
589
590
590 class _PullRequestForm(formencode.Schema):
591 class _PullRequestForm(formencode.Schema):
591 allow_extra_fields = True
592 allow_extra_fields = True
592 filter_extra_fields = True
593 filter_extra_fields = True
593
594
594 common_ancestor = v.UnicodeString(strip=True, required=True)
595 common_ancestor = v.UnicodeString(strip=True, required=True)
595 source_repo = v.UnicodeString(strip=True, required=True)
596 source_repo = v.UnicodeString(strip=True, required=True)
596 source_ref = v.UnicodeString(strip=True, required=True)
597 source_ref = v.UnicodeString(strip=True, required=True)
597 target_repo = v.UnicodeString(strip=True, required=True)
598 target_repo = v.UnicodeString(strip=True, required=True)
598 target_ref = v.UnicodeString(strip=True, required=True)
599 target_ref = v.UnicodeString(strip=True, required=True)
599 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
600 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
600 v.UniqueList(localizer)(not_empty=True))
601 v.UniqueList(localizer)(not_empty=True))
601 review_members = formencode.ForEach(ReviewerForm())
602 review_members = formencode.ForEach(ReviewerForm())
602 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3, max=255)
603 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3, max=255)
603 pullrequest_desc = v.UnicodeString(strip=True, required=False)
604 pullrequest_desc = v.UnicodeString(strip=True, required=False)
604
605
605 return _PullRequestForm
606 return _PullRequestForm
606
607
607
608
608 def IssueTrackerPatternsForm(localizer):
609 def IssueTrackerPatternsForm(localizer):
609 _ = localizer
610 _ = localizer
610
611
611 class _IssueTrackerPatternsForm(formencode.Schema):
612 class _IssueTrackerPatternsForm(formencode.Schema):
612 allow_extra_fields = True
613 allow_extra_fields = True
613 filter_extra_fields = False
614 filter_extra_fields = False
614 chained_validators = [v.ValidPattern(localizer)]
615 chained_validators = [v.ValidPattern(localizer)]
615 return _IssueTrackerPatternsForm
616 return _IssueTrackerPatternsForm
@@ -1,270 +1,278 b''
1 // summary.less
1 // summary.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // Used for headers and file detail summary screens.
3 // Used for headers and file detail summary screens.
4
4
5 .summary {
5 .summary {
6 float: left;
6 float: left;
7 position: relative;
7 position: relative;
8 width: 100%;
8 width: 100%;
9 margin: 0;
9 margin: 0;
10 padding: 0;
10 padding: 0;
11
11
12 .summary-detail-header {
12 .summary-detail-header {
13 float: left;
13 float: left;
14 display: block;
14 display: block;
15 width: 100%;
15 width: 100%;
16 margin-bottom: @textmargin;
16 margin-bottom: @textmargin;
17 padding: 0 0 .5em 0;
17 padding: 0 0 .5em 0;
18 border-bottom: @border-thickness solid @border-default-color;
18 border-bottom: @border-thickness solid @border-default-color;
19
19
20 .breadcrumbs {
20 .breadcrumbs {
21 float: left;
21 float: left;
22 display: inline;
22 display: inline;
23 margin: 0;
23 margin: 0;
24 padding: 0;
24 padding: 0;
25 }
25 }
26 h4 {
26 h4 {
27 float: left;
27 float: left;
28 margin: 0 1em 0 0;
28 margin: 0 1em 0 0;
29 padding: 0;
29 padding: 0;
30 line-height: 1.2em;
30 line-height: 1.2em;
31 font-size: @basefontsize;
31 font-size: @basefontsize;
32 }
32 }
33
33
34 .action_link {
34 .action_link {
35 float: right;
35 float: right;
36 }
36 }
37
37
38 .new-file {
38 .new-file {
39 float: right;
39 float: right;
40 margin-top: -1.5em;
40 margin-top: -1.5em;
41 }
41 }
42 }
42 }
43
43
44 .summary-detail {
44 .summary-detail {
45 float: left;
45 float: left;
46 position: relative;
46 position: relative;
47 width: 73%;
47 width: 73%;
48 margin: 0 3% @space 0;
48 margin: 0 3% @space 0;
49 padding: 0;
49 padding: 0;
50
50
51 .file_diff_buttons {
51 .file_diff_buttons {
52 margin-top: @space;
52 margin-top: @space;
53 }
53 }
54
54
55 // commit message
55 // commit message
56 .commit {
56 .commit {
57 white-space: pre-wrap;
57 white-space: pre-wrap;
58 }
58 }
59
59
60 #clone_url {
60 .left-clone {
61 width: ~"calc(100% - 103px)";
61 float: left;
62 padding: @padding/4;
62 height: 30px;
63 margin: 0;
64 padding: 0;
65 font-family: @text-semibold;
63 }
66 }
64
67
65 #clone_url_id {
68 .right-clone {
66 width: ~"calc(100% - 125px)";
69 float: right;
67 padding: @padding/4;
70 width: 83%;
71 }
72
73 .clone_url_input {
74 width: ~"calc(100% - 35px)";
75 padding: 5px;
68 }
76 }
69
77
70 &.directory {
78 &.directory {
71 margin-bottom: 0;
79 margin-bottom: 0;
72 }
80 }
73
81
74 .desc {
82 .desc {
75 white-space: pre-wrap;
83 white-space: pre-wrap;
76 }
84 }
77 .disabled {
85 .disabled {
78 opacity: .5;
86 opacity: .5;
79 cursor: inherit;
87 cursor: inherit;
80 }
88 }
81 .help-block {
89 .help-block {
82 color: inherit;
90 color: inherit;
83 margin: 0;
91 margin: 0;
84 }
92 }
85 }
93 }
86
94
87 .sidebar-right {
95 .sidebar-right {
88 float: left;
96 float: left;
89 width: 24%;
97 width: 24%;
90 margin: 0;
98 margin: 0;
91 padding: 0;
99 padding: 0;
92
100
93 ul {
101 ul {
94 margin-left: 0;
102 margin-left: 0;
95 padding-left: 0;
103 padding-left: 0;
96
104
97 li {
105 li {
98
106
99 &:before {
107 &:before {
100 content: none;
108 content: none;
101 width: 0;
109 width: 0;
102 }
110 }
103 }
111 }
104 }
112 }
105 }
113 }
106
114
107 #clone_by_name, #clone_by_id{
115 #clone_by_name, #clone_by_id{
108 display: inline-block;
116 display: inline-block;
109 margin-left: 0px;
117 margin-left: 0px;
110 }
118 }
111
119
112 .codeblock {
120 .codeblock {
113 border: none;
121 border: none;
114 background-color: transparent;
122 background-color: transparent;
115 }
123 }
116
124
117 .code-body {
125 .code-body {
118 border: @border-thickness solid @border-default-color;
126 border: @border-thickness solid @border-default-color;
119 .border-radius(@border-radius);
127 .border-radius(@border-radius);
120 }
128 }
121 }
129 }
122
130
123 // this is used outside of just the summary
131 // this is used outside of just the summary
124 .fieldset, // similar to form fieldset
132 .fieldset, // similar to form fieldset
125 .summary .sidebar-right-content { // these have to match
133 .summary .sidebar-right-content { // these have to match
126 clear: both;
134 clear: both;
127 float: left;
135 float: left;
128 position: relative;
136 position: relative;
129 display:block;
137 display:block;
130 width: 100%;
138 width: 100%;
131 min-height: 1em;
139 min-height: 1em;
132 margin-bottom: @textmargin;
140 margin-bottom: @textmargin;
133 padding: 0;
141 padding: 0;
134 line-height: 1.2em;
142 line-height: 1.2em;
135
143
136 &:after { // clearfix
144 &:after { // clearfix
137 content: "";
145 content: "";
138 clear: both;
146 clear: both;
139 width: 100%;
147 width: 100%;
140 height: 1em;
148 height: 1em;
141 }
149 }
142 }
150 }
143
151
144 .summary .sidebar-right-content {
152 .summary .sidebar-right-content {
145 margin-bottom: @space;
153 margin-bottom: @space;
146
154
147 .rc-user {
155 .rc-user {
148 min-width: 0;
156 min-width: 0;
149 }
157 }
150 }
158 }
151
159
152 .fieldset {
160 .fieldset {
153
161
154 .left-label { // similar to form legend
162 .left-label { // similar to form legend
155 float: left;
163 float: left;
156 display: block;
164 display: block;
157 width: 25%;
165 width: 25%;
158 margin: 0;
166 margin: 0;
159 padding: 0;
167 padding: 0;
160 font-family: @text-semibold;
168 font-family: @text-semibold;
161 }
169 }
162
170
163 .right-content { // similar to form fields
171 .right-content { // similar to form fields
164 float: left;
172 float: left;
165 display: block;
173 display: block;
166 width: 75%;
174 width: 75%;
167 margin: 0 0 0 -15%;
175 margin: 0 0 0 -15%;
168 padding: 0 0 0 15%;
176 padding: 0 0 0 15%;
169
177
170 .truncate-wrap,
178 .truncate-wrap,
171 .truncate {
179 .truncate {
172 max-width: 100%;
180 max-width: 100%;
173 width: 100%;
181 width: 100%;
174 }
182 }
175
183
176 .commit-long {
184 .commit-long {
177 overflow-x: auto;
185 overflow-x: auto;
178 }
186 }
179 }
187 }
180 .commit.truncate-wrap {
188 .commit.truncate-wrap {
181 overflow:hidden;
189 overflow:hidden;
182 text-overflow: ellipsis;
190 text-overflow: ellipsis;
183 }
191 }
184 }
192 }
185
193
186 // expand commit message
194 // expand commit message
187 #message_expand {
195 #message_expand {
188 clear: both;
196 clear: both;
189 display: block;
197 display: block;
190 color: @rcblue;
198 color: @rcblue;
191 cursor: pointer;
199 cursor: pointer;
192 }
200 }
193
201
194 #trimmed_message_box {
202 #trimmed_message_box {
195 max-height: floor(2 * @basefontsize * 1.2); // 2 lines * line-height
203 max-height: floor(2 * @basefontsize * 1.2); // 2 lines * line-height
196 overflow: hidden;
204 overflow: hidden;
197 }
205 }
198
206
199 // show/hide comments button
207 // show/hide comments button
200 .show-inline-comments {
208 .show-inline-comments {
201 display: inline;
209 display: inline;
202 cursor: pointer;
210 cursor: pointer;
203
211
204 .comments-show { display: inline; }
212 .comments-show { display: inline; }
205 .comments-hide { display: none; }
213 .comments-hide { display: none; }
206
214
207 &.comments-visible {
215 &.comments-visible {
208 .comments-show { display: none; }
216 .comments-show { display: none; }
209 .comments-hide { display: inline; }
217 .comments-hide { display: inline; }
210 }
218 }
211 }
219 }
212
220
213 // Quick Start section
221 // Quick Start section
214 .quick_start {
222 .quick_start {
215 float: left;
223 float: left;
216 display: block;
224 display: block;
217 position: relative;
225 position: relative;
218 width: 100%;
226 width: 100%;
219
227
220 // adds some space to make copy and paste easier
228 // adds some space to make copy and paste easier
221 .left-label,
229 .left-label,
222 .right-content {
230 .right-content {
223 line-height: 1.6em;
231 line-height: 1.6em;
224 }
232 }
225 }
233 }
226
234
227 .submodule {
235 .submodule {
228 .summary-detail {
236 .summary-detail {
229 width: 100%;
237 width: 100%;
230
238
231 .btn-collapse {
239 .btn-collapse {
232 display: none;
240 display: none;
233 }
241 }
234 }
242 }
235 }
243 }
236
244
237 .codeblock-header {
245 .codeblock-header {
238 float: left;
246 float: left;
239 display: block;
247 display: block;
240 width: 100%;
248 width: 100%;
241 margin: 0;
249 margin: 0;
242 padding: @space 0 @padding 0;
250 padding: @space 0 @padding 0;
243 border-top: @border-thickness solid @border-default-color;
251 border-top: @border-thickness solid @border-default-color;
244
252
245 .stats {
253 .stats {
246 float: left;
254 float: left;
247 width: 50%;
255 width: 50%;
248 }
256 }
249
257
250 .buttons {
258 .buttons {
251 float: right;
259 float: right;
252 width: 50%;
260 width: 50%;
253 text-align: right;
261 text-align: right;
254 color: @grey4;
262 color: @grey4;
255 }
263 }
256 }
264 }
257
265
258 #summary-menu-stats {
266 #summary-menu-stats {
259
267
260 .stats-bullet {
268 .stats-bullet {
261 color: @grey3;
269 color: @grey3;
262 min-width: 3em;
270 min-width: 3em;
263 }
271 }
264
272
265 .repo-size {
273 .repo-size {
266 margin-bottom: .5em;
274 margin-bottom: .5em;
267 }
275 }
268
276
269 }
277 }
270
278
@@ -1,226 +1,230 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')}</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)}
174 ${h.text('rhodecode_clone_uri_tmpl', size=60)} HTTP[S]
175 </div>
175 </div>
176
176 <div class="field">
177 ${h.text('rhodecode_clone_uri_ssh_tmpl', size=60)} SSH
178 </div>
177 <div class="field">
179 <div class="field">
178 <span class="help-block">
180 <span class="help-block">
179 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
181 ${_('''Schema of clone url construction eg. '{scheme}://{user}@{netloc}/{repo}', available vars:
180 {scheme} 'http' or 'https' sent from running RhodeCode server,
182 {scheme} 'http' or 'https' sent from running RhodeCode server,
181 {user} current user username,
183 {user} current user username,
184 {sys_user} current system user running this process, usefull for ssh,
185 {hostname} hostname of this server running RhodeCode,
182 {netloc} network location/server host of running RhodeCode server,
186 {netloc} network location/server host of running RhodeCode server,
183 {repo} full repository name,
187 {repo} full repository name,
184 {repoid} ID of repository, can be used to contruct clone-by-id''')}
188 {repoid} ID of repository, can be used to contruct clone-by-id''')}
185 </span>
189 </span>
186 </div>
190 </div>
187 </div>
191 </div>
188 </div>
192 </div>
189
193
190 <div class="panel panel-default">
194 <div class="panel panel-default">
191 <div class="panel-heading">
195 <div class="panel-heading">
192 <h3 class="panel-title">${_('Custom Support Link')}</h3>
196 <h3 class="panel-title">${_('Custom Support Link')}</h3>
193 </div>
197 </div>
194 <div class="panel-body">
198 <div class="panel-body">
195 <div class="field">
199 <div class="field">
196 ${h.text('rhodecode_support_url', size=60)}
200 ${h.text('rhodecode_support_url', size=60)}
197 </div>
201 </div>
198 <div class="field">
202 <div class="field">
199 <span class="help-block">
203 <span class="help-block">
200 ${_('''Custom url for the support link located at the bottom.
204 ${_('''Custom url for the support link located at the bottom.
201 The default is set to %(default_url)s. In case there's a need
205 The default is set to %(default_url)s. In case there's a need
202 to change the support link to internal issue tracker, it should be done here.
206 to change the support link to internal issue tracker, it should be done here.
203 ''') % {'default_url': h.route_url('rhodecode_support')}}
207 ''') % {'default_url': h.route_url('rhodecode_support')}}
204 </span>
208 </span>
205 </div>
209 </div>
206 </div>
210 </div>
207 </div>
211 </div>
208
212
209 <div class="buttons">
213 <div class="buttons">
210 ${h.submit('save',_('Save settings'),class_="btn")}
214 ${h.submit('save',_('Save settings'),class_="btn")}
211 ${h.reset('reset',_('Reset'),class_="btn")}
215 ${h.reset('reset',_('Reset'),class_="btn")}
212 </div>
216 </div>
213
217
214
218
215 ${h.end_form()}
219 ${h.end_form()}
216
220
217 <script>
221 <script>
218 $(document).ready(function() {
222 $(document).ready(function() {
219 $('#rhodecode_markup_renderer').select2({
223 $('#rhodecode_markup_renderer').select2({
220 containerCssClass: 'drop-menu',
224 containerCssClass: 'drop-menu',
221 dropdownCssClass: 'drop-menu-dropdown',
225 dropdownCssClass: 'drop-menu-dropdown',
222 dropdownAutoWidth: true,
226 dropdownAutoWidth: true,
223 minimumResultsForSearch: -1
227 minimumResultsForSearch: -1
224 });
228 });
225 });
229 });
226 </script>
230 </script>
@@ -1,210 +1,214 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${_ungettext(
4 <i class="icon-branch"></i>${_ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.route_path('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${_ungettext(
11 <i class="icon-branch"></i>${_ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.route_path('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${_ungettext(
18 <i class="icon-tag"></i>${_ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.route_path('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${_ungettext(
25 <i class="icon-bookmark"></i>${_ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
29 </%def>
29 </%def>
30
30
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33
33
34 <div id="summary-menu-stats" class="summary-detail">
34 <div id="summary-menu-stats" class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <div class="breadcrumbs files_location">
36 <div class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${breadcrumbs_links}
38 ${breadcrumbs_links}
39 </h4>
39 </h4>
40 </div>
40 </div>
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 ${_('Show More')}
42 ${_('Show More')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
48 <div class="left-label disabled">
49 ${_('Read-only url')}:
50 </div>
51 <div class="right-content disabled">
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
53 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
54
47
55 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
48 <div class="left-clone">
56 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
49 <select id="clone_option" name="clone_option">
57
50 <option value="http" selected="selected">HTTP</option>
58 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
51 <option value="http_id">HTTP UID</option>
59 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
52 <option value="ssh">SSH</option>
60
53 </select>
61 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
62 </div>
54 </div>
63 %else:
55 <div class="right-clone">
64 <div class="left-label">
56 <%
65 ${_('Clone url')}:
57 maybe_disabled = ''
66 </div>
58 if h.is_svn_without_proxy(c.rhodecode_db_repo):
67 <div class="right-content">
59 maybe_disabled = 'disabled'
68 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
60 %>
69 <i id="clone_by_name_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
61
62 <span id="clone_option_http">
63 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url}"/>
64 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url}" title="${_('Copy the clone url')}"></i>
65 </span>
70
66
71 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
67 <span style="display: none;" id="clone_option_http_id">
72 <i id="clone_by_id_copy" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}" style="display: none"></i>
68 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_id}"/>
69 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_id}" title="${_('Copy the clone by id url')}"></i>
70 </span>
73
71
74 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
72 <span style="display: none;" id="clone_option_ssh">
75 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
73 <input type="text" class="input-monospace clone_url_input" ${maybe_disabled} readonly="readonly" value="${c.clone_repo_url_ssh}"/>
74 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.clone_repo_url_ssh}" title="${_('Copy the clone by ssh url')}"></i>
75 </span>
76
77 % if maybe_disabled:
78 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
79 % endif
80
76 </div>
81 </div>
77 %endif
78 </div>
82 </div>
79
83
80 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
84 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
81 <div class="left-label">
85 <div class="left-label">
82 ${_('Description')}:
86 ${_('Description')}:
83 </div>
87 </div>
84 <div class="right-content">
88 <div class="right-content">
85 <div class="input ${summary(c.show_stats)}">
89 <div class="input ${summary(c.show_stats)}">
86 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
90 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
87 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
91 ${dt.repo_desc(c.rhodecode_db_repo.description_safe, c.visual.stylify_metatags)}
88 </div>
92 </div>
89 </div>
93 </div>
90 </div>
94 </div>
91
95
92 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
96 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
93 <div class="left-label">
97 <div class="left-label">
94 ${_('Information')}:
98 ${_('Information')}:
95 </div>
99 </div>
96 <div class="right-content">
100 <div class="right-content">
97
101
98 <div class="repo-size">
102 <div class="repo-size">
99 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
103 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
100
104
101 ## commits
105 ## commits
102 % if commit_rev == -1:
106 % if commit_rev == -1:
103 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
107 ${_ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
104 % else:
108 % else:
105 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
109 <a href="${h.route_path('repo_changelog', repo_name=c.repo_name)}">
106 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
110 ${_ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
107 % endif
111 % endif
108
112
109 ## forks
113 ## forks
110 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
114 <a title="${_('Number of Repository Forks')}" href="${h.route_path('repo_forks_show_all', repo_name=c.repo_name)}">
111 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
115 ${c.repository_forks} ${_ungettext('Fork', 'Forks', c.repository_forks)}</a>,
112
116
113 ## repo size
117 ## repo size
114 % if commit_rev == -1:
118 % if commit_rev == -1:
115 <span class="stats-bullet">0 B</span>
119 <span class="stats-bullet">0 B</span>
116 % else:
120 % else:
117 <span class="stats-bullet" id="repo_size_container">
121 <span class="stats-bullet" id="repo_size_container">
118 ${_('Calculating Repository Size...')}
122 ${_('Calculating Repository Size...')}
119 </span>
123 </span>
120 % endif
124 % endif
121 </div>
125 </div>
122
126
123 <div class="commit-info">
127 <div class="commit-info">
124 <div class="tags">
128 <div class="tags">
125 % if c.rhodecode_repo:
129 % if c.rhodecode_repo:
126 ${refs_counters(
130 ${refs_counters(
127 c.rhodecode_repo.branches,
131 c.rhodecode_repo.branches,
128 c.rhodecode_repo.branches_closed,
132 c.rhodecode_repo.branches_closed,
129 c.rhodecode_repo.tags,
133 c.rhodecode_repo.tags,
130 c.rhodecode_repo.bookmarks)}
134 c.rhodecode_repo.bookmarks)}
131 % else:
135 % else:
132 ## missing requirements can make c.rhodecode_repo None
136 ## missing requirements can make c.rhodecode_repo None
133 ${refs_counters([], [], [], [])}
137 ${refs_counters([], [], [], [])}
134 % endif
138 % endif
135 </div>
139 </div>
136 </div>
140 </div>
137
141
138 </div>
142 </div>
139 </div>
143 </div>
140
144
141 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
145 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
142 <div class="left-label">
146 <div class="left-label">
143 ${_('Statistics')}:
147 ${_('Statistics')}:
144 </div>
148 </div>
145 <div class="right-content">
149 <div class="right-content">
146 <div class="input ${summary(c.show_stats)} statistics">
150 <div class="input ${summary(c.show_stats)} statistics">
147 % if c.show_stats:
151 % if c.show_stats:
148 <div id="lang_stats" class="enabled">
152 <div id="lang_stats" class="enabled">
149 ${_('Calculating Code Statistics...')}
153 ${_('Calculating Code Statistics...')}
150 </div>
154 </div>
151 % else:
155 % else:
152 <span class="disabled">
156 <span class="disabled">
153 ${_('Statistics are disabled for this repository')}
157 ${_('Statistics are disabled for this repository')}
154 </span>
158 </span>
155 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
159 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
156 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
160 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_statistics'))}
157 % endif
161 % endif
158 % endif
162 % endif
159 </div>
163 </div>
160
164
161 </div>
165 </div>
162 </div>
166 </div>
163
167
164 % if show_downloads:
168 % if show_downloads:
165 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
169 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
166 <div class="left-label">
170 <div class="left-label">
167 ${_('Downloads')}:
171 ${_('Downloads')}:
168 </div>
172 </div>
169 <div class="right-content">
173 <div class="right-content">
170 <div class="input ${summary(c.show_stats)} downloads">
174 <div class="input ${summary(c.show_stats)} downloads">
171 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
175 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
172 <span class="disabled">
176 <span class="disabled">
173 ${_('There are no downloads yet')}
177 ${_('There are no downloads yet')}
174 </span>
178 </span>
175 % elif not c.enable_downloads:
179 % elif not c.enable_downloads:
176 <span class="disabled">
180 <span class="disabled">
177 ${_('Downloads are disabled for this repository')}
181 ${_('Downloads are disabled for this repository')}
178 </span>
182 </span>
179 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
183 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
180 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
184 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, _anchor='repo_enable_downloads'))}
181 % endif
185 % endif
182 % else:
186 % else:
183 <span class="enabled">
187 <span class="enabled">
184 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
188 <a id="archive_link" class="btn btn-small" href="${h.route_path('repo_archivefile',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
185 <i class="icon-archive"></i> tip.zip
189 <i class="icon-archive"></i> tip.zip
186 ## replaced by some JS on select
190 ## replaced by some JS on select
187 </a>
191 </a>
188 </span>
192 </span>
189 ${h.hidden('download_options')}
193 ${h.hidden('download_options')}
190 % endif
194 % endif
191 </div>
195 </div>
192 </div>
196 </div>
193 </div>
197 </div>
194 % endif
198 % endif
195
199
196 </div><!--end summary-detail-->
200 </div><!--end summary-detail-->
197 </%def>
201 </%def>
198
202
199 <%def name="summary_stats(gravatar_function)">
203 <%def name="summary_stats(gravatar_function)">
200 <div class="sidebar-right">
204 <div class="sidebar-right">
201 <div class="summary-detail-header">
205 <div class="summary-detail-header">
202 <h4 class="item">
206 <h4 class="item">
203 ${_('Owner')}
207 ${_('Owner')}
204 </h4>
208 </h4>
205 </div>
209 </div>
206 <div class="sidebar-right-content">
210 <div class="sidebar-right-content">
207 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
211 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
208 </div>
212 </div>
209 </div><!--end sidebar-right-->
213 </div><!--end sidebar-right-->
210 </%def>
214 </%def>
@@ -1,137 +1,119 b''
1 <%inherit file="/summary/summary_base.mako"/>
1 <%inherit file="/summary/summary_base.mako"/>
2
2
3 <%namespace name="components" file="/summary/components.mako"/>
3 <%namespace name="components" file="/summary/components.mako"/>
4
4
5
5
6 <%def name="menu_bar_subnav()">
6 <%def name="menu_bar_subnav()">
7 ${self.repo_menu(active='summary')}
7 ${self.repo_menu(active='summary')}
8 </%def>
8 </%def>
9
9
10 <%def name="main()">
10 <%def name="main()">
11
11
12 <div class="title">
12 <div class="title">
13 ${self.repo_page_title(c.rhodecode_db_repo)}
13 ${self.repo_page_title(c.rhodecode_db_repo)}
14 <ul class="links icon-only-links block-right">
14 <ul class="links icon-only-links block-right">
15 <li>
15 <li>
16 %if c.rhodecode_user.username != h.DEFAULT_USER:
16 %if c.rhodecode_user.username != h.DEFAULT_USER:
17 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
17 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
18 %else:
18 %else:
19 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
19 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
20 %endif
20 %endif
21 </li>
21 </li>
22 </ul>
22 </ul>
23 </div>
23 </div>
24
24
25 <div id="repo-summary" class="summary">
25 <div id="repo-summary" class="summary">
26 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=True)}
26 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=True)}
27 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
27 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
28 </div><!--end repo-summary-->
28 </div><!--end repo-summary-->
29
29
30
30
31 <div class="box" >
31 <div class="box" >
32 %if not c.repo_commits:
32 %if not c.repo_commits:
33 <div class="title">
33 <div class="title">
34 <h3>${_('Quick start')}</h3>
34 <h3>${_('Quick start')}</h3>
35 </div>
35 </div>
36 %endif
36 %endif
37 <div class="table">
37 <div class="table">
38 <div id="shortlog_data">
38 <div id="shortlog_data">
39 <%include file='summary_commits.mako'/>
39 <%include file='summary_commits.mako'/>
40 </div>
40 </div>
41 </div>
41 </div>
42 </div>
42 </div>
43
43
44 %if c.readme_data:
44 %if c.readme_data:
45 <div id="readme" class="anchor">
45 <div id="readme" class="anchor">
46 <div class="box" >
46 <div class="box" >
47 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
47 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
48 <h3 class="breadcrumbs">
48 <h3 class="breadcrumbs">
49 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">${c.readme_file}</a>
49 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">${c.readme_file}</a>
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="readme codeblock">
52 <div class="readme codeblock">
53 <div class="readme_box">
53 <div class="readme_box">
54 ${c.readme_data|n}
54 ${c.readme_data|n}
55 </div>
55 </div>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59 %endif
59 %endif
60
60
61 <script type="text/javascript">
61 <script type="text/javascript">
62 $(document).ready(function(){
62 $(document).ready(function(){
63 $('#clone_by_name').on('click',function(e){
63 $('#clone_option').on('change', function(e) {
64 // show url by name and hide name button
64 var selected = $(this).val();
65 $('#clone_url').show();
65 $.each(['http', 'http_id', 'ssh'], function (idx, val) {
66 $('#clone_by_name').hide();
66 if(val === selected){
67
67 $('#clone_option_' + val).show();
68 // hide url by id and show name button
68 } else {
69 $('#clone_by_id').show();
69 $('#clone_option_' + val).hide();
70 $('#clone_url_id').hide();
70 }
71
72 // hide copy by id
73 $('#clone_by_name_copy').show();
74 $('#clone_by_id_copy').hide();
75
76 });
71 });
77 $('#clone_by_id').on('click',function(e){
78
79 // show url by id and hide id button
80 $('#clone_by_id').hide();
81 $('#clone_url_id').show();
82
83 // hide url by name and show id button
84 $('#clone_by_name').show();
85 $('#clone_url').hide();
86
87 // hide copy by id
88 $('#clone_by_id_copy').show();
89 $('#clone_by_name_copy').hide();
90 });
72 });
91
73
92 var initialCommitData = {
74 var initialCommitData = {
93 id: null,
75 id: null,
94 text: 'tip',
76 text: 'tip',
95 type: 'tag',
77 type: 'tag',
96 raw_id: null,
78 raw_id: null,
97 files_url: null
79 files_url: null
98 };
80 };
99
81
100 select2RefSwitcher('#download_options', initialCommitData);
82 select2RefSwitcher('#download_options', initialCommitData);
101
83
102 // on change of download options
84 // on change of download options
103 $('#download_options').on('change', function(e) {
85 $('#download_options').on('change', function(e) {
104 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
86 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
105 var ext = '.zip';
87 var ext = '.zip';
106 var selected_cs = e.added;
88 var selected_cs = e.added;
107 var fname = e.added.raw_id + ext;
89 var fname = e.added.raw_id + ext;
108 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
90 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
109 // set new label
91 // set new label
110 $('#archive_link').html('<i class="icon-archive"></i> {0}{1}'.format(escapeHtml(e.added.text), ext));
92 $('#archive_link').html('<i class="icon-archive"></i> {0}{1}'.format(escapeHtml(e.added.text), ext));
111
93
112 // set new url to button,
94 // set new url to button,
113 $('#archive_link').attr('href', href)
95 $('#archive_link').attr('href', href)
114 });
96 });
115
97
116
98
117 // load details on summary page expand
99 // load details on summary page expand
118 $('#summary_details_expand').on('click', function() {
100 $('#summary_details_expand').on('click', function() {
119
101
120 var callback = function (data) {
102 var callback = function (data) {
121 % if c.show_stats:
103 % if c.show_stats:
122 showRepoStats('lang_stats', data);
104 showRepoStats('lang_stats', data);
123 % endif
105 % endif
124 };
106 };
125
107
126 showRepoSize(
108 showRepoSize(
127 'repo_size_container',
109 'repo_size_container',
128 templateContext.repo_name,
110 templateContext.repo_name,
129 templateContext.repo_landing_commit,
111 templateContext.repo_landing_commit,
130 callback);
112 callback);
131
113
132 })
114 })
133
115
134 })
116 })
135 </script>
117 </script>
136
118
137 </%def>
119 </%def>
General Comments 0
You need to be logged in to leave comments. Login now